/* * fpgadl core driver * * Copyright (C) 2008 Lyrtech * * Based on code found in book "Linux Device Drivers" by * Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MODULE_NAME "fpgadl" /* Define this to enable verbose debug messages */ #define FPGADL_DEBUG 1 static const char fpgadl_driver_version[] = "v1.0"; /* Module parameters */ static unsigned int fpgadl_debug; module_param_named(debug, fpgadl_debug, int, 0644); #ifdef FPGADL_DEBUG #define INFOMSG(fmt, args...) \ do { \ printk(KERN_INFO "%s: "fmt"\n", MODULE_NAME, ## args); \ } while (0) #define DBGMSG(fmt, args...) \ do { \ if (fpgadl_debug > 0) \ printk(KERN_DEBUG "%s: "fmt"\n", \ MODULE_NAME, ## args); \ } while (0) #define DBGMSG_ENTER() \ DBGMSG("%s() enter", __func__); #define DBGMSG_LEAVE() \ DBGMSG("%s() leave", __func__); #else #define INFOMSG(fmt, args...) do {} while (0) #define DBGMSG(fmt, args...) do {} while (0) #define DBGMSG_ENTER() do {} while (0) #define DBGMSG_LEAVE() do {} while (0) #endif #define FAILMSG(fmt, args...) \ do { \ printk(KERN_ERR "%s: "fmt"\n", MODULE_NAME, ## args); \ } while (0) #define FPGA_WAIT_TIMEOUT 100000 #define XFER_SIZE 100 /* Transfer size when writing bytes to * device. */ #define XC3S_WORD_SIZE 2 #define XC4V_WORD_SIZE 4 #define BITSTREAM_SYNC_BYTE1 0xAA #define BITSTREAM_SYNC_BYTE2 0x99 #define BITSTREAM_SYNC_BYTE3 0x55 #define BITSTREAM_SYNC_BYTE4 0x66 #define BITSTREAM_PACKET_HEADER_TYPE1 1 #define BITSTREAM_PACKET_HEADER_TYPE2 2 #define BITSTREAM_TYPE1_OPCODE_WRITE 2 #define BITSTREAM_TYPE1_REG_ADDR_FDRI 2 /* Structure of a TYPE1 packet. */ struct t1_pkt_xc4v_t { u32 word_count:11; u32 reserved2:2; u32 address:5; u32 reserved1:9; u32 opcode:2; u32 header:3; }; struct t1_pkt_xc3s_t { u16 word_count:5; u16 address:6; u16 opcode:2; u16 header:3; /* type */ }; /* Structure of a TYPE2 packet. */ struct t2_pkt_xc4v_t { u32 word_count:27; u32 opcode:2; /* Reserved. */ u32 header:3; }; struct t2_pkt_xc3s_t { u16 word_count:11; u16 opcode:2; /* Reserved. */ u16 header:3; }; struct bitstream_info { char strStatus[32]; ///< Status char strDesignName[129]; ///< Design name char strPartName[33]; ///< Part name char strDate[11]; ///< Date char strTime[9]; ///< Time u32 u32Length; ///< Bitstream length u8 *pu8RawBits; ///< Raw bitstream pointer }; #define MAX_FPGADL_DEV 4 static int fpgadl_dev_count; static struct fpgadl_device *fpgadl_dev_array[MAX_FPGADL_DEV]; int fpgadl_is_bitstream_loaded(const char *name) { int k; struct fpgadl_device *fpgadl_dev; DBGMSG_ENTER(); for (k = 0; k < MAX_FPGADL_DEV; k++) { fpgadl_dev = fpgadl_dev_array[k]; if (fpgadl_dev) if (strncmp(fpgadl_dev->name, name, strlen(name)) == 0) return fpgadl_dev->fw_loaded; } FAILMSG(" Device <%s> not found", name); return -ENOMEM; } EXPORT_SYMBOL(fpgadl_is_bitstream_loaded); /* Respond to hotplug events. */ static int fpgadl_uevent(struct device *dev, struct kobj_uevent_env *env) { DBGMSG_ENTER(); if (add_uevent_var(env, "FPGADL_BUS_VERSION=%s", fpgadl_driver_version)) return -ENOMEM; return 0; }; /* * Toggles the CCLK line on the board-specific interface the number of times * specified by . */ static int bitstr_load_make_clock(struct fpgadl_device *fpgadl_dev, int cycles) { int retval; int k; u8 dummy = 0; for (k = 0; k < cycles; k++) { retval = fpgadl_dev->write_byte(fpgadl_dev, &dummy, 1); if (retval < 0) return retval; } return 0; } /* * Return value: * 0: Error * 1: Full bitstream * 2: Partial bitstream */ static int bitstream_parse_header(const u8 *buffer, size_t length, struct bitstream_info *pbInfo) { u32 flength; memset(pbInfo, 0, sizeof(struct bitstream_info)); // Skip the heasder buffer += 13; // Design name if ( *buffer++ != 'a' ) { DBGMSG(" Invalid file format.\n"); return 0; } flength = (*buffer++ << 8); flength |= *buffer++; memcpy(pbInfo->strPartName, buffer, flength); buffer += flength; // Part name if ( *buffer++ != 'b' ) { DBGMSG(" Invalid file format.\n"); return 0; } flength = (*buffer++ << 8); flength |= *buffer++; memcpy( pbInfo->strPartName, buffer, flength ); buffer += flength; // Date if ( *buffer++ != 'c' ) { DBGMSG(" Invalid file format.\n"); return 0; } flength = (*buffer++ << 8); flength |= *buffer++; memcpy( pbInfo->strDate, buffer, flength ); buffer += flength; // Time if ( *buffer++ != 'd' ) { DBGMSG(" Invalid file format.\n"); return 0; } flength = (*buffer++ << 8); flength |= *buffer++; memcpy(pbInfo->strTime, buffer, flength); buffer += flength; // Bitstream length if ( *buffer++ != 'e' ) { DBGMSG(" Invalid file format.\n"); return 0; } pbInfo->u32Length = (*buffer++ << 24); pbInfo->u32Length |= (*buffer++ << 16); pbInfo->u32Length |= (*buffer++ << 8); pbInfo->u32Length |= *buffer++; // Bitstream pointer pbInfo->pu8RawBits = (u8 *)buffer; DBGMSG("Bitstream info"); DBGMSG("\tStatus : %s", pbInfo->strStatus); DBGMSG("\tDesign name : %s", pbInfo->strDesignName); DBGMSG("\tPart name : %s", pbInfo->strPartName); DBGMSG("\tDate : %s", pbInfo->strDate); DBGMSG("\tTime : %s", pbInfo->strTime); DBGMSG("\tLength : %d bytes", pbInfo->u32Length); return BITSTREAM_MODE_FULL; } /* * Bitstreams supported: Full or Partial. * Note: Full bitstream that supports partial bitstream must be generated with * option "Persist = true" in ISE. */ static int fpgadl_bitstream_load(struct fpgadl_device *fpgadl_dev, const u8 *data, size_t size) { int pad; int retval; int timeout_counter; struct bitstream_info bInfo; if (fpgadl_dev->fw_loaded != 0) { fpgadl_dev->fw_loaded = 0; /* Unregister device inside the FPGA */ if (fpgadl_dev->pdata->teardown != 0) { fpgadl_dev->pdata->teardown(); } } fpgadl_dev->bitstream_mode = bitstream_parse_header(data, size, &bInfo); switch (fpgadl_dev->bitstream_mode) { case BITSTREAM_MODE_FULL: DBGMSG(" Bitstream type: FULL"); /* Toggle PROG_B Pin and wait 300nS before proceeding. */ gpio_set_value(fpgadl_dev->pdata->program_b, 0); udelay(1); /* Confirm that INIT_B is low */ if (gpio_get_value(fpgadl_dev->pdata->init_b) != 0) { FAILMSG("Error: INIT_B not LOW when PROG is LOW"); return -EIO; } break; case BITSTREAM_MODE_PARTIAL: DBGMSG(" Bitstream type: PARTIAL"); break; case BITSTREAM_MODE_UNKNOWN: default: DBGMSG(" Bitstream type: UNKNOWN"); return -EINVAL; break; } /* For partial bitstream, PROGRAM_B is already high. */ retval = bitstr_load_make_clock(fpgadl_dev, 3); if (retval < 0) { return retval; } gpio_set_value(fpgadl_dev->pdata->program_b, 1); /* Wait for INIT_B pin to go high. */ timeout_counter = 0; while ((gpio_get_value(fpgadl_dev->pdata->init_b) == 0) && (timeout_counter < FPGA_WAIT_TIMEOUT)) { retval = bitstr_load_make_clock(fpgadl_dev, 3); if (retval < 0) { return retval; } timeout_counter++; } if (timeout_counter == FPGA_WAIT_TIMEOUT) { /* Timeout error. */ FAILMSG("Error: timeout while waiting for INIT_B to go HIGH"); return -EIO; } /* Send actual bitstream data to FPGA one byte at a time. */ retval = fpgadl_dev->write_byte(fpgadl_dev, bInfo.pu8RawBits, bInfo.u32Length); if (retval < 0) { return retval; } for (pad=0; pad<1000; pad++) { if (gpio_get_value(fpgadl_dev->pdata->done) != 0) { INFOMSG("Bitstream loaded"); fpgadl_dev->fw_loaded = 1; /* Register device inside the FPGA */ if (fpgadl_dev->pdata->setup != 0) { fpgadl_dev->pdata->setup(); } return 0; } if (gpio_get_value(fpgadl_dev->pdata->init_b) == 0) { if (gpio_get_value(fpgadl_dev->pdata->done) == 0) { /* Error if INIT_B goes low here. */ FAILMSG("Error: INIT_B LOW during programming"); return -EIO; } } retval = bitstr_load_make_clock(fpgadl_dev, 1); if (retval < 0) { return retval; } } FAILMSG("Error: timeout while waiting for DONE to go HIGH"); return -EIO; } /* Open method. */ static int fpgadl_open(struct inode *inode, struct file *filp) { int k; int found = 0; struct fpgadl_device *fpgadl_dev; DBGMSG_ENTER(); DBGMSG(" Opening device minor %d", MINOR(inode->i_rdev)); for (k = 0; k < fpgadl_dev_count; k++) { fpgadl_dev = fpgadl_dev_array[k]; if (fpgadl_dev) { if (fpgadl_dev->miscdev.minor == MINOR(inode->i_rdev)) { found = 1; break; } } } if (!found) { FAILMSG(" Invalid minor device"); return -ENOMEM; } filp->private_data = fpgadl_dev; fpgadl_dev->fw_length = 0; #ifdef USE_KMALLOC // Use kmalloc fpgadl_dev->fw_data = kmalloc(fpgadl_dev->fw_max_size, GFP_KERNEL); #else // Use vmalloc fpgadl_dev->fw_data = vmalloc(fpgadl_dev->fw_max_size); #endif if (!fpgadl_dev->fw_data) { FAILMSG("Failed to allocate memory for bitstream"); return -ENOMEM; } fpgadl_dev->fw_buffer_allocated = 1; return 0; } /* Write method. Fill buffer with bitstream data. */ static ssize_t fpgadl_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp) { struct fpgadl_device *fpgadl_dev = filp->private_data; if ((fpgadl_dev->fw_length + count) >= fpgadl_dev->fw_max_size) { FAILMSG("Bitstream buffer size exceeded"); fpgadl_dev->fw_length = 0; return -EFBIG; } if (copy_from_user(fpgadl_dev->fw_data + fpgadl_dev->fw_length, (void __user *) buff, count)) return -EFAULT; fpgadl_dev->fw_length += count; return count; } /* Release method. This will initiate the FPGA programming. */ static int fpgadl_release(struct inode *inode, struct file *filp) { int retry; int retval; struct fpgadl_device *fpgadl_dev = filp->private_data; if (!fpgadl_dev->fw_data) return -EFAULT; /* If there was an error in write method, length will be 0. */ if (fpgadl_dev->fw_length != 0) { for (retry=0; retry<5; retry++) { retval = fpgadl_bitstream_load(fpgadl_dev, fpgadl_dev->fw_data, fpgadl_dev->fw_length); if (retval == 0) break; } } else retval = -EFAULT; #ifdef USE_KMALLOC // Use kmalloc kfree(fpgadl_dev->fw_data); #else // Use vmalloc vfree(fpgadl_dev->fw_data); #endif fpgadl_dev->fw_buffer_allocated = 0; return retval; } static struct file_operations fops_fpgadl = { .owner = THIS_MODULE, .open = fpgadl_open, .write = fpgadl_write, .release = fpgadl_release }; /* Match fpgadl devices to drivers. Just do a simple name test. */ static int fpgadl_device_match(struct device *dev, struct device_driver *drv) { DBGMSG_ENTER(); return !strncmp(dev_name(dev), drv->name, strlen(drv->name)); } static ssize_t show_version(struct device_driver *driver, char *buf) { struct fpgadl_driver *fpgadldriver = to_fpgadl_driver(driver); sprintf(buf, "%s\n", fpgadldriver->version); return strlen(buf); } int fpgadl_register_driver(struct fpgadl_driver *drv) { int res; DBGMSG_ENTER(); /* Initialize common driver fields */ drv->driver.bus = &fpgadl_bus_type; /* Register with core */ res = driver_register(&drv->driver); if (res) FAILMSG(" driver_register() failed"); drv->version_attr.attr.name = "version"; drv->version_attr.attr.mode = S_IRUGO; drv->version_attr.show = show_version; drv->version_attr.store = NULL; res = driver_create_file(&drv->driver, &drv->version_attr); return res; } EXPORT_SYMBOL(fpgadl_register_driver); void fpgadl_unregister_driver(struct fpgadl_driver *drv) { DBGMSG_ENTER(); driver_unregister(&drv->driver); } EXPORT_SYMBOL(fpgadl_unregister_driver); /* The fpgadl bus device. */ static void fpgadl_bus_release(struct device *dev) { DBGMSG_ENTER(); } struct device fpgadl_bus = { .init_name = "fpgadl0", .release = fpgadl_bus_release }; struct bus_type fpgadl_bus_type = { .name = "fpgadl", .match = fpgadl_device_match, .uevent = fpgadl_uevent, }; EXPORT_SYMBOL(fpgadl_bus_type); /* Export a simple sysfs attribute. */ static ssize_t show_bus_version(struct bus_type *bus, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", fpgadl_driver_version); } static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); /* * fpgadl devices. * For now, no references to fpgadlbus devices go out which are not * tracked via the module reference count, so we use a no-op * release function. */ static void fpgadl_dev_release(struct device *dev) { DBGMSG_ENTER(); } /* Release DaVinci GPIO to FPGA control pins. */ static void fpgadl_release_gpio(struct fpgadl_pdata_t *pdata) { gpio_free(pdata->busy); gpio_free(pdata->done); gpio_free(pdata->init_b); gpio_free(pdata->program_b); } static int fpgadl_setup_gpio(struct fpgadl_pdata_t *pdata) { int retval; /* Configure FPGA PROGRAM_B GPIO. */ retval = gpio_request(pdata->program_b, "fpga_program_b"); if (retval == 0) /* FPGA_PROGRAM_B must be initially HIGH. */ retval = gpio_direction_output(pdata->program_b, 1); if (retval != 0) goto gpio_error; /* Configure FPGA INIT_B GPIO. */ retval = gpio_request(pdata->init_b, "fpga_init_b"); if (retval == 0) retval = gpio_direction_input(pdata->init_b); if (retval != 0) goto gpio_error; /* Configure FPGA DONE GPIO. */ retval = gpio_request(pdata->done, "fpga_done"); if (retval == 0) retval = gpio_direction_input(pdata->done); if (retval != 0) goto gpio_error; /* Configure FPGA BUSY GPIO. */ retval = gpio_request(pdata->busy, "fpga_busy"); if (retval == 0) retval = gpio_direction_input(pdata->busy); if (retval != 0) goto gpio_error; return 0; gpio_error: fpgadl_release_gpio(pdata); return retval; } static void fpgadl_cleanup(struct fpgadl_device *fpgadl_dev) { DBGMSG_ENTER(); if (!fpgadl_dev) return; fpgadl_dev_array[fpgadl_dev->id] = NULL; /* Get rid of any allocated buffer, not freed */ if (fpgadl_dev->fw_buffer_allocated) #ifdef USE_KMALLOC // Use kmalloc kfree(fpgadl_dev->fw_data); #else // Use vmalloc vfree(fpgadl_dev->fw_data); #endif switch (fpgadl_dev->state) { case FPGADL_DEV_STATE_CHAR_DEV_REGISTERED: misc_deregister(&fpgadl_dev->miscdev); case FPGADL_DEV_STATE_GPIO_REGISTERED: fpgadl_release_gpio(fpgadl_dev->pdata); case FPGADL_DEV_STATE_DEVICE_REGISTERED: device_unregister(&fpgadl_dev->dev); case FPGADL_DEV_STATE_START: break; } } int fpgadl_register_device(struct fpgadl_device *fpgadl_dev) { int res; const struct firmware *fw_entry; DBGMSG_ENTER(); fpgadl_dev->state = FPGADL_DEV_STATE_START; /* Sanity checks. */ if (!fpgadl_dev->name) { FAILMSG(" Error, missing device name"); res = -EFAULT; goto error; } if (!fpgadl_dev->write_byte) { FAILMSG(" Error, missing write_byte() callback"); res = -ENOMEM; goto error; } if (fpgadl_dev_count == MAX_FPGADL_DEV) { FAILMSG("Maximum number of devices reached (%d)", fpgadl_dev_count); res = -ENODEV; goto error; } DBGMSG(" device %d", fpgadl_dev_count); /* Set some default values. */ fpgadl_dev->fw_loaded = 0; fpgadl_dev->fw_buffer_allocated = 0; if (!fpgadl_dev->pdata->bitstream_max_size) { FAILMSG("Maximum bitstream size not specified"); res = -ENODEV; goto error; } fpgadl_dev->fw_max_size = fpgadl_dev->pdata->bitstream_max_size; fpgadl_dev->dev.bus = &fpgadl_bus_type; fpgadl_dev->dev.parent = &fpgadl_bus; fpgadl_dev->dev.release = fpgadl_dev_release; dev_set_name(&fpgadl_dev->dev, fpgadl_dev->name ); res = device_register(&fpgadl_dev->dev); if (res) { FAILMSG(" device_register() failed"); goto error; } fpgadl_dev->state = FPGADL_DEV_STATE_DEVICE_REGISTERED; res = fpgadl_setup_gpio(fpgadl_dev->pdata); if (res < 0) { FAILMSG("Error registering GPIOs"); goto error; } fpgadl_dev->state = FPGADL_DEV_STATE_GPIO_REGISTERED; fpgadl_dev->miscdev.name = fpgadl_dev->name; fpgadl_dev->miscdev.minor = MISC_DYNAMIC_MINOR; fpgadl_dev->miscdev.fops = &fops_fpgadl; res = misc_register(&fpgadl_dev->miscdev); if (res < 0) { FAILMSG("Error registering misc driver"); goto error; } DBGMSG(" MINOR = %d", fpgadl_dev->miscdev.minor); fpgadl_dev->state = FPGADL_DEV_STATE_CHAR_DEV_REGISTERED; /* Try to load firmware through hotplug if available. */ if (fpgadl_dev->fw_name) { res = request_firmware(&fw_entry, fpgadl_dev->fw_name, &fpgadl_dev->dev); if (res < 0) { /* Not an error preventing the driver from being loaded. */ FAILMSG("firmware not available (err %d)", res); /* Output warning */ res = 0; } else { int retry; for (retry=0; retry<5; retry++) { res = fpgadl_bitstream_load(fpgadl_dev, fw_entry->data, fw_entry->size); if (res == 0) break; } release_firmware(fw_entry); } } fpgadl_dev->id = fpgadl_dev_count; fpgadl_dev_array[fpgadl_dev_count] = fpgadl_dev; fpgadl_dev_count++; return 0; error: fpgadl_cleanup(fpgadl_dev); return res; } EXPORT_SYMBOL(fpgadl_register_device); void fpgadl_unregister_device(struct fpgadl_device *fpgadl_dev) { DBGMSG_ENTER(); /* Warning: fix this, potential bug if removing a device in-between. */ fpgadl_dev_array[fpgadl_dev->id] = NULL; fpgadl_dev_count--; fpgadl_cleanup(fpgadl_dev); } EXPORT_SYMBOL(fpgadl_unregister_device); static int __init fpgadl_init(void) { int res; DBGMSG_ENTER(); INFOMSG("FPGA bitstream loader %s", fpgadl_driver_version); res = bus_register(&fpgadl_bus_type); if (res) { FAILMSG(" bus_register() failed"); goto fail_bus; } if (bus_create_file(&fpgadl_bus_type, &bus_attr_version)) { FAILMSG("Unable to create version attribute"); goto fail_create_file; } res = device_register(&fpgadl_bus); if (res) { FAILMSG(" failed registering %s", fpgadl_bus.init_name); goto fail_dev_reg; } return 0; fail_dev_reg: fail_create_file: bus_unregister(&fpgadl_bus_type); fail_bus: return res; } module_init(fpgadl_init); static void __exit fpgadl_exit(void) { DBGMSG_ENTER(); device_unregister(&fpgadl_bus); bus_unregister(&fpgadl_bus_type); } module_exit(fpgadl_exit); MODULE_AUTHOR("Hugo Villeneuve "); MODULE_DESCRIPTION("FPGA bitstream loader"); MODULE_LICENSE("GPL");