Based on an earlier patch submitted by Christina Quast: https://patches.linaro.org/project/linux-serial/patch/20220928192421.11908-1-contact@xxxxxxxxxxxxxxxxxx/ Simplified and reworked to use the UART API rather than the TTY layer directly. Transmit, receive and baud rate changes are supported. Signed-off-by: Daniel Beer <daniel.beer@xxxxxxxxxxxxxxxxx> --- drivers/hid/hid-ft260.c | 586 +++++++++++++++++++++++++++++-- include/uapi/linux/major.h | 2 + include/uapi/linux/serial_core.h | 3 + 3 files changed, 552 insertions(+), 39 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 333341e80b0e..0d0afb5ec3da 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -13,6 +13,17 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/usb.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/kfifo.h> +#include <linux/tty_flip.h> +#include <linux/minmax.h> +#include <linux/timer.h> +#include <asm-generic/unaligned.h> + +#define UART_COUNT_MAX 4 /* Number of UARTs this driver can handle */ +#define FIFO_SIZE 256 +#define TTY_WAKEUP_WATERMARK (FIFO_SIZE / 2) #ifdef DEBUG static int ft260_debug = 1; @@ -30,8 +41,8 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages"); #define FT260_REPORT_MAX_LENGTH (64) #define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4) - #define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */ +#define FT260_UART_DATA_REPORT_ID(len) (FT260_UART_REPORT_MIN + (len - 1) / 4) /* * The ft260 input report format defines 62 bytes for the data payload, but @@ -81,7 +92,8 @@ enum { FT260_UART_INTERRUPT_STATUS = 0xB1, FT260_UART_STATUS = 0xE0, FT260_UART_RI_DCD_STATUS = 0xE1, - FT260_UART_REPORT = 0xF0, + FT260_UART_REPORT_MIN = 0xF0, + FT260_UART_REPORT_MAX = 0xFE, }; /* Feature Out */ @@ -220,12 +232,59 @@ struct ft260_i2c_read_request_report { __le16 length; /* data payload length */ } __packed; -struct ft260_i2c_input_report { - u8 report; /* FT260_I2C_REPORT */ +struct ft260_input_report { + u8 report; /* FT260_I2C_REPORT or FT260_UART_REPORT */ u8 length; /* data payload length */ - u8 data[2]; /* data payload */ + u8 data[0]; /* data payload */ +} __packed; + +/* UART reports */ + +struct ft260_uart_write_request_report { + u8 report; /* FT260_UART_REPORT */ + u8 length; /* data payload length */ + u8 data[FT260_WR_DATA_MAX]; /* data payload */ +} __packed; + +struct ft260_configure_uart_request { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_UART_CONFIG */ + u8 flow_ctrl; /* 0: OFF, 1: RTS_CTS, 2: DTR_DSR */ + /* 3: XON_XOFF, 4: No flow ctrl */ + __le32 baudrate; /* little endian, 9600 = 0x2580, 19200 = 0x4B00 */ + u8 data_bit; /* 7 or 8 */ + u8 parity; /* 0: no parity, 1: odd, 2: even, 3: high, 4: low */ + u8 stop_bit; /* 0: one stop bit, 1: 2 stop bits */ + u8 breaking; /* 0: no break */ } __packed; +/* UART interface configuration */ +enum { + FT260_CFG_FLOW_CTRL_OFF = 0x00, + FT260_CFG_FLOW_CTRL_RTS_CTS = 0x01, + FT260_CFG_FLOW_CTRL_DTR_DSR = 0x02, + FT260_CFG_FLOW_CTRL_XON_XOFF = 0x03, + FT260_CFG_FLOW_CTRL_NONE = 0x04, + + FT260_CFG_DATA_BITS_7 = 0x07, + FT260_CFG_DATA_BITS_8 = 0x08, + + FT260_CFG_PAR_NO = 0x00, + FT260_CFG_PAR_ODD = 0x01, + FT260_CFG_PAR_EVEN = 0x02, + FT260_CFG_PAR_HIGH = 0x03, + FT260_CFG_PAR_LOW = 0x04, + + FT260_CFG_STOP_ONE_BIT = 0x00, + FT260_CFG_STOP_TWO_BIT = 0x01, + + FT260_CFG_BREAKING_NO = 0x00, + FT260_CFG_BREAKING_YES = 0x01, + + FT260_CFG_BAUD_MIN = 1200, + FT260_CFG_BAUD_MAX = 12000000, +}; + static const struct hid_device_id ft260_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_FT260) }, @@ -236,6 +295,7 @@ MODULE_DEVICE_TABLE(hid, ft260_devices); struct ft260_device { struct i2c_adapter adap; struct hid_device *hdev; + struct completion wait; struct mutex lock; u8 write_buf[FT260_REPORT_MAX_LENGTH]; @@ -244,6 +304,13 @@ struct ft260_device { u16 read_idx; u16 read_len; u16 clock; + + int ft260_is_serial; + struct uart_port port; + struct work_struct tx_work; + + struct timer_list wakeup_timer; + struct work_struct wakeup_work; }; static int ft260_hid_feature_report_get(struct hid_device *hdev, @@ -803,12 +870,12 @@ static int ft260_is_interface_enabled(struct hid_device *hdev) case FT260_MODE_ALL: case FT260_MODE_BOTH: if (interface == 1) - hid_info(hdev, "uart interface is not supported\n"); + ret = 2; else ret = 1; break; case FT260_MODE_UART: - hid_info(hdev, "uart interface is not supported\n"); + ret = 2; break; case FT260_MODE_I2C: ret = 1; @@ -957,6 +1024,427 @@ static const struct attribute_group ft260_attr_group = { } }; +static struct ft260_device *ft260_uart_table[UART_COUNT_MAX]; +static DEFINE_MUTEX(ft260_uart_table_lock); + +static int ft260_tx_data(struct ft260_device *port, const u8 *data, + unsigned int len) +{ + struct ft260_uart_write_request_report *rep = + (struct ft260_uart_write_request_report *)port->write_buf; + + while (len) { + unsigned int r = len; + int ret; + + if (r > FT260_WR_DATA_MAX) + r = FT260_WR_DATA_MAX; + + rep->report = FT260_UART_DATA_REPORT_ID(r); + rep->length = r; + memcpy(rep->data, data, r); + + ret = ft260_hid_output_report(port->hdev, port->write_buf, + r + sizeof(*rep)); + if (ret < 0) { + hid_err(port->hdev, + "%s: failed to start transfer, ret %d\n", + __func__, ret); + return ret; + } + + data += r; + len -= r; + } + + return 0; +} + +/* The FT260 has a "power saving mode" that causes the device to switch + * to a 30 kHz oscillator if there's no activity for 5 seconds. + * Unfortunately this mode can only be disabled by reprogramming + * internal fuses, which requires an additional programming voltage. + * + * One effect of this mode is to cause data loss on a fast UART that + * transmits after being idle for longer than 5 seconds. We work around + * this by sending a dummy report at least once per 4 seconds if the + * UART is in use. + */ +static void ft260_uart_start_wakeup(struct timer_list *t) +{ + struct ft260_device *dev = + container_of(t, struct ft260_device, wakeup_timer); + + schedule_work(&dev->wakeup_work); + mod_timer(&dev->wakeup_timer, jiffies + + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS)); +} + +static void ft260_uart_do_wakeup(struct work_struct *work) +{ + struct ft260_device *dev = + container_of(work, struct ft260_device, wakeup_work); + struct ft260_get_chip_version_report version; + int ret; + + ret = ft260_hid_feature_report_get(dev->hdev, FT260_CHIP_VERSION, + (u8 *)&version, sizeof(version)); + if (ret < 0) + hid_err(dev->hdev, + "%s: failed to start transfer, ret %d\n", + __func__, ret); +} + +static void ft260_uart_do_tx(struct work_struct *work) +{ + struct ft260_device *dev = + container_of(work, struct ft260_device, tx_work); + struct uart_port *port = &dev->port; + struct circ_buf *xmit = &port->state->xmit; + int to_send; + + if (port->x_char) { + ft260_tx_data(dev, (u8 *)&port->x_char, 1); + port->x_char = 0; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) + return; + + to_send = uart_circ_chars_pending(xmit); + + if (to_send) { + int until_end = CIRC_CNT_TO_END(xmit->head, + xmit->tail, UART_XMIT_SIZE); + + if (until_end < to_send) { + ft260_tx_data(dev, xmit->buf + xmit->tail, until_end); + ft260_tx_data(dev, xmit->buf, to_send - until_end); + } else { + ft260_tx_data(dev, xmit->buf + xmit->tail, to_send); + } + + port->icount.tx += to_send; + xmit->tail = (xmit->tail + to_send) & (UART_XMIT_SIZE - 1); + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); +} + +static void ft260_uart_start_tx(struct uart_port *port) +{ + struct ft260_device *dev = + container_of(port, struct ft260_device, port); + + schedule_work(&dev->tx_work); +} + +static void ft260_uart_stop_tx(struct uart_port *port) +{ + struct ft260_device *dev = + container_of(port, struct ft260_device, port); + + cancel_work(&dev->tx_work); +} + +static void ft260_uart_receive_chars(struct ft260_device *dev, + u8 *data, u8 length) +{ + struct uart_port *port = &dev->port; + int i; + + for (i = 0; i < length; i++) + uart_insert_char(port, 0, 0, data[i], TTY_NORMAL); + port->icount.rx += length; + + tty_flip_buffer_push(&port->state->port); +} + +static void ft260_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old_termios) +{ + struct ft260_device *dev = container_of(port, struct ft260_device, port); + struct hid_device *hdev = dev->hdev; + unsigned int baud; + struct ft260_configure_uart_request req; + int ret; + + ft260_dbg("%s uart\n", __func__); + memset(&req, 0, sizeof(req)); + + req.report = FT260_SYSTEM_SETTINGS; + req.request = FT260_SET_UART_CONFIG; + + switch (termios->c_cflag & CSIZE) { + case CS7: + req.data_bit = FT260_CFG_DATA_BITS_7; + break; + case CS5: + case CS6: + hid_err(hdev, + "Invalid data bit size, setting to default (8 bit)\n"); + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + fallthrough; + default: + case CS8: + req.data_bit = 0x08; + break; + } + + req.stop_bit = (termios->c_cflag & CSTOPB) ? + FT260_CFG_STOP_TWO_BIT : FT260_CFG_STOP_ONE_BIT; + + if (termios->c_cflag & PARENB) { + req.parity = (termios->c_cflag & PARODD) ? + FT260_CFG_PAR_ODD : FT260_CFG_PAR_EVEN; + } else { + req.parity = FT260_CFG_PAR_NO; + } + + baud = tty_termios_baud_rate(termios); + if (baud == 0 || baud < FT260_CFG_BAUD_MIN || baud > FT260_CFG_BAUD_MAX) { + hid_err(hdev, + "Invalid baud rate %d, setting to default (9600)\n", + baud); + baud = 9600; + tty_termios_encode_baud_rate(termios, baud, baud); + } + put_unaligned_le32(baud, &req.baudrate); + + if (termios->c_cflag & CRTSCTS) + req.flow_ctrl = FT260_CFG_FLOW_CTRL_RTS_CTS; + else + req.flow_ctrl = FT260_CFG_FLOW_CTRL_NONE; + + ft260_dbg("Configured termios: flow control: %d, baudrate: %d, ", + req.flow_ctrl, baud); + ft260_dbg("data_bit: %d, parity: %d, stop_bit: %d, breaking: %d\n", + req.data_bit, req.parity, + req.stop_bit, req.breaking); + + ret = ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req)); + if (ret < 0) + hid_err(hdev, "ft260_hid_feature_report_set failed: %d\n", ret); +} + +static int ft260_i2c_probe(struct hid_device *hdev, struct ft260_device *dev) +{ + int ret; + + ft260_dbg("%s i2c\n", __func__); + + dev->adap.owner = THIS_MODULE; + dev->adap.class = I2C_CLASS_HWMON; + dev->adap.algo = &ft260_i2c_algo; + dev->adap.quirks = &ft260_i2c_quirks; + dev->adap.dev.parent = &hdev->dev; + snprintf(dev->adap.name, sizeof(dev->adap.name), + "FT260 usb-i2c bridge"); + + ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY); + if (ret) + ft260_i2c_reset(hdev); + + i2c_set_adapdata(&dev->adap, dev); + ret = i2c_add_adapter(&dev->adap); + if (ret) { + hid_err(hdev, "failed to add i2c adapter\n"); + return ret; + } + + ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); + if (ret < 0) { + hid_err(hdev, "failed to create sysfs attrs\n"); + goto err_i2c_free; + } +err_i2c_free: + i2c_del_adapter(&dev->adap); + return ret; +} + +static unsigned int ft260_uart_tx_empty(struct uart_port *port) +{ + /* Not implemented */ + return TIOCSER_TEMT; +} + +static unsigned int ft260_uart_get_mctrl(struct uart_port *port) +{ + /* Not implemented */ + return TIOCM_DSR | TIOCM_CAR; +} + +static void ft260_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* Not implemented */ +} + +static void ft260_uart_break_ctl(struct uart_port *port, int break_state) +{ + /* Not implemented */ +} + +static int ft260_uart_startup(struct uart_port *port) +{ + struct ft260_device *dev = + container_of(port, struct ft260_device, port); + + ft260_dbg("%s uart\n", __func__); + mod_timer(&dev->wakeup_timer, jiffies + + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS)); + return 0; +} + +static void ft260_uart_shutdown(struct uart_port *port) +{ + struct ft260_device *dev = + container_of(port, struct ft260_device, port); + + ft260_dbg("%s uart\n", __func__); + del_timer_sync(&dev->wakeup_timer); +} + +static const char *ft260_uart_type(struct uart_port *port) +{ + return (port->type == PORT_FT260) ? "FT260 UART" : NULL; +} + +static int ft260_uart_request_port(struct uart_port *port) +{ + /* Not implemented */ + ft260_dbg("%s uart\n", __func__); + return 0; +} + +static void ft260_uart_release_port(struct uart_port *port) +{ + /* Not implemented */ + ft260_dbg("%s uart\n", __func__); +} + +static void ft260_uart_config_port(struct uart_port *port, int flags) +{ + ft260_dbg("%s uart\n", __func__); + port->type = PORT_FT260; +} + +static int ft260_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_FT260) + return -EINVAL; + return 0; +} + +static void ft260_uart_stop_rx(struct uart_port *port) +{ + /* Not implemented */ +} + +static const struct uart_ops ft260_uart_ops = { + .tx_empty = ft260_uart_tx_empty, + .get_mctrl = ft260_uart_get_mctrl, + .set_mctrl = ft260_uart_set_mctrl, + .start_tx = ft260_uart_start_tx, + .stop_tx = ft260_uart_stop_tx, + .stop_rx = ft260_uart_stop_rx, + .break_ctl = ft260_uart_break_ctl, + .startup = ft260_uart_startup, + .shutdown = ft260_uart_shutdown, + .set_termios = ft260_uart_set_termios, + .type = ft260_uart_type, + .request_port = ft260_uart_request_port, + .release_port = ft260_uart_release_port, + .config_port = ft260_uart_config_port, + .verify_port = ft260_uart_verify_port, +}; + +static struct uart_driver ft260_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "ft260_uart", + .dev_name = "ttyFT", + .major = FT260_MAJOR, + .minor = 0, + .nr = UART_COUNT_MAX, +}; + +static int alloc_port_number(struct ft260_device *dev) +{ + int ret = -ENOMEM; + int i; + + mutex_lock(&ft260_uart_table_lock); + for (i = 0; i < ARRAY_SIZE(ft260_uart_table); i++) { + if (!ft260_uart_table[i]) { + ft260_uart_table[i] = dev; + ret = i; + break; + } + } + mutex_unlock(&ft260_uart_table_lock); + + return ret; +} + +static void free_port_number(struct ft260_device *dev) +{ + int line = dev->port.line; + + mutex_lock(&ft260_uart_table_lock); + WARN_ON(ft260_uart_table[line] != dev); + ft260_uart_table[line] = NULL; + mutex_unlock(&ft260_uart_table_lock); +} + +static int ft260_uart_probe(struct hid_device *hdev, struct ft260_device *dev) +{ + struct ft260_configure_uart_request req; + int line; + int ret; + + ft260_dbg("%s uart\n", __func__); + + /* Send Feature Report to Configure FT260 as UART 9600-8-N-1 */ + memset(&req, 0, sizeof(req)); + req.report = FT260_SYSTEM_SETTINGS; + req.request = FT260_SET_UART_CONFIG; + req.flow_ctrl = FT260_CFG_FLOW_CTRL_NONE; + put_unaligned_le32(9600, &req.baudrate); + req.data_bit = FT260_CFG_DATA_BITS_8; + req.parity = FT260_CFG_PAR_NO; + req.stop_bit = FT260_CFG_STOP_ONE_BIT; + req.breaking = FT260_CFG_BREAKING_NO; + + ret = ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req)); + if (ret < 0) { + hid_err(hdev, "ft260_hid_feature_report_set failed: %d\n", ret); + return ret; + } + + line = alloc_port_number(dev); + if (line < 0) { + hid_err(hdev, "too many ports\n"); + return line; + } + + dev->port.line = line; + dev->port.type = PORT_FT260; + dev->port.dev = &hdev->dev; + dev->port.iotype = SERIAL_IO_MEM; + dev->port.ops = &ft260_uart_ops; + dev->port.flags = UPF_BOOT_AUTOCONF; + + INIT_WORK(&dev->tx_work, ft260_uart_do_tx); + INIT_WORK(&dev->wakeup_work, ft260_uart_do_wakeup); + timer_setup(&dev->wakeup_timer, ft260_uart_start_wakeup, 0); + + uart_add_one_port(&ft260_uart_driver, &dev->port); + return 0; +} + static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct ft260_device *dev; @@ -1009,38 +1497,21 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_set_drvdata(hdev, dev); dev->hdev = hdev; - dev->adap.owner = THIS_MODULE; - dev->adap.class = I2C_CLASS_HWMON; - dev->adap.algo = &ft260_i2c_algo; - dev->adap.quirks = &ft260_i2c_quirks; - dev->adap.dev.parent = &hdev->dev; - snprintf(dev->adap.name, sizeof(dev->adap.name), - "FT260 usb-i2c bridge"); - mutex_init(&dev->lock); init_completion(&dev->wait); - ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY); - if (ret) - ft260_i2c_reset(hdev); - - i2c_set_adapdata(&dev->adap, dev); - ret = i2c_add_adapter(&dev->adap); - if (ret) { - hid_err(hdev, "failed to add i2c adapter\n"); - goto err_hid_close; - } - - ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); - if (ret < 0) { - hid_err(hdev, "failed to create sysfs attrs\n"); - goto err_i2c_free; + if (!dev->ft260_is_serial) { + ret = ft260_i2c_probe(hdev, dev); + if (ret) + goto err_hid_close; + } else { + ret = ft260_uart_probe(hdev, dev); + if (ret) + goto err_hid_close; } return 0; -err_i2c_free: - i2c_del_adapter(&dev->adap); err_hid_close: hid_hw_close(hdev); err_hid_stop: @@ -1055,8 +1526,15 @@ static void ft260_remove(struct hid_device *hdev) if (!dev) return; - sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); - i2c_del_adapter(&dev->adap); + if (dev->ft260_is_serial) { + cancel_work_sync(&dev->tx_work); + cancel_work_sync(&dev->wakeup_work); + uart_remove_one_port(&ft260_uart_driver, &dev->port); + free_port_number(dev); + } else { + sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + i2c_del_adapter(&dev->adap); + } hid_hw_close(hdev); hid_hw_stop(hdev); @@ -1066,10 +1544,11 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct ft260_device *dev = hid_get_drvdata(hdev); - struct ft260_i2c_input_report *xfer = (void *)data; + struct ft260_input_report *xfer = (void *)data; if (xfer->report >= FT260_I2C_REPORT_MIN && - xfer->report <= FT260_I2C_REPORT_MAX) { + xfer->report <= FT260_I2C_REPORT_MAX && + !dev->ft260_is_serial) { ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report, xfer->length); @@ -1086,10 +1565,14 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, if (dev->read_idx == dev->read_len) complete(&dev->wait); - + } else if (xfer->report >= FT260_UART_REPORT_MIN && + xfer->report <= FT260_UART_REPORT_MAX && + dev->ft260_is_serial) { + ft260_uart_receive_chars(dev, xfer->data, xfer->length); } else { hid_err(hdev, "unhandled report %#02x\n", xfer->report); } + return 0; } @@ -1101,7 +1584,32 @@ static struct hid_driver ft260_driver = { .raw_event = ft260_raw_event, }; -module_hid_driver(ft260_driver); -MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge"); +static int __init ft260_driver_init(void) +{ + int ret; + + ret = uart_register_driver(&ft260_uart_driver); + if (ret) + return ret; + + ret = hid_register_driver(&(ft260_driver)); + if (ret) { + pr_err("hid_register_driver failed: %d\n", ret); + uart_unregister_driver(&ft260_uart_driver); + } + + return ret; +} + +static void __exit ft260_driver_exit(void) +{ + hid_unregister_driver(&(ft260_driver)); + uart_unregister_driver(&ft260_uart_driver); +} + +module_init(ft260_driver_init); +module_exit(ft260_driver_exit); + +MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge and TTY driver"); MODULE_AUTHOR("Michael Zaidman <michael.zaidman@xxxxxxxxx>"); MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/major.h b/include/uapi/linux/major.h index 4e5f2b3a3d54..dc5845114228 100644 --- a/include/uapi/linux/major.h +++ b/include/uapi/linux/major.h @@ -175,4 +175,6 @@ #define BLOCK_EXT_MAJOR 259 #define SCSI_OSD_MAJOR 260 /* open-osd's OSD scsi device */ +#define FT260_MAJOR 261 + #endif diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 3ba34d8378bd..d9a7025f467e 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -276,4 +276,7 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* FT260 HID UART */ +#define PORT_FT260 124 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- 2.38.1