diff options
Diffstat (limited to 'drivers/usb/serial/io_ti.c')
| -rw-r--r-- | drivers/usb/serial/io_ti.c | 279 | 
1 files changed, 213 insertions, 66 deletions
diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index ddbb8fe1046d..0ac1b10be1f7 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -71,6 +71,25 @@ struct product_info {  	__u8	hardware_type;		/* Type of hardware */  } __attribute__((packed)); +/* + * Edgeport firmware header + * + * "build_number" has been set to 0 in all three of the images I have + * seen, and Digi Tech Support suggests that it is safe to ignore it. + * + * "length" is the number of bytes of actual data following the header. + * + * "checksum" is the low order byte resulting from adding the values of + * all the data bytes. + */ +struct edgeport_fw_hdr { +	u8 major_version; +	u8 minor_version; +	__le16 build_number; +	__le16 length; +	u8 checksum; +} __packed; +  struct edgeport_port {  	__u16 uart_base;  	__u16 dma_address; @@ -101,6 +120,9 @@ struct edgeport_serial {  	struct mutex es_lock;  	int num_ports_open;  	struct usb_serial *serial; +	struct delayed_work heartbeat_work; +	int fw_version; +	bool use_heartbeat;  }; @@ -187,10 +209,6 @@ static const struct usb_device_id id_table_combined[] = {  MODULE_DEVICE_TABLE(usb, id_table_combined); -static unsigned char OperationalMajorVersion; -static unsigned char OperationalMinorVersion; -static unsigned short OperationalBuildNumber; -  static int closing_wait = EDGE_CLOSING_WAIT;  static bool ignore_cpu_rev;  static int default_uart_mode;		/* RS232 */ @@ -209,6 +227,26 @@ static void edge_send(struct usb_serial_port *port, struct tty_struct *tty);  static int edge_create_sysfs_attrs(struct usb_serial_port *port);  static int edge_remove_sysfs_attrs(struct usb_serial_port *port); +/* + * Some release of Edgeport firmware "down3.bin" after version 4.80 + * introduced code to automatically disconnect idle devices on some + * Edgeport models after periods of inactivity, typically ~60 seconds. + * This occurs without regard to whether ports on the device are open + * or not.  Digi International Tech Support suggested: + * + * 1.  Adding driver "heartbeat" code to reset the firmware timer by + *     requesting a descriptor record every 15 seconds, which should be + *     effective with newer firmware versions that require it, and benign + *     with older versions that do not. In practice 40 seconds seems often + *     enough. + * 2.  The heartbeat code is currently required only on Edgeport/416 models. + */ +#define FW_HEARTBEAT_VERSION_CUTOFF ((4 << 8) + 80) +#define FW_HEARTBEAT_SECS 40 + +/* Timeouts in msecs: firmware downloads take longer */ +#define TI_VSEND_TIMEOUT_DEFAULT 1000 +#define TI_VSEND_TIMEOUT_FW_DOWNLOAD 10000  static int ti_vread_sync(struct usb_device *dev, __u8 request,  				__u16 value, __u16 index, u8 *data, int size) @@ -228,14 +266,14 @@ static int ti_vread_sync(struct usb_device *dev, __u8 request,  	return 0;  } -static int ti_vsend_sync(struct usb_device *dev, __u8 request, -				__u16 value, __u16 index, u8 *data, int size) +static int ti_vsend_sync(struct usb_device *dev, u8 request, u16 value, +		u16 index, u8 *data, int size, int timeout)  {  	int status;  	status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,  			(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT), -			value, index, data, size, 1000); +			value, index, data, size, timeout);  	if (status < 0)  		return status;  	if (status != size) { @@ -250,7 +288,8 @@ static int send_cmd(struct usb_device *dev, __u8 command,  				__u8 moduleid, __u16 value, u8 *data,  				int size)  { -	return ti_vsend_sync(dev, command, value, moduleid, data, size); +	return ti_vsend_sync(dev, command, value, moduleid, data, size, +			TI_VSEND_TIMEOUT_DEFAULT);  }  /* clear tx/rx buffers and fifo in TI UMP */ @@ -378,9 +417,9 @@ static int write_boot_mem(struct edgeport_serial *serial,  	}  	for (i = 0; i < length; ++i) { -		status = ti_vsend_sync(serial->serial->dev, -				UMPC_MEMORY_WRITE, buffer[i], -				(__u16)(i + start_address), NULL, 0); +		status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE, +				buffer[i], (u16)(i + start_address), NULL, +				0, TI_VSEND_TIMEOUT_DEFAULT);  		if (status)  			return status;  	} @@ -421,10 +460,9 @@ static int write_i2c_mem(struct edgeport_serial *serial,  	 *       regardless of host byte order.  	 */  	be_start_address = swab16((u16)start_address); -	status = ti_vsend_sync(serial->serial->dev, -				UMPC_MEMORY_WRITE, (__u16)address_type, -				be_start_address, -				buffer,	write_length); +	status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE, +				(u16)address_type, be_start_address, +				buffer,	write_length, TI_VSEND_TIMEOUT_DEFAULT);  	if (status) {  		dev_dbg(dev, "%s - ERROR %d\n", __func__, status);  		return status; @@ -454,9 +492,8 @@ static int write_i2c_mem(struct edgeport_serial *serial,  		 */  		be_start_address = swab16((u16)start_address);  		status = ti_vsend_sync(serial->serial->dev, UMPC_MEMORY_WRITE, -				(__u16)address_type, -				be_start_address, -				buffer, write_length); +				(u16)address_type, be_start_address, buffer, +				write_length, TI_VSEND_TIMEOUT_DEFAULT);  		if (status) {  			dev_err(dev, "%s - ERROR %d\n", __func__, status);  			return status; @@ -748,18 +785,17 @@ exit:  }  /* Build firmware header used for firmware update */ -static int build_i2c_fw_hdr(__u8 *header, struct device *dev) +static int build_i2c_fw_hdr(u8 *header, struct device *dev, +		const struct firmware *fw)  {  	__u8 *buffer;  	int buffer_size;  	int i; -	int err;  	__u8 cs = 0;  	struct ti_i2c_desc *i2c_header;  	struct ti_i2c_image_header *img_header;  	struct ti_i2c_firmware_rec *firmware_rec; -	const struct firmware *fw; -	const char *fw_name = "edgeport/down3.bin"; +	struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data;  	/* In order to update the I2C firmware we must change the type 2 record  	 * to type 0xF2.  This will force the UMP to come up in Boot Mode. @@ -782,24 +818,11 @@ static int build_i2c_fw_hdr(__u8 *header, struct device *dev)  	// Set entire image of 0xffs  	memset(buffer, 0xff, buffer_size); -	err = request_firmware(&fw, fw_name, dev); -	if (err) { -		dev_err(dev, "Failed to load image \"%s\" err %d\n", -			fw_name, err); -		kfree(buffer); -		return err; -	} - -	/* Save Download Version Number */ -	OperationalMajorVersion = fw->data[0]; -	OperationalMinorVersion = fw->data[1]; -	OperationalBuildNumber = fw->data[2] | (fw->data[3] << 8); -  	/* Copy version number into firmware record */  	firmware_rec = (struct ti_i2c_firmware_rec *)buffer; -	firmware_rec->Ver_Major	= OperationalMajorVersion; -	firmware_rec->Ver_Minor	= OperationalMinorVersion; +	firmware_rec->Ver_Major	= fw_hdr->major_version; +	firmware_rec->Ver_Minor	= fw_hdr->minor_version;  	/* Pointer to fw_down memory image */  	img_header = (struct ti_i2c_image_header *)&fw->data[4]; @@ -808,8 +831,6 @@ static int build_i2c_fw_hdr(__u8 *header, struct device *dev)  		&fw->data[4 + sizeof(struct ti_i2c_image_header)],  		le16_to_cpu(img_header->Length)); -	release_firmware(fw); -  	for (i=0; i < buffer_size; i++) {  		cs = (__u8)(cs + buffer[i]);  	} @@ -823,8 +844,8 @@ static int build_i2c_fw_hdr(__u8 *header, struct device *dev)  	i2c_header->Type	= I2C_DESC_TYPE_FIRMWARE_BLANK;  	i2c_header->Size	= cpu_to_le16(buffer_size);  	i2c_header->CheckSum	= cs; -	firmware_rec->Ver_Major	= OperationalMajorVersion; -	firmware_rec->Ver_Minor	= OperationalMinorVersion; +	firmware_rec->Ver_Major	= fw_hdr->major_version; +	firmware_rec->Ver_Minor	= fw_hdr->minor_version;  	return 0;  } @@ -925,13 +946,49 @@ static int ti_cpu_rev(struct edge_ti_manuf_descriptor *desc)  	return TI_GET_CPU_REVISION(desc->CpuRev_BoardRev);  } +static int check_fw_sanity(struct edgeport_serial *serial, +		const struct firmware *fw) +{ +	u16 length_total; +	u8 checksum = 0; +	int pos; +	struct device *dev = &serial->serial->interface->dev; +	struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + +	if (fw->size < sizeof(struct edgeport_fw_hdr)) { +		dev_err(dev, "incomplete fw header\n"); +		return -EINVAL; +	} + +	length_total = le16_to_cpu(fw_hdr->length) + +			sizeof(struct edgeport_fw_hdr); + +	if (fw->size != length_total) { +		dev_err(dev, "bad fw size (expected: %u, got: %zu)\n", +				length_total, fw->size); +		return -EINVAL; +	} + +	for (pos = sizeof(struct edgeport_fw_hdr); pos < fw->size; ++pos) +		checksum += fw->data[pos]; + +	if (checksum != fw_hdr->checksum) { +		dev_err(dev, "bad fw checksum (expected: 0x%x, got: 0x%x)\n", +				fw_hdr->checksum, checksum); +		return -EINVAL; +	} + +	return 0; +} +  /**   * DownloadTIFirmware - Download run-time operating firmware to the TI5052   *   * This routine downloads the main operating code into the TI5052, using the   * boot code already burned into E2PROM or ROM.   */ -static int download_fw(struct edgeport_serial *serial) +static int download_fw(struct edgeport_serial *serial, +		const struct firmware *fw)  {  	struct device *dev = &serial->serial->dev->dev;  	int status = 0; @@ -940,6 +997,14 @@ static int download_fw(struct edgeport_serial *serial)  	struct usb_interface_descriptor *interface;  	int download_cur_ver;  	int download_new_ver; +	struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + +	if (check_fw_sanity(serial, fw)) +		return -EINVAL; + +	/* If on-board version is newer, "fw_version" will be updated below. */ +	serial->fw_version = (fw_hdr->major_version << 8) + +			fw_hdr->minor_version;  	/* This routine is entered by both the BOOT mode and the Download mode  	 * We can determine which code is running by the reading the config @@ -1047,14 +1112,13 @@ static int download_fw(struct edgeport_serial *serial)  			   version in I2c */  			download_cur_ver = (firmware_version->Ver_Major << 8) +  					   (firmware_version->Ver_Minor); -			download_new_ver = (OperationalMajorVersion << 8) + -					   (OperationalMinorVersion); +			download_new_ver = (fw_hdr->major_version << 8) + +					   (fw_hdr->minor_version);  			dev_dbg(dev, "%s - >> FW Versions Device %d.%d  Driver %d.%d\n",  				__func__, firmware_version->Ver_Major,  				firmware_version->Ver_Minor, -				OperationalMajorVersion, -				OperationalMinorVersion); +				fw_hdr->major_version, fw_hdr->minor_version);  			/* Check if we have an old version in the I2C and  			   update if necessary */ @@ -1063,8 +1127,8 @@ static int download_fw(struct edgeport_serial *serial)  					__func__,  					firmware_version->Ver_Major,  					firmware_version->Ver_Minor, -					OperationalMajorVersion, -					OperationalMinorVersion); +					fw_hdr->major_version, +					fw_hdr->minor_version);  				record = kmalloc(1, GFP_KERNEL);  				if (!record) { @@ -1129,7 +1193,8 @@ static int download_fw(struct edgeport_serial *serial)  				/* Reset UMP -- Back to BOOT MODE */  				status = ti_vsend_sync(serial->serial->dev,  						UMPC_HARDWARE_RESET, -						0, 0, NULL, 0); +						0, 0, NULL, 0, +						TI_VSEND_TIMEOUT_DEFAULT);  				dev_dbg(dev, "%s - HARDWARE RESET return %d\n", __func__, status); @@ -1139,6 +1204,9 @@ static int download_fw(struct edgeport_serial *serial)  				kfree(rom_desc);  				kfree(ti_manuf_desc);  				return -ENODEV; +			} else { +				/* Same or newer fw version is already loaded */ +				serial->fw_version = download_cur_ver;  			}  			kfree(firmware_version);  		} @@ -1177,7 +1245,7 @@ static int download_fw(struct edgeport_serial *serial)  			 * UMP Ram to I2C and the firmware will update the  			 * record type from 0xf2 to 0x02.  			 */ -			status = build_i2c_fw_hdr(header, dev); +			status = build_i2c_fw_hdr(header, dev, fw);  			if (status) {  				kfree(vheader);  				kfree(header); @@ -1229,7 +1297,9 @@ static int download_fw(struct edgeport_serial *serial)  			/* Tell firmware to copy download image into I2C */  			status = ti_vsend_sync(serial->serial->dev, -					UMPC_COPY_DNLD_TO_I2C, 0, 0, NULL, 0); +					UMPC_COPY_DNLD_TO_I2C, +					0, 0, NULL, 0, +					TI_VSEND_TIMEOUT_FW_DOWNLOAD);  		  	dev_dbg(dev, "%s - Update complete 0x%x\n", __func__, status);  			if (status) { @@ -1278,9 +1348,6 @@ static int download_fw(struct edgeport_serial *serial)  		__u8 cs = 0;  		__u8 *buffer;  		int buffer_size; -		int err; -		const struct firmware *fw; -		const char *fw_name = "edgeport/down3.bin";  		/* Validate Hardware version number  		 * Read Manufacturing Descriptor from TI Based Edgeport @@ -1328,16 +1395,7 @@ static int download_fw(struct edgeport_serial *serial)  		/* Initialize the buffer to 0xff (pad the buffer) */  		memset(buffer, 0xff, buffer_size); - -		err = request_firmware(&fw, fw_name, dev); -		if (err) { -			dev_err(dev, "Failed to load image \"%s\" err %d\n", -				fw_name, err); -			kfree(buffer); -			return err; -		}  		memcpy(buffer, &fw->data[4], fw->size - 4); -		release_firmware(fw);  		for (i = sizeof(struct ti_i2c_image_header);  				i < buffer_size; i++) { @@ -1352,7 +1410,9 @@ static int download_fw(struct edgeport_serial *serial)  		header->CheckSum = cs;  		/* Download the operational code  */ -		dev_dbg(dev, "%s - Downloading operational code image (TI UMP)\n", __func__); +		dev_dbg(dev, "%s - Downloading operational code image version %d.%d (TI UMP)\n", +				__func__, +				fw_hdr->major_version, fw_hdr->minor_version);  		status = download_code(serial, buffer, buffer_size);  		kfree(buffer); @@ -2373,10 +2433,44 @@ static void edge_break(struct tty_struct *tty, int break_state)  			__func__, status);  } +static void edge_heartbeat_schedule(struct edgeport_serial *edge_serial) +{ +	if (!edge_serial->use_heartbeat) +		return; + +	schedule_delayed_work(&edge_serial->heartbeat_work, +			FW_HEARTBEAT_SECS * HZ); +} + +static void edge_heartbeat_work(struct work_struct *work) +{ +	struct edgeport_serial *serial; +	struct ti_i2c_desc *rom_desc; + +	serial = container_of(work, struct edgeport_serial, +			heartbeat_work.work); + +	rom_desc = kmalloc(sizeof(*rom_desc), GFP_KERNEL); + +	/* Descriptor address request is enough to reset the firmware timer */ +	if (!rom_desc || !get_descriptor_addr(serial, I2C_DESC_TYPE_ION, +			rom_desc)) { +		dev_err(&serial->serial->interface->dev, +				"%s - Incomplete heartbeat\n", __func__); +	} +	kfree(rom_desc); + +	edge_heartbeat_schedule(serial); +} +  static int edge_startup(struct usb_serial *serial)  {  	struct edgeport_serial *edge_serial;  	int status; +	const struct firmware *fw; +	const char *fw_name = "edgeport/down3.bin"; +	struct device *dev = &serial->interface->dev; +	u16 product_id;  	/* create our private serial structure */  	edge_serial = kzalloc(sizeof(struct edgeport_serial), GFP_KERNEL); @@ -2387,12 +2481,35 @@ static int edge_startup(struct usb_serial *serial)  	edge_serial->serial = serial;  	usb_set_serial_data(serial, edge_serial); -	status = download_fw(edge_serial); +	status = request_firmware(&fw, fw_name, dev);  	if (status) { +		dev_err(dev, "Failed to load image \"%s\" err %d\n", +				fw_name, status);  		kfree(edge_serial);  		return status;  	} +	status = download_fw(edge_serial, fw); +	release_firmware(fw); +	if (status) { +		kfree(edge_serial); +		return status; +	} + +	product_id = le16_to_cpu( +			edge_serial->serial->dev->descriptor.idProduct); + +	/* Currently only the EP/416 models require heartbeat support */ +	if (edge_serial->fw_version > FW_HEARTBEAT_VERSION_CUTOFF) { +		if (product_id == ION_DEVICE_ID_TI_EDGEPORT_416 || +			product_id == ION_DEVICE_ID_TI_EDGEPORT_416B) { +			edge_serial->use_heartbeat = true; +		} +	} + +	INIT_DELAYED_WORK(&edge_serial->heartbeat_work, edge_heartbeat_work); +	edge_heartbeat_schedule(edge_serial); +  	return 0;  } @@ -2402,7 +2519,10 @@ static void edge_disconnect(struct usb_serial *serial)  static void edge_release(struct usb_serial *serial)  { -	kfree(usb_get_serial_data(serial)); +	struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + +	cancel_delayed_work_sync(&edge_serial->heartbeat_work); +	kfree(edge_serial);  }  static int edge_port_probe(struct usb_serial_port *port) @@ -2506,6 +2626,25 @@ static int edge_remove_sysfs_attrs(struct usb_serial_port *port)  	return 0;  } +#ifdef CONFIG_PM +static int edge_suspend(struct usb_serial *serial, pm_message_t message) +{ +	struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + +	cancel_delayed_work_sync(&edge_serial->heartbeat_work); + +	return 0; +} + +static int edge_resume(struct usb_serial *serial) +{ +	struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + +	edge_heartbeat_schedule(edge_serial); + +	return 0; +} +#endif  static struct usb_serial_driver edgeport_1port_device = {  	.driver = { @@ -2538,6 +2677,10 @@ static struct usb_serial_driver edgeport_1port_device = {  	.read_int_callback	= edge_interrupt_callback,  	.read_bulk_callback	= edge_bulk_in_callback,  	.write_bulk_callback	= edge_bulk_out_callback, +#ifdef CONFIG_PM +	.suspend		= edge_suspend, +	.resume			= edge_resume, +#endif  };  static struct usb_serial_driver edgeport_2port_device = { @@ -2571,6 +2714,10 @@ static struct usb_serial_driver edgeport_2port_device = {  	.read_int_callback	= edge_interrupt_callback,  	.read_bulk_callback	= edge_bulk_in_callback,  	.write_bulk_callback	= edge_bulk_out_callback, +#ifdef CONFIG_PM +	.suspend		= edge_suspend, +	.resume			= edge_resume, +#endif  };  static struct usb_serial_driver * const serial_drivers[] = {  |