From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f47.google.com (mail-ej1-f47.google.com [209.85.218.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1538D38F656 for ; Sat, 22 Aug 2026 21:40:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434828; cv=none; b=bNhV4xXhrhSD1hDMkGyBOYcBjhu2CI+eN6WrO919R1PKSFm3cbdzIYH7nPsCP1L6vTK5nHxjt4DQfs2EFCWhkxg3sFKXL89S24uuVYkGbOrQoG4InxYZaRD+3jkxIHFa2seSuT5OymsKJYr5dI0aQA8oAl+JDszJonFRd3BEaLI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434828; c=relaxed/simple; bh=7f0QVj0zHmo0xATi9jc7VIMnR+d2RBoo4YOYW45dwW0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=isSjQJwXNQ+b774CWEt4eMKbQHB2qJoKw3sUq4az8G4uM88j9aRgJCMp2WmGoVHIZe7uJPhA3Zhf54VnWnaIn/4/VDWT2hcTZMSZVf553BVNnrEOEZvVS5hAj7fQ1NAipWIxZEkDQ1mV/EQtWb03p31L+XKwZrjzCCVl5ySbvCk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=WIpRESuQ; arc=none smtp.client-ip=209.85.218.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="WIpRESuQ" Received: by mail-ej1-f47.google.com with SMTP id a640c23a62f3a-c2022323c37so344612866b.0 for ; Sat, 22 Aug 2026 14:40:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434819; x=1788039619; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=UM6IZfeBL9/u40zAxGhV8WIx3wzwmRrrwDjQmQ2+hu0=; b=WIpRESuQRU3VDX+56rkfrk+pKKCUvzgb/FhwOvXUET9ztwV5ue4A6oIUEMsOrwosoG 4SRQEh2Q7sVOJnVsJmuxYRedbuvonFaCFZzFGkcUEe4JTOxVftWGtcL3rvd3t/zo64TZ PdQhDLU/ayA048Hasz2OfyHLy6CVaMz0MQHwYHeEudoB/hDU9jlTE/wjEi2lgo8o2fHw 6ugHanZNwD9tWhVPHqGlep5NiPFcOyTv3rn0H38zrZg93hGyt0P3GU2Yvo+VpiVSfGNb r3VssC9sGANUBl8+h71XxFNC0LQu/85ncwHoq2E2maej/xthPV7yAiE8u98aTHMT4fuw 0m8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434819; x=1788039619; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=UM6IZfeBL9/u40zAxGhV8WIx3wzwmRrrwDjQmQ2+hu0=; b=C48G2V9/82/uXu3yX0CjeWBWekCYxHbpaZ6bUUdN4hAcPZnGqodaZW5UjJo22ZSVKb PcfoM/NvXu7MgKNKi+lb+26YGJX4/aXlPhtSxOBzic4lbl2AMF0FXRSFLiiUtJGOcTQr xRABFdp6tsgkAaka9p8lrTWZ6dNb+09PidO3r6MygglyifqB9rKEjsoTqCBnAohfO7FF SrXYXn3ZzfBkIFB5ss3SNuDa/j/vWOOJderHlMRsuO9KsDlRYN8iSzC9GQ/gp6+RtpeI bCW0W0jVAoVZgcfCkv4LJQKQEyvlg0mQ0OJ0waLroEKJdQKwk7mPJo+C8Iic3udhGQBW P/gQ== X-Forwarded-Encrypted: i=1; AHgh+RrTAUsL3F4vKOSiD17XJKCUV1omRNyNFGN+6cRPylwW+4lMB0wMoagAu1eM63LCeYGaHHvsnoD/6NRjrBM=@vger.kernel.org X-Gm-Message-State: AFuF++nSX5mhQZiy4oCF0l6yVNfee8Y8O/C3YgqgpeTAFITc61bSaq3i oJ3OZxQ97fvJxS8B9SIHdY0KVr2aSRiMCNTSBlj7NtGvRtPOjrZYdx7w X-Gm-Gg: AR+sD11EJJBRdKuRWA9N5qczTONykPCLCw42oDY5jXv23YodWZY7R/kLAvjzDmCVkcA mjCBOQPUe+nTwUsJRpBeKNI9QsH3LWkiLgLRe0Ie8+3Gfqw/zUSOPXnm7ZTFvmeiBRQH93TPgnX 22YOH/K9CGNwbMXygTa2XgJo1VrS4jArXDWLLf8S3fpvSgDZQ2W/n3oCE7JGXJBhZ2FWBGz803Q US5bKOkEf0tl4QGpzGEVF8j633q/tCcXFJp8LoGEDBOTSkKdB7xT4tKD/U4pJXfCWMG51Nh+bTJ 0Or9cy6vm7i6r14sDVcWPT/gb4E6y56aZzmo5+20AXRSJaiDtv+oEcveWOFHQe4l5WVoCzL4Il5 HMoMxGEGP/gLkuF2oZjMLb1rCK+SY/u+ZpIxT/01ziZBozYakJgnjYvcuq3mtSq04ZA0EuWZu56 gBfH7xWLKacJPPAXQO+i2PZcHs+biRD6ZsoLyG9Ig5z2tHfOWijug01nQ/fow= X-Received: by 2002:a17:907:9628:b0:c16:13e7:fd63 with SMTP id a640c23a62f3a-c2469f94a1bmr1736161266b.0.1787434818757; Sat, 22 Aug 2026 14:40:18 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:18 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Daniel Beer , Michael Zaidman Subject: [PATCH 01/13] HID: ft260: add serial driver Date: Sun, 23 Aug 2026 00:39:29 +0300 Message-ID: <20260822213941.98882-2-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Christina Quast Add a TTY serial interface (/dev/ttyFTx) for FT260 USB HID devices, providing baud rate configuration, data transfer, and termios settings without requiring userspace HID report formatting. The FT260 exposes I2C and UART through separate HID interfaces. Only I2C was supported previously; UART required FTDI hidraw example code. This is a port of the v4 patch, adapted to the I2C hardening that has since been merged upstream: input-report length validation, the read_buf/raw_event race fix with read_lock, and SMBus block-read START/STOP handling. HID_FT260 now depends on TTY because the UART code is part of this driver. Routing UART reports through ft260_raw_event() also made v4 fall through the report dispatch after an I2C input report had already been consumed, so every I2C input report was additionally reported as unhandled. This regression is fixed here. Ported from: https://lore.kernel.org/all/20231218093153.192268-1-contact@christina-quast= .de/ The follow-up v5 series stalled without being applied: https://lore.kernel.org/linux-input/20240216-ft260_review5-v5-1-36db44673ac= 7@christina-quast.de/ Signed-off-by: Christina Quast Co-developed-by: Daniel Beer Signed-off-by: Daniel Beer Co-developed-by: Michael Zaidman Signed-off-by: Michael Zaidman --- drivers/hid/Kconfig | 10 +- drivers/hid/hid-ft260.c | 842 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 790 insertions(+), 62 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index aa7fa11a0197..e898adb331ba 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -386,12 +386,12 @@ config HID_EZKEY Support for Ezkey BTC 8193 keyboard. =20 config HID_FT260 - tristate "FTDI FT260 USB HID to I2C host support" - depends on USB_HID && HIDRAW && I2C + tristate "FTDI FT260 USB HID to I2C/UART host support" + depends on USB_HID && HIDRAW && I2C && TTY help - Provides I2C host adapter functionality over USB-HID through FT260 - device. The customizable USB descriptor fields are exposed as sysfs - attributes. + Provides I2C host adapter and UART/TTY functionality over USB-HID + through the FT260 device. The customizable USB descriptor fields + are exposed as sysfs attributes. =20 To compile this driver as a module, choose M here: the module will be called hid-ft260. diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 64c9c940db46..3faedfc5c413 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -13,6 +13,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#define UART_COUNT_MAX 4 /* Number of UARTs this driver can handle */ +#define FIFO_SIZE 256 +#define TTY_WAKEUP_WATERMARK (FIFO_SIZE / 2) =20 #ifdef DEBUG static int ft260_debug =3D 1; @@ -30,6 +40,7 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages"= ); =20 #define FT260_REPORT_MAX_LENGTH (64) #define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / = 4) +#define FT260_UART_DATA_REPORT_ID(len) (FT260_UART_REPORT_MIN + (len - 1) = / 4) =20 #define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */ =20 @@ -43,7 +54,7 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages"= ); * or optoe limit the i2c reads to 128 bytes. To not block other drivers o= ut * of I2C for potentially troublesome amounts of time, we select the maxim= um * read payload length to be 180 bytes. -*/ + */ #define FT260_RD_DATA_MAX (180) #define FT260_WR_DATA_MAX (60) =20 @@ -81,7 +92,8 @@ enum { FT260_UART_INTERRUPT_STATUS =3D 0xB1, FT260_UART_STATUS =3D 0xE0, FT260_UART_RI_DCD_STATUS =3D 0xE1, - FT260_UART_REPORT =3D 0xF0, + FT260_UART_REPORT_MIN =3D 0xF0, + FT260_UART_REPORT_MAX =3D 0xFE, }; =20 /* Feature Out */ @@ -132,6 +144,13 @@ enum { FT260_FLAG_START_STOP_REPEATED =3D 0x07, }; =20 +/* Return values for ft260_get_interface_type func */ +enum { + FT260_IFACE_NONE, + FT260_IFACE_I2C, + FT260_IFACE_UART +}; + #define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_= id) =20 /* Feature In reports */ @@ -220,12 +239,59 @@ struct ft260_i2c_read_request_report { __le16 length; /* data payload length */ } __packed; =20 -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 */ } __packed; =20 +/* UART reports */ +struct ft260_uart_write_request_report { + u8 report; /* FT260_UART_REPORT */ + u8 length; /* data payload length */ + u8 data[] __counted_by(length); /* variable 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 */ + /* The baudrate field is unaligned: */ + __le32 baudrate; /* little endian, 9600 =3D 0x2580, 19200 =3D 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, 2: 2 stop bits */ + u8 breaking; /* 0: no break */ +} __packed; + +/* UART interface configuration */ +enum { + FT260_CFG_FLOW_CTRL_OFF =3D 0x00, + FT260_CFG_FLOW_CTRL_RTS_CTS =3D 0x01, + FT260_CFG_FLOW_CTRL_DTR_DSR =3D 0x02, + FT260_CFG_FLOW_CTRL_XON_XOFF =3D 0x03, + FT260_CFG_FLOW_CTRL_NONE =3D 0x04, + + FT260_CFG_DATA_BITS_7 =3D 0x07, + FT260_CFG_DATA_BITS_8 =3D 0x08, + + FT260_CFG_PAR_NO =3D 0x00, + FT260_CFG_PAR_ODD =3D 0x01, + FT260_CFG_PAR_EVEN =3D 0x02, + FT260_CFG_PAR_HIGH =3D 0x03, + FT260_CFG_PAR_LOW =3D 0x04, + + FT260_CFG_STOP_ONE_BIT =3D 0x00, + FT260_CFG_STOP_TWO_BIT =3D 0x02, + + FT260_CFG_BREAKING_NO =3D 0x00, + FT260_CFG_BEAKING_YES =3D 0x01, + + FT260_CFG_BAUD_MIN =3D 1200, + FT260_CFG_BAUD_MAX =3D 12000000, +}; + static const struct hid_device_id ft260_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_FT260) }, @@ -236,6 +302,23 @@ MODULE_DEVICE_TABLE(hid, ft260_devices); struct ft260_device { struct i2c_adapter adap; struct hid_device *hdev; + + bool ft260_is_serial; + struct list_head device_list; + + /* tty_port lifetime is equal to device lifetime */ + struct tty_port port; + unsigned int index; + struct kfifo xmit_fifo; + /* write_lock: lock to serialize access to xmit fifo */ + spinlock_t write_lock; + struct uart_icount icount; + + struct timer_list wakeup_timer; + struct work_struct wakeup_work; + bool reschedule_work; + + struct completion wait; struct mutex lock; u8 write_buf[FT260_REPORT_MAX_LENGTH]; @@ -379,7 +462,7 @@ static int ft260_hid_output_report_check_status(struct = ft260_device *dev, =20 ret =3D ft260_hid_output_report(hdev, data, len); if (ret < 0) { - hid_err(hdev, "%s: failed to start transfer, ret %d\n", + hid_dbg(hdev, "%s: failed to start transfer, ret %d\n", __func__, ret); ft260_i2c_reset(hdev); return ret; @@ -609,7 +692,7 @@ static int ft260_i2c_write_read(struct ft260_device *de= v, struct i2c_msg *msgs) else read_off =3D *msgs[0].buf; =20 - pr_info("%s: off %#x rlen %d wlen %d\n", __func__, + ft260_dbg("%s: off %#x rlen %d wlen %d\n", __func__, read_off, rd_len, wr_len); } =20 @@ -826,7 +909,7 @@ static int ft260_get_system_config(struct hid_device *h= dev, return 0; } =20 -static int ft260_is_interface_enabled(struct hid_device *hdev) +static int ft260_get_interface_type(struct hid_device *hdev, struct ft260_= device *dev) { struct ft260_get_system_status_report cfg; struct usb_interface *usbif =3D to_usb_interface(hdev->dev.parent); @@ -843,21 +926,27 @@ static int ft260_is_interface_enabled(struct hid_devi= ce *hdev) ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable); ft260_dbg("uart_mode: 0x%02x\n", cfg.uart_mode); =20 + dev->ft260_is_serial =3D false; + switch (cfg.chip_mode) { case FT260_MODE_ALL: case FT260_MODE_BOTH: - if (interface =3D=3D 1) - hid_info(hdev, "uart interface is not supported\n"); - else - ret =3D 1; + if (interface =3D=3D 1) { + ret =3D FT260_IFACE_UART; + dev->ft260_is_serial =3D true; + } else { + ret =3D FT260_IFACE_I2C; + } break; case FT260_MODE_UART: - hid_info(hdev, "uart interface is not supported\n"); + ret =3D FT260_IFACE_UART; + dev->ft260_is_serial =3D true; break; case FT260_MODE_I2C: - ret =3D 1; + ret =3D FT260_IFACE_I2C; break; } + return ret; } =20 @@ -1001,6 +1090,583 @@ static const struct attribute_group ft260_attr_grou= p =3D { } }; =20 +/*** + * START Serial dev part + */ +static DEFINE_MUTEX(ft260_uart_list_lock); +static LIST_HEAD(ft260_uart_device_list); + +static struct ft260_device *ft260_dev_by_index(int index) +{ + struct ft260_device *port; + + list_for_each_entry(port, &ft260_uart_device_list, device_list) { + if (index =3D=3D port->index) + return port; + } + return NULL; +} + +static int ft260_uart_add_port(struct ft260_device *port) +{ + int index =3D 0, ret =3D 0; + struct ft260_device *dev; + + spin_lock_init(&port->write_lock); + if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL)) + return -ENOMEM; + + mutex_lock(&ft260_uart_list_lock); + list_for_each_entry(dev, &ft260_uart_device_list, device_list) { + if (dev->index !=3D index) + break; + index++; + } + + port->index =3D index; + list_add(&port->device_list, &ft260_uart_device_list); + mutex_unlock(&ft260_uart_list_lock); + + return ret; +} + +static void ft260_uart_port_put(struct ft260_device *port) +{ + tty_port_put(&port->port); +} + +static void ft260_uart_port_remove(struct ft260_device *port) +{ + timer_delete_sync(&port->wakeup_timer); + + mutex_lock(&ft260_uart_list_lock); + list_del(&port->device_list); + mutex_unlock(&ft260_uart_list_lock); + + spin_lock(&port->write_lock); + kfifo_free(&port->xmit_fifo); + spin_unlock(&port->write_lock); + + mutex_lock(&port->port.mutex); + port->reschedule_work =3D false; + tty_port_tty_hangup(&port->port, false); + mutex_unlock(&port->port.mutex); + + ft260_uart_port_put(port); +} + +static struct ft260_device *ft260_uart_port_get(unsigned int index) +{ + struct ft260_device *port; + + if (index >=3D UART_COUNT_MAX) + return NULL; + + mutex_lock(&ft260_uart_list_lock); + port =3D ft260_dev_by_index(index); + if (port) + tty_port_get(&port->port); + mutex_unlock(&ft260_uart_list_lock); + + return port; +} + +static int ft260_uart_open(struct tty_struct *tty, struct file *filp) +{ + int ret; + struct ft260_device *port =3D tty->driver_data; + + ret =3D tty_port_open(&port->port, tty, filp); + + return ret; +} + +static void ft260_uart_close(struct tty_struct *tty, struct file *filp) +{ + struct ft260_device *port =3D tty->driver_data; + + tty_port_close(&port->port, tty, filp); +} + +static void ft260_uart_hangup(struct tty_struct *tty) +{ + struct ft260_device *port =3D tty->driver_data; + + tty_port_hangup(&port->port); +} + +static int ft260_uart_transmit_chars(struct ft260_device *port) +{ + struct hid_device *hdev =3D port->hdev; + struct kfifo *xmit =3D &port->xmit_fifo; + struct tty_struct *tty; + struct ft260_uart_write_request_report *rep; + int len, data_len, ret =3D 0; + + tty =3D tty_port_tty_get(&port->port); + + data_len =3D kfifo_len(xmit); + if (!tty || !data_len) { + ret =3D -EINVAL; + goto tty_out; + } + + rep =3D (struct ft260_uart_write_request_report *)port->write_buf; + + do { + len =3D min(data_len, FT260_WR_DATA_MAX); + + rep->report =3D FT260_UART_DATA_REPORT_ID(len); + rep->length =3D len; + + len =3D kfifo_out_locked(xmit, rep->data, len, &port->write_lock); + + ret =3D ft260_hid_output_report(hdev, (u8 *)rep, len + sizeof(*rep)); + if (ret < 0) { + hid_err(hdev, "Failed to start transfer, ret %d\n", ret); + goto tty_out; + } + + data_len -=3D len; + port->icount.tx +=3D len; + } while (data_len > 0); + + len =3D kfifo_len(xmit); + if ((FIFO_SIZE - len) > TTY_WAKEUP_WATERMARK) + tty_wakeup(tty); + + ret =3D 0; + +tty_out: + tty_kref_put(tty); + return ret; +} + +static int ft260_uart_receive_chars(struct ft260_device *port, + u8 *data, u8 length) +{ + struct hid_device *hdev =3D port->hdev; + int ret =3D 0; + + if (length > FT260_RD_DATA_MAX) { + hid_err(hdev, "Received too much data (%d)\n", length); + return -EBADR; + } + + ret =3D tty_insert_flip_string(&port->port, data, length); + if (ret !=3D length) + hid_err(hdev, "%d char not inserted to flip buffer\n", length - ret); + port->icount.rx +=3D ret; + + if (ret) + tty_flip_buffer_push(&port->port); + + return ret; +} + +static ssize_t ft260_uart_write(struct tty_struct *tty, const u8 *buf, + size_t count) +{ + struct ft260_device *port =3D tty->driver_data; + struct hid_device *hdev =3D port->hdev; + int len, ret; + + len =3D kfifo_in_locked(&port->xmit_fifo, buf, count, &port->write_lock); + ft260_dbg("count: %zu, len: %d", count, len); + + ret =3D ft260_uart_transmit_chars(port); + if (ret < 0) { + hid_dbg(hdev, "Failed to transmit chars: %d\n", ret); + return 0; + } + + ret =3D kfifo_len(&port->xmit_fifo); + if (ret > 0) { + hid_dbg(hdev, "Failed to all kfifo data bytes\n"); + ft260_dbg("return: %d", len - ret); + return len - ret; + } + + return len; +} + +static unsigned int ft260_uart_write_room(struct tty_struct *tty) +{ + struct ft260_device *port =3D tty->driver_data; + + return FIFO_SIZE - kfifo_len(&port->xmit_fifo); +} + +static unsigned int ft260_uart_chars_in_buffer(struct tty_struct *tty) +{ + struct ft260_device *port =3D tty->driver_data; + + return kfifo_len(&port->xmit_fifo); +} + +static int ft260_uart_change_speed(struct ft260_device *port, + struct ktermios *termios, + struct ktermios *old) +{ + struct hid_device *hdev =3D port->hdev; + unsigned int baud; + struct ft260_configure_uart_request req; + int ret; + + memset(&req, 0, sizeof(req)); + + req.report =3D FT260_SYSTEM_SETTINGS; + req.request =3D FT260_SET_UART_CONFIG; + + switch (termios->c_cflag & CSIZE) { + case CS7: + req.data_bit =3D FT260_CFG_DATA_BITS_7; + break; + case CS5: + case CS6: + hid_err(hdev, "Invalid data bit size, setting to default (8 bit)\n"); + req.data_bit =3D FT260_CFG_DATA_BITS_8; + termios->c_cflag &=3D ~CSIZE; + termios->c_cflag |=3D CS8; + break; + default: + case CS8: + req.data_bit =3D FT260_CFG_DATA_BITS_8; + break; + } + + req.stop_bit =3D (termios->c_cflag & CSTOPB) ? + FT260_CFG_STOP_TWO_BIT : FT260_CFG_STOP_ONE_BIT; + + if (termios->c_cflag & PARENB) { + req.parity =3D (termios->c_cflag & PARODD) ? + FT260_CFG_PAR_ODD : FT260_CFG_PAR_EVEN; + } else { + req.parity =3D FT260_CFG_PAR_NO; + } + + baud =3D tty_termios_baud_rate(termios); + if (baud =3D=3D 0 || baud < FT260_CFG_BAUD_MIN || baud > FT260_CFG_BAUD_M= AX) { + struct tty_struct *tty =3D tty_port_tty_get(&port->port); + + hid_err(hdev, "Invalid baud rate %d\n", baud); + baud =3D 9600; + tty_encode_baud_rate(tty, baud, baud); + tty_kref_put(tty); + } + put_unaligned_le32(cpu_to_le32(baud), &req.baudrate); + + if (termios->c_cflag & CRTSCTS) + req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_RTS_CTS; + else + req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_OFF; + + 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); + + req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_NONE; + req.breaking =3D FT260_CFG_BREAKING_NO; + + ret =3D 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; +} + +static int ft260_uart_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct ft260_device *port =3D tty->driver_data; + + memcpy(icount, &port->icount, sizeof(struct uart_icount)); + + return 0; +} + +static void ft260_uart_set_termios(struct tty_struct *tty, + const struct ktermios *old_termios) +{ + struct ft260_device *port =3D tty->driver_data; + + ft260_uart_change_speed(port, &tty->termios, NULL); +} + +static int ft260_uart_install(struct tty_driver *driver, struct tty_struct= *tty) +{ + int idx =3D tty->index; + struct ft260_device *port =3D ft260_uart_port_get(idx); + int ret =3D tty_standard_install(driver, tty); + + if (ret =3D=3D 0) + /* This is the ref ft260_uart_port get provided */ + tty->driver_data =3D port; + else + ft260_uart_port_put(port); + + return ret; +} + +static void ft260_uart_cleanup(struct tty_struct *tty) +{ + struct ft260_device *port =3D tty->driver_data; + + tty->driver_data =3D NULL; /* Bug trap */ + ft260_uart_port_put(port); +} + +static int ft260_uart_proc_show(struct seq_file *m, void *v) +{ + int i; + + seq_printf(m, "ft260 info:1.0 driver%s%s revision:%s\n", + "", "", ""); + for (i =3D 0; i < UART_COUNT_MAX; i++) { + struct ft260_device *port =3D ft260_uart_port_get(i); + + if (port) { + seq_printf(m, "%d: uart:FT260", i); + if (capable(CAP_SYS_ADMIN)) { + seq_printf(m, " tx:%d rx:%d", + port->icount.tx, port->icount.rx); + if (port->icount.frame) + seq_printf(m, " fe:%d", + port->icount.frame); + if (port->icount.parity) + seq_printf(m, " pe:%d", + port->icount.parity); + if (port->icount.brk) + seq_printf(m, " brk:%d", + port->icount.brk); + if (port->icount.overrun) + seq_printf(m, " oe:%d", + port->icount.overrun); + if (port->icount.cts) + seq_printf(m, " cts:%d", + port->icount.cts); + if (port->icount.dsr) + seq_printf(m, " dsr:%d", + port->icount.dsr); + if (port->icount.rng) + seq_printf(m, " rng:%d", + port->icount.rng); + if (port->icount.dcd) + seq_printf(m, " dcd:%d", + port->icount.dcd); + } + ft260_uart_port_put(port); + seq_putc(m, '\n'); + } + } + return 0; +} + +static const struct tty_operations ft260_uart_ops =3D { + .open =3D ft260_uart_open, + .close =3D ft260_uart_close, + .write =3D ft260_uart_write, + .write_room =3D ft260_uart_write_room, + .chars_in_buffer =3D ft260_uart_chars_in_buffer, + .set_termios =3D ft260_uart_set_termios, + .hangup =3D ft260_uart_hangup, + .install =3D ft260_uart_install, + .cleanup =3D ft260_uart_cleanup, + .proc_show =3D ft260_uart_proc_show, + .get_icount =3D ft260_uart_get_icount, +}; + +/* 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 =3D + container_of(t, struct ft260_device, wakeup_timer); + + if (dev->reschedule_work) { + 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 =3D + container_of(work, struct ft260_device, wakeup_work); + struct ft260_get_chip_version_report version; + int ret; + + if (dev->reschedule_work) { + ret =3D 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_shutdown(struct tty_port *tport) +{ + struct ft260_device *port =3D + container_of(tport, struct ft260_device, port); + + port->reschedule_work =3D false; +} + +static int ft260_uart_activate(struct tty_port *tport, struct tty_struct *= tty) +{ + struct ft260_device *port =3D + container_of(tport, struct ft260_device, port); + + /* + * Set the TTY IO error marker - we will only clear this + * once we have successfully opened the port. + */ + set_bit(TTY_IO_ERROR, &tty->flags); + + spin_lock(&port->write_lock); + kfifo_reset(&port->xmit_fifo); + spin_unlock(&port->write_lock); + + ft260_uart_change_speed(port, &tty->termios, NULL); + clear_bit(TTY_IO_ERROR, &tty->flags); + + if (port->reschedule_work) { + mod_timer(&port->wakeup_timer, jiffies + + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS)); + } + + return 0; +} + +static void ft260_uart_port_destroy(struct tty_port *tport) +{ + struct ft260_device *port =3D + container_of(tport, struct ft260_device, port); + + kfree(port); +} + +static const struct tty_port_operations ft260_uart_port_ops =3D { + .shutdown =3D ft260_uart_shutdown, + .activate =3D ft260_uart_activate, + .destruct =3D ft260_uart_port_destroy, +}; + +static struct tty_driver *ft260_tty_driver; + +static int ft260_i2c_probe(struct hid_device *hdev, struct ft260_device *d= ev) +{ + int ret; + + hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", + hdev->version >> 8, hdev->version & 0xff, hdev->name, + hdev->phys); + + hid_set_drvdata(hdev, dev); + dev->hdev =3D hdev; + dev->adap.owner =3D THIS_MODULE; + dev->adap.class =3D I2C_CLASS_HWMON; + dev->adap.algo =3D &ft260_i2c_algo; + dev->adap.quirks =3D &ft260_i2c_quirks; + dev->adap.dev.parent =3D &hdev->dev; + snprintf(dev->adap.name, sizeof(dev->adap.name), + "FT260 usb-i2c bridge"); + + ret =3D ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY); + if (ret) + ft260_i2c_reset(hdev); + + i2c_set_adapdata(&dev->adap, dev); + ret =3D i2c_add_adapter(&dev->adap); + if (ret) { + hid_err(hdev, "failed to add i2c adapter\n"); + return ret; + } + + ret =3D 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; + } + return 0; + +err_i2c_free: + i2c_del_adapter(&dev->adap); + return ret; +} + +static int ft260_uart_probe(struct hid_device *hdev, struct ft260_device *= dev) +{ + struct ft260_configure_uart_request req; + int ret; + struct device *devt; + + INIT_WORK(&dev->wakeup_work, ft260_uart_do_wakeup); + // FIXME: Do I need that if I have cancel_work_sync? + // FIXME: are all kfifo access secured by lock? with irq or not? + dev->reschedule_work =3D false; + /* Work not started at this point */ + timer_setup(&dev->wakeup_timer, ft260_uart_start_wakeup, 0); + + tty_port_init(&dev->port); + dev->port.ops =3D &ft260_uart_port_ops; + + ret =3D ft260_uart_add_port(dev); + if (ret) { + hid_err(hdev, "failed to add port\n"); + return ret; + } + devt =3D tty_port_register_device_attr(&dev->port, + ft260_tty_driver, + dev->index, &hdev->dev, + dev, NULL); + if (IS_ERR(devt)) { + hid_err(hdev, "failed to register tty port\n"); + ret =3D PTR_ERR(devt); + goto err_register_tty; + } + hid_info(hdev, "Registering device /dev/%s%d\n", + ft260_tty_driver->name, dev->index); + + /* Send Feature Report to Configure FT260 as UART 9600-8-N-1 */ + req.report =3D FT260_SYSTEM_SETTINGS; + req.request =3D FT260_SET_UART_CONFIG; + req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_NONE; + put_unaligned_le32(cpu_to_le32(9600), &req.baudrate); + req.data_bit =3D FT260_CFG_DATA_BITS_8; + req.parity =3D FT260_CFG_PAR_NO; + req.stop_bit =3D FT260_CFG_STOP_ONE_BIT; + req.breaking =3D FT260_CFG_BREAKING_NO; + + ret =3D 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); + goto err_hid_report; + } + + return 0; + +err_hid_report: + tty_port_unregister_device(&dev->port, ft260_tty_driver, dev->index); +err_register_tty: + ft260_uart_port_remove(dev); + return ret; +} + static int ft260_probe(struct hid_device *hdev, const struct hid_device_id= *id) { struct ft260_device *dev; @@ -1010,20 +1676,26 @@ static int ft260_probe(struct hid_device *hdev, con= st struct hid_device_id *id) if (!hid_is_usb(hdev)) return -EINVAL; =20 - dev =3D devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; + /* We cannot used devm_kzalloc here, because port has to survive until + * destroy function call + */ + dev =3D kzalloc_obj(*dev, GFP_KERNEL); + if (!dev) { + ret =3D -ENOMEM; + goto alloc_fail; + } + hid_set_drvdata(hdev, dev); =20 ret =3D hid_parse(hdev); if (ret) { hid_err(hdev, "failed to parse HID\n"); - return ret; + goto hid_fail; } =20 ret =3D hid_hw_start(hdev, 0); if (ret) { hid_err(hdev, "failed to start HID HW\n"); - return ret; + goto hid_fail; } =20 ret =3D hid_hw_open(hdev); @@ -1043,53 +1715,36 @@ static int ft260_probe(struct hid_device *hdev, con= st struct hid_device_id *id) version.chip_code[0], version.chip_code[1], version.chip_code[2], version.chip_code[3]); =20 - ret =3D ft260_is_interface_enabled(hdev); - if (ret <=3D 0) + ret =3D ft260_get_interface_type(hdev, dev); + if (ret <=3D FT260_IFACE_NONE) goto err_hid_close; =20 - hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", - hdev->version >> 8, hdev->version & 0xff, hdev->name, - hdev->phys); - hid_set_drvdata(hdev, dev); dev->hdev =3D hdev; - dev->adap.owner =3D THIS_MODULE; - dev->adap.class =3D I2C_CLASS_HWMON; - dev->adap.algo =3D &ft260_i2c_algo; - dev->adap.quirks =3D &ft260_i2c_quirks; - dev->adap.dev.parent =3D &hdev->dev; - snprintf(dev->adap.name, sizeof(dev->adap.name), - "FT260 usb-i2c bridge"); =20 mutex_init(&dev->lock); spin_lock_init(&dev->read_lock); init_completion(&dev->wait); =20 - ret =3D ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY); - if (ret) - ft260_i2c_reset(hdev); - - i2c_set_adapdata(&dev->adap, dev); - ret =3D i2c_add_adapter(&dev->adap); - if (ret) { - hid_err(hdev, "failed to add i2c adapter\n"); - goto err_hid_close; - } - - ret =3D 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 =3D ft260_i2c_probe(hdev, dev); + if (ret) + goto err_hid_close; + } else { + ret =3D ft260_uart_probe(hdev, dev); + if (ret) + goto err_hid_close; } =20 return 0; =20 -err_i2c_free: - i2c_del_adapter(&dev->adap); err_hid_close: hid_hw_close(hdev); err_hid_stop: hid_hw_stop(hdev); +hid_fail: + kfree(dev); +alloc_fail: return ret; } =20 @@ -1100,8 +1755,18 @@ static void ft260_remove(struct hid_device *hdev) if (!dev) return; =20 - sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); - i2c_del_adapter(&dev->adap); + if (dev->ft260_is_serial) { + // FIXME: + cancel_work_sync(&dev->wakeup_work); + tty_port_unregister_device(&dev->port, ft260_tty_driver, + dev->index); + ft260_uart_port_remove(dev); + /* dev still needed, so we will free it in _destroy func */ + } else { + sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + i2c_del_adapter(&dev->adap); + kfree(dev); + } =20 hid_hw_close(hdev); hid_hw_stop(hdev); @@ -1111,10 +1776,10 @@ static int ft260_raw_event(struct hid_device *hdev,= struct hid_report *report, u8 *data, int size) { struct ft260_device *dev =3D hid_get_drvdata(hdev); - struct ft260_i2c_input_report *xfer =3D (void *)data; + struct ft260_input_report *xfer =3D (void *)data; unsigned long irqflags; =20 - if (size < offsetof(struct ft260_i2c_input_report, data)) { + if (size < offsetof(struct ft260_input_report, data)) { hid_err(hdev, "short report %d\n", size); return -1; } @@ -1127,7 +1792,7 @@ static int ft260_raw_event(struct hid_device *hdev, s= truct hid_report *report, xfer->report, xfer->length, size); =20 if (xfer->length > size - - offsetof(struct ft260_i2c_input_report, data)) { + offsetof(struct ft260_input_report, data)) { hid_err(hdev, "report %#02x: length %d exceeds HID report size\n", xfer->report, xfer->length); return -1; @@ -1157,9 +1822,17 @@ static int ft260_raw_event(struct hid_device *hdev, = struct hid_report *report, if (complete_read) complete(&dev->wait); =20 - } else { - hid_err(hdev, "unhandled report %#02x\n", xfer->report); + return 0; + + } else if (xfer->length > FT260_RD_DATA_MAX) { + hid_err(hdev, "received data too long (%d)\n", xfer->length); + return -EBADR; + } else if (xfer->report >=3D FT260_UART_REPORT_MIN && + xfer->report <=3D FT260_UART_REPORT_MAX) { + return ft260_uart_receive_chars(dev, xfer->data, xfer->length); } + hid_err(hdev, "unhandled report %#02x\n", xfer->report); + return 0; } =20 @@ -1171,7 +1844,62 @@ static struct hid_driver ft260_driver =3D { .raw_event =3D ft260_raw_event, }; =20 -module_hid_driver(ft260_driver); -MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge"); +static int __init ft260_driver_init(void) +{ + int ret; + + ft260_tty_driver =3D tty_alloc_driver(UART_COUNT_MAX, + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(ft260_tty_driver)) { + pr_err("tty_alloc_driver failed: %d\n", + (int)PTR_ERR(ft260_tty_driver)); + return PTR_ERR(ft260_tty_driver); + } + + ft260_tty_driver->driver_name =3D "ft260_ser"; + ft260_tty_driver->name =3D "ttyFT"; + ft260_tty_driver->major =3D 0; + ft260_tty_driver->minor_start =3D 0; + ft260_tty_driver->type =3D TTY_DRIVER_TYPE_SERIAL; + ft260_tty_driver->subtype =3D SERIAL_TYPE_NORMAL; + ft260_tty_driver->init_termios =3D tty_std_termios; + ft260_tty_driver->init_termios.c_cflag =3D B9600 | CS8 | CREAD | HUPCL | = CLOCAL; + ft260_tty_driver->init_termios.c_ispeed =3D 9600; + ft260_tty_driver->init_termios.c_ospeed =3D 9600; + tty_set_operations(ft260_tty_driver, &ft260_uart_ops); + + ret =3D tty_register_driver(ft260_tty_driver); + if (ret) { + pr_err("tty_register_driver failed: %d\n", ret); + goto err_reg_driver; + } + + ret =3D hid_register_driver(&(ft260_driver)); + if (ret) { + pr_err("hid_register_driver failed: %d\n", ret); + goto err_reg_hid; + } + + return 0; + +err_reg_hid: + tty_unregister_driver(ft260_tty_driver); +err_reg_driver: + tty_driver_kref_put(ft260_tty_driver); + + return ret; +} + +static void __exit ft260_driver_exit(void) +{ + hid_unregister_driver(&(ft260_driver)); + tty_unregister_driver(ft260_tty_driver); + tty_driver_kref_put(ft260_tty_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 "); MODULE_LICENSE("GPL v2"); --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f48.google.com (mail-ej1-f48.google.com [209.85.218.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D311C3909B3 for ; Sat, 22 Aug 2026 21:40:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434836; cv=none; b=KfD3YasG5RJR5W/Je3PaoH4N+bLHsGH68eD6QYkm8IcuQhppWUhMsSaZK9rp9PAi3ADQcU+iG8xRl6cn+5yOMpdUIigHo+3+Z/FU4X+92ZpUXPopHYbFHBUK+oBam+j54+L3pbM3csCCCCTlZTR0f/5sCm+30pvHDrOtbrtGgcg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434836; c=relaxed/simple; bh=aD+HtX7tnz+ZeAsjCDqb4WQb1+koGa8J8vb951IkdCY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dFKK+mdqu5zLbvkDBvKPryfvn3RGUP5UIy6Vi+Q+4aaofGCHSaofSGmYLtaTGwptZJvbOoUd79S8UaXlDlbAxYb7EQlKINY1FC50s50zGT67DA/78xfIzn7qgTUdsSTCZsadDQwl6uLkEOVDKtCTOm/EUb/d0GvYH/rmhujvOTc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JWBy7bDQ; arc=none smtp.client-ip=209.85.218.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JWBy7bDQ" Received: by mail-ej1-f48.google.com with SMTP id a640c23a62f3a-c1712a04ddaso364453166b.2 for ; Sat, 22 Aug 2026 14:40:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434826; x=1788039626; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qwHWguOsSrLzaE9O7187K9INJCM1brdEKh0/stGa2ZM=; b=JWBy7bDQmn+B6v/hRvno7toh4esvLJXh9Ta6lMzGW7vl/lj6waLrUXO2w152Ui5TqQ ZFVrtVaS2u8745Eb+vAEh2aKXuYSbfgVBgkK6Ob1RzzDVpgRYcjWvFJ7i/rdR+tGMjBM 9ve+HskqCP3SV+n/LOjOqNm9tOErliX+hMzazPWbhk9R2i1ZANUYEj5af/6aPv90XJdZ TJZtv2ZO3bIZuht0NG084Xpl7CZRkdp8kz1GBx6Apa1kkyTGH0p+QsXsh6IJ9Q+BPUGK 7GBSokN9SEOns5/BLVExhOD6AHwS/XEBxYhuiyPMntS1KezuELbVQfjiKMIE59LJJF1s lXEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434826; x=1788039626; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=qwHWguOsSrLzaE9O7187K9INJCM1brdEKh0/stGa2ZM=; b=chd09BHaJsnUxb1d/LEDNfxcxbJ6ctTkwSIl971QWCUHFqZf7JZG+VEEdKnutYqVUn dq+8XWTnQJoCr7CHOh3+0TUcXoJJ+ewKS1R4lRow/R7gNtWYXtfGiIcCykVvF5KcFzm9 lsG3TX4dAOf9wuwahERMF/+f9SaiLvX2TWE7rpPHwAH97lHTJm9ra7YQjGdk3AoIrT/4 HKTMKyfzjiFWWtdEfuqgr+HIYCOy5NQh6AUNAKxKwQAsvYWTVoxj6QdWV85BmVPug7mp uqc2LPHs20xpBiZyqHv+WPRTj4nb57+EvtphhqCI2EsqnMV62oxjTI7h2mpopaDe1eDB 3hng== X-Forwarded-Encrypted: i=1; AHgh+RrH8vzB2uWe8SmgY5wG2D2/gQtaBK0ABxocAbt0CWcXnYnEqBZbBTjEbfMZ0xzr9AdwGNUlx7sLZi5C5cc=@vger.kernel.org X-Gm-Message-State: AFuF++lW0wg3L3Wp02+lH2VUoT11hh8gpVqx4lYqg8Z7A4Qm67J8Btee xaN/ZoeripKJ5+zQX9Xu+H16OHKgMAXyv9KSZHQaF5msZH4cOWaMosUx X-Gm-Gg: AR+sD11sHB+j9mls+qTg6XJ5yPTbA+kLtIKMdwSvsqRNrOUUj0cI6xsX1NwtFB4+JPg OYWswrMFnk39Lb1Fg11bdYCv0By/RTj6u0ET1E+G2oImfLT44JCp/u2FUpzlX4AOuD0RmZbdX/u W9jMH9zATKQboT4zLC6tg0WMdQEDD04zia1K0fuEnx6qUiymzlW4+UnbY8+vuFnElwAacRoD7pb 9FMobz53x43id6GB80t7FpHnVJHeNEeFKveCUzY89CU0YbF5j75F0Kp+8BYs7WQDczAuDRvxqNL lFM3Uq2MiV7RMrjcKF6IyOyTUYB0x0geMw1s6zuR1BBNh4KqUNuWtScIzb8QCFlom0Ol7Vq2nzS GJL4oAS42ce1U3yhZBSm9gnEQdGBu36rHJe7lcQFiLt4gqPbckSbzTJuhyDCvQODeAxkq7x2EEl 6Q06A/vvQHn6vg8p1Vzp2BnBCsTxQvdJZMuzgKGrkcgkhFeoe/W2lenjYIj3OEPlwkv4oJqg== X-Received: by 2002:a17:907:c8c7:b0:c24:680a:1c42 with SMTP id a640c23a62f3a-c2491c9a6eamr963194566b.7.1787434826382; Sat, 22 Aug 2026 14:40:26 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:25 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 02/13] HID: ft260: uart: bring-up fixes Date: Sun, 23 Aug 2026 00:39:30 +0300 Message-ID: <20260822213941.98882-3-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Fix the UART path so the serial interface added previously works reliably in practice. This squashes the contiguous UART bring-up fixes that followed the initial serial-driver addition. The UART and I2C paths shared write_buf without proper separation, so concurrent use could corrupt transfers on both channels. Give UART its own TX buffer. The write request report is now built in that fixed per-port buffer, so its payload member becomes a fixed FT260_WR_UART_DATA_MAX (62) array rather than a __counted_by() flexible array, which describes a runtime-counted allocation this path no longer makes. The FT260 enters power-save after ~5s idle, which drops RX data at higher baud rates. Keep a timed dummy wakeup, but only when needed: skip it below 4800 baud and when EEPROM disables power-save; wake earlier before RX; avoid configuring baud or disabling the workaround twice; and arm the timer when a tty session starts. The received report length was validated against the actual HID report size only on the I2C path. The UART path instead bounded the device-supplied length against FT260_RD_DATA_MAX (180), which is the maximum I2C read payload, not a bound on the report buffer. As a UART payload cannot exceed FT260_WR_UART_DATA_MAX (62) bytes, a report claiming more would make ft260_uart_receive_chars() push data past the end of the report buffer into the tty flip buffer. Validate the length against the received report size once, before dispatching on the report type, and drop the two now-redundant FT260_RD_DATA_MAX checks. Also fix RX data loss after reopening the device, improve TX throughput, simplify HID interface-type detection, use kfifo_avail for write-room, and ignore spurious UART interrupt status (0xb1) reports instead of treating them as errors. Also includes UART cleanup and refactoring to match the rest of the driver style (including replacing kfifo_*_locked aliases with kfifo_*_spinlocked). This replaces the UART bring-up fix series: https://lore.kernel.org/all/20240210215147.77629-1-michael.zaidman@gmail.co= m/ Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 340 ++++++++++++++++++++++------------------ 1 file changed, 188 insertions(+), 152 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 3faedfc5c413..b6c408267a68 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge + * FTDI FT260 USB HID to I2C/UART host bridge * * Copyright (c) 2021, Michael Zaidman * @@ -20,10 +20,6 @@ #include #include =20 -#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 =3D 1; #else @@ -38,9 +34,12 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages= "); pr_info("%s: " format, __func__, ##arg); \ } while (0) =20 -#define FT260_REPORT_MAX_LENGTH (64) -#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / = 4) -#define FT260_UART_DATA_REPORT_ID(len) (FT260_UART_REPORT_MIN + (len - 1) = / 4) +#define FT260_REPORT_MAX_LEN (64) +#define FT260_DATA_REPORT_ID(min, len) (min + (len - 1) / 4) +#define FT260_I2C_DATA_REPORT_ID(len) \ + FT260_DATA_REPORT_ID(FT260_I2C_REPORT_MIN, len) +#define FT260_UART_DATA_REPORT_ID(len) \ + FT260_DATA_REPORT_ID(FT260_UART_REPORT_MIN, len) =20 #define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */ =20 @@ -56,7 +55,8 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages"= ); * read payload length to be 180 bytes. */ #define FT260_RD_DATA_MAX (180) -#define FT260_WR_DATA_MAX (60) +#define FT260_WR_I2C_DATA_MAX (60) +#define FT260_WR_UART_DATA_MAX (62) =20 /* * Device interface configuration. @@ -90,7 +90,7 @@ enum { FT260_I2C_REPORT_MAX =3D 0xDE, FT260_GPIO =3D 0xB0, FT260_UART_INTERRUPT_STATUS =3D 0xB1, - FT260_UART_STATUS =3D 0xE0, + FT260_UART_SETTINGS =3D 0xE0, FT260_UART_RI_DCD_STATUS =3D 0xE1, FT260_UART_REPORT_MIN =3D 0xF0, FT260_UART_REPORT_MAX =3D 0xFE, @@ -144,7 +144,7 @@ enum { FT260_FLAG_START_STOP_REPEATED =3D 0x07, }; =20 -/* Return values for ft260_get_interface_type func */ +/* USB interface type values */ enum { FT260_IFACE_NONE, FT260_IFACE_I2C, @@ -190,6 +190,18 @@ struct ft260_get_i2c_status_report { u8 reserved; } __packed; =20 +struct ft260_get_uart_settings_report { + u8 report; /* FT260_UART_SETTINGS */ + u8 flow_ctrl; /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */ + /* 3 - XON_XOFF, 4 - No flow control */ + /* The baudrate field is unaligned */ + __le32 baudrate; /* little endian, 9600 =3D 0x2580, 19200 =3D 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, 2: 2 stop bits */ + u8 breaking; /* 0: no break */ +} __packed; + /* Feature Out reports */ =20 struct ft260_set_system_clock_report { @@ -229,7 +241,7 @@ struct ft260_i2c_write_request_report { u8 address; /* 7-bit I2C address */ u8 flag; /* I2C transaction condition */ u8 length; /* data payload length */ - u8 data[FT260_WR_DATA_MAX]; /* data payload */ + u8 data[FT260_WR_I2C_DATA_MAX]; /* data payload */ } __packed; =20 struct ft260_i2c_read_request_report { @@ -246,18 +258,19 @@ struct ft260_input_report { } __packed; =20 /* UART reports */ + struct ft260_uart_write_request_report { u8 report; /* FT260_UART_REPORT */ u8 length; /* data payload length */ - u8 data[] __counted_by(length); /* variable data payload */ + u8 data[FT260_WR_UART_DATA_MAX]; /* data payload */ } __packed; =20 -struct ft260_configure_uart_request { +struct ft260_configure_uart_request_report { 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 */ - /* The baudrate field is unaligned: */ + /* The baudrate field is unaligned */ __le32 baudrate; /* little endian, 9600 =3D 0x2580, 19200 =3D 0x4B00 */ u8 data_bit; /* 7 or 8 */ u8 parity; /* 0: no parity, 1: odd, 2: even, 3: high, 4: low */ @@ -292,6 +305,11 @@ enum { FT260_CFG_BAUD_MAX =3D 12000000, }; =20 +#define FT260_UART_EN_PW_SAVE_BAUD (4800) + +#define UART_COUNT_MAX (4) /* Number of supported UARTs */ +#define XMIT_FIFO_SIZE (PAGE_SIZE) + static const struct hid_device_id ft260_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_FT260) }, @@ -302,26 +320,22 @@ MODULE_DEVICE_TABLE(hid, ft260_devices); struct ft260_device { struct i2c_adapter adap; struct hid_device *hdev; - - bool ft260_is_serial; + int iface_type; struct list_head device_list; - - /* tty_port lifetime is equal to device lifetime */ struct tty_port port; + /* tty port index */ unsigned int index; struct kfifo xmit_fifo; - /* write_lock: lock to serialize access to xmit fifo */ - spinlock_t write_lock; + spinlock_t xmit_fifo_lock; struct uart_icount icount; - struct timer_list wakeup_timer; struct work_struct wakeup_work; bool reschedule_work; - - + bool power_saving_en; struct completion wait; struct mutex lock; - u8 write_buf[FT260_REPORT_MAX_LENGTH]; + u8 i2c_wr_buf[FT260_REPORT_MAX_LEN]; + u8 uart_wr_buf[FT260_REPORT_MAX_LEN]; unsigned long need_wakeup_at; /* Protects read_buf, read_idx and read_len against ft260_raw_event() */ spinlock_t read_lock; @@ -332,8 +346,7 @@ struct ft260_device { }; =20 static int ft260_hid_feature_report_get(struct hid_device *hdev, - unsigned char report_id, u8 *data, - size_t len) + u8 report_id, u8 *data, size_t len) { u8 *buf; int ret; @@ -462,8 +475,6 @@ static int ft260_hid_output_report_check_status(struct = ft260_device *dev, =20 ret =3D ft260_hid_output_report(hdev, data, len); if (ret < 0) { - hid_dbg(hdev, "%s: failed to start transfer, ret %d\n", - __func__, ret); ft260_i2c_reset(hdev); return ret; } @@ -505,7 +516,7 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, int ret, wr_len, idx =3D 0; struct hid_device *hdev =3D dev->hdev; struct ft260_i2c_write_request_report *rep =3D - (struct ft260_i2c_write_request_report *)dev->write_buf; + (struct ft260_i2c_write_request_report *)dev->i2c_wr_buf; =20 if (len < 1) return -EINVAL; @@ -513,12 +524,12 @@ static int ft260_i2c_write(struct ft260_device *dev, = u8 addr, u8 *data, rep->flag =3D FT260_FLAG_START; =20 do { - if (len <=3D FT260_WR_DATA_MAX) { + if (len <=3D FT260_WR_I2C_DATA_MAX) { wr_len =3D len; if (flag =3D=3D FT260_FLAG_START_STOP) rep->flag |=3D FT260_FLAG_STOP; } else { - wr_len =3D FT260_WR_DATA_MAX; + wr_len =3D FT260_WR_I2C_DATA_MAX; } =20 rep->report =3D FT260_I2C_DATA_REPORT_ID(wr_len); @@ -554,7 +565,7 @@ static int ft260_smbus_write(struct ft260_device *dev, = u8 addr, u8 cmd, int len =3D 4; =20 struct ft260_i2c_write_request_report *rep =3D - (struct ft260_i2c_write_request_report *)dev->write_buf; + (struct ft260_i2c_write_request_report *)dev->i2c_wr_buf; =20 if (data_len >=3D sizeof(rep->data)) return -EINVAL; @@ -574,6 +585,8 @@ static int ft260_smbus_write(struct ft260_device *dev, = u8 addr, u8 cmd, rep->report, addr, cmd, rep->length, len); =20 ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len); + if (ret < 0) + hid_err(dev->hdev, "%s: failed with %d\n", __func__, ret); =20 return ret; } @@ -692,8 +705,7 @@ static int ft260_i2c_write_read(struct ft260_device *de= v, struct i2c_msg *msgs) else read_off =3D *msgs[0].buf; =20 - ft260_dbg("%s: off %#x rlen %d wlen %d\n", __func__, - read_off, rd_len, wr_len); + ft260_dbg("off %#x rlen %d wlen %d\n", read_off, rd_len, wr_len); } =20 ret =3D ft260_i2c_write(dev, addr, msgs[0].buf, wr_len, @@ -926,27 +938,25 @@ static int ft260_get_interface_type(struct hid_device= *hdev, struct ft260_device ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable); ft260_dbg("uart_mode: 0x%02x\n", cfg.uart_mode); =20 - dev->ft260_is_serial =3D false; + dev->power_saving_en =3D cfg.power_saving_en; =20 switch (cfg.chip_mode) { case FT260_MODE_ALL: case FT260_MODE_BOTH: - if (interface =3D=3D 1) { + if (interface =3D=3D 1) ret =3D FT260_IFACE_UART; - dev->ft260_is_serial =3D true; - } else { + else ret =3D FT260_IFACE_I2C; - } break; case FT260_MODE_UART: ret =3D FT260_IFACE_UART; - dev->ft260_is_serial =3D true; break; case FT260_MODE_I2C: ret =3D FT260_IFACE_I2C; break; } =20 + dev->iface_type =3D ret; return ret; } =20 @@ -1090,12 +1100,36 @@ static const struct attribute_group ft260_attr_grou= p =3D { } }; =20 -/*** - * START Serial dev part - */ static DEFINE_MUTEX(ft260_uart_list_lock); static LIST_HEAD(ft260_uart_device_list); =20 +static void ft260_uart_wakeup(struct ft260_device *dev); + +static int ft260_get_uart_settings(struct hid_device *hdev, + struct ft260_get_uart_settings_report *cfg) +{ + int ret; + int len =3D sizeof(struct ft260_get_uart_settings_report); + + ret =3D ft260_hid_feature_report_get(hdev, FT260_UART_SETTINGS, + (u8 *)cfg, len); + if (ret < 0) { + hid_err(hdev, "failed to retrieve uart settings\n"); + return ret; + } + return 0; +} + +static void ft260_uart_wakeup_workaraund_enable(struct ft260_device *port, + bool enable) +{ + if (port->power_saving_en) { + port->reschedule_work =3D enable; + ft260_dbg("%s wakeup workaround", + enable ? "activate" : "deactivate"); + } +} + static struct ft260_device *ft260_dev_by_index(int index) { struct ft260_device *port; @@ -1112,8 +1146,8 @@ static int ft260_uart_add_port(struct ft260_device *p= ort) int index =3D 0, ret =3D 0; struct ft260_device *dev; =20 - spin_lock_init(&port->write_lock); - if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL)) + spin_lock_init(&port->xmit_fifo_lock); + if (kfifo_alloc(&port->xmit_fifo, XMIT_FIFO_SIZE, GFP_KERNEL)) return -ENOMEM; =20 mutex_lock(&ft260_uart_list_lock); @@ -1143,19 +1177,18 @@ static void ft260_uart_port_remove(struct ft260_dev= ice *port) list_del(&port->device_list); mutex_unlock(&ft260_uart_list_lock); =20 - spin_lock(&port->write_lock); + spin_lock(&port->xmit_fifo_lock); kfifo_free(&port->xmit_fifo); - spin_unlock(&port->write_lock); + spin_unlock(&port->xmit_fifo_lock); =20 mutex_lock(&port->port.mutex); - port->reschedule_work =3D false; tty_port_tty_hangup(&port->port, false); mutex_unlock(&port->port.mutex); =20 ft260_uart_port_put(port); } =20 -static struct ft260_device *ft260_uart_port_get(unsigned int index) +static struct ft260_device *ft260_uart_port_get(int index) { struct ft260_device *port; =20 @@ -1211,30 +1244,24 @@ static int ft260_uart_transmit_chars(struct ft260_d= evice *port) goto tty_out; } =20 - rep =3D (struct ft260_uart_write_request_report *)port->write_buf; + rep =3D (struct ft260_uart_write_request_report *)port->uart_wr_buf; =20 do { - len =3D min(data_len, FT260_WR_DATA_MAX); + len =3D min(data_len, FT260_WR_UART_DATA_MAX); =20 rep->report =3D FT260_UART_DATA_REPORT_ID(len); rep->length =3D len; =20 - len =3D kfifo_out_locked(xmit, rep->data, len, &port->write_lock); + len =3D kfifo_out_spinlocked(xmit, rep->data, len, &port->xmit_fifo_lock= ); =20 - ret =3D ft260_hid_output_report(hdev, (u8 *)rep, len + sizeof(*rep)); - if (ret < 0) { - hid_err(hdev, "Failed to start transfer, ret %d\n", ret); + ret =3D ft260_hid_output_report(hdev, (u8 *)rep, len + 2); + if (ret < 0) goto tty_out; - } =20 data_len -=3D len; port->icount.tx +=3D len; } while (data_len > 0); =20 - len =3D kfifo_len(xmit); - if ((FIFO_SIZE - len) > TTY_WAKEUP_WATERMARK) - tty_wakeup(tty); - ret =3D 0; =20 tty_out: @@ -1242,20 +1269,14 @@ static int ft260_uart_transmit_chars(struct ft260_d= evice *port) return ret; } =20 -static int ft260_uart_receive_chars(struct ft260_device *port, - u8 *data, u8 length) +static int ft260_uart_receive_chars(struct ft260_device *port, u8 *data, u= 8 length) { - struct hid_device *hdev =3D port->hdev; - int ret =3D 0; - - if (length > FT260_RD_DATA_MAX) { - hid_err(hdev, "Received too much data (%d)\n", length); - return -EBADR; - } + int ret; =20 ret =3D tty_insert_flip_string(&port->port, data, length); if (ret !=3D length) - hid_err(hdev, "%d char not inserted to flip buffer\n", length - ret); + ft260_dbg("%d char not inserted to flip buf\n", length - ret); + port->icount.rx +=3D ret; =20 if (ret) @@ -1265,26 +1286,26 @@ static int ft260_uart_receive_chars(struct ft260_de= vice *port, } =20 static ssize_t ft260_uart_write(struct tty_struct *tty, const u8 *buf, - size_t count) + size_t cnt) { struct ft260_device *port =3D tty->driver_data; - struct hid_device *hdev =3D port->hdev; - int len, ret; + ssize_t len, ret, diff; =20 - len =3D kfifo_in_locked(&port->xmit_fifo, buf, count, &port->write_lock); - ft260_dbg("count: %zu, len: %d", count, len); + len =3D kfifo_in_spinlocked(&port->xmit_fifo, buf, cnt, + &port->xmit_fifo_lock); + ft260_dbg("count: %zu, len: %zd", cnt, len); =20 ret =3D ft260_uart_transmit_chars(port); if (ret < 0) { - hid_dbg(hdev, "Failed to transmit chars: %d\n", ret); + ft260_dbg("failed to transmit %zd\n", ret); return 0; } =20 ret =3D kfifo_len(&port->xmit_fifo); if (ret > 0) { - hid_dbg(hdev, "Failed to all kfifo data bytes\n"); - ft260_dbg("return: %d", len - ret); - return len - ret; + diff =3D len - ret; + ft260_dbg("failed to send %zd out of %zd bytes\n", diff, len); + return diff; } =20 return len; @@ -1294,7 +1315,7 @@ static unsigned int ft260_uart_write_room(struct tty_= struct *tty) { struct ft260_device *port =3D tty->driver_data; =20 - return FIFO_SIZE - kfifo_len(&port->xmit_fifo); + return kfifo_avail(&port->xmit_fifo); } =20 static unsigned int ft260_uart_chars_in_buffer(struct tty_struct *tty) @@ -1310,7 +1331,8 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, { struct hid_device *hdev =3D port->hdev; unsigned int baud; - struct ft260_configure_uart_request req; + struct ft260_configure_uart_request_report req; + bool wakeup_workaraund =3D false; int ret; =20 memset(&req, 0, sizeof(req)); @@ -1324,7 +1346,7 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, break; case CS5: case CS6: - hid_err(hdev, "Invalid data bit size, setting to default (8 bit)\n"); + hid_err(hdev, "invalid data bit size, setting a default\n"); req.data_bit =3D FT260_CFG_DATA_BITS_8; termios->c_cflag &=3D ~CSIZE; termios->c_cflag |=3D CS8; @@ -1349,11 +1371,17 @@ static int ft260_uart_change_speed(struct ft260_dev= ice *port, if (baud =3D=3D 0 || baud < FT260_CFG_BAUD_MIN || baud > FT260_CFG_BAUD_M= AX) { struct tty_struct *tty =3D tty_port_tty_get(&port->port); =20 - hid_err(hdev, "Invalid baud rate %d\n", baud); + hid_err(hdev, "invalid baud rate %d\n", baud); baud =3D 9600; tty_encode_baud_rate(tty, baud, baud); tty_kref_put(tty); } + + if (baud > FT260_UART_EN_PW_SAVE_BAUD) + wakeup_workaraund =3D true; + + ft260_uart_wakeup_workaraund_enable(port, wakeup_workaraund); + put_unaligned_le32(cpu_to_le32(baud), &req.baudrate); =20 if (termios->c_cflag & CRTSCTS) @@ -1361,7 +1389,7 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, else req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_OFF; =20 - ft260_dbg("Configured termios: flow control: %d, baudrate: %d, ", + 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, @@ -1372,7 +1400,7 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, =20 ret =3D 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); + hid_err(hdev, "failed to change termios: %d\n", ret); =20 return ret; } @@ -1422,8 +1450,8 @@ static int ft260_uart_proc_show(struct seq_file *m, v= oid *v) { int i; =20 - seq_printf(m, "ft260 info:1.0 driver%s%s revision:%s\n", - "", "", ""); + seq_printf(m, "ft260 info:1.0 driver%s%s revision:%s\n", "", "", ""); + for (i =3D 0; i < UART_COUNT_MAX; i++) { struct ft260_device *port =3D ft260_uart_port_get(i); =20 @@ -1478,15 +1506,16 @@ static const struct tty_operations ft260_uart_ops = =3D { .get_icount =3D ft260_uart_get_icount, }; =20 -/* The FT260 has a "power saving mode" that causes the device to switch +/* + * 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 + * 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. + * One effect of this mode is to cause data loss on an Rx line at baud + * rates higher than 4800 after being idle for longer than 5 seconds. + * We work around this by sending a dummy report at least once per 4.8 + * seconds if the UART is in use. */ static void ft260_uart_start_wakeup(struct timer_list *t) { @@ -1500,53 +1529,68 @@ static void ft260_uart_start_wakeup(struct timer_li= st *t) } } =20 -static void ft260_uart_do_wakeup(struct work_struct *work) +static void ft260_uart_wakeup(struct ft260_device *dev) { - struct ft260_device *dev =3D - container_of(work, struct ft260_device, wakeup_work); - struct ft260_get_chip_version_report version; + struct ft260_get_chip_version_report ver; int ret; =20 if (dev->reschedule_work) { ret =3D ft260_hid_feature_report_get(dev->hdev, FT260_CHIP_VERSION, - (u8 *)&version, sizeof(version)); + (u8 *)&ver, sizeof(ver)); if (ret < 0) - hid_err(dev->hdev, - "%s: failed to start transfer, ret %d\n", - __func__, ret); + hid_err(dev->hdev, "%s: failed with %d\n", __func__, ret); } } =20 -static void ft260_uart_shutdown(struct tty_port *tport) +static void ft260_uart_do_wakeup(struct work_struct *work) +{ + struct ft260_device *dev =3D + container_of(work, struct ft260_device, wakeup_work); + + ft260_uart_wakeup(dev); +} + +static void ft260_uart_port_shutdown(struct tty_port *tport) { struct ft260_device *port =3D container_of(tport, struct ft260_device, port); =20 - port->reschedule_work =3D false; + ft260_uart_wakeup_workaraund_enable(port, false); } =20 -static int ft260_uart_activate(struct tty_port *tport, struct tty_struct *= tty) +static int ft260_uart_port_activate(struct tty_port *tport, struct tty_str= uct *tty) { - struct ft260_device *port =3D - container_of(tport, struct ft260_device, port); + int ret; + int baudrate; + struct ft260_get_uart_settings_report cfg; + struct ft260_device *port =3D container_of(tport, struct ft260_device, po= rt); =20 - /* - * Set the TTY IO error marker - we will only clear this - * once we have successfully opened the port. - */ set_bit(TTY_IO_ERROR, &tty->flags); =20 - spin_lock(&port->write_lock); + spin_lock(&port->xmit_fifo_lock); kfifo_reset(&port->xmit_fifo); - spin_unlock(&port->write_lock); + spin_unlock(&port->xmit_fifo_lock); =20 - ft260_uart_change_speed(port, &tty->termios, NULL); clear_bit(TTY_IO_ERROR, &tty->flags); =20 - if (port->reschedule_work) { - mod_timer(&port->wakeup_timer, jiffies + - msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS)); - } + /* + * The port setting may remain intact after session termination. + * Then, when reopening the port without configuring the port + * setting, we need to retrieve the baud rate from the device to + * reactivate the wakeup workaround if needed. + */ + ret =3D ft260_get_uart_settings(port->hdev, &cfg); + if (ret) + return ret; + + baudrate =3D get_unaligned_le32(&cfg.baudrate); + if (baudrate > FT260_UART_EN_PW_SAVE_BAUD) + ft260_uart_wakeup_workaraund_enable(port, true); + + ft260_dbg("configured baudrate =3D %d", baudrate); + + mod_timer(&port->wakeup_timer, jiffies + + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS)); =20 return 0; } @@ -1560,8 +1604,8 @@ static void ft260_uart_port_destroy(struct tty_port *= tport) } =20 static const struct tty_port_operations ft260_uart_port_ops =3D { - .shutdown =3D ft260_uart_shutdown, - .activate =3D ft260_uart_activate, + .shutdown =3D ft260_uart_port_shutdown, + .activate =3D ft260_uart_port_activate, .destruct =3D ft260_uart_port_destroy, }; =20 @@ -1610,14 +1654,13 @@ static int ft260_i2c_probe(struct hid_device *hdev,= struct ft260_device *dev) =20 static int ft260_uart_probe(struct hid_device *hdev, struct ft260_device *= dev) { - struct ft260_configure_uart_request req; + struct ft260_configure_uart_request_report req; int ret; struct device *devt; =20 INIT_WORK(&dev->wakeup_work, ft260_uart_do_wakeup); - // FIXME: Do I need that if I have cancel_work_sync? // FIXME: are all kfifo access secured by lock? with irq or not? - dev->reschedule_work =3D false; + ft260_uart_wakeup_workaraund_enable(dev, true); /* Work not started at this point */ timer_setup(&dev->wakeup_timer, ft260_uart_start_wakeup, 0); =20 @@ -1638,10 +1681,10 @@ static int ft260_uart_probe(struct hid_device *hdev= , struct ft260_device *dev) ret =3D PTR_ERR(devt); goto err_register_tty; } - hid_info(hdev, "Registering device /dev/%s%d\n", + hid_info(hdev, "registering device /dev/%s%d\n", ft260_tty_driver->name, dev->index); =20 - /* Send Feature Report to Configure FT260 as UART 9600-8-N-1 */ + /* Configure UART to 9600n8 */ req.report =3D FT260_SYSTEM_SETTINGS; req.request =3D FT260_SET_UART_CONFIG; req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_NONE; @@ -1653,8 +1696,7 @@ static int ft260_uart_probe(struct hid_device *hdev, = struct ft260_device *dev) =20 ret =3D 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); + hid_err(hdev, "failed to configure uart: %d\n", ret); goto err_hid_report; } =20 @@ -1675,9 +1717,9 @@ static int ft260_probe(struct hid_device *hdev, const= struct hid_device_id *id) =20 if (!hid_is_usb(hdev)) return -EINVAL; - - /* We cannot used devm_kzalloc here, because port has to survive until - * destroy function call + /* + * We cannot use devm_kzalloc here because the port has to survive + * until destroy function call. */ dev =3D kzalloc_obj(*dev, GFP_KERNEL); if (!dev) { @@ -1726,15 +1768,12 @@ static int ft260_probe(struct hid_device *hdev, con= st struct hid_device_id *id) spin_lock_init(&dev->read_lock); init_completion(&dev->wait); =20 - if (!dev->ft260_is_serial) { + if (ret =3D=3D FT260_IFACE_I2C) ret =3D ft260_i2c_probe(hdev, dev); - if (ret) - goto err_hid_close; - } else { + else ret =3D ft260_uart_probe(hdev, dev); - if (ret) - goto err_hid_close; - } + if (ret) + goto err_hid_close; =20 return 0; =20 @@ -1755,13 +1794,12 @@ static void ft260_remove(struct hid_device *hdev) if (!dev) return; =20 - if (dev->ft260_is_serial) { - // FIXME: + if (dev->iface_type =3D=3D FT260_IFACE_UART) { cancel_work_sync(&dev->wakeup_work); tty_port_unregister_device(&dev->port, ft260_tty_driver, dev->index); ft260_uart_port_remove(dev); - /* dev still needed, so we will free it in _destroy func */ + /* dev is still needed, so we will free it in _destroy func */ } else { sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); i2c_del_adapter(&dev->adap); @@ -1784,6 +1822,12 @@ static int ft260_raw_event(struct hid_device *hdev, = struct hid_report *report, return -1; } =20 + if (xfer->length > size - offsetof(struct ft260_input_report, data)) { + hid_err(hdev, "report %#02x: length %d exceeds HID report size\n", + xfer->report, xfer->length); + return -1; + } + if (xfer->report >=3D FT260_I2C_REPORT_MIN && xfer->report <=3D FT260_I2C_REPORT_MAX) { bool complete_read; @@ -1791,13 +1835,6 @@ static int ft260_raw_event(struct hid_device *hdev, = struct hid_report *report, ft260_dbg("i2c resp: rep %#02x len %d size %d\n", xfer->report, xfer->length, size); =20 - if (xfer->length > size - - offsetof(struct ft260_input_report, data)) { - hid_err(hdev, "report %#02x: length %d exceeds HID report size\n", - xfer->report, xfer->length); - return -1; - } - /* * Hold read_lock so a timed-out ft260_i2c_read() cannot * clear read_buf between the NULL check and the memcpy. @@ -1824,12 +1861,11 @@ static int ft260_raw_event(struct hid_device *hdev,= struct hid_report *report, =20 return 0; =20 - } else if (xfer->length > FT260_RD_DATA_MAX) { - hid_err(hdev, "received data too long (%d)\n", xfer->length); - return -EBADR; } else if (xfer->report >=3D FT260_UART_REPORT_MIN && xfer->report <=3D FT260_UART_REPORT_MAX) { return ft260_uart_receive_chars(dev, xfer->data, xfer->length); + } else if (xfer->report =3D=3D FT260_UART_INTERRUPT_STATUS) { + return 0; } hid_err(hdev, "unhandled report %#02x\n", xfer->report); =20 @@ -1874,7 +1910,7 @@ static int __init ft260_driver_init(void) goto err_reg_driver; } =20 - ret =3D hid_register_driver(&(ft260_driver)); + ret =3D hid_register_driver(&ft260_driver); if (ret) { pr_err("hid_register_driver failed: %d\n", ret); goto err_reg_hid; @@ -1892,7 +1928,7 @@ static int __init ft260_driver_init(void) =20 static void __exit ft260_driver_exit(void) { - hid_unregister_driver(&(ft260_driver)); + hid_unregister_driver(&ft260_driver); tty_unregister_driver(ft260_tty_driver); tty_driver_kref_put(ft260_tty_driver); } --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f44.google.com (mail-ej1-f44.google.com [209.85.218.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7B638390986 for ; Sat, 22 Aug 2026 21:40:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434844; cv=none; b=rlbNzhKh8Se1VtnhbVZO/Zq47+xCXSa9UQtI2NFXWx8sScNU9l7WmSAP5s4bXeIufJASNo95MVS7+6O8vdazLCdYlZvRG6hECXPD/AhCRjJiXF38uYbaEDYo9FAlmwRNH0MpWSi1eiTFj0WDxExNDuvtKk+cJBPpHx9ZxeAGE08= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434844; c=relaxed/simple; bh=0m7Cttfvo8LCPzHjgNxVmLZjhMYzpm1CMfw2zEHoQgk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ji6BFDLbxqGziHOtyHJLg9otITzthBimwVRGD7JqLukk/ykJO+4HwV5U8NXehztx1Qo5FGr6dZWfQJHTNthklYqpECEOmnFDF6KdYtsTQnp9N6l0SrdxB3l4145Fcf1V/DG1D1Qe/xMleEgPJJWN7ha1XnZzJ84zrlwqvG1qFyE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=YeqJnbvW; arc=none smtp.client-ip=209.85.218.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="YeqJnbvW" Received: by mail-ej1-f44.google.com with SMTP id a640c23a62f3a-c1677c91969so261791766b.1 for ; Sat, 22 Aug 2026 14:40:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434833; x=1788039633; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=LUHq6orUk6dX8RpgoUJEImzKdwDguH+0tNnHHWNQJOY=; b=YeqJnbvW2zGfDH4IC40fjx6OludDcvdWfqlGyT4zTIhvKypbcM4CdGP5wYj9wisFs6 pXPNoj0poJiswEfwNhfDU3glTOFnd+I3b7VLL6fbWXeyw/ocboMsLv+GH/yvstIcG22n z7tp+yIaZuaE8DS1xHO1CA/3Bcnjt1Y5Xk1BeuPJXi3XLxniOQHV086TPyWy7wHgx7c6 QqB+Z+50Jq5tkDuC47yChB8s2mjj4fEAzZ+CEoRUxnYHpl8PFM0Q3+ac1NnUGcaClTkp Uoda074/XNyXJUzp0Svn1D0GoWV1U9InHElOHcVO5vNcuKNFnD2WfHeZ7kTn2ftgBol8 MEoQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434833; x=1788039633; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=LUHq6orUk6dX8RpgoUJEImzKdwDguH+0tNnHHWNQJOY=; b=UOApb/DeJX9hyVNV+r89fOqFkKsXqConuQ9lSVPPWBd+GGnbY0gBIZAOgaaIgl/Faf 7/qplzVYmuGpClByZjxJWwWtU3I+w/CMZG3dAZKKIhmAdxpXo3gdIkEoJD2tQuniCPM8 ZD2UIxLrzSVyBhp2UTe+0lft/K1c+YzgPc/QO2hV02X3s+lCyjP08vZlIkY9YUex2RSh WRJXpY5RwTBwKnqXd17xwGD+9vKGXE+nAssut6LfJ1xbxwmSX4QxlupuPtJYa9DdY8LZ FklFbBGXMFptH+NU2OYXlG4c7s2m7wuzxIdxUecY3uyadSfJrPoCvhSg3c+IkM3mU3kq PkmA== X-Forwarded-Encrypted: i=1; AHgh+RrGWLNqJKW5Z2HKXnnSOksM9supiU403ZP3krZqTmTLaHO6zX8qeDBItiM4+E+U8R1D0/j9lVC7GFAOLaY=@vger.kernel.org X-Gm-Message-State: AFuF++mi2n6sv+AAchxUHc92eMA27paSLg1+a0y3MM9oUy9bYLwJW9EU gxvAdV/YljTF+BP5M93ZNFsQaRHjACsek/mVTiUrPNITjgHBsjfwxpEC X-Gm-Gg: AR+sD11DM4AIoZnovb5zMSf84H2EZ677YEGdEAlmgbhkedI9KxQ3vfmdv6+tXobgAnC 5WiGqzC5gdo30e+AqlH3OW6Bvysw6aEireKVjBIljNTgDnPuLVvCCb8n/VkopNcDIKRx3XYvdbs g6b2B4m8yHMXuIznlXAcGwD/L86tA0v8xDKAgg8P161JHccqYAS9dxSlL22GL5p+nn62iymHleq JffTE9obP0bWnXlJMafkBBng/X/tcExwX7Ltg+8OQMDLWP0RGsmCyYe/is4PCZznGBvorhLzRfc dXAYEcX9tXeDoLujEA188CmBAoYPG8STGij7QM3rWw2a2SPR+0qAHWH3yCa49+hEi8ZVH0ZlCBW Y86j6KbqSFP71DzQc1JnMLK4AJmCbQzafVb+P0+1IcwhNlTWW84x1E1E4QziwOF/0vcb/J4bou4 lBZJjR/9uHn6chSREuz/ImrJk8MqDWY1ySR5AewZewhvRN7gdPYu0jVOXV1Wxtpw8qnn8bbQ== X-Received: by 2002:a17:907:890e:b0:c24:7f65:9c8a with SMTP id a640c23a62f3a-c24926a1ae2mr925290666b.12.1787434833296; Sat, 22 Aug 2026 14:40:33 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:32 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 03/13] HID: ft260: add GPIO support on top of UART Date: Sun, 23 Aug 2026 00:39:31 +0300 Message-ID: <20260822213941.98882-4-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add gpiochip support for the FT260 multifunctional pins, including GPIO2/GPIOA/GPIOG function selection via sysfs, and enable the available GPIOs according to the active UART mode. This supersedes the earlier pre-UART GPIO series: https://lore.kernel.org/lkml/20230211115752.26276-2-michael.zaidman@gmail.c= om/ The gpio_chip.set callback returns int to match the current gpiolib API, based on the adaptation originally contributed by Rio Liu . Signed-off-by: Michael Zaidman --- drivers/hid/Kconfig | 10 +- drivers/hid/hid-ft260.c | 660 +++++++++++++++++++++++++++++++++++----- 2 files changed, 586 insertions(+), 84 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e898adb331ba..8e161c440346 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -386,12 +386,12 @@ config HID_EZKEY Support for Ezkey BTC 8193 keyboard. =20 config HID_FT260 - tristate "FTDI FT260 USB HID to I2C/UART host support" - depends on USB_HID && HIDRAW && I2C && TTY + tristate "FTDI FT260 USB HID to I2C/UART/GPIO host support" + depends on USB_HID && HIDRAW && I2C && TTY && GPIOLIB help - Provides I2C host adapter and UART/TTY functionality over USB-HID - through the FT260 device. The customizable USB descriptor fields - are exposed as sysfs attributes. + Provides I2C host adapter, UART/TTY, and GPIO functionality over + USB-HID through the FT260 device. The customizable USB descriptor + fields are exposed as sysfs attributes. =20 To compile this driver as a module, choose M here: the module will be called hid-ft260. diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index b6c408267a68..cc3b8f5791de 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -19,6 +19,7 @@ #include #include #include +#include =20 #ifdef DEBUG static int ft260_debug =3D 1; @@ -57,6 +58,11 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages= "); #define FT260_RD_DATA_MAX (180) #define FT260_WR_I2C_DATA_MAX (60) #define FT260_WR_UART_DATA_MAX (62) +#define FT260_GPIOCHIP "ft260_gpio" +#define FT260_GPIO_MAX (6) +#define FT260_GPIO_EX_MAX (8) +#define FT260_GPIO_TOTAL (FT260_GPIO_MAX + FT260_GPIO_EX_MAX) +#define FT260_GPIO_MASK (~(0xffff << FT260_GPIO_TOTAL)) =20 /* * Device interface configuration. @@ -151,7 +157,69 @@ enum { FT260_IFACE_UART }; =20 -#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_= id) +/* Multi-function pin functions */ +enum { + FT260_MFPIN_GPIO =3D 0x00, + FT260_MFPIN_SUSPOUT =3D 0x01, + FT260_MFPIN_PWREN =3D 0x02, + FT260_MFPIN_TX_ACTIVE =3D 0x03, + FT260_MFPIN_TX_LED =3D 0x04, + FT260_MFPIN_RX_LED =3D 0x05, + FT260_MFPIN_BCD_DET =3D 0x06, +}; + +enum { + FT260_GPIO_VALUE =3D 0x00, + FT260_GPIO_DIRECTION =3D 0x01, + FT260_GPIO_DIR_INPUT =3D 0x00, + FT260_GPIO_DIR_OUTPUT =3D 0x01, +}; + +/* GPIO offsets */ +enum { + FT260_GPIO_0 =3D (1 << 0), + FT260_GPIO_1 =3D (1 << 1), + FT260_GPIO_2 =3D (1 << 2), + FT260_GPIO_3 =3D (1 << 3), + FT260_GPIO_4 =3D (1 << 4), + FT260_GPIO_5 =3D (1 << 5), + FT260_GPIO_A =3D (1 << (FT260_GPIO_MAX + 0)), + FT260_GPIO_B =3D (1 << (FT260_GPIO_MAX + 1)), + FT260_GPIO_C =3D (1 << (FT260_GPIO_MAX + 2)), + FT260_GPIO_D =3D (1 << (FT260_GPIO_MAX + 3)), + FT260_GPIO_E =3D (1 << (FT260_GPIO_MAX + 4)), + FT260_GPIO_F =3D (1 << (FT260_GPIO_MAX + 5)), + FT260_GPIO_G =3D (1 << (FT260_GPIO_MAX + 6)), + FT260_GPIO_H =3D (1 << (FT260_GPIO_MAX + 7)), +}; + +/* GPIO groups */ +enum { + FT260_GPIO_WAKEUP =3D (FT260_GPIO_3), + FT260_GPIO_I2C_DEFAULT =3D (FT260_GPIO_0 | FT260_GPIO_1), + FT260_GPIO_UART_RX_TX =3D (FT260_GPIO_C | FT260_GPIO_D), + FT260_GPIO_UART_DCD_RI =3D (FT260_GPIO_4 | FT260_GPIO_5), + FT260_GPIO_UART_RTS_CTS =3D (FT260_GPIO_B | FT260_GPIO_E), + FT260_GPIO_UART_DTR_DSR =3D (FT260_GPIO_F | FT260_GPIO_H), + FT260_GPIO_UART_MODE_0_SET =3D (FT260_GPIO_UART_RX_TX | + FT260_GPIO_UART_DCD_RI | + FT260_GPIO_UART_RTS_CTS | + FT260_GPIO_UART_DTR_DSR), + FT260_GPIO_UART_MODE_1_SET =3D (FT260_GPIO_UART_DTR_DSR), + FT260_GPIO_UART_MODE_2_SET =3D (FT260_GPIO_UART_RTS_CTS), + FT260_GPIO_UART_MODE_3_SET =3D (FT260_GPIO_UART_RTS_CTS | + FT260_GPIO_UART_DTR_DSR), + FT260_GPIO_UART_MODE_4_SET =3D (FT260_GPIO_UART_MODE_3_SET), + FT260_GPIO_UART_DEFAULT =3D (FT260_GPIO_UART_MODE_0_SET), + FT260_GPIO_UART_MODE_1_CLR =3D (FT260_GPIO_UART_RX_TX | + FT260_GPIO_UART_RTS_CTS), + FT260_GPIO_UART_MODE_2_CLR =3D (FT260_GPIO_UART_RX_TX | + FT260_GPIO_UART_DTR_DSR), + FT260_GPIO_UART_MODE_3_CLR =3D (FT260_GPIO_UART_RX_TX), + FT260_GPIO_UART_MODES =3D (5), +}; + +#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | (report= _id)) =20 /* Feature In reports */ =20 @@ -171,10 +239,10 @@ struct ft260_get_system_status_report { u8 uart_mode; /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */ /* 3 - XON_XOFF, 4 - No flow control */ u8 hid_over_i2c_en; /* 0 - disabled, 1 - enabled */ - u8 gpio2_function; /* 0 - GPIO, 1 - SUSPOUT, */ + u8 gpio2_func; /* 0 - GPIO, 1 - SUSPOUT, */ /* 2 - PWREN, 4 - TX_LED */ - u8 gpioA_function; /* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */ - u8 gpioG_function; /* 0 - GPIO, 2 - PWREN, */ + u8 gpioa_func; /* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */ + u8 gpiog_func; /* 0 - GPIO, 2 - PWREN, */ /* 5 - RX_LED, 6 - BCD_DET */ u8 suspend_out_pol; /* 0 - active-high, 1 - active-low */ u8 enable_wakeup_int; /* 0 - disabled, 1 - enabled */ @@ -202,6 +270,18 @@ struct ft260_get_uart_settings_report { u8 breaking; /* 0: no break */ } __packed; =20 +struct ft260_gpio_state { + u8 vals; /* GPIO[0-5] values in bits 0 - 5 */ + u8 dirs; /* GPIO[0-5] directions, 0 - in, 1 - out */ + u8 ex_vals; /* GPIO[A-H] values in bits 0 - 7 */ + u8 ex_dirs; /* GPIO[A-H] directions, 0 - in, 1 - out */ +} __packed; + +struct ft260_gpio_read_request_report { + u8 report; /* FT260_GPIO */ + struct ft260_gpio_state gpio; +} __packed; + /* Feature Out reports */ =20 struct ft260_set_system_clock_report { @@ -223,6 +303,12 @@ struct ft260_set_uart_mode_report { /* 3 - XON_XOFF, 4 - No flow control */ } __packed; =20 +struct ft260_set_uart_dcd_ri_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_ENABLE_UART_DCD_RI */ + u8 uart_dcd_ri; /* Pins func: 0 - GPIO4,GPIO5, 1 - DCD,RI */ +} __packed; + struct ft260_set_i2c_reset_report { u8 report; /* FT260_SYSTEM_SETTINGS */ u8 request; /* FT260_SET_I2C_RESET */ @@ -234,6 +320,33 @@ struct ft260_set_i2c_speed_report { __le16 clock; /* I2C bus clock in range 60-3400 KHz */ } __packed; =20 +struct ft260_set_gpio2_func_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SELECT_GPIO2_FUNC */ + u8 gpio2_func; /* Pin func: 0 - GPIO, 1 - SUSPOUT, */ + /* 2 - PWREN# (active-low), 4 - TX_LED */ +} __packed; + +struct ft260_set_gpioa_func_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SELECT_GPIOA_FUNC */ + u8 gpioa_func; /* Pin func: 0 - GPIO, */ + /* 3 - TX_ACTIVE, 4 - TX_LED */ +} __packed; + +struct ft260_set_gpiog_func_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SELECT_GPIOG_FUNC */ + u8 gpiog_func; /* Pin func: 0 - GPIO, */ + /* 2 - PWREN# (active-low), */ + /* 5 - RX_LED, 6 - BCD_DET */ +} __packed; + +struct ft260_gpio_write_request_report { + u8 report; /* FT260_GPIO */ + struct ft260_gpio_state gpio; +} __packed; + /* Data transfer reports */ =20 struct ft260_i2c_write_request_report { @@ -280,32 +393,32 @@ struct ft260_configure_uart_request_report { =20 /* UART interface configuration */ enum { - FT260_CFG_FLOW_CTRL_OFF =3D 0x00, - FT260_CFG_FLOW_CTRL_RTS_CTS =3D 0x01, - FT260_CFG_FLOW_CTRL_DTR_DSR =3D 0x02, - FT260_CFG_FLOW_CTRL_XON_XOFF =3D 0x03, - FT260_CFG_FLOW_CTRL_NONE =3D 0x04, + FT260_UART_CFG_FLOW_CTRL_OFF =3D 0x00, + FT260_UART_CFG_FLOW_CTRL_RTS_CTS =3D 0x01, + FT260_UART_CFG_FLOW_CTRL_DTR_DSR =3D 0x02, + FT260_UART_CFG_FLOW_CTRL_XON_XOFF =3D 0x03, + FT260_UART_CFG_FLOW_CTRL_NONE =3D 0x04, =20 - FT260_CFG_DATA_BITS_7 =3D 0x07, - FT260_CFG_DATA_BITS_8 =3D 0x08, + FT260_UART_CFG_DATA_BITS_7 =3D 0x07, + FT260_UART_CFG_DATA_BITS_8 =3D 0x08, =20 - FT260_CFG_PAR_NO =3D 0x00, - FT260_CFG_PAR_ODD =3D 0x01, - FT260_CFG_PAR_EVEN =3D 0x02, - FT260_CFG_PAR_HIGH =3D 0x03, - FT260_CFG_PAR_LOW =3D 0x04, + FT260_UART_CFG_PAR_NO =3D 0x00, + FT260_UART_CFG_PAR_ODD =3D 0x01, + FT260_UART_CFG_PAR_EVEN =3D 0x02, + FT260_UART_CFG_PAR_HIGH =3D 0x03, + FT260_UART_CFG_PAR_LOW =3D 0x04, =20 - FT260_CFG_STOP_ONE_BIT =3D 0x00, - FT260_CFG_STOP_TWO_BIT =3D 0x02, + FT260_UART_CFG_STOP_ONE_BIT =3D 0x00, + FT260_UART_CFG_STOP_TWO_BIT =3D 0x02, =20 - FT260_CFG_BREAKING_NO =3D 0x00, - FT260_CFG_BEAKING_YES =3D 0x01, + FT260_UART_CFG_BREAKING_NO =3D 0x00, + FT260_UART_CFG_BEAKING_YES =3D 0x01, =20 - FT260_CFG_BAUD_MIN =3D 1200, - FT260_CFG_BAUD_MAX =3D 12000000, + FT260_UART_CFG_BAUD_MIN =3D 1200, + FT260_UART_CFG_BAUD_MAX =3D 12000000, }; =20 -#define FT260_UART_EN_PW_SAVE_BAUD (4800) +#define FT260_UART_EN_PW_SAVE_BAUD (4800) =20 #define UART_COUNT_MAX (4) /* Number of supported UARTs */ #define XMIT_FIFO_SIZE (PAGE_SIZE) @@ -321,6 +434,7 @@ struct ft260_device { struct i2c_adapter adap; struct hid_device *hdev; int iface_type; + int iface_id; struct list_head device_list; struct tty_port port; /* tty port index */ @@ -343,6 +457,10 @@ struct ft260_device { u16 read_idx; u16 read_len; u16 clock; + u16 gpio_en; + struct gpio_chip *gc; + struct ft260_gpio_state gpio; + u16 gpio_uart_mode[FT260_GPIO_UART_MODES]; }; =20 static int ft260_hid_feature_report_get(struct hid_device *hdev, @@ -375,8 +493,6 @@ static int ft260_hid_feature_report_set(struct hid_devi= ce *hdev, u8 *data, if (!buf) return -ENOMEM; =20 - buf[0] =3D FT260_SYSTEM_SETTINGS; - ret =3D hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); =20 @@ -389,6 +505,7 @@ static int ft260_i2c_reset(struct hid_device *hdev) struct ft260_set_i2c_reset_report report; int ret; =20 + report.report =3D FT260_SYSTEM_SETTINGS; report.request =3D FT260_SET_I2C_RESET; =20 ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report)); @@ -906,6 +1023,322 @@ static const struct i2c_algorithm ft260_i2c_algo =3D= { .functionality =3D ft260_functionality, }; =20 +static void ft260_gpio_en_set(struct ft260_device *dev, u16 bitmap) +{ + dev->gpio_en |=3D bitmap & FT260_GPIO_MASK; +} + +static void ft260_gpio_en_clr(struct ft260_device *dev, u16 bitmap) +{ + dev->gpio_en &=3D ~bitmap & FT260_GPIO_MASK; +} + +static void ft260_gpio_en_update(struct hid_device *hdev, u8 req, u8 value) +{ + u16 bitmap; + struct ft260_device *dev =3D hid_get_drvdata(hdev); + + switch (req) { + + case FT260_SET_I2C_MODE: + bitmap =3D FT260_GPIO_I2C_DEFAULT; + break; + case FT260_SET_UART_MODE: + switch (value) { + case FT260_UART_CFG_FLOW_CTRL_OFF: + bitmap =3D (u16)FT260_GPIO_MASK; + break; + case FT260_UART_CFG_FLOW_CTRL_RTS_CTS: + bitmap =3D FT260_GPIO_UART_MODE_1_CLR; + break; + case FT260_UART_CFG_FLOW_CTRL_DTR_DSR: + bitmap =3D FT260_GPIO_UART_MODE_2_CLR; + break; + case FT260_UART_CFG_FLOW_CTRL_XON_XOFF: + case FT260_UART_CFG_FLOW_CTRL_NONE: + bitmap =3D FT260_GPIO_UART_MODE_3_CLR; + break; + default: + return; + } + ft260_gpio_en_clr(dev, bitmap); + bitmap =3D dev->gpio_uart_mode[value]; + ft260_gpio_en_set(dev, bitmap); + goto exit; + + case FT260_ENABLE_UART_DCD_RI: + bitmap =3D FT260_GPIO_UART_DCD_RI; + break; + + case FT260_SELECT_GPIO2_FUNC: + bitmap =3D FT260_GPIO_2; + break; + case FT260_SELECT_GPIOA_FUNC: + bitmap =3D FT260_GPIO_A; + break; + case FT260_SELECT_GPIOG_FUNC: + bitmap =3D FT260_GPIO_G; + break; + default: + return; + } + + if (value =3D=3D FT260_MFPIN_GPIO) + ft260_gpio_en_set(dev, bitmap); + else + ft260_gpio_en_clr(dev, bitmap); +exit: + hid_info(hdev, "enabled GPIOs: %04x\n", dev->gpio_en); +} + +static int ft260_gpio_set(struct gpio_chip *gc, u32 offset, int value) +{ + int ret =3D 0; + struct ft260_gpio_write_request_report rep; + struct ft260_device *dev =3D gpiochip_get_data(gc); + struct hid_device *hdev =3D dev->hdev; + + if (offset >=3D FT260_GPIO_TOTAL) { + hid_err(hdev, "%s: invalid offset %d\n", __func__, offset); + return -EINVAL; + } + + ft260_dbg("offset %d val %d\n", offset, value); + + mutex_lock(&dev->lock); + + if (!(dev->gpio_en & (1 << offset))) { + hid_err(hdev, "%s: wrong pin function %d\n", __func__, offset); + ret =3D -EINVAL; + goto exit; + } + + rep.report =3D FT260_GPIO; + rep.gpio =3D dev->gpio; + + if (offset < FT260_GPIO_MAX) { + if (value) + rep.gpio.vals |=3D !!value << offset; + else + rep.gpio.vals &=3D ~(1 << offset); + } else { + offset =3D offset - FT260_GPIO_MAX; + if (value) + rep.gpio.ex_vals |=3D !!value << offset; + else + rep.gpio.ex_vals &=3D ~(1 << offset); + } + + ft260_dbg("dirs %#02x vals %#02x ex_dir %#02x ex_vals %#02x\n", + rep.gpio.dirs, rep.gpio.vals, + rep.gpio.ex_dirs, rep.gpio.ex_vals); + + ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&rep, sizeof(rep)); + if (unlikely(ret < 0)) { + hid_err(hdev, "%s: cannot set GPIO: %d\n", __func__, ret); + goto exit; + } + + dev->gpio =3D rep.gpio; + ret =3D 0; +exit: + mutex_unlock(&dev->lock); + return ret; +} + +static int ft260_gpio_direction_set(struct gpio_chip *gc, u32 offset, + int value, int direction) +{ + int ret; + struct ft260_gpio_read_request_report buf; + struct ft260_gpio_write_request_report *rep; + struct ft260_device *dev =3D gpiochip_get_data(gc); + struct hid_device *hdev =3D dev->hdev; + + if (offset >=3D FT260_GPIO_TOTAL) { + hid_err(hdev, "%s: invalid offset %d\n", __func__, offset); + return -EINVAL; + } + + ft260_dbg("offset %d val %d direction %d\n", offset, value, direction); + + mutex_lock(&dev->lock); + + if (!(dev->gpio_en & (1 << offset))) { + hid_err(hdev, "%s: wrong pin function %d\n", __func__, offset); + ret =3D -EIO; + goto exit; + } + + ret =3D ft260_hid_feature_report_get(hdev, FT260_GPIO, (u8 *)&buf, sizeof= (buf)); + if (unlikely(ret < 0)) { + hid_err(hdev, "%s: cannot get GPIO: %d\n", __func__, ret); + goto exit; + } + + rep =3D (struct ft260_gpio_write_request_report *)&buf; + + if (direction =3D=3D FT260_GPIO_DIR_OUTPUT) + if (offset < FT260_GPIO_MAX) + rep->gpio.dirs |=3D 1 << offset; + else + rep->gpio.ex_dirs |=3D 1 << (offset - FT260_GPIO_MAX); + else + if (offset < FT260_GPIO_MAX) + rep->gpio.dirs &=3D ~(1 << offset); + else + rep->gpio.ex_dirs &=3D ~(1 << (offset - FT260_GPIO_MAX)); + + ft260_dbg("dirs %#02x val %#02x ex_dirs %#02x ex_vals %#02x\n", + rep->gpio.dirs, rep->gpio.vals, + rep->gpio.ex_dirs, rep->gpio.ex_vals); + + ret =3D ft260_hid_feature_report_set(hdev, (u8 *)rep, sizeof(*rep)); + if (unlikely(ret < 0)) { + hid_err(hdev, "%s: cannot set GPIO: %d\n", __func__, ret); + goto exit; + } + + dev->gpio =3D rep->gpio; + mutex_unlock(&dev->lock); + + if (direction =3D=3D FT260_GPIO_DIR_OUTPUT) + ft260_gpio_set(gc, offset, value); + + return 0; +exit: + mutex_unlock(&dev->lock); + return ret; +} + +static int ft260_gpio_direction_output(struct gpio_chip *gc, + u32 offset, int value) +{ + return ft260_gpio_direction_set(gc, offset, value, + FT260_GPIO_DIR_OUTPUT); +} + +static int ft260_gpio_direction_input(struct gpio_chip *gc, u32 offset) +{ + return ft260_gpio_direction_set(gc, offset, 0, + FT260_GPIO_DIR_INPUT); +} + +static int ft260_gpio_get_all(struct gpio_chip *gc, int item) +{ + int ret; + struct ft260_gpio_read_request_report rep; + struct ft260_device *dev =3D gpiochip_get_data(gc); + struct hid_device *hdev =3D dev->hdev; + + ret =3D ft260_hid_feature_report_get(hdev, FT260_GPIO, (u8 *)&rep, sizeof= (rep)); + if (unlikely(ret < 0)) { + hid_err(hdev, "%s: cannot get GPIO: %d\n", __func__, ret); + goto exit; + } + + if (item =3D=3D FT260_GPIO_VALUE) + ret =3D (rep.gpio.ex_vals << FT260_GPIO_MAX) | rep.gpio.vals; + else + ret =3D (rep.gpio.ex_dirs << FT260_GPIO_MAX) | rep.gpio.dirs; +exit: + return ret; +} + +static int ft260_gpio_get_direction(struct gpio_chip *gc, u32 offset) +{ + int ret =3D ft260_gpio_get_all(gc, FT260_GPIO_DIRECTION); + + if (ret < 0) + return ret; + return !((ret >> offset) & 1); +} + +static int ft260_gpio_get(struct gpio_chip *gc, u32 offset) +{ + int ret =3D ft260_gpio_get_all(gc, FT260_GPIO_VALUE); + + if (ret < 0) + return ret; + return (ret >> offset) & 1; +} + +static int ft260_gpio_init(struct ft260_device *dev, + struct ft260_get_system_status_report *cfg) +{ + int ret; + int label_sz; + char *label; + struct ft260_get_chip_version_report ver; + struct hid_device *hdev =3D dev->hdev; + char prefix[] =3D "ft260_"; + u8 mode =3D cfg->chip_mode; + + hid_info(hdev, "initialize gpio chip\n"); + + dev->gpio_uart_mode[0] =3D (u16)FT260_GPIO_UART_MODE_0_SET; + dev->gpio_uart_mode[1] =3D (u16)FT260_GPIO_UART_MODE_1_SET; + dev->gpio_uart_mode[2] =3D (u16)FT260_GPIO_UART_MODE_2_SET; + dev->gpio_uart_mode[3] =3D (u16)FT260_GPIO_UART_MODE_3_SET; + dev->gpio_uart_mode[4] =3D (u16)FT260_GPIO_UART_MODE_4_SET; + + if (mode =3D=3D FT260_MODE_ALL) + mode =3D FT260_MODE_BOTH; + + if (mode & FT260_MODE_UART) + dev->gpio_en |=3D dev->gpio_uart_mode[cfg->uart_mode]; + else + dev->gpio_en |=3D FT260_GPIO_UART_DEFAULT; + + if (!(mode & FT260_MODE_I2C)) + dev->gpio_en |=3D FT260_GPIO_I2C_DEFAULT; + + if (cfg->gpio2_func =3D=3D FT260_MFPIN_GPIO) + dev->gpio_en |=3D FT260_GPIO_2; + if (cfg->enable_wakeup_int =3D=3D FT260_MFPIN_GPIO) + dev->gpio_en |=3D FT260_GPIO_3; + if (cfg->gpioa_func =3D=3D FT260_MFPIN_GPIO) + dev->gpio_en |=3D FT260_GPIO_A; + if (cfg->gpiog_func =3D=3D FT260_MFPIN_GPIO) + dev->gpio_en |=3D FT260_GPIO_G; + + hid_info(hdev, "enabled GPIOs: %04x\n", dev->gpio_en); + + dev->gc =3D devm_kzalloc(&hdev->dev, sizeof(*dev->gc), GFP_KERNEL); + if (!dev->gc) + return -ENOMEM; + + label_sz =3D strlen(dev_name(&hdev->dev)) + strlen(prefix) + 1; + label =3D devm_kzalloc(&hdev->dev, label_sz, GFP_KERNEL); + if (!label) { + ret =3D -ENOMEM; + goto exit; + } + snprintf(label, label_sz, "%s%s", prefix, dev_name(&hdev->dev)); + hid_info(hdev, "initialize gpio chip on %s\n", label); + + dev->gc->label =3D label; + dev->gc->direction_input =3D ft260_gpio_direction_input; + dev->gc->direction_output =3D ft260_gpio_direction_output; + dev->gc->get_direction =3D ft260_gpio_get_direction; + dev->gc->set =3D ft260_gpio_set; + dev->gc->get =3D ft260_gpio_get; + dev->gc->base =3D -1; + dev->gc->ngpio =3D FT260_GPIO_TOTAL; + dev->gc->can_sleep =3D true; + dev->gc->parent =3D &hdev->dev; + + /* Wakeup chip */ + (void)ft260_hid_feature_report_get(dev->hdev, FT260_CHIP_VERSION, + (u8 *)&ver, sizeof(ver)); + + ret =3D devm_gpiochip_add_data(&hdev->dev, dev->gc, dev); + if (ret < 0) + hid_err(hdev, "cannot add GPIO chip %d\n", ret); +exit: + return ret; +} + static int ft260_get_system_config(struct hid_device *hdev, struct ft260_get_system_status_report *cfg) { @@ -921,29 +1354,36 @@ static int ft260_get_system_config(struct hid_device= *hdev, return 0; } =20 -static int ft260_get_interface_type(struct hid_device *hdev, struct ft260_= device *dev) +static int ft260_get_interface_type(struct ft260_device *dev, + struct ft260_get_system_status_report *cfg) + { - struct ft260_get_system_status_report cfg; - struct usb_interface *usbif =3D to_usb_interface(hdev->dev.parent); - int interface =3D usbif->cur_altsetting->desc.bInterfaceNumber; int ret; + struct hid_device *hdev =3D dev->hdev; + struct usb_interface *usbif =3D to_usb_interface(hdev->dev.parent); =20 - ret =3D ft260_get_system_config(hdev, &cfg); + dev->iface_id =3D usbif->cur_altsetting->desc.bInterfaceNumber; + + ret =3D ft260_get_system_config(hdev, cfg); if (ret < 0) return ret; =20 - ft260_dbg("interface: 0x%02x\n", interface); - ft260_dbg("chip mode: 0x%02x\n", cfg.chip_mode); - ft260_dbg("clock_ctl: 0x%02x\n", cfg.clock_ctl); - ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable); - ft260_dbg("uart_mode: 0x%02x\n", cfg.uart_mode); + ft260_dbg("interface: 0x%02x\n", dev->iface_id); + ft260_dbg("chip mode: 0x%02x\n", cfg->chip_mode); + ft260_dbg("clock_ctl: 0x%02x\n", cfg->clock_ctl); + ft260_dbg("i2c_enable: 0x%02x\n", cfg->i2c_enable); + ft260_dbg("uart_mode: 0x%02x\n", cfg->uart_mode); + ft260_dbg("gpio2_func: 0x%02x\n", cfg->gpio2_func); + ft260_dbg("gpioA_func: 0x%02x\n", cfg->gpioa_func); + ft260_dbg("gpioG_func: 0x%02x\n", cfg->gpiog_func); + ft260_dbg("wakeup_int: 0x%02x\n", cfg->enable_wakeup_int); =20 - dev->power_saving_en =3D cfg.power_saving_en; + dev->power_saving_en =3D cfg->power_saving_en; =20 - switch (cfg.chip_mode) { + switch (cfg->chip_mode) { case FT260_MODE_ALL: case FT260_MODE_BOTH: - if (interface =3D=3D 1) + if (dev->iface_id =3D=3D 1) ret =3D FT260_IFACE_UART; else ret =3D FT260_IFACE_I2C; @@ -984,6 +1424,10 @@ static int ft260_word_show(struct hid_device *hdev, i= nt id, u8 *cfg, int len, return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field)); } =20 +static void ft260_attr_dummy_func(struct hid_device *hdev, u8 req, u16 val= ue) +{ +} + #define FT260_ATTR_SHOW(name, reptype, id, type, func) \ static ssize_t name##_show(struct device *kdev, \ struct device_attribute *attr, char *buf) \ @@ -1004,37 +1448,42 @@ static int ft260_word_show(struct hid_device *hdev,= int id, u8 *cfg, int len, FT260_ATTR_SHOW(name, ft260_get_i2c_status_report, \ FT260_I2C_STATUS, __le16, ft260_word_show) =20 -#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func) = \ +#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, strtou, func= ) \ static ssize_t name##_store(struct device *kdev, \ struct device_attribute *attr, \ const char *buf, size_t count) \ { \ struct reptype rep; \ struct hid_device *hdev =3D to_hid_device(kdev); \ + struct ft260_device *dev =3D hid_get_drvdata(hdev); \ type name; \ int ret; \ \ - if (!func(buf, 10, (ctype *)&name)) { \ + if (!strtou(buf, 10, (ctype *)&name)) { \ rep.name =3D name; \ rep.report =3D id; \ rep.request =3D req; \ + mutex_lock(&dev->lock); \ ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&rep, \ sizeof(rep)); \ - if (!ret) \ - ret =3D count; \ + if (ret < 0) \ + hid_err(hdev, "%s: failed!\n", __func__); \ + else \ + func(hdev, req, name); \ + mutex_unlock(&dev->lock); \ } else { \ ret =3D -EINVAL; \ } \ return ret; \ } =20 -#define FT260_BYTE_ATTR_STORE(name, reptype, req) \ +#define FT260_BYTE_ATTR_STORE(name, reptype, req, func) \ FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \ - u8, u8, kstrtou8) + u8, u8, kstrtou8, func) =20 -#define FT260_WORD_ATTR_STORE(name, reptype, req) \ +#define FT260_WORD_ATTR_STORE(name, reptype, req, func) \ FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \ - __le16, u16, kstrtou16) + __le16, u16, kstrtou16, func) =20 FT260_SSTAT_ATTR_SHOW(chip_mode); static DEVICE_ATTR_RO(chip_mode); @@ -1048,27 +1497,46 @@ static DEVICE_ATTR_RO(suspend_status); FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en); static DEVICE_ATTR_RO(hid_over_i2c_en); =20 +FT260_SSTAT_ATTR_SHOW(gpio2_func); +FT260_BYTE_ATTR_STORE(gpio2_func, ft260_set_gpio2_func_report, + FT260_SELECT_GPIO2_FUNC, ft260_gpio_en_update); +static DEVICE_ATTR_RW(gpio2_func); + +FT260_SSTAT_ATTR_SHOW(gpioa_func); +FT260_BYTE_ATTR_STORE(gpioa_func, ft260_set_gpioa_func_report, + FT260_SELECT_GPIOA_FUNC, ft260_gpio_en_update); +static DEVICE_ATTR_RW(gpioa_func); + +FT260_SSTAT_ATTR_SHOW(gpiog_func); +FT260_BYTE_ATTR_STORE(gpiog_func, ft260_set_gpiog_func_report, + FT260_SELECT_GPIOG_FUNC, ft260_gpio_en_update); +static DEVICE_ATTR_RW(gpiog_func); + FT260_SSTAT_ATTR_SHOW(power_saving_en); static DEVICE_ATTR_RO(power_saving_en); =20 FT260_SSTAT_ATTR_SHOW(i2c_enable); FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report, - FT260_SET_I2C_MODE); + FT260_SET_I2C_MODE, ft260_gpio_en_update); static DEVICE_ATTR_RW(i2c_enable); =20 FT260_SSTAT_ATTR_SHOW(uart_mode); FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report, - FT260_SET_UART_MODE); + FT260_SET_UART_MODE, ft260_gpio_en_update); static DEVICE_ATTR_RW(uart_mode); =20 +FT260_BYTE_ATTR_STORE(uart_dcd_ri, ft260_set_uart_dcd_ri_report, + FT260_ENABLE_UART_DCD_RI, ft260_gpio_en_update); +static DEVICE_ATTR_WO(uart_dcd_ri); + FT260_SSTAT_ATTR_SHOW(clock_ctl); FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report, - FT260_SET_CLOCK); + FT260_SET_CLOCK, ft260_attr_dummy_func); static DEVICE_ATTR_RW(clock_ctl); =20 FT260_I2CST_ATTR_SHOW(clock); FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report, - FT260_SET_I2C_CLOCK_SPEED); + FT260_SET_I2C_CLOCK_SPEED, ft260_attr_dummy_func); static DEVICE_ATTR_RW(clock); =20 static ssize_t i2c_reset_store(struct device *kdev, @@ -1092,7 +1560,11 @@ static const struct attribute_group ft260_attr_group= =3D { &dev_attr_hid_over_i2c_en.attr, &dev_attr_power_saving_en.attr, &dev_attr_i2c_enable.attr, + &dev_attr_gpio2_func.attr, + &dev_attr_gpioa_func.attr, + &dev_attr_gpiog_func.attr, &dev_attr_uart_mode.attr, + &dev_attr_uart_dcd_ri.attr, &dev_attr_clock_ctl.attr, &dev_attr_i2c_reset.attr, &dev_attr_clock.attr, @@ -1342,33 +1814,33 @@ static int ft260_uart_change_speed(struct ft260_dev= ice *port, =20 switch (termios->c_cflag & CSIZE) { case CS7: - req.data_bit =3D FT260_CFG_DATA_BITS_7; + req.data_bit =3D FT260_UART_CFG_DATA_BITS_7; break; case CS5: case CS6: hid_err(hdev, "invalid data bit size, setting a default\n"); - req.data_bit =3D FT260_CFG_DATA_BITS_8; + req.data_bit =3D FT260_UART_CFG_DATA_BITS_8; termios->c_cflag &=3D ~CSIZE; termios->c_cflag |=3D CS8; break; default: case CS8: - req.data_bit =3D FT260_CFG_DATA_BITS_8; + req.data_bit =3D FT260_UART_CFG_DATA_BITS_8; break; } =20 req.stop_bit =3D (termios->c_cflag & CSTOPB) ? - FT260_CFG_STOP_TWO_BIT : FT260_CFG_STOP_ONE_BIT; + FT260_UART_CFG_STOP_TWO_BIT : FT260_UART_CFG_STOP_ONE_BIT; =20 if (termios->c_cflag & PARENB) { req.parity =3D (termios->c_cflag & PARODD) ? - FT260_CFG_PAR_ODD : FT260_CFG_PAR_EVEN; + FT260_UART_CFG_PAR_ODD : FT260_UART_CFG_PAR_EVEN; } else { - req.parity =3D FT260_CFG_PAR_NO; + req.parity =3D FT260_UART_CFG_PAR_NO; } =20 baud =3D tty_termios_baud_rate(termios); - if (baud =3D=3D 0 || baud < FT260_CFG_BAUD_MIN || baud > FT260_CFG_BAUD_M= AX) { + if (baud =3D=3D 0 || baud < FT260_UART_CFG_BAUD_MIN || baud > FT260_UART_= CFG_BAUD_MAX) { struct tty_struct *tty =3D tty_port_tty_get(&port->port); =20 hid_err(hdev, "invalid baud rate %d\n", baud); @@ -1385,9 +1857,9 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, put_unaligned_le32(cpu_to_le32(baud), &req.baudrate); =20 if (termios->c_cflag & CRTSCTS) - req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_RTS_CTS; + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_RTS_CTS; else - req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_OFF; + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_OFF; =20 ft260_dbg("configured termios: flow control: %d, baudrate: %d, ", req.flow_ctrl, baud); @@ -1395,12 +1867,18 @@ static int ft260_uart_change_speed(struct ft260_dev= ice *port, req.data_bit, req.parity, req.stop_bit, req.breaking); =20 - req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_NONE; - req.breaking =3D FT260_CFG_BREAKING_NO; + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_NONE; + req.breaking =3D FT260_UART_CFG_BREAKING_NO; + + mutex_lock(&port->lock); =20 ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req)); if (ret < 0) hid_err(hdev, "failed to change termios: %d\n", ret); + else + ft260_gpio_en_update(hdev, FT260_SET_UART_MODE, req.flow_ctrl); + + mutex_unlock(&port->lock); =20 return ret; } @@ -1611,9 +2089,11 @@ static const struct tty_port_operations ft260_uart_p= ort_ops =3D { =20 static struct tty_driver *ft260_tty_driver; =20 -static int ft260_i2c_probe(struct hid_device *hdev, struct ft260_device *d= ev) +static int ft260_i2c_probe(struct ft260_device *dev, + struct ft260_get_system_status_report *cfg) { int ret; + struct hid_device *hdev =3D dev->hdev; =20 hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8, hdev->version & 0xff, hdev->name, @@ -1640,11 +2120,16 @@ static int ft260_i2c_probe(struct hid_device *hdev,= struct ft260_device *dev) return ret; } =20 + ret =3D ft260_gpio_init(dev, cfg); + if (ret) + goto err_i2c_free; + ret =3D 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; } + return 0; =20 err_i2c_free: @@ -1652,14 +2137,15 @@ static int ft260_i2c_probe(struct hid_device *hdev,= struct ft260_device *dev) return ret; } =20 -static int ft260_uart_probe(struct hid_device *hdev, struct ft260_device *= dev) +static int ft260_uart_probe(struct ft260_device *dev, + struct ft260_get_system_status_report *cfg) { struct ft260_configure_uart_request_report req; - int ret; + struct hid_device *hdev =3D dev->hdev; struct device *devt; + int ret; =20 INIT_WORK(&dev->wakeup_work, ft260_uart_do_wakeup); - // FIXME: are all kfifo access secured by lock? with irq or not? ft260_uart_wakeup_workaraund_enable(dev, true); /* Work not started at this point */ timer_setup(&dev->wakeup_timer, ft260_uart_start_wakeup, 0); @@ -1687,12 +2173,12 @@ static int ft260_uart_probe(struct hid_device *hdev= , struct ft260_device *dev) /* Configure UART to 9600n8 */ req.report =3D FT260_SYSTEM_SETTINGS; req.request =3D FT260_SET_UART_CONFIG; - req.flow_ctrl =3D FT260_CFG_FLOW_CTRL_NONE; + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_NONE; put_unaligned_le32(cpu_to_le32(9600), &req.baudrate); - req.data_bit =3D FT260_CFG_DATA_BITS_8; - req.parity =3D FT260_CFG_PAR_NO; - req.stop_bit =3D FT260_CFG_STOP_ONE_BIT; - req.breaking =3D FT260_CFG_BREAKING_NO; + req.data_bit =3D FT260_UART_CFG_DATA_BITS_8; + req.parity =3D FT260_UART_CFG_PAR_NO; + req.stop_bit =3D FT260_UART_CFG_STOP_ONE_BIT; + req.breaking =3D FT260_UART_CFG_BREAKING_NO; =20 ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req)); if (ret < 0) { @@ -1700,6 +2186,20 @@ static int ft260_uart_probe(struct hid_device *hdev,= struct ft260_device *dev) goto err_hid_report; } =20 + cfg->uart_mode =3D FT260_UART_CFG_FLOW_CTRL_NONE; + + if (dev->iface_id =3D=3D 0) { + ret =3D ft260_gpio_init(dev, cfg); + if (ret) + goto err_hid_report; + + ret =3D sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); + if (ret < 0) { + hid_err(hdev, "failed to create sysfs attrs\n"); + goto err_hid_report; + } + } + return 0; =20 err_hid_report: @@ -1713,6 +2213,7 @@ static int ft260_probe(struct hid_device *hdev, const= struct hid_device_id *id) { struct ft260_device *dev; struct ft260_get_chip_version_report version; + struct ft260_get_system_status_report cfg; int ret; =20 if (!hid_is_usb(hdev)) @@ -1727,6 +2228,7 @@ static int ft260_probe(struct hid_device *hdev, const= struct hid_device_id *id) goto alloc_fail; } hid_set_drvdata(hdev, dev); + dev->hdev =3D hdev; =20 ret =3D hid_parse(hdev); if (ret) { @@ -1757,21 +2259,18 @@ static int ft260_probe(struct hid_device *hdev, con= st struct hid_device_id *id) version.chip_code[0], version.chip_code[1], version.chip_code[2], version.chip_code[3]); =20 - ret =3D ft260_get_interface_type(hdev, dev); - if (ret <=3D FT260_IFACE_NONE) - goto err_hid_close; - - hid_set_drvdata(hdev, dev); - dev->hdev =3D hdev; - mutex_init(&dev->lock); spin_lock_init(&dev->read_lock); init_completion(&dev->wait); =20 + ret =3D ft260_get_interface_type(dev, &cfg); + if (ret <=3D FT260_IFACE_NONE) + goto err_hid_close; + if (ret =3D=3D FT260_IFACE_I2C) - ret =3D ft260_i2c_probe(hdev, dev); + ret =3D ft260_i2c_probe(dev, &cfg); else - ret =3D ft260_uart_probe(hdev, dev); + ret =3D ft260_uart_probe(dev, &cfg); if (ret) goto err_hid_close; =20 @@ -1800,6 +2299,9 @@ static void ft260_remove(struct hid_device *hdev) dev->index); ft260_uart_port_remove(dev); /* dev is still needed, so we will free it in _destroy func */ + if (dev->iface_id =3D=3D 0) + sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + } else { sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); i2c_del_adapter(&dev->adap); --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f49.google.com (mail-ej1-f49.google.com [209.85.218.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 15CDA3909AB for ; Sat, 22 Aug 2026 21:40:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; cv=none; b=qSZeOGappmVDtRGHYEf9C3lO0rD1vzxyCOAPe4TuteSnI3Gare6pDFRZGHKghgZx1TWUiBpscAjgNJ/EU+DZ6apSjudQaLhzd4L9CYZep0PMwArsBdtzfdoDWJKbL247y/WswZYQ4bvy7ohLGlYL+dJw9Q34+ofQrQqkz6wQp3M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; c=relaxed/simple; bh=DYI0nXh+SYMeDAto8oGYKYCMKaYc4QTLn0hrspq5Dx8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fxg52+LsadvwlEOrYipZ2gim1geZb2m0hzLb5LJ1CTHYMYLQ5bQPaVaCjOXPxJMfls6I3fIPChbhk2p2aNCsoVAEMuK7SbfUrVbELGiUVA6NF2KWo5ZImHrxHKMtB/reBN4c8uUjiTlNQdZcGOEc1aQfZC0nFpAAs9BnX8QiAVM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MhVzEaBh; arc=none smtp.client-ip=209.85.218.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MhVzEaBh" Received: by mail-ej1-f49.google.com with SMTP id a640c23a62f3a-c214321dc32so428918766b.0 for ; Sat, 22 Aug 2026 14:40:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434835; x=1788039635; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=x8pH0U2xaFDXVXpg5ddmhyUBND/R6AGE2iAuyBIz7BE=; b=MhVzEaBhfwNqlisK5Nl+jlWSnlOs7d0h8b29c8vA3nl9uWAGaq8b05wGe2d0IMMf4R BBMLrul65uMwj9GhGqj7Pm+BWXifMhgzO1Znd0QR9Q1+kFpycK20upEMjuELlYbbhg+6 C1fu0JiIfFWWmxvQufHgWTyQcHX90uPa4396hj+0xKDEE3qPh8rDsDuEfj7DdjxckKxs liJu7E7+6M2IrF79pe3pUs5RQ23q9h28Y6xAG/tvyexUNs+0MFrOPjP8aHICnRaKQHHf lDYV66bcf5xLZiSF3Lg6oVZGmE2QgJWrqvceUZkubsCrOMdsojn/OBHnga32ESOJp4RP je8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434835; x=1788039635; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=x8pH0U2xaFDXVXpg5ddmhyUBND/R6AGE2iAuyBIz7BE=; b=OL2FytHKJpjhCCAQ1JQhT+x8TDPMXc/KeFbGtp42l2Vn7+POm4hKtX0lfspwJUXEnv YCIhPjIptxp3U2sWOFnlPb1FfOSDnJM4DXSNTKBYxUzXCQGAH2lMcgmo+FvPb1nTGU+V pXg/xjZhlRn71+ulhU64C/NwEwvhx0PrnWa8dPHc0CFhefmy1XkEinoB/Bs4MPoUBteZ 3KF0qtRS0LtDMRKo7JYPKVLSfrZ4JiThjDeaWXwaPN3TTISizozAxMKsuKUSeFepnZe7 5DoZIu5Dco5EV5cj+XsxztYrN5pl+Y3Q3BXn65aC3+ASD+LIOsVhxaQLaVud5kQIbpRe G3Ag== X-Forwarded-Encrypted: i=1; AHgh+Rq7RcPZ9DnuA8jBzgeF8mvMO93T9fxwzCgNeo1cP72zJug7MMp6I20SIHibdfpQt7gEl9bkFnbuaGR8NFs=@vger.kernel.org X-Gm-Message-State: AFuF++ka+s7gtfEKNziLHa3ULeBZ68h3XRuywmpWlGpZqvQNLrl95+NX UJkzvdPenyEMVWgQ7Ny+Qm9FE7+3gcvBNyb27RL4jYP4V3+KPjcB90D+ X-Gm-Gg: AR+sD11NBGODtaRf4sCyEZxM4Tbd5f3bhDpRs4aumTikR3/CLXDfJSHV1Ua2jvQAWQk Gzo2Dt+8ftD1EvmWq1Wp6LwmSDHqErB+pQ0x675BIOVxk7Kg2XdYv/nMqO9Q0kL8zmErSL3kZD0 9AMXFYepU5v2191prBFSWOxci0VQTjlDqddoT3bYPyS1W/89HONbHBBujHjri4FOjm5IocbJ8nk +cOIswGP//nQo3JoHu6MQFvb+0v5vnKPDiLzFt39aJyg06r8VObhJ82ekeGIVX1/jqemt+YZ44j IIjgdlslptySXMOmvHTCZ+wadEHaM8bJ/aipW1uMWsjk9k77glR4Ax68CzkzkyXbmxoUHII/hso ZmQXRygnaZ9dWJ+JYX9VosLhLn5iY9ssy69gFuOokBUy2T30L11ll7AnZXuCHsCVomQqZHUE+uF n43JzmkNLS+cYLVr/MQFXZ7OUlNskBSFiS4k3epnOBPHTY8eU2zbjCVrrCRo0= X-Received: by 2002:a17:907:3e16:b0:c16:3074:657f with SMTP id a640c23a62f3a-c246a4ce3b8mr1701833566b.9.1787434835007; Sat, 22 Aug 2026 14:40:35 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:34 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 04/13] HID: ft260: i2c: reduce driver module loading time Date: Sun, 23 Aug 2026 00:39:32 +0300 Message-ID: <20260822213941.98882-5-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The HWMON class I2C client drivers register the address range to scan during the I2C master module probing. For example, the lm75 module registers eight bus addresses scanned during the ft260 module loading. In the case of ft260, the bus is scanned by ft260_i2c_read, but due to an excessive read completion timeout, it took about 40 seconds for the ft260 driver to scan the lm75 address range before exiting the probe function. Adjust the i2c_read timeout so it depends on the amount of data to read, reducing the lm75 address range scan time to about 200ms. The 25 ms per-report budget was checked on a UMFT260EV1A with a 24LC512 EEPROM by shrinking the timeout until reads began to fail: a 60-byte read still completes at 9 ms and times out at 7 ms, against 5.4 ms of wire time at 100 kHz, so the fixed USB, HID and scheduling overhead costs a few milliseconds. At the chip's slowest 60 kHz clock that chunk needs about 9 ms on the wire and the 180-byte chunk about 27 ms, so both keep roughly a factor of two under the 25 ms and 75 ms limits and the timeouts do not need retuning for slower bus clocks. Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index cc3b8f5791de..4435a39fce23 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -56,6 +56,13 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages= "); * read payload length to be 180 bytes. */ #define FT260_RD_DATA_MAX (180) + +/* Time in ms to wait for a single report read data transfer completion */ +#define FT260_RD_ONE_REPORT_TO (25) + +/* Time in ms to wait for a multi-report read data transfer completion */ +#define FT260_RD_MULTI_REPORT_TO (FT260_RD_ONE_REPORT_TO * FT260_RD_DATA_M= AX / 60) + #define FT260_WR_I2C_DATA_MAX (60) #define FT260_WR_UART_DATA_MAX (62) #define FT260_GPIOCHIP "ft260_gpio" @@ -713,7 +720,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 = addr, u8 *data, { u16 rd_len; u16 rd_data_max =3D 60; - int timeout, ret =3D 0; + int timeout, timeout_jiffies, ret =3D 0; struct ft260_i2c_read_request_report rep; struct hid_device *hdev =3D dev->hdev; unsigned long irqflags; @@ -732,10 +739,12 @@ static int ft260_i2c_read(struct ft260_device *dev, u= 8 addr, u8 *data, flag =3D 0; /* no fresh START - continue current transaction */ do { if (len <=3D rd_data_max) { + timeout =3D FT260_RD_ONE_REPORT_TO; rd_len =3D len; if (want_stop) flag |=3D FT260_FLAG_STOP; } else { + timeout =3D FT260_RD_MULTI_REPORT_TO; rd_len =3D rd_data_max; } rd_data_max =3D FT260_RD_DATA_MAX; @@ -762,8 +771,8 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 = addr, u8 *data, goto ft260_i2c_read_exit; } =20 - timeout =3D msecs_to_jiffies(5000); - if (!wait_for_completion_timeout(&dev->wait, timeout)) { + timeout_jiffies =3D msecs_to_jiffies(timeout); + if (!wait_for_completion_timeout(&dev->wait, timeout_jiffies)) { ret =3D -ETIMEDOUT; ft260_i2c_reset(hdev); goto ft260_i2c_read_exit; --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f41.google.com (mail-ej1-f41.google.com [209.85.218.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9E3B53909AC for ; Sat, 22 Aug 2026 21:40:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; cv=none; b=bxiKFzRjeApC3xPWf3zMQKp94El0DW4n7jZSVBEX4cKob5tBhfVyAjFQ15ror0veEzHv093u2Gyb0tJioAdkav3w9TEPpmd8awjlskOiuVoRgRKqok/yZfooZEBmSeG78tzYrt7oUzFvuZZbqeD97lG1YsAR0CEBki1Qb8a8HoY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; c=relaxed/simple; bh=tSaYsW5ZA87m0Wg9YJ+wfmB/gHIRTOyA3syuwyg8qeQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MZS5PSfIMF8JZTiR0jF7c4X30h86QfVIekJPxpjpI+2kbrixgApPEolH/+XCyWiskpX+yuZl3OTuddKPUAji/CWf4Ot8IFox1JxumVdDXGnuZJD/X/C7hLhGMKXZU0Cu6Gaf3bwTtGxVVYxo0otEOu9NkK2txp1Zx2JS8MNMD/s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=oTQtPDkb; arc=none smtp.client-ip=209.85.218.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="oTQtPDkb" Received: by mail-ej1-f41.google.com with SMTP id a640c23a62f3a-c2020421077so372283766b.3 for ; Sat, 22 Aug 2026 14:40:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434837; x=1788039637; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=a4eLlaKD/QchVb0r7zAM59fvM/PJvb2qK03bPG46mJc=; b=oTQtPDkblPAwvUkJILdH3vavLZI6Uk5u13K9Ty3z0sAfJwsMNIn5HHspUOj/MbClK9 i/gC5WT6TRpLrB/v+f4+FSlpMYBxviJFaHQBbWx5OZe/3PxaCQfXjb8ODwmWqN0syodk PLGE5SeHFYlu1aei1esnpmpkwSibc2rwC6ldYusk8RW4WdhxJvRfAmp1/LRo6rbdBiAQ U7J3Cq5XVpdfa9sC1fGgaRtqjhxRUWHLMFolk6SUDVl1QLN14Dyi/obQGb+72Ajt7+Mo uIY4ZI1I6AGcHImG499TdlfkMy6BnBy8w09JaqLNU2wB+P0wn7t2KRWHAnl2WG75vGbN b24A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434837; x=1788039637; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=a4eLlaKD/QchVb0r7zAM59fvM/PJvb2qK03bPG46mJc=; b=GSgoFH8isV+rgj2hAyp8IbXI+A5cj24jVGMcz0Um1Obgy6vhr5xivgqPbSEG7lcb0A xjTaLMC8FPej+zqdgPIuOBamCU5c7F6NZG5keI6QTCJPZ577ixaU69GAgafmnvo7NhIv jTaPg/CUtrpDbws+AIgExFC85jCkXn0lHb6AgFnoWW56IfKSLtiP/5ldxjs10p/vv398 ueCzVnR8W8FIWVd5hlHIZHM5RNNLxXV1ufS8k37l2Ir7WQu7YFP7PDp1QiRY4N0pk0y1 U36rqjVHxTykyzKICu41Gs/xOOOKLdFWOL5k+uiQQInxQMXkgBkglQkws6xe2Ef+qXS0 KwMQ== X-Forwarded-Encrypted: i=1; AHgh+RrdAspxJQ7L2itk4r3yxdLkz+QrrWoY51bfDG5MxbRqUs3RFhuqgNfiSmZkZE+HPkmPxCr60Qqx0yPkGPY=@vger.kernel.org X-Gm-Message-State: AFuF++nR/cLyRM/IQM4+D4jeDexEmeYVzJ3J1rh1iMB1ZfePIzOBw6M+ XBAzpwwBfDHS/Qk7ipI7s1kSL9ys33S3jBdaeOdgMeOCm4b2zWz8zoCf X-Gm-Gg: AR+sD10uNsPHMHUV1ynb4r7ZzV/A4ctVYWDewScwdkxeAvRjFuXWmDFYFx5lmQzT1ok hclFIlG7M2Pg/yMsRq5UEVIlAglNFusxJxF22gCUlxVxTrO/gJH6EXuQ/foE9I0lYTIzb5735sK wrHlH0NMJudT7rUscwLmKDsNXUye9Jj6aRCgdGU0q6x5nn/jp6G38t4OOvWwGJ1XN2rosvIPHar URKrCKKSbXU6ZmFfLu2A13IJORltsodsJrigdS4NVybaGPLOp+PoJEBQVxAqqxcQUW9PuD+s9c1 0Jgg8Mbq90XSvm8pnlo2c1QeAqsI7QsqZ0NwReq6ts7OxI+qJLIXSdHDoAQmYuwCT0dxAI/jL04 EjDeOTJWi696iP3pBQ2jxdc119nIn2SwF6Yvjq7f5t/pHjSF9gwS7iDmbUvyiCNYauTntwy2asz ca5uNHc7A+vwSOoc9EBCPOsk+JBPm0qhA7T3Ck2kBQ4LyGkJgUSB9Kaw/npkU3a21hT8RO8g== X-Received: by 2002:a17:907:1c27:b0:c21:382e:9a38 with SMTP id a640c23a62f3a-c2491c2a110mr884931266b.2.1787434836795; Sat, 22 Aug 2026 14:40:36 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:36 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 05/13] HID: ft260: i2c: silence sysfs store big-numbers Date: Sun, 23 Aug 2026 00:39:33 +0300 Message-ID: <20260822213941.98882-6-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" FT260_ATTR_STORE returned the feature-report transfer length from ft260_hid_feature_report_set() instead of the sysfs write size. For values like 1000, userspace writes more bytes than the HID report length, so the VFS retries the remainder and reports "Invalid argument" even though the attribute was applied correctly: $ echo 1000 > .../clock bash: line 1: echo: write error: Invalid argument $ cat .../clock 1000 Return count after a successful parse so the full write is consumed. Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 4435a39fce23..8db896f164cd 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1479,6 +1479,7 @@ static void ft260_attr_dummy_func(struct hid_device *= hdev, u8 req, u16 value) hid_err(hdev, "%s: failed!\n", __func__); \ else \ func(hdev, req, name); \ + ret =3D count; \ mutex_unlock(&dev->lock); \ } else { \ ret =3D -EINVAL; \ --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f50.google.com (mail-ej1-f50.google.com [209.85.218.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E29453911DF for ; Sat, 22 Aug 2026 21:40:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; cv=none; b=DVV58Dqq5FeObYMjHsBQQVEf+BKlfnGk6/+DWNjaybRZDQHHMi2MtDxwQ15cCqvz+GpJdZCrd0QYrTf3A+FnuNiHWX9foW0eMxkipHPw+m1tKW5E6VNHEzQ+TgbD3dz2lxfTZSbeUx7hrYKE9Cqy9FlIzgOQKK5frbp92MncRCI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434846; c=relaxed/simple; bh=O1b4qJuU5h0jkjtN1tmFHZg98/5Kor0pOfpmFSJmhKo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YfXdSko44AJX+0orZ0772JyndFkuwAwDuGRniwOS2pOW6a3Ux01YSLIlvR5Trwy/GdaUBHeL5LY7TGHG7msGxTwH70En6y2OmvgWmBNIKg2jVZTwG5nHfD6FZttYyVb/ZCXs1DxIoqTPxVkrDy+QQv31apftptuF8oKKb8tPUS0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Cv/DmAO0; arc=none smtp.client-ip=209.85.218.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Cv/DmAO0" Received: by mail-ej1-f50.google.com with SMTP id a640c23a62f3a-c207cb16cf5so349494866b.1 for ; Sat, 22 Aug 2026 14:40:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434839; x=1788039639; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=7d0fiyXH8Gw0QiNflyUYHvDRi8XIlp2AkUMiyQfpLiI=; b=Cv/DmAO0fbr4HMZI6EQTZ1jjqyYgFpkJODZ7fxs+QkhM/iphqwzhNkTIenH0HihBWb 3AyRVV20O8AYDEsZ+SNdQ7tgUWJV/6g5z+o7+o2SpQdYrLIQMNwAsIns9oFyfMRb+SW/ 9pNZfSFNAPd5waFOWbY4wXqZJ++Cd/C/xMMXCdYOQSCl8U0MzlIzeWQS1M7EiYT1bRJD pGzw3Gc3a7y9qHEtk0/6gGTvY3W9jGTi1XmWX14ZFNHPCOm8ZXtQ+8EgmEHRroDVxPWT W06y0AkTSkxhS5I2dOhT1ypRjBkuPZPttybBv8WMzLBjo1Hk4OVyhOWsZ0QF2iEKidAe osZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434839; x=1788039639; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=7d0fiyXH8Gw0QiNflyUYHvDRi8XIlp2AkUMiyQfpLiI=; b=E/1hpokV8SEbrx2mOSE89dIxAyoXorVJsXcJixRbOuqxcZ4ZQJA9iRlwCCrjB9hsTi TtC7/SKQqgoqBpAv+6ER2rDjMJutO8OhrlCYJBM/CPipEHzxsIsMrrvlhyuwCqk5QgSK 9DOik0oBM4/ipYa0CJ8oLibc5RQZOX8f422E0QS4ClRXoJRYb4Xc0Wlsdi0ojK13ie9m 27bNeIZzv/anEPZDFTkBN+MMAcxficlZojcrdPO19Vnf3pEnIqMpxuwcqQI1F6cJMYnw UH7kDekjsvQpNsxCxcIQ4lYE+cbIkwUSJM7Gq3fwtzbB99AHVBC09lcXgG8XDDEazkAF G5tg== X-Forwarded-Encrypted: i=1; AHgh+RrFLL68NFPK/VzNRugUlIiFq95cMnnQtPz/dLDqc8yQP1tcBuqhnYiyZaDjIpJ1bTjZ7J5mDCi0K1bB46s=@vger.kernel.org X-Gm-Message-State: AFuF++lcZKNxAQYDHJ9eGIXhMswgObNT/GntCFggjNVa3jt4hEweZjFE w18p5y9IFV9BKzgUgeVNiQGBkkGLlYow6b1Wnf+kQefDKb/nm7zt7Xwe X-Gm-Gg: AR+sD12ykE4V5gDs1ZcyEaw/iar6ttj9fz5BSsG8Ou5HQaFHEHaZTFXxZWsbikMoOeL /nJcxYStBXmFVg8URiIrW0iAyjsxORlkQz2fo+1Jn/yLo6Hh/nlcbnI2QgK6ggwwywVFY/eJk+B UaOc2Yvn3FNS2A7Lz2Lf07mJ0DTorP//MSR1tn+Ucz6CjuROf5+qe6z/hC+LFdy21m6ew11Vuyg J26XjWhIrNbKU6PoDGz5frrgA4LRv4c1hfRlczGl1w0TPeTiV6tSgbWlg3PUZP+fYdwCd/Ss+HZ OZrwGiCGRIGiJYGV87h/whtdJ5Olm0vmnAPe1yYnVDD1yMBtBREjdGTgrggPRDy9bBdxsRubfXZ PJsSc2C9bSkOaSWhqioBGg05uYZIVrKTDY3l/Eymqc0f2dDyV8SPb6/eB6VNiX2DII91kmQb3wl q9GYe55W1y25FpcCyCwDIMotVx4am1RQ21LaYQ7KYVHk8248fr7CeAq2Volm9BJsN94OdLdQ== X-Received: by 2002:a17:907:3e2a:b0:c21:7feb:4574 with SMTP id a640c23a62f3a-c246a2cece6mr1544595666b.1.1787434838519; Sat, 22 Aug 2026 14:40:38 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:38 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 06/13] HID: ft260: i2c: reduce bus-error message severity Date: Sun, 23 Aug 2026 00:39:34 +0300 Message-ID: <20260822213941.98882-7-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The driver logged errors when the FT260 reported an I2C bus error or when ft260_i2c_write / ft260_smbus_write failed after such a status. That was meant to flag unexpected failures, but bus scans (i2cdetect, HWMON discovery, transceiver monitoring, and similar) hit missing devices routinely and should not spam dmesg with either: i2c bus error: %#02x ft260_i2c_write: failed with -5 Keep returning -EIO, but print those via ft260_dbg. Still use hid_err in ft260_hid_output_report_check_status for actual HID/USB transport failures, which are genuine errors. Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 8db896f164cd..1ed3701eaaf7 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -565,7 +565,7 @@ static int ft260_xfer_status(struct ft260_device *dev, = u8 bus_busy) * to 1, bit 1 is also set to 1. */ if (report.bus_status & FT260_I2C_STATUS_ERROR) { - hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status); + ft260_dbg("i2c bus error: %#02x\n", report.bus_status); return -EIO; } =20 @@ -599,6 +599,7 @@ static int ft260_hid_output_report_check_status(struct = ft260_device *dev, =20 ret =3D ft260_hid_output_report(hdev, data, len); if (ret < 0) { + hid_err(hdev, "%s: failed with %d\n", __func__, ret); ft260_i2c_reset(hdev); return ret; } @@ -638,7 +639,6 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, int len, u8 flag) { int ret, wr_len, idx =3D 0; - struct hid_device *hdev =3D dev->hdev; struct ft260_i2c_write_request_report *rep =3D (struct ft260_i2c_write_request_report *)dev->i2c_wr_buf; =20 @@ -669,7 +669,7 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, wr_len + 4); if (ret < 0) { - hid_err(hdev, "%s: failed with %d\n", __func__, ret); + ft260_dbg("%s: failed with %d\n", __func__, ret); return ret; } =20 @@ -685,7 +685,7 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd, u8 *data, u8 data_len, u8 flag) { - int ret =3D 0; + int ret; int len =3D 4; =20 struct ft260_i2c_write_request_report *rep =3D @@ -710,7 +710,7 @@ static int ft260_smbus_write(struct ft260_device *dev, = u8 addr, u8 cmd, =20 ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len); if (ret < 0) - hid_err(dev->hdev, "%s: failed with %d\n", __func__, ret); + ft260_dbg("%s: failed with %d\n", __func__, ret); =20 return ret; } --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f46.google.com (mail-ej1-f46.google.com [209.85.218.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F3FE738F620 for ; Sat, 22 Aug 2026 21:40:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434850; cv=none; b=kaGtLNidmmfphbHxS9ej4+52r2JGE81k83CnFSx9FaPTF4LBJsGPBHp4s0TWA5ocSEF/79GygH9QYeP0249b0oR7xZKeWjYMVvbAw9cZEons+7jWI+NsVnSp7odN9RahR0SPuj6SK6CECLcf+SRhet83Q0urnig3Q2QPHhBVjXg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434850; c=relaxed/simple; bh=KJT81DzlFzree0N2sQdZCS66jmiyMjzLoVvJJAuquTs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GoYKKAu2dI+Eo1FSGQwYbXETqf7OsGpzwY9z+Ap9/DkAJsrJ1HkMVpOo8EUz1LRVi7+YkzHM4/nzC4Wg+wk43HTbF25cbnMOjMaGZZsrz6aAxis49zxa1JhWKXZGjQ6czpWQfyemJK80NmPbNofJcnNZUD2wu/khPd7OEAzPccw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=s2zZUjGz; arc=none smtp.client-ip=209.85.218.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="s2zZUjGz" Received: by mail-ej1-f46.google.com with SMTP id a640c23a62f3a-c15e2dab83eso421729666b.1 for ; Sat, 22 Aug 2026 14:40:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434840; x=1788039640; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=8xIFPtL0bXPCQ9Wv1lusuTgPka354Csl8JdgcZxBeVs=; b=s2zZUjGzASqYCl7eYhoarDStryZT2nxU2QAGicis0Ajt5adlzBMz/6+19AHu3Qz+ef 9JMCA+3oO0tN87vRjKjIJjZcUbxeojSXIezk3MaVaEdUeMS9v5SyvYPmXgD2gCyqZK8C 9IiV/Zwhglz+8RVa67dNhO3DrUFK4CE1IN3et0oJPAg6LebGRih/ebxeifv4Wc/UElDF lOp6fsv8sfAbaSjF7awSlENOLCnJre0bB6JI92VDIZ+gs0G2MeT3eEA4vnZJlAsXcVOW i+pV37dgMXriNAdfbYiR68xdEWjaMupEFdA8OBuICFv3rpvfkO4NQBMMtzoG7V8FXLB2 j/yA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434840; x=1788039640; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=8xIFPtL0bXPCQ9Wv1lusuTgPka354Csl8JdgcZxBeVs=; b=QhwdE/UXEqwMW+fFoaOYR5nMwpScxI2sua/OSJnBV6VvhHVxrmpTkBHZ1oz5VT6xDa 6XgYntueiljFrpxvw8Qjv0VU8qXibNbhvpnpQMtHJWpYsVRvoKdzdaZoTPkr96R7l19l mHp9rKjK+V4e5crhefIY/RewPoy6tZamE4Fz6zL8snJadAWKBs/jB2s+WwXGlB44zT/k mQjsI6o30bCUJyAS7h5qVll3VlA5CDs/LxSFHSOQyabuuNHiHWRSF2QDbPD9+NZb/2dX oiz/DMAkbjD+Br6Mgo+svaoM+IhfmR8jZNEGHTwYcmHAD5CoOXnO6oj2mnmnTRcBaosr 0kgg== X-Forwarded-Encrypted: i=1; AHgh+RrpgvHCNwtWRHJLdqjnGLUSENNTHrUbUmnleFQQJFGuAYKe3jNuy+2MBcXA0IlPt9lcgGA9MCfIsqAVv60=@vger.kernel.org X-Gm-Message-State: AFuF++m3yCTmVXi8Mdm1W9dppqFekquP9I8ny4jwHdPhVaJC+ikzeHEo SOEwz8GARXjGthQI5vEoCN+DJwIeWps8VyZO8PCYSjqln91XPsnZbjWc X-Gm-Gg: AR+sD11KDJCJJTNIbaUYnbS2+ufLNuNUpFZYoodWt/S2b0HFQwtj2Oh9Y69bT2WHf0i N6XMRprNzKR8W9Wf70tRqZKCxHHJadaSDp2nOZBh295JZv0xHhrz4bFfE5mic5NOf5A0HHpn44m SgWLSy4AaG+1GWviEH2r1XEtGVGRMND/djWFL65qHCmXufI/D2W4wzgsT5oFcIIBtDzO4BDsg/W 1HeNA8v8QCTu2yofKnvKXGebbICFjKuD9c5VlnHA/x+maWYbnI/37XmRk9jdDL6ksSKuixuhEFz qhErsMXqWX/uJGvhhm6bfIdKHeXRXfy7YNyFGMm8kpojtra6cnqJdoLzV2yGAO08Pez0xbF6Bur CbkwmojTjH6xH3BJD/M9MnilKWqaDuelJDAZ598q/NOkj9Fs5+G/J0AP4lcbzTyXYnJO4qrAsk9 rv9Tk5MdL3zM2wAESWwSzMRcQAAebL3CVD8BTGy3im+GB6S7uGykbPHAcDcHg= X-Received: by 2002:a17:907:3cc2:b0:c21:180c:d82d with SMTP id a640c23a62f3a-c246a725749mr1773091566b.20.1787434840266; Sat, 22 Aug 2026 14:40:40 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:39 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 07/13] HID: ft260: uart: enable flow control Date: Sun, 23 Aug 2026 00:39:35 +0300 Message-ID: <20260822213941.98882-8-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The FT260 is a full-speed USB device. Interrupt transfers poll at 1 ms with a 64-byte max packet size, so the theoretical UART payload ceiling is about 60 kB/s (~600 kbaud in 8N1). The chip advertises baud rates above 1M and small transfers can work there, but large transfers are not reliable above 460800 baud without flow control. Stop forcing FT260_UART_CFG_FLOW_CTRL_NONE after termios setup. Honor CRTSCTS (RTS/CTS) and IXON/IXOFF (XON/XOFF) when configuring the UART. Tested at 1500000 baud both directions against an FT2232H with XON/XOFF and with RTS/CTS (~2.9 MB transfers, no data corruption). Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 1ed3701eaaf7..1da42b8756d8 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1868,8 +1868,12 @@ static int ft260_uart_change_speed(struct ft260_devi= ce *port, =20 if (termios->c_cflag & CRTSCTS) req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_RTS_CTS; + else if (termios->c_iflag & (IXON | IXOFF)) + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_XON_XOFF; else - req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_OFF; + req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_NONE; + + req.breaking =3D FT260_UART_CFG_BREAKING_NO; =20 ft260_dbg("configured termios: flow control: %d, baudrate: %d, ", req.flow_ctrl, baud); @@ -1877,9 +1881,6 @@ static int ft260_uart_change_speed(struct ft260_devic= e *port, req.data_bit, req.parity, req.stop_bit, req.breaking); =20 - req.flow_ctrl =3D FT260_UART_CFG_FLOW_CTRL_NONE; - req.breaking =3D FT260_UART_CFG_BREAKING_NO; - mutex_lock(&port->lock); =20 ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req)); --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f52.google.com (mail-ej1-f52.google.com [209.85.218.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A864538F94F for ; Sat, 22 Aug 2026 21:40:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434853; cv=none; b=XIy3zDVC+Rl7grMGHdeyZIjH8OK0PVNasIKgy5Ta7Ng6B4NuyOSz8ZKVCBEp1EnBHSS7vemPXSpCFLCgOxUNvI7DWOwi8++0enzsiFUthzUV/4lW4bpVy/ReD1/1Gzju8UJ42UO7awfY5xwFgNI2r+eyFhlZbA0WQ7Di8oD/XTs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434853; c=relaxed/simple; bh=JemtDxSak9CH8m+QcA9xOVUHH1e+ZtvHVWtttn9IBRA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kvvLTZGCkze7ht2t9hWO9APs8eCEqHA+ahVTmTMvKG3T0oiL5hi2RVX59cNjxJz81m+B9fEEfakugc5rfF55LPSep0e1k1KYGJNULFAokcZCiyvNEICr/OXufqhkkJLpLWBz9y5yUgwIBrrm5E4veqUEOf9tZlv/ctkqqu4vig0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Sqa1z+qs; arc=none smtp.client-ip=209.85.218.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Sqa1z+qs" Received: by mail-ej1-f52.google.com with SMTP id a640c23a62f3a-c207cb16cf5so349500766b.1 for ; Sat, 22 Aug 2026 14:40:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434842; x=1788039642; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=lNdo+XJr21QNjnvuiWuBquyrAWvVkVmt97Y5Z2nWQBg=; b=Sqa1z+qsueW/5qmuQCC5EoVmrHwxTAuk3QedJpskUX/molW+AW1P7xNI+JtrXsZU+K n7LIQ80mIhsHl4hP5/RNb3eiTe5b5cQmt2NzZoZuPyU1pDeOQv4D8g8BV3JCaolPdgdv gknKmHgSfdSDa63VNmEWSaGdQgGidUneOHUUwVJoQHrIrdyDDU17WGf4YM6JwJD9n29v xZ+3XSW+pma447kO0tdERtzXgY5EdtVqS3azlj5b5AbxjiPQ2yGy773eGpD13heYbZiI TaiFEXaozAqGyXP0ge3SuUUvtZb5yVmv6CaFsa/Vl+KCfcpE2apdeaZLGWheccR6joOx IHwA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434842; x=1788039642; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=lNdo+XJr21QNjnvuiWuBquyrAWvVkVmt97Y5Z2nWQBg=; b=PpbUh791EvKBFva4//1gImlTjjMjWGfU0EhV1qLQJiV6Hp87bm1fXvr8sezsaFK++B fKnb22aR6k441JT7Woxm/SCEuA84smhLtU6Im2Wa+VJiRqh+aFGRyrxTtdLvWi5kuCRt minOO6WbPXhDPidh/K2R3+JExk+x+5jtUF28dn0PyM6IEpaL7kvNAFACKIYTGyT90oI1 /wrz+cgp2oN6Jo+ukCxlN2X45MZHBKwLU6y763olQ6qaga+KvjH1UuFOAu72wrHA0kMm t85TREXo8RWBndXZ5nlgkO4CZmN9gTSnDXL2VWgrCXl2452D09kApiMv3NsuXm8q1jHi Hd2Q== X-Forwarded-Encrypted: i=1; AHgh+Rq3auJ9nByQu3tvhpXOo9vNC+RApJx0IwTO59vxwdh2FB1RsZGi2nw9ani4Rp1jgIq94QEasRVFxZu6DTk=@vger.kernel.org X-Gm-Message-State: AFuF++kyNae9WKF7zpA+cpdqChmXTVa+Jezj/BJN/1vwLtVENvnX7TEJ 2bnrJIRQxvkuVn696+k3MCpQyUr/vNoBq8RoSyuk+QXk7GpfBUYPXYuq X-Gm-Gg: AR+sD110f7kwqlibbDIFv3mWgFIxC1akJ2807BIaeipFvm5nyaEA9LpYrQ0Yy13CAHG ZKRcJl5X237I84doPKvR3DuBtrbIf3PMq1BeY/SLGA7hz5kYnil5M5aLyOXXaK2XMGXNyNC6ldD TZoTlyXiqOb5aVehAFN0Mc+Djg2NDSr1sr+RkWRD7vh2z7VKtw+hr35r9JJUuyBdqeQ+zDNmR/4 aTDQIevL4dSmoybs9k+qEGHkScquVm4bzpdtCC6sORh+Q3GExrcpPysRAlBnKiGF4hfUtlQqUv2 8S63Aw89yJQp83pydI4TyveWjazDAvT+/L0aJ7sRz9266pAuBkKlH5qWV0q5HTPVxQLtbT8jQJK hHjsZ+tUHmGDz6HKMYNxj7ZCJ7yMiMcXo63kz5NF7fJX2uElrCRbp25ai1hrxdjuBUZ6E2SZmBu hzQhG+XCLVz0fxRHDndqutKe1J70tASiWzQhihPTc/Q6TEsHyes++AjCrZ2m0= X-Received: by 2002:a17:907:a892:b0:c16:5cf0:3aff with SMTP id a640c23a62f3a-c246a62afb5mr1592238866b.18.1787434842064; Sat, 22 Aug 2026 14:40:42 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:41 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 08/13] HID: ft260: uart: add modem pins control via ioctl Date: Sun, 23 Aug 2026 00:39:36 +0300 Message-ID: <20260822213941.98882-9-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add TIOCMGET/TIOCMSET (and thus TIOCMBIS/TIOCMBIC) support so userspace can drive DTR/RTS when the modem pins are in GPIO mode. This enables programming an ESP32 over the FT260 with esptool, which resets the chip via those lines. Also key GPIO sysfs registration off chip_mode rather than USB interface number 0, and include DCD/RI in UART mode 3/4 GPIO sets so the modem mask matches the pins esptool needs. Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 151 ++++++++++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 1da42b8756d8..554d8e3ddb6d 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -182,7 +183,7 @@ enum { FT260_GPIO_DIR_OUTPUT =3D 0x01, }; =20 -/* GPIO offsets */ +/* GPIO bit masks */ enum { FT260_GPIO_0 =3D (1 << 0), FT260_GPIO_1 =3D (1 << 1), @@ -215,7 +216,8 @@ enum { FT260_GPIO_UART_MODE_1_SET =3D (FT260_GPIO_UART_DTR_DSR), FT260_GPIO_UART_MODE_2_SET =3D (FT260_GPIO_UART_RTS_CTS), FT260_GPIO_UART_MODE_3_SET =3D (FT260_GPIO_UART_RTS_CTS | - FT260_GPIO_UART_DTR_DSR), + FT260_GPIO_UART_DTR_DSR | + FT260_GPIO_UART_DCD_RI), FT260_GPIO_UART_MODE_4_SET =3D (FT260_GPIO_UART_MODE_3_SET), FT260_GPIO_UART_DEFAULT =3D (FT260_GPIO_UART_MODE_0_SET), FT260_GPIO_UART_MODE_1_CLR =3D (FT260_GPIO_UART_RX_TX | @@ -226,6 +228,16 @@ enum { FT260_GPIO_UART_MODES =3D (5), }; =20 +/* UART modem control GPIO offsets */ +enum { + FT260_GPIO_UART_DCD =3D (4), + FT260_GPIO_UART_RI =3D (5), + FT260_GPIO_UART_RTS =3D (7), + FT260_GPIO_UART_CTS =3D (10), + FT260_GPIO_UART_DTR =3D (11), + FT260_GPIO_UART_DSR =3D (13), +}; + #define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | (report= _id)) =20 /* Feature In reports */ @@ -440,6 +452,7 @@ MODULE_DEVICE_TABLE(hid, ft260_devices); struct ft260_device { struct i2c_adapter adap; struct hid_device *hdev; + int chip_mode; int iface_type; int iface_id; struct list_head device_list; @@ -1097,7 +1110,27 @@ static void ft260_gpio_en_update(struct hid_device *= hdev, u8 req, u8 value) else ft260_gpio_en_clr(dev, bitmap); exit: - hid_info(hdev, "enabled GPIOs: %04x\n", dev->gpio_en); + hid_info(hdev, "enabled GPIOs: %04x, bitmap %04x\n", + dev->gpio_en, bitmap); +} + +static void ft260_gpio_output_cfg(struct ft260_gpio_state *gpio, + u32 offset, int value) +{ + if (offset < FT260_GPIO_MAX) { + gpio->dirs |=3D 1 << offset; + if (value) + gpio->vals |=3D !!value << offset; + else + gpio->vals &=3D ~(1 << offset); + } else { + offset =3D offset - FT260_GPIO_MAX; + gpio->ex_dirs |=3D 1 << offset; + if (value) + gpio->ex_vals |=3D !!value << offset; + else + gpio->ex_vals &=3D ~(1 << offset); + } } =20 static int ft260_gpio_set(struct gpio_chip *gc, u32 offset, int value) @@ -1138,7 +1171,7 @@ static int ft260_gpio_set(struct gpio_chip *gc, u32 o= ffset, int value) rep.gpio.ex_vals &=3D ~(1 << offset); } =20 - ft260_dbg("dirs %#02x vals %#02x ex_dir %#02x ex_vals %#02x\n", + ft260_dbg("dirs %#02x vals %#02x ex_dirs %#02x ex_vals %#02x\n", rep.gpio.dirs, rep.gpio.vals, rep.gpio.ex_dirs, rep.gpio.ex_vals); =20 @@ -1283,8 +1316,6 @@ static int ft260_gpio_init(struct ft260_device *dev, char prefix[] =3D "ft260_"; u8 mode =3D cfg->chip_mode; =20 - hid_info(hdev, "initialize gpio chip\n"); - dev->gpio_uart_mode[0] =3D (u16)FT260_GPIO_UART_MODE_0_SET; dev->gpio_uart_mode[1] =3D (u16)FT260_GPIO_UART_MODE_1_SET; dev->gpio_uart_mode[2] =3D (u16)FT260_GPIO_UART_MODE_2_SET; @@ -1324,7 +1355,6 @@ static int ft260_gpio_init(struct ft260_device *dev, goto exit; } snprintf(label, label_sz, "%s%s", prefix, dev_name(&hdev->dev)); - hid_info(hdev, "initialize gpio chip on %s\n", label); =20 dev->gc->label =3D label; dev->gc->direction_input =3D ft260_gpio_direction_input; @@ -1388,6 +1418,7 @@ static int ft260_get_interface_type(struct ft260_devi= ce *dev, ft260_dbg("wakeup_int: 0x%02x\n", cfg->enable_wakeup_int); =20 dev->power_saving_en =3D cfg->power_saving_en; + dev->chip_mode =3D cfg->chip_mode; =20 switch (cfg->chip_mode) { case FT260_MODE_ALL: @@ -1405,7 +1436,6 @@ static int ft260_get_interface_type(struct ft260_devi= ce *dev, break; } =20 - dev->iface_type =3D ret; return ret; } =20 @@ -1912,6 +1942,78 @@ static void ft260_uart_set_termios(struct tty_struct= *tty, ft260_uart_change_speed(port, &tty->termios, NULL); } =20 +static int ft260_uart_tiocmget(struct tty_struct *tty) +{ + struct ft260_device *port =3D tty->driver_data; + int gpios; + + /* + * The modem lines control via ioctl is allowed only + * when the related pins are in the gpio mode. + */ + int mask =3D FT260_GPIO_UART_MODE_4_SET; + + if ((port->gpio_en & mask) !=3D mask) { + ft260_dbg("Illegal gpio config %#02x !=3D mask %#04x\n", + port->gpio_en, mask); + return 0; + } + + gpios =3D ft260_gpio_get_all(port->gc, FT260_GPIO_VALUE); + + return (((gpios & FT260_GPIO_B) ? TIOCM_RTS : 0) | + ((gpios & FT260_GPIO_E) ? TIOCM_CTS : 0) | + ((gpios & FT260_GPIO_F) ? TIOCM_DTR : 0) | + ((gpios & FT260_GPIO_H) ? TIOCM_DSR : 0) | + ((gpios & FT260_GPIO_4) ? TIOCM_CAR : 0) | + ((gpios & FT260_GPIO_5) ? TIOCM_RNG : 0)); +} + +static int ft260_uart_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + int ret; + struct ft260_device *port =3D tty->driver_data; + struct ft260_gpio_write_request_report rep; + struct hid_device *hdev =3D port->hdev; + int mask =3D FT260_GPIO_UART_MODE_4_SET; + + if ((port->gpio_en & mask) !=3D mask) { + ft260_dbg("Illegal gpio config %#02x !=3D mask %#04x\n", + port->gpio_en, mask); + return 0; + } + rep.report =3D FT260_GPIO; + rep.gpio =3D port->gpio; + + mutex_lock(&port->lock); + + if (set & TIOCM_RTS) + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 1); + if (set & TIOCM_DTR) + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 1); + if (clear & TIOCM_RTS) + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 0); + if (clear & TIOCM_DTR) + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 0); + + ft260_dbg("dirs %#02x vals %#02x ex_dirs %#02x ex_vals %#02x\n", + rep.gpio.dirs, rep.gpio.vals, + rep.gpio.ex_dirs, rep.gpio.ex_vals); + + ret =3D ft260_hid_feature_report_set(hdev, (u8 *)&rep, sizeof(rep)); + if (unlikely(ret < 0)) { + hid_err(hdev, "%s: cannot set GPIO: %d\n", __func__, ret); + mutex_unlock(&port->lock); + return -EIO; + } + + port->gpio =3D rep.gpio; + mutex_unlock(&port->lock); + + return 0; +} + static int ft260_uart_install(struct tty_driver *driver, struct tty_struct= *tty) { int idx =3D tty->index; @@ -1988,6 +2090,8 @@ static const struct tty_operations ft260_uart_ops =3D= { .write_room =3D ft260_uart_write_room, .chars_in_buffer =3D ft260_uart_chars_in_buffer, .set_termios =3D ft260_uart_set_termios, + .tiocmget =3D ft260_uart_tiocmget, + .tiocmset =3D ft260_uart_tiocmset, .hangup =3D ft260_uart_hangup, .install =3D ft260_uart_install, .cleanup =3D ft260_uart_cleanup, @@ -2131,14 +2235,16 @@ static int ft260_i2c_probe(struct ft260_device *dev, return ret; } =20 - ret =3D ft260_gpio_init(dev, cfg); - if (ret) - goto err_i2c_free; + if (cfg->chip_mode =3D=3D FT260_MODE_I2C) { + ret =3D ft260_gpio_init(dev, cfg); + if (ret) + goto err_i2c_free; =20 - ret =3D 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; + ret =3D 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; + } } =20 return 0; @@ -2199,7 +2305,8 @@ static int ft260_uart_probe(struct ft260_device *dev, =20 cfg->uart_mode =3D FT260_UART_CFG_FLOW_CTRL_NONE; =20 - if (dev->iface_id =3D=3D 0) { + if (dev->chip_mode & FT260_MODE_UART || + dev->chip_mode =3D=3D FT260_MODE_ALL) { ret =3D ft260_gpio_init(dev, cfg); if (ret) goto err_hid_report; @@ -2274,11 +2381,11 @@ static int ft260_probe(struct hid_device *hdev, con= st struct hid_device_id *id) spin_lock_init(&dev->read_lock); init_completion(&dev->wait); =20 - ret =3D ft260_get_interface_type(dev, &cfg); - if (ret <=3D FT260_IFACE_NONE) + dev->iface_type =3D ft260_get_interface_type(dev, &cfg); + if (dev->iface_type <=3D FT260_IFACE_NONE) goto err_hid_close; =20 - if (ret =3D=3D FT260_IFACE_I2C) + if (dev->iface_type =3D=3D FT260_IFACE_I2C) ret =3D ft260_i2c_probe(dev, &cfg); else ret =3D ft260_uart_probe(dev, &cfg); @@ -2310,11 +2417,13 @@ static void ft260_remove(struct hid_device *hdev) dev->index); ft260_uart_port_remove(dev); /* dev is still needed, so we will free it in _destroy func */ - if (dev->iface_id =3D=3D 0) + if (dev->chip_mode & FT260_MODE_UART || + dev->chip_mode =3D=3D FT260_MODE_ALL) sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); =20 } else { - sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + if (dev->chip_mode =3D=3D FT260_MODE_I2C) + sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); i2c_del_adapter(&dev->adap); kfree(dev); } --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 51D84390C81 for ; Sat, 22 Aug 2026 21:40:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434856; cv=none; b=PVkqoKYvEaaXo49gstWF8QhI6OqGUid5+vkz/DfYA4bc3p5Y/bmDCzUoqi7AwbXre0U/2PRoFs3ZTVczOrtgrW8hbGr7ZuwqgeiLA+Joo+pufShy7SOMfbWJPYCi8Re40Oz1y4Cs7MPOudU66LOdiOk3TBUmQaH0yqrr8GOkEWI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434856; c=relaxed/simple; bh=Nwvv872uZXReY6PvLRaHQqXN99TKLRYH+/ajSwSPBXU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pkFLg+92Au5XDSGmzjqOXclBg9TH32nutbt4Dt5QhGmi/9bNITdXp4zbKVVXsIfpOGLUdHWhpsJ2UcP8CpcYW94TV+qtmJ8Qp2kithBr6M9jhB2jNjqPCH9Mb3xgfoLSJ86DWqHAVsB5NgbRa0tDL+O37657scNm4lOdJrzR11k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=CmwRiIf2; arc=none smtp.client-ip=209.85.218.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="CmwRiIf2" Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-c20e70a0962so331922366b.2 for ; Sat, 22 Aug 2026 14:40:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434844; x=1788039644; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=2pv2EGjaR4WPSMZF2hTxBbXWP8nqmyPnemdCtbz8Odo=; b=CmwRiIf2c7rrpHRUESnk7X+hgFba9k0MwF4Xg9wK9E+3cpVwjwf/ny7w/kqzxZrEbj X9Jfepm4ouae2ZtJL81Lid4Z5WCVe0lrpX/8BKCZhf0ABZW9wul4IA0KRs9+2Eu5VBmx p4NjvXvwfYIitnfDq04dtjwbeYCZYKYm8xvQ9HCaT8fCrp8WN9Kdzvzqqiub+vDxlhdn T/APRsZl4OA/yWs4ydU/hWyGNxzbM9hXbbjbXyqARrCd5FQnd4JmU26kpeYMmTecqLUx Js89bvt3CodGfZiryNCzYL/5OiqU680w1s+NN4oFZ61Y63XR92Nk8D5WYcVUGObqPaGU OrMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434844; x=1788039644; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=2pv2EGjaR4WPSMZF2hTxBbXWP8nqmyPnemdCtbz8Odo=; b=ApnVCROQceJMH+033z8Y86UhpGvPKXQJQyITULygF1qQkRMeQK/gm30NEojfN0dzNb ZgmOgIPD7o6xKc/tZSrDm8wTekgUIDZ4u6w8mQlab/S9zuFTGpG29OwleV5YHI3HPy1l eksQU068Ht+23Mq2qPc4fTYcuiRSl6m19QptXHYHNBtotBC8MJ1OM+hKSGaWFVSS29qN sDwKqITo9I5FbR+CVwZ26ATWwNcXepdvmUUz1AOnxVeCjUlH2TYKwBEIJSYA6nzuSAy2 yEZgZEfvbNNA1jXhgiEXRL1W8zFNVDKzKEV3wkLFyzkjPbfISGxYSaWVnC4N5C2ICtzF ciXw== X-Forwarded-Encrypted: i=1; AHgh+Rrb+9pk9o6ZFa4fo6xM+gP9/lYEnAcbQhFfvgl522m9RlkNChuCtf17NU/d8kcQTheDWWqj8yMlOkdAx/g=@vger.kernel.org X-Gm-Message-State: AFuF++kVAYBnOMAHC9btGJaqsqgu0z/+2g9z9jJcXcXxkf1Qw51DxcLV wyfBV26B7wCRwabK7YSwhJgu8V4/D3jjzlpDxVS7v4uFmNASqZBlWHD+ X-Gm-Gg: AR+sD11PZrgvDfR61iyyJhXu+3Ig1sB6bty9HOlzX8AeOH7sd/fiCrRmi9/RdZUOHIA eEbxi2rFgxpkuPmBUPALzoEzqd+yVSJSIsO4wUW8zBv+1titK1t8/bXKJJJsNdsyM6HovtaIbft SGA2Ihi9qw89fjtEFpx8835nEOsR4SGWKBXCc7iE5Si+Nfnv7OQVDXcm/5qOsRM+NV4Ui7SXFhT I633Pt82juOCuIkccMGD02eU9eoTIKPWb1vE2Z9K7Oy15S06NsU/X3+ZpFofgcJIAbABymxlIhy 38+ABuXhCb3zz91NfbYY2FoKZGJZfXoF0cJjf3qCbrURQdgCA3Tqy3+Uhz+KHVe2vYDrjQ5OZIv xnTicgKriulDF1rCsSPlaJ+jLYt3MT+1pwd3Gt9GS10N5EgrscumX33QmihF8Oy4w7ZBmRo7LsC WTYKy1Y93DudgQy85N/qnHfG0NHLQbB8TY4rfgYK7oXvKETFb1u796OYvoHIA= X-Received: by 2002:a17:906:d8d6:b0:c21:34a3:4d9c with SMTP id a640c23a62f3a-c246a72174bmr1372738766b.21.1787434843762; Sat, 22 Aug 2026 14:40:43 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:43 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 09/13] HID: ft260: gpio: group sysfs attrs per HID interface Date: Sun, 23 Aug 2026 00:39:37 +0300 Message-ID: <20260822213941.98882-10-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Split the single sysfs attribute group into per-interface groups based on chip mode (DCNF0/DCNF1): - I2C-only (mode 1): full I2C + GPIO attributes on the I2C HID iface - Dual mode (0/3): I2C-facing attrs on the I2C iface; GPIO and UART attrs on the UART iface - UART-only (mode 2): GPIO and UART attrs on the UART iface Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 58 +++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 554d8e3ddb6d..0a1875a1a7ec 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1592,7 +1592,7 @@ static ssize_t i2c_reset_store(struct device *kdev, } static DEVICE_ATTR_WO(i2c_reset); =20 -static const struct attribute_group ft260_attr_group =3D { +static const struct attribute_group ft260_i2c_chip_mode_1_attr_group =3D { .attrs =3D (struct attribute *[]) { &dev_attr_chip_mode.attr, &dev_attr_pwren_status.attr, @@ -1603,11 +1603,35 @@ static const struct attribute_group ft260_attr_grou= p =3D { &dev_attr_gpio2_func.attr, &dev_attr_gpioa_func.attr, &dev_attr_gpiog_func.attr, + &dev_attr_clock_ctl.attr, + &dev_attr_i2c_reset.attr, + &dev_attr_clock.attr, + NULL + } +}; + +static const struct attribute_group ft260_i2c_chip_mode_0_3_attr_group =3D= { + .attrs =3D (struct attribute *[]) { + &dev_attr_hid_over_i2c_en.attr, + &dev_attr_i2c_reset.attr, + &dev_attr_clock.attr, + NULL + } +}; + +static const struct attribute_group ft260_uart_chip_mode_0_2_3_attr_group = =3D { + .attrs =3D (struct attribute *[]) { + &dev_attr_chip_mode.attr, + &dev_attr_pwren_status.attr, + &dev_attr_suspend_status.attr, + &dev_attr_power_saving_en.attr, + &dev_attr_gpio2_func.attr, + &dev_attr_gpioa_func.attr, + &dev_attr_gpiog_func.attr, &dev_attr_uart_mode.attr, &dev_attr_uart_dcd_ri.attr, &dev_attr_clock_ctl.attr, &dev_attr_i2c_reset.attr, - &dev_attr_clock.attr, NULL } }; @@ -2240,7 +2264,16 @@ static int ft260_i2c_probe(struct ft260_device *dev, if (ret) goto err_i2c_free; =20 - ret =3D sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); + ret =3D sysfs_create_group(&hdev->dev.kobj, + &ft260_i2c_chip_mode_1_attr_group); + if (ret < 0) { + hid_err(hdev, "failed to create sysfs attrs\n"); + goto err_i2c_free; + } + } else if (cfg->chip_mode =3D=3D FT260_MODE_ALL || + cfg->chip_mode =3D=3D FT260_MODE_BOTH) { + ret =3D sysfs_create_group(&hdev->dev.kobj, + &ft260_i2c_chip_mode_0_3_attr_group); if (ret < 0) { hid_err(hdev, "failed to create sysfs attrs\n"); goto err_i2c_free; @@ -2306,12 +2339,14 @@ static int ft260_uart_probe(struct ft260_device *de= v, cfg->uart_mode =3D FT260_UART_CFG_FLOW_CTRL_NONE; =20 if (dev->chip_mode & FT260_MODE_UART || - dev->chip_mode =3D=3D FT260_MODE_ALL) { + dev->chip_mode =3D=3D FT260_MODE_ALL || + dev->chip_mode =3D=3D FT260_MODE_BOTH) { ret =3D ft260_gpio_init(dev, cfg); if (ret) goto err_hid_report; =20 - ret =3D sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); + ret =3D sysfs_create_group(&hdev->dev.kobj, + &ft260_uart_chip_mode_0_2_3_attr_group); if (ret < 0) { hid_err(hdev, "failed to create sysfs attrs\n"); goto err_hid_report; @@ -2418,12 +2453,19 @@ static void ft260_remove(struct hid_device *hdev) ft260_uart_port_remove(dev); /* dev is still needed, so we will free it in _destroy func */ if (dev->chip_mode & FT260_MODE_UART || - dev->chip_mode =3D=3D FT260_MODE_ALL) - sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + dev->chip_mode =3D=3D FT260_MODE_ALL || + dev->chip_mode =3D=3D FT260_MODE_BOTH) + sysfs_remove_group(&hdev->dev.kobj, + &ft260_uart_chip_mode_0_2_3_attr_group); =20 } else { if (dev->chip_mode =3D=3D FT260_MODE_I2C) - sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + sysfs_remove_group(&hdev->dev.kobj, + &ft260_i2c_chip_mode_1_attr_group); + else if (dev->chip_mode =3D=3D FT260_MODE_ALL || + dev->chip_mode =3D=3D FT260_MODE_BOTH) + sysfs_remove_group(&hdev->dev.kobj, + &ft260_i2c_chip_mode_0_3_attr_group); i2c_del_adapter(&dev->adap); kfree(dev); } --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f43.google.com (mail-ej1-f43.google.com [209.85.218.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C6C83911C6 for ; Sat, 22 Aug 2026 21:40:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434855; cv=none; b=YYFgi4+QGUdj3lRbWE+SmQ8zTugONNT4muxrgHqHoPUlD5L7iKRB5Cmi1iALVydiiL1Yvyk028i7mkes5X6DFzPDDn25yssF3tFKLifkvxfGz3QxniG69c/qyJm67NjadbvNueUnnjL5BS5YHy8xz4hVh33Ky4pSVsjvncdRJhA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434855; c=relaxed/simple; bh=nUNbmShuRQtFklLJUC7OALhPN/3USFry3dwtYtcWKow=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rUtcg1c5fWAlH02G+eKmkRGU1UBz8xd514KlPhVcAPyfDIzJkAMHQbzYihGa+dTDxtJ5uAOD2UOTX9WbX1oUM624jGD7DouAlNFFGQZSWNhu1qygYDFELdxFWQYd6jY+ntjy3Rgh8rj6lwHM0TGblAg/Za3EBHYJFqSEvVqNszk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=kBWjGRVQ; arc=none smtp.client-ip=209.85.218.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kBWjGRVQ" Received: by mail-ej1-f43.google.com with SMTP id a640c23a62f3a-c15d3cd51b2so345047066b.3 for ; Sat, 22 Aug 2026 14:40:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434846; x=1788039646; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=JMqBfKwQvAfQXaOYyEjha8jlIKlrCWyN6q7aDWdtxQg=; b=kBWjGRVQ5Tsqfej/Q/PXV0Qkvw43nVbNz2LERrDYIddPeRUnUwsv04llgVcbZWUqwP pjmMz2vR160XZ+WiWza1RyxZ4znOblNF6KZw0sPU3zoog8Tx3iJkw9x/GAdp0bFDSsKH pSaRx9jjghROZ5YguUPn4hfrj8aymIOYGTqyHz9kYyrVomSrHPJIEmmbN3yYhYCHNIxp IUEnSbQOuJF4+IK6DO+BwFP8MaI8IEYYri+GxJfwMfIDqw7cb1nZmmz9ehXnnIzwLDXn aZ+J1+70mXCfzIG4u9eRfxyY3eXJAr9s/nvgbhcEG/AwHaCXNsf6elrcDhG0RN669zH4 +Byw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434846; x=1788039646; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=JMqBfKwQvAfQXaOYyEjha8jlIKlrCWyN6q7aDWdtxQg=; b=cDbJoiCL3NeM7hgdyMShByRIJcS9AsqBviJCmWS20zSAElFilX+rNqHoAzXjwWqWmI DEiCTXEd1GmlxVJ/QhXp2w4l9m0um2q4KoaZyTsfdZduILhdQu57yVSEAHqhb5UQLJpL oYcjMCnzOPhri2U/wsNfuMtSY0gUGKo062AwdXNga8P4rTLKPpynicX6HPBngzqKvixd IVMjc8r1qaoioN+RO1MZ5txrKrt6LfR5QIo+z2GSmXrN6IMgK3WvoHcyznomZ8V9O+rj H2pAFOzG7TbvJKJ3fNx6ZzTVx/I5GLmu7qP10voar45Zz/R0o3Drq2NwYqSfI4hYUFnE 6Lbg== X-Forwarded-Encrypted: i=1; AHgh+RooC5sz5xKyzqyMlJPlPqF+uzy4frFhyhGT72+/Ixwggl7CVhgy+3+LI1+Al3lV+NeCUM45sPxmcwfbPN0=@vger.kernel.org X-Gm-Message-State: AFuF++k8ipeT0AGOwwU1xydwghV0pJ8FIgfv2T3kddATmeQLrRfXepmS N1FHQVkDgh7f2Klpai0Nl6ch2AaQj3svpE4mN33XYz3IHx39w164NFFqGEopId/Y X-Gm-Gg: AR+sD12Kpch9fYuEeo5eXGh8JTBPCHg2+XRcav0KpC0wJ3An7I0NifXOyS/gk/4Ktku ynnfQcZ4KbNeQYIGdCsl0KZZBmC5TrIHZlDg6Oa+KRzj4Ob0wcYxX0ELeocUfvzf1SGIFuyT709 s3IpdHeSHwDcgP7zuU8m0mIS27mutql17a+/NkWk+o38T39od4OxMW6k/Nx45OEOkJdaLBYk+LR pGkaJrZ+8c0ylqFUEkZs6vva+65edIsHt+NpHofeo+CrbMGEta1HVPHEl0lkoOIz5zZa6gH7GmG LROoOYz6ZqT1v9AuM3+zGGGcmo5JmKXBFVOAW7ye26IytbYK2WuR+irZi4NBuVbrTXylbHcdNNi iiG/OOkQR4W14iyvDCJsDZE5Gmfo7kCLTtcXj9ujfeGueMdIle21lvMjLdl2ly/iNZ0LO2bj7YA wMgOQu+ySOaIvSSFrfVNnlbyRHrn8DVje+6kgIpGALBAh1hPU1Cb4Tomh/BbU= X-Received: by 2002:a17:906:9c93:b0:c24:6687:bf6a with SMTP id a640c23a62f3a-c2492663ee3mr836396066b.21.1787434845967; Sat, 22 Aug 2026 14:40:45 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:45 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 10/13] HID: ft260: uart: fix active-low RTS/CTS/DTR/DSR polarity Date: Sun, 23 Aug 2026 00:39:38 +0300 Message-ID: <20260822213941.98882-11-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Rio Liu The FT260 modem control GPIOs for RTS, CTS, DTR, and DSR are active-low. Invert the TIOCMGET sense and the TIOCMSET drive levels for those pins so userspace ioctl state matches the electrical lines. Leave DCD and RI unchanged. Signed-off-by: Rio Liu Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 0a1875a1a7ec..a2f4b6e5a16b 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1985,10 +1985,10 @@ static int ft260_uart_tiocmget(struct tty_struct *t= ty) =20 gpios =3D ft260_gpio_get_all(port->gc, FT260_GPIO_VALUE); =20 - return (((gpios & FT260_GPIO_B) ? TIOCM_RTS : 0) | - ((gpios & FT260_GPIO_E) ? TIOCM_CTS : 0) | - ((gpios & FT260_GPIO_F) ? TIOCM_DTR : 0) | - ((gpios & FT260_GPIO_H) ? TIOCM_DSR : 0) | + return ((!(gpios & FT260_GPIO_B) ? TIOCM_RTS : 0) | + (!(gpios & FT260_GPIO_E) ? TIOCM_CTS : 0) | + (!(gpios & FT260_GPIO_F) ? TIOCM_DTR : 0) | + (!(gpios & FT260_GPIO_H) ? TIOCM_DSR : 0) | ((gpios & FT260_GPIO_4) ? TIOCM_CAR : 0) | ((gpios & FT260_GPIO_5) ? TIOCM_RNG : 0)); } @@ -2013,13 +2013,13 @@ static int ft260_uart_tiocmset(struct tty_struct *t= ty, mutex_lock(&port->lock); =20 if (set & TIOCM_RTS) - ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 1); + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 0); if (set & TIOCM_DTR) - ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 1); + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 0); if (clear & TIOCM_RTS) - ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 0); + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_RTS, 1); if (clear & TIOCM_DTR) - ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 0); + ft260_gpio_output_cfg(&rep.gpio, FT260_GPIO_UART_DTR, 1); =20 ft260_dbg("dirs %#02x vals %#02x ex_dirs %#02x ex_vals %#02x\n", rep.gpio.dirs, rep.gpio.vals, --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f46.google.com (mail-ej1-f46.google.com [209.85.218.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 09B073909A7 for ; Sat, 22 Aug 2026 21:40:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434858; cv=none; b=KqvEg0XMQfBgX651/oPNvbrqBf9mVmQuNGvjifFnuw4lvHtrg+ommCgaaaYXmsPeCQRBgF7hzhqZGwMf1sv1hGgAiMYJqsRP54KWQ/iQYFrYzzn3jWuzKYi+P8TZ8C4QHGZ2LzzRTInqmVPzJPPFw8v+Q63JYANfYXQoKfYeUmc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434858; c=relaxed/simple; bh=+tTBPf/ITWlpNCLZqiExxTpUAgaWoqxWpXFOOAp5x4M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=N0ZWHSg2W7nfBnWEybHLZnhXK6JGq+ZH1bsF8a7qirSUkzCEIh3wr6F9Hq8fpY4ePhzlXyuHJrKFZePW11QA/rcIdXcOQs3f5QsZl9H2Kdt9LNZDd7KVagvMBrLbfRyGvraJ7GUB+CqlMCexyGW9VtfvMIuRZAdXfAij+6JJz9Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hu5x/Oxa; arc=none smtp.client-ip=209.85.218.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hu5x/Oxa" Received: by mail-ej1-f46.google.com with SMTP id a640c23a62f3a-c197eaaab00so353384766b.0 for ; Sat, 22 Aug 2026 14:40:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434848; x=1788039648; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=10Eq5R/N2bfHPdqI9SBKLnnx+VXtAYQbeV+dq9RoJTo=; b=hu5x/Oxa/d3y6zWFCg3G6PNcMQX82Q+drWOWuByIgA6VhI6ZegzD5MUAvNMyarunZf bKgIYjg7agqc87krmzC8O3kNHWpnB311UfyWnPZNvvNtzWyXNojfgq17hmPsjWocUinJ Dh2Abt3ZgWqZj8rbRQgM04Gu8L0VFlzDdfBo0rQBTJ+2BpM2zo/3EL1lDaKaWrcdPU7/ gX7cRsLrSk0CoVSTepl7ZwwBr/LxxNCZFhGguErMZRgBYDSsMilwOnKk0fBuIs4W5Whq apmrT7q4J2N7Bwi22UqoFJc4Mv6FqjEB5dTVblDowuh/+df0CDv/AAB5ikAS+k5gwpR+ ql1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434848; x=1788039648; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=10Eq5R/N2bfHPdqI9SBKLnnx+VXtAYQbeV+dq9RoJTo=; b=GZbszajzXmvtLTGJl6w9ZSr3WcolaRtUibrLfWfR7IPwH5ZGaSkiRwskuS6iRTlQgv coJVCJwnuI/fQ3OcOHyO+jLHI3m6TYR3oDaceZzPMkYcPwU0Sb/8mNKaZtuPZ8++OyqJ Tbkttu4fiGpHb6Y2rxfTcN7T6ZDi26hZPLM6N70UnFN1kvY9qOZVBYWwzRjdBaZxNzmR WLJkMGhmUJc2EnjPfYOPFtiwt9ivixkpoN4aloCMnh3hnlxc1qJgZtP2suTPP1sG+dMq WWQt9EiHf128S/fg1mdLHur9snmeMT4R7Z0aiogFRqIhcJ54/92RNbRPhwG2K4wqnOdz 9vQQ== X-Forwarded-Encrypted: i=1; AHgh+RpxmLO028bf0jNGPieucc1YVqNsQc4wTzSKJHApnSuCsAFVR2bv9yQQmBdq2RFrpZKI0JHKYE4/DN0ZKc8=@vger.kernel.org X-Gm-Message-State: AFuF++k3ZTuRZW8r3JQDS0WmEn3/99Q6fWQIhaXvedcHnADFo8mmPzjv 40uj4ECBOcbjxDKwVfILHC/crtAlIlHdRO2ABu2JQmwUpr87qxUrlASg X-Gm-Gg: AR+sD12HxA0u6WRBWUToANS6M29Ig13K54vrhyrcHZ9r2GeH0NAcxTKQeNeXFId7ZKt G9iyjXO+oSO7juiOj3+Icbg3GC0fG7lgNNVLutiyUiVUmwdxcXRW8OtCJ9Z0GbmtVUO0LfHekrg nVJyNMgxynY0QEOOobtJ11aML5qQ9BfzxaWSxTnNVmQcIwmlFYMWCVruT+OitY0I8r6C9DfJZJd zD+bfirVOiEQc314lXSjuVTVtHW1UmUeRxA83bcx7dA6k3k86N1vPr+eC74exr2JfHrECKBW+aJ wRZ7IrRz1ic3uRzW3QiKJjHUyUDSyUKBC6OAj5c45Stukt3+g9b7BmOt/kuRaXb0csmMccrBHdr 1nvW65PXyN5qnx1prXcQo7Kn3z8Q+0TwigIvl2H6ZneM/MCp7uv6V9cMoIpBj2CU2Jnh1HlKhX6 y1htVxfz7nm2lE8Oy83rPZBYquJCMLuz4h09gxrlw8uO61lCSzE1PaHUZNDvWP X-Received: by 2002:a05:6938:a086:20b0:c20:38c2:2b7f with SMTP id a640c23a62f3a-c246a684286mr1173749266b.12.1787434847817; Sat, 22 Aug 2026 14:40:47 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:47 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman , Chris Keeser Subject: [PATCH 11/13] HID: ft260: i2c: fix large write transaction failure Date: Sun, 23 Aug 2026 00:39:39 +0300 Message-ID: <20260822213941.98882-12-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Fixes a regression from commit 5afac727defa ("HID: ft260: missed NACK from busy device"), which waited for bus-idle after every HID write report except when the report flag was exactly FT260_FLAG_START. Multi-report I2C writes keep the bus busy between chunks for atomicity. Treating those middle reports like a final STOP caused ft260_xfer_status() to spin on -EAGAIN after the second HID report and fail large writes. Write-then-read combined transfers still worked. Pass the bus-busy check policy from the caller: ft260_i2c_write() and ft260_smbus_write() wait for idle only when the report they send carries STOP; intermediate chunks and the command phase of a combined transfer do not. The SMBus side matters for register reads. The command phase is written with FT260_FLAG_START and no STOP, because a repeated START read follows and the controller deliberately keeps the bus busy to keep the transaction atomic. Checking the busy bit there makes ft260_xfer_status() return -EAGAIN on every retry and the command write fail with -EIO, which breaks probing of clients that read registers: ads1x19 6-0040: Failed to read config register: -5 leds-pca963x: probe of 6-0008 failed with error -5 Fixes: 5afac727defa ("HID: ft260: missed NACK from busy device") Reported-by: Chris Keeser Closes: https://github.com/MichaelZaidman/hid-ft260/issues/35 Tested-by: Chris Keeser Reported-by: Bruno Giacomazzi Closes: https://github.com/MichaelZaidman/hid-ft260/issues/42 Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index a2f4b6e5a16b..36687c086b40 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -566,9 +566,14 @@ static int ft260_xfer_status(struct ft260_device *dev,= u8 bus_busy) } =20 dev->clock =3D le16_to_cpu(report.clock); - ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status, - dev->clock); + ft260_dbg("bus_status %#02x, clock %u, bus_busy %#02x\n", + report.bus_status, dev->clock, bus_busy); =20 + /* + * Do not check the busy bit for combined transactions + * since the controller keeps the bus busy between writing + * and reading IOs to ensure an atomic operation. + */ if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy)) return -EAGAIN; =20 @@ -602,13 +607,10 @@ static int ft260_hid_output_report(struct hid_device = *hdev, u8 *data, } =20 static int ft260_hid_output_report_check_status(struct ft260_device *dev, - u8 *data, int len) + u8 *data, int len, u8 bus_busy) { - u8 bus_busy; int ret, usec, try =3D 100; struct hid_device *hdev =3D dev->hdev; - struct ft260_i2c_write_request_report *rep =3D - (struct ft260_i2c_write_request_report *)data; =20 ret =3D ft260_hid_output_report(hdev, data, len); if (ret < 0) { @@ -625,16 +627,6 @@ static int ft260_hid_output_report_check_status(struct= ft260_device *dev, ft260_dbg("wait %d usec, len %d\n", usec, len); } =20 - /* - * Do not check the busy bit for combined transactions - * since the controller keeps the bus busy between writing - * and reading IOs to ensure an atomic operation. - */ - if (rep->flag =3D=3D FT260_FLAG_START) - bus_busy =3D 0; - else - bus_busy =3D FT260_I2C_STATUS_BUS_BUSY; - do { ret =3D ft260_xfer_status(dev, bus_busy); if (ret !=3D -EAGAIN) @@ -651,6 +643,7 @@ static int ft260_hid_output_report_check_status(struct = ft260_device *dev, static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data, int len, u8 flag) { + u8 bus_busy =3D 0; int ret, wr_len, idx =3D 0; struct ft260_i2c_write_request_report *rep =3D (struct ft260_i2c_write_request_report *)dev->i2c_wr_buf; @@ -663,8 +656,10 @@ static int ft260_i2c_write(struct ft260_device *dev, u= 8 addr, u8 *data, do { if (len <=3D FT260_WR_I2C_DATA_MAX) { wr_len =3D len; - if (flag =3D=3D FT260_FLAG_START_STOP) + if (flag =3D=3D FT260_FLAG_START_STOP) { rep->flag |=3D FT260_FLAG_STOP; + bus_busy =3D FT260_I2C_STATUS_BUS_BUSY; + } } else { wr_len =3D FT260_WR_I2C_DATA_MAX; } @@ -680,7 +675,7 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, rep->flag, data[0]); =20 ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, - wr_len + 4); + wr_len + 4, bus_busy); if (ret < 0) { ft260_dbg("%s: failed with %d\n", __func__, ret); return ret; @@ -721,7 +716,9 @@ static int ft260_smbus_write(struct ft260_device *dev, = u8 addr, u8 cmd, ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n", rep->report, addr, cmd, rep->length, len); =20 - ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len); + ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len, + (flag & FT260_FLAG_STOP) ? + FT260_I2C_STATUS_BUS_BUSY : 0); if (ret < 0) ft260_dbg("%s: failed with %d\n", __func__, ret); =20 --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 68EA8391E60 for ; Sat, 22 Aug 2026 21:40:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434864; cv=none; b=hBXGiGGFUGClSxOQsieKiH+hvSdNHEuvMpwkvVX5u1fc1DOm/O385vL/hGZ5YBpZcNmQLkUhWQKiTr4MToaIpsBO9/KH5YF2S6Zp5JJuyjtr6Y7vCQeCb6v1kSPf1MmFnetc/VOIC78JPdT31DzzNo2gRi+kQWpntepsUTpJUhI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434864; c=relaxed/simple; bh=QPQz+OgTmgC1KaCD/XgPz2pDEBSmMGR36tdcIU1HSwA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=p0/lCQkBe8+qQQDREoSwUXevUmZaPaa/0AYYTwEvxai7husRsWBisE1x7XEHsneW72Z3oW6AiQswc5PSgMUAFrOqVkOi1BVmBMbDctZsv0/2ZigaGmmQnbqzFTrxCoDF62p7kcpsWf5+2rP8Z2LQcrucWvo7rhdNH40NwLUZ1jk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=SHRBRis0; arc=none smtp.client-ip=209.85.218.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SHRBRis0" Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-c15f020a223so324189966b.1 for ; Sat, 22 Aug 2026 14:40:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434850; x=1788039650; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=PIZ8vk7AvIlf+MJCS8KXSoMKwVDTGVsLnVhkK3vPSy4=; b=SHRBRis0lBZaK8GBErHz7T18BK35h22ZcZcu+TDgtay7MDsMHlYFoHQ3UQA3jK1k05 6+gC7j3e06Zvxap0oESMTZx7pngyy3MydoouUFwTzCJYBchgzqlNMUgComaYa2p34CKx E3sYOI598yEV/lna3TyYvtuj3t4CC/6PJ1EeuPFzew7wvdnAab5V/C2vToqhyCSls/mD tutylu2sISSB04LOHg6JZD0c0/Cey3GPIb6fY1aEAhuKgtt3OswUTc9TG85rbNC1hJ3d RVPP+HRNwkeGdob7Rirs1BVgohW2hwovXkhhL2escZ8Joq7in/89+f+a6fE0pvAwZQ7r 2gEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434850; x=1788039650; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=PIZ8vk7AvIlf+MJCS8KXSoMKwVDTGVsLnVhkK3vPSy4=; b=rXsSTSfQVt5ifuLE4tc4QAsXW2Svwld/tg3Bh3nfrnHTJztDX00u7EDzwDge2GM12T ryTtBBTMKP59y9a8K+YMjzBxv+/j9nOVRLupSawJ29Qfg1wnTTqO8DXp941GYTX49Q47 XWY3zNgXtYzHkMK9IYTSHpUUbg6JFB/HxNePcuErT7Vc93J6QbIlM92lmh7bVGC4UDmG CT+n4m5QOC10LVe92i+ydjQnk4wYCEslF8tz0CTPGFjSP3cg9uygr7uPKJ5cHMQAseO6 aKU1tavAO4jZo/Rs8Umab5NDBIXJdnkDMZC5bLZuVzEV8LedxdUCyT2oaK0pNPDOf+RT Komw== X-Forwarded-Encrypted: i=1; AHgh+RrCFECI/Dntwx9rWv/ux+kf7NdtzEIH7V00R4GJod4fPuEKGL0SmiL3PqgKffC7CVQXHOuozfKdkQikLIY=@vger.kernel.org X-Gm-Message-State: AFuF++kBctTqZgydFccKJaVOYJMqePXWND4xIhFwiTZ35JuFGg1roVFc OOV9cRRwVFtsoVxqdTBkPuifh2SI5Vran0wGxfgnh4L5j5rZzNYt+1SN X-Gm-Gg: AR+sD13jp2mvIWjwxGVR+A4+fE6eSj3sXkypbHDGyAtL/7sY8I27VEuiNricVpaJKjv /KthBxX9YCO1j8wZiRTlyWPpAAiK2YQ7/ncQNJ/73yTxD2pufoPeZVl9cntJQGXK5XnNPkiDGbk 3Gv9eR5+47axh34YHItMj9sCLYj9hkMt6q8h+d/6SaSzdQ66W4FtxpUIuIVOMPBX812D0JcGPfd rihOyoXnrdq21CdFKogu4mPsbktvMf61sNzJ/FGwExxFOat2yTb2sQ9FTDY76+nFHawM5q+1Q97 H0oua6kHQFybP5YxgnfhUb3JmuhJ/lnpFKUE00eBxcT1jPtATmo1uLWRJd95rzoQoV7X9fufp+T WepXILqX5GrGfn+/whNrOYUyUGpOeUAgU8aboSn+x9mq4cmEAsRkNextTsMxCTegcH1MaU+zMAA fJsVzCmo3yrqWl7KoNXiz/6tYO7BsCjXTo63l75cFtZ4SdLcph40rUVeNgb3Y= X-Received: by 2002:a17:907:7283:b0:c24:b11a:470f with SMTP id a640c23a62f3a-c24b11a493emr163528966b.0.1787434849751; Sat, 22 Aug 2026 14:40:49 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:49 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman , Andreas Boose Subject: [PATCH 12/13] HID: ft260: workaround for TN_189 errata endpoint STALL after enumeration Date: Sun, 23 Aug 2026 00:39:40 +0300 Message-ID: <20260822213941.98882-13-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" FTDI errata TN_189 (Section 2.1) documents a silicon bug where the FT260's USB interrupt endpoints are occasionally halted right after enumeration. When this happens, Clear-Feature ENDPOINT_HALT does not recover the endpoint and the only known recovery is a USB device reset. This patch implements an in-driver workaround: 1. ft260_check_intr_ep_health() observes the STALL by attempting an actual interrupt IN transfer. The FT260 does not honestly report its halt state via USB_REQ_GET_STATUS (returns 0 even when STALLed; confirmed separately by FTDI engineering with a USB analyzer trace), so we cannot rely on it; instead we let the host controller return -EPIPE when it sees the STALL handshake. 2. ft260_check_dev_responsive() catches the broader broken state where the interrupt endpoint may look healthy but the device still fails to respond to control transfers. A USB_REQ_GET_STATUS to the device with a short 500 ms timeout fails fast on a broken device, preventing later probe stages from hanging on usbhid's default 10 s timeouts and starving the usb_hub_wq workqueue. 3. When either check fails, probe schedules a deferred work item and returns -ENODEV so that hub_event releases the device lock quickly. The work item retries usb_lock_device_for_reset() up to 10 times (~10 s; each attempt already polls for up to one second) before giving up, then calls usb_reset_device() and explicitly unbinds/rebinds all USB interfaces to force usbhid to recreate the HID devices and trigger a fresh ft260_probe(). The unbind+rebind step is needed because usbhid's pre_reset and post_reset both return 0, so usb_reset_device() alone keeps usbhid bound to stale HID device state. FTDI engineering tested this on a Raspberry Pi 4 Model B Rev 1.5 running Linux 6.12.62-v8+ on an xhci_hcd host, with the FT260 connected at full-speed through a downstream USB 2.0 hub. Across 28,684 re-enumeration cycles, 350 cycles triggered the recovery path. Two of those required two consecutive USB resets before the device returned. All 28,684 cycles recovered to a fully functional state with I2C and UART working end-to-end. Reported-by: Andreas Boose Closes: https://github.com/MichaelZaidman/hid-ft260/issues/40 Link: https://ftdichip.com/wp-content/uploads/2026/05/TN_189-FT260-Errata-T= echnical-Note.pdf Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 229 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 36687c086b40..9ae688f6208f 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -2359,15 +2359,227 @@ static int ft260_uart_probe(struct ft260_device *d= ev, return ret; } =20 +/* + * FT260 errata TN_189 Section 2.1: the USB interrupt endpoints are + * occasionally halted right after enumeration. When this happens: + * - Standard Clear-Feature ENDPOINT_HALT does not recover the endpoint + * - Subsequent communication with the device is dead + * - The only known recovery is a USB device reset + * + * A separate finding from FTDI engineering (confirmed by USB analyzer + * trace while testing this workaround) is that the FT260 does NOT + * honestly report the halt state via USB_REQ_GET_STATUS: it returns 0 + * even when the endpoint is STALLed. Detection must therefore observe + * the STALL handshake at the host controller level rather than ask + * the device. + * + * Recovery is performed by a deferred work item that resets the USB + * device and unbinds/rebinds all interfaces to force usbhid to + * destroy stale HID devices and create fresh ones, which triggers a + * new ft260_probe() that succeeds. + * + * https://ftdichip.com/wp-content/uploads/2026/05/TN_189-FT260-Errata-Tec= hnical-Note.pdf + */ +struct ft260_reset_work { + struct work_struct work; + struct usb_interface *usbif; +}; + +static void ft260_reset_and_rebind(struct work_struct *ws) +{ + struct ft260_reset_work *rw =3D + container_of(ws, struct ft260_reset_work, work); + struct usb_interface *usbif =3D rw->usbif; + struct usb_device *usbdev =3D interface_to_usbdev(usbif); + struct usb_host_config *actconfig; + int ret, i, attempt; + + /* + * Retry the device lock for up to ~10 seconds. The lock is held + * by hub_event for the duration of device enumeration; with the + * fast-fail responsiveness check in probe, both interfaces should + * abort within ~1-2 seconds, after which the lock becomes free. + * Each usb_lock_device_for_reset() attempt already polls for up to + * one second internally. + */ + for (attempt =3D 0; attempt < 10; attempt++) { + ret =3D usb_lock_device_for_reset(usbdev, NULL); + if (ret >=3D 0) + break; + if (ret =3D=3D -ENODEV || ret =3D=3D -EHOSTUNREACH) { + dev_dbg(&usbif->dev, + "device gone before reset (%d), abort\n", ret); + goto out; + } + /* -EBUSY: someone else holds the lock; retry. */ + } + if (ret < 0) { + dev_err(&usbif->dev, + "failed to acquire USB device lock for reset after %d attempts: %d\n", + attempt, ret); + goto out; + } + + ret =3D usb_reset_device(usbdev); + if (ret < 0) { + dev_err(&usbif->dev, "USB reset failed: %d\n", ret); + usb_unlock_device(usbdev); + goto out; + } + + /* + * usb_reset_device() keeps usbhid bound (its pre_reset/post_reset + * both return 0) and does not re-trigger HID-level driver probing. + * Unbind and rebind all USB interfaces to force usbhid to destroy + * stale HID devices and create new ones, which triggers fresh + * ft260_probe() calls. + */ + actconfig =3D usbdev->actconfig; + for (i =3D 0; actconfig && i < actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *intf =3D actconfig->interface[i]; + + if (intf && intf->dev.driver) + device_release_driver(&intf->dev); + } + for (i =3D 0; actconfig && i < actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *intf =3D actconfig->interface[i]; + + if (!intf) + continue; + ret =3D device_attach(&intf->dev); + if (ret < 0) + dev_err(&intf->dev, + "failed to rebind USB interface: %d\n", ret); + } + + usb_unlock_device(usbdev); +out: + usb_put_intf(usbif); + kfree(rw); +} + +static int ft260_schedule_reset(struct usb_interface *usbif) +{ + struct ft260_reset_work *rw; + + rw =3D kmalloc_obj(*rw, GFP_KERNEL); + if (!rw) + return -ENOMEM; + + usb_get_intf(usbif); + rw->usbif =3D usbif; + INIT_WORK(&rw->work, ft260_reset_and_rebind); + schedule_work(&rw->work); + + return 0; +} + +/* + * Detect whether the device's interrupt IN endpoint is in the STALL + * state described by TN_189. GET_STATUS is unreliable on the FT260 + * (returns 0 even when halted, confirmed by FTDI with a USB analyzer + * trace), so observe the STALL handshake by attempting an actual + * interrupt IN transfer. The host controller returns -EPIPE when it + * receives a STALL handshake. + * + * Must be called before hid_hw_open() so it does not race against + * usbhid's own interrupt IN URB. + */ +static int ft260_check_intr_ep_health(struct hid_device *hdev) +{ + struct usb_interface *usbif =3D to_usb_interface(hdev->dev.parent); + struct usb_device *usbdev =3D interface_to_usbdev(usbif); + struct usb_host_interface *iface_desc =3D usbif->cur_altsetting; + struct usb_endpoint_descriptor *ep =3D NULL; + unsigned int pipe; + u8 *buf; + int ret, actual_length, i; + + for (i =3D 0; i < iface_desc->desc.bNumEndpoints; i++) { + if (usb_endpoint_is_int_in(&iface_desc->endpoint[i].desc)) { + ep =3D &iface_desc->endpoint[i].desc; + break; + } + } + if (!ep) + return 0; + + buf =3D kmalloc(FT260_REPORT_MAX_LEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pipe =3D usb_rcvintpipe(usbdev, ep->bEndpointAddress); + ret =3D usb_interrupt_msg(usbdev, pipe, buf, FT260_REPORT_MAX_LEN, + &actual_length, 100); + kfree(buf); + + if (ret =3D=3D -EPIPE) { + hid_warn(hdev, + "interrupt IN ep %#x halted (TN_189 errata), scheduling USB reset and = rebind\n", + ep->bEndpointAddress); + return -ENODEV; + } + + return 0; +} + +/* + * Quick check that the device responds to a standard control transfer. + * When the FT260 is in the buggy post-enumeration state, control + * transfers initiated by later probe stages (chip version retrieval, + * UART/I2C configuration, etc.) can hang for very long periods, + * starving the usb_hub_wq workqueue and preventing the reset work + * from acquiring the device lock. + * + * Issue USB_REQ_GET_STATUS to the device (any compliant USB device + * must answer immediately) with a short explicit timeout. If it + * fails, treat the device as broken and bail out before reaching + * anything that can block. + * + * The interrupt-endpoint health check above only catches STALLs on + * the interrupt IN path; this check catches the broader broken state + * that affects the other interface even when its interrupt endpoint + * happens to look healthy. + */ +static int ft260_check_dev_responsive(struct hid_device *hdev) +{ + struct usb_interface *usbif =3D to_usb_interface(hdev->dev.parent); + struct usb_device *usbdev =3D interface_to_usbdev(usbif); + __le16 *status; + int ret; + + status =3D kmalloc_obj(*status, GFP_KERNEL); + if (!status) + return -ENOMEM; + + ret =3D usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_RECIP_DEVICE, + 0, 0, status, sizeof(*status), 500); + kfree(status); + + if (ret < 0) { + hid_warn(hdev, + "device unresponsive to GET_STATUS (%d), suspected TN_189 errata, sche= duling USB reset and rebind\n", + ret); + return -ENODEV; + } + + return 0; +} + static int ft260_probe(struct hid_device *hdev, const struct hid_device_id= *id) { struct ft260_device *dev; + struct usb_interface *usbif; struct ft260_get_chip_version_report version; struct ft260_get_system_status_report cfg; int ret; =20 if (!hid_is_usb(hdev)) return -EINVAL; + + usbif =3D to_usb_interface(hdev->dev.parent); /* * We cannot use devm_kzalloc here because the port has to survive * until destroy function call. @@ -2392,6 +2604,23 @@ static int ft260_probe(struct hid_device *hdev, cons= t struct hid_device_id *id) goto hid_fail; } =20 + /* + * TN_189 errata workaround: bail out fast on a broken device so + * that hub_event releases the device lock quickly, allowing the + * scheduled reset work to acquire it and recover the device. + */ + ret =3D ft260_check_intr_ep_health(hdev); + if (ret) { + ft260_schedule_reset(usbif); + goto err_hid_stop; + } + + ret =3D ft260_check_dev_responsive(hdev); + if (ret) { + ft260_schedule_reset(usbif); + goto err_hid_stop; + } + ret =3D hid_hw_open(hdev); if (ret) { hid_err(hdev, "failed to open HID HW\n"); --=20 2.43.0 From nobody Mon Sep 28 10:43:18 2026 Received: from mail-ej1-f52.google.com (mail-ej1-f52.google.com [209.85.218.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 66A393911AA for ; Sat, 22 Aug 2026 21:40:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434860; cv=none; b=a0uVuRc1P6bvU6dyeSCKYtlQ6u1Xy5WG6K7rKnL+kjXQt9ZJ16dY7rPIeEPehWIel6Hr0mRfga0IO5hsxs2mY5zqgVN4YWu5Cz4fE+bKCiib9GhAWuL6tZIh1WMO37AFoA9jtDSNMbvxCFK/YA4g4U5ZH1r6y7WIdDfVvY1ppvo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787434860; c=relaxed/simple; bh=IP4K+y+s3kto6i7CgO79hJGPSKvInsmESILBbssd/80=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IDyDEjrBEbaBAkxRhtCOo9jjf1eBEbGaldfoUUm16vEjQYQucaofUcTA27roI/977Czb+hi8svrCJh/3zRxFD4dCsLKMZXhAN3ptB3lIfSf9EJXxjUevFmqVsqeX5jwuTXBO/+hWG9TZxnQF8JuQapWWyfWpSPkMznBdOtoTZSI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=cqXClZv/; arc=none smtp.client-ip=209.85.218.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="cqXClZv/" Received: by mail-ej1-f52.google.com with SMTP id a640c23a62f3a-c247f6687dcso229500866b.2 for ; Sat, 22 Aug 2026 14:40:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787434851; x=1788039651; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=9qg1KAy+mSZMSfDHaheDRm1TaMRLDEaVjJ1MAUbT2Qc=; b=cqXClZv/ypz/2NVes789SP1HBhY9X2xtbc8gJtx1/loMaYEw0LSp9TPmfxv9M4HpSk 7qccz38FKSnguvHAK8iYZjfmRNeaFusUkPy+WlwSGnJ9reP5OMB19axjSLTL8NTN8+mx 6MWYNMN0W6BO8Z076+eey8BtgYOnXaiGXxBJbeZyDzLa/wkEd8seoO7tBcB93L4qP9cU 96yzQUh4bxxJbXePDizj8duPiNdKTxDGXCjhfP6d8Gh1E5hJ7JBCEQJZ2oo0/Rfcw8AN 885hs+IaKQZ3vcuRk4fIKQEAQkfGRI69PfWlniLd8XvIOI+SAiCIcrlF14c8ylRwGCAC 0sbQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787434851; x=1788039651; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=9qg1KAy+mSZMSfDHaheDRm1TaMRLDEaVjJ1MAUbT2Qc=; b=a1eJr0Lrpmlk3e64SOCDPY8IJji83GGbKRKLUpBk1J5Hu137ze1HNBSfQntijmWP1t 6ituFhRoH7GYKnC+sw4shrRIkyDyQRiWGGqsPsy+FNFqAWSjCNfagwAbcQk1DzDqGMTy 6rhbukSJYZUvFS1q7ZW8BKAUY+4U1afhFR1sRWa1xQ6ZqgO1iPfd5W+jH/Akh8HMtt2x kmIQ3xJPteWj7rX0CML6HNZWlP3h8moa2scTEjxwDMTAXY881PvlZwJILdXcqdoHwYIh WUF68FT++ZMl6cIx9MQQfUYV12b1ctq/qH7Xrgc4acL5XH1Q/xtlYtY8hbSvyRj/aHhj QXdQ== X-Forwarded-Encrypted: i=1; AHgh+RpA+F1Tde2C+nXN33s4biBg6QR0HqPr5rUDwaoljRp/6myWvdZxrNQlyvNxLAokt5JAjUK+t3s6FN0mnsM=@vger.kernel.org X-Gm-Message-State: AFuF++kesV3CxSePcURW7jaj5WV/TwPGiobVY6F5j1Lyy2I3C0TGPRgd Zq8jWDOly76FUaDWddmvXo2vSmBodjS5mA9KXQvtIHRatoxa5i1MhGa3 X-Gm-Gg: AR+sD10fj8DYQ+4eJez7MCH+311QPO4+5quIbOvDRY6VxkqAsPvSXyXyGpZ77mc54Dq Gd8taAbajVBczNEjDb46bIa98y96Zd21aaKPUqR+IXWsHvCBkGLOrDmLLg11+irer7bdoH5H+sD cWgdwymGjzF70ArP9aD9VoFev+txjXxhrrS9pXaUZAgx7cAqoko4J71lxxgRHGY5TymHsjPWz0q hBA+a8kYc1P9cbx8+l7bZOw6xAMJ1oaJwhOEkYYWbCsvjLLBJj63fy2R/sqBkdHV6FshcVIVhsU vdp/0uqluAXYD3s56Zos7V9KDXZm5t4QlKGPaEJPgvvdi/jEGe4mjQIkRoYCZNESaJpd9Hy2hbw 0XWYw7WjqRVbFKvqtUdsn244sCSeOq35L7jEhczZDc3a5y3o1bonnzK4a+Q385Jj++Fy7dJETg/ qguVTkPSp1sQOZggbz+brCBMaAogDJnu7Zm18w5txa5VriwSOQkLYfw31+YGo= X-Received: by 2002:a17:907:e1c6:20b0:c20:d90b:2ccf with SMTP id a640c23a62f3a-c246a306b53mr1173876266b.7.1787434851479; Sat, 22 Aug 2026 14:40:51 -0700 (PDT) Received: from m2.. ([37.142.151.124]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-c24966f99basm466778466b.37.2026.08.22.14.40.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 22 Aug 2026 14:40:51 -0700 (PDT) From: Michael Zaidman To: Jiri Kosina , Benjamin Tissoires Cc: Linus Walleij , Bartosz Golaszewski , Germain Hebert , Rio Liu , Bruno Giacomazzi , Christina Quast , linux-input@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Zaidman Subject: [PATCH 13/13] HID: ft260: i2c: abort in-flight transfers with STOP before reset Date: Sun, 23 Aug 2026 00:39:41 +0300 Message-ID: <20260822213941.98882-14-michael.zaidman@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com> References: <20260822213941.98882-1-michael.zaidman@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A transfer that ends without STOP leaves the FT260 I2C controller with BUS_BUSY set. I2C reset (AN_394 section 4.4.15) restarts the master but, with the driver still bound, status stays 0x60 (CTRL_IDLE | BUS_BUSY) even when SCL and SDA are pulled up. The next START then fails with 0x72 (arbitration lost + error) and further resets do not recover; only a USB replug did. Add ft260_i2c_abort(): a STOP-only write (flag 0x04, zero payload) then FT260_SET_I2C_RESET. Use it wherever the host tears down an open transaction: - read wait timeout - read status check failure after the HID report arrived - write status poll failure after the report was sent - SMBus block read with an invalid count (count phase has no STOP) Leave ft260_i2c_reset() alone for HID output failure (the request may never have reached the controller), sysfs i2c_reset, and probe. Tested on a UMFT260EV1A with a 24LC512 at 0x51. Five 1 ms read timeouts each followed by STOP+reset left bus_status 0x20; raising the timeout to 25 ms on the same loaded module then read 16 bytes successfully. This matters more with the 25/75 ms completion timeout from "HID: ft260: i2c: reduce driver module loading time", which makes read timeouts more likely than mainline's 5000 ms wait. Signed-off-by: Michael Zaidman --- drivers/hid/hid-ft260.c | 43 ++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 9ae688f6208f..a35a1af2d7e5 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -538,6 +538,33 @@ static int ft260_i2c_reset(struct hid_device *hdev) return ret; } =20 +static int ft260_hid_output_report(struct hid_device *hdev, u8 *data, + size_t len); + +/* + * STOP with no START and no payload. Used by ft260_i2c_abort() when + * tearing down an in-flight transfer: I2C reset alone can leave + * BUS_BUSY set even when SCL/SDA are idle, and the next START then + * fails with arbitration lost. + */ +static int ft260_i2c_stop(struct hid_device *hdev, u8 addr) +{ + u8 buf[4] =3D { + FT260_I2C_REPORT_MIN, + addr, + FT260_FLAG_STOP, + 0, + }; + + return ft260_hid_output_report(hdev, buf, sizeof(buf)); +} + +static int ft260_i2c_abort(struct hid_device *hdev, u8 addr) +{ + ft260_i2c_stop(hdev, addr); + return ft260_i2c_reset(hdev); +} + static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy) { struct hid_device *hdev =3D dev->hdev; @@ -607,7 +634,8 @@ static int ft260_hid_output_report(struct hid_device *h= dev, u8 *data, } =20 static int ft260_hid_output_report_check_status(struct ft260_device *dev, - u8 *data, int len, u8 bus_busy) + u8 *data, int len, u8 addr, + u8 bus_busy) { int ret, usec, try =3D 100; struct hid_device *hdev =3D dev->hdev; @@ -636,7 +664,7 @@ static int ft260_hid_output_report_check_status(struct = ft260_device *dev, if (ret =3D=3D 0) return 0; =20 - ft260_i2c_reset(hdev); + ft260_i2c_abort(hdev, addr); return -EIO; } =20 @@ -675,7 +703,8 @@ static int ft260_i2c_write(struct ft260_device *dev, u8= addr, u8 *data, rep->flag, data[0]); =20 ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, - wr_len + 4, bus_busy); + wr_len + 4, addr, + bus_busy); if (ret < 0) { ft260_dbg("%s: failed with %d\n", __func__, ret); return ret; @@ -716,7 +745,7 @@ static int ft260_smbus_write(struct ft260_device *dev, = u8 addr, u8 cmd, ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n", rep->report, addr, cmd, rep->length, len); =20 - ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len, + ret =3D ft260_hid_output_report_check_status(dev, (u8 *)rep, len, addr, (flag & FT260_FLAG_STOP) ? FT260_I2C_STATUS_BUS_BUSY : 0); if (ret < 0) @@ -784,7 +813,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 = addr, u8 *data, timeout_jiffies =3D msecs_to_jiffies(timeout); if (!wait_for_completion_timeout(&dev->wait, timeout_jiffies)) { ret =3D -ETIMEDOUT; - ft260_i2c_reset(hdev); + ft260_i2c_abort(hdev, addr); goto ft260_i2c_read_exit; } =20 @@ -798,7 +827,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 = addr, u8 *data, ret =3D ft260_xfer_status(dev, bus_busy); if (ret < 0) { ret =3D -EIO; - ft260_i2c_reset(hdev); + ft260_i2c_abort(hdev, addr); goto ft260_i2c_read_exit; } =20 @@ -982,7 +1011,7 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapte= r, u16 addr, u16 flags, hid_warn(hdev, "smbus block read: invalid count %u from slave 0x%02x\n", count, addr); - ft260_i2c_reset(hdev); + ft260_i2c_abort(hdev, addr); ret =3D -EPROTO; goto smbus_exit; } --=20 2.43.0