[PATCH] USB: serial: ftdi_sio: only bind FT232H if UART mode is enabled

From: Anatolij Gustschin
Date: Thu Jul 13 2017 - 12:25:41 EST


On FT232H the interface mode can be configured in the EEPROM,
and the async UART mode is configured by default. The chip is
also in async UART mode if no EEPROM is connected.

Check the EEPROM configuration and do not bind as serial device
when different interface mode is programmed in the EEPROM.

Signed-off-by: Anatolij Gustschin <agust@xxxxxxx>
---
drivers/usb/serial/ftdi_sio.c | 62 ++++++++++++++++++++++++++++++++++++++++++-
drivers/usb/serial/ftdi_sio.h | 12 +++++++++
2 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
index 1cec037..624ce8f 100644
--- a/drivers/usb/serial/ftdi_sio.c
+++ b/drivers/usb/serial/ftdi_sio.c
@@ -92,6 +92,7 @@ static int ftdi_stmclite_probe(struct usb_serial *serial);
static int ftdi_8u2232c_probe(struct usb_serial *serial);
static void ftdi_USB_UIRT_setup(struct ftdi_private *priv);
static void ftdi_HE_TIRA1_setup(struct ftdi_private *priv);
+static int ftdi_ft232h_probe(struct usb_serial *serial);

static const struct ftdi_sio_quirk ftdi_jtag_quirk = {
.probe = ftdi_jtag_probe,
@@ -117,6 +118,10 @@ static const struct ftdi_sio_quirk ftdi_8u2232c_quirk = {
.probe = ftdi_8u2232c_probe,
};

+static const struct ftdi_sio_quirk ftdi_ft232h_quirk = {
+ .probe = ftdi_ft232h_probe,
+};
+
/*
* The 8U232AM has the same API as the sio except for:
* - it can support MUCH higher baudrates; up to:
@@ -173,7 +178,8 @@ static const struct usb_device_id id_table_combined[] = {
{ USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) ,
.driver_info = (kernel_ulong_t)&ftdi_8u2232c_quirk },
{ USB_DEVICE(FTDI_VID, FTDI_4232H_PID) },
- { USB_DEVICE(FTDI_VID, FTDI_232H_PID) },
+ { USB_DEVICE(FTDI_VID, FTDI_232H_PID),
+ .driver_info = (kernel_ulong_t)&ftdi_ft232h_quirk },
{ USB_DEVICE(FTDI_VID, FTDI_FTX_PID) },
{ USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) },
{ USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) },
@@ -1905,6 +1911,60 @@ static int ftdi_8u2232c_probe(struct usb_serial *serial)
}

/*
+ * Check this amount of FT232H EEPROM bytes. If all are 0xff,
+ * we assume that the EEPROM is erased or is not populated.
+ * Then, async. UART is the default interface mode.
+ */
+#define EEPROM_CHK_BUF_SZ 6
+
+static inline bool ftdi_sio_chk_erased(const void *buf, int size)
+{
+ const unsigned char *data = buf;
+ unsigned int i;
+
+ for (i = 0; i < size; i++)
+ if (data[i] != 0xff)
+ return false;
+ return true;
+}
+
+static int ftdi_ft232h_probe(struct usb_serial *serial)
+{
+ struct usb_device *udev = serial->dev;
+ unsigned char *buf;
+ unsigned int i;
+ int ret;
+
+ buf = kmalloc(EEPROM_CHK_BUF_SZ, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Try to read the configured interface mode in EEPROM */
+ for (i = 0; i < EEPROM_CHK_BUF_SZ / 2; i++) {
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ FTDI_SIO_READ_EEPROM_REQUEST,
+ FTDI_SIO_READ_EEPROM_REQUEST_TYPE,
+ 0, i, buf + i * 2, 2,
+ USB_CTRL_GET_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&udev->dev, "reading EEPROM failed: %d\n", ret);
+ goto out;
+ }
+ }
+
+ ret = 0;
+ if (ftdi_sio_chk_erased(buf, EEPROM_CHK_BUF_SZ))
+ goto out;
+
+ /* Lower nibble contains interface mode bits, if zero -> UART mode */
+ if (buf[0] & 0x0f)
+ ret = -ENODEV; /* No UART, don't bind! */
+out:
+ kfree(buf);
+ return ret;
+}
+
+/*
* First two ports on JTAG adaptors using an FT4232 such as STMicroelectronics's
* ST Micro Connect Lite are reserved for JTAG or other non-UART interfaces and
* can be accessed from userspace.
diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h
index bbcc13df..82d56cb 100644
--- a/drivers/usb/serial/ftdi_sio.h
+++ b/drivers/usb/serial/ftdi_sio.h
@@ -432,6 +432,18 @@ enum ftdi_sio_baudrate {
* 1 = active
*/

+/*
+ * BmRequestType: 1100 0000b
+ * bRequest: FTDI_SIO_READ_EEPROM_REQUEST
+ * wValue: 0
+ * wIndex: Address of word to read
+ * wLength: 2
+ * Data: return a word of data from EEPROM
+ * at address in wIndex
+ */
+#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE \
+ (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN)
+#define FTDI_SIO_READ_EEPROM_REQUEST 0x90


/* Descriptors returned by the device
--
2.7.4