From: Michael Zaidman <michael.zaidman@gmail.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: Linus Walleij <linusw@kernel.org>,
Bartosz Golaszewski <brgl@kernel.org>,
Germain Hebert <germain.hebert@ca.abb.com>, Rio Liu <rio@r26.me>,
Bruno Giacomazzi <brunoceg1@gmail.com>,
Christina Quast <contact@christina-quast.de>,
linux-input@vger.kernel.org, linux-gpio@vger.kernel.org,
linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,
Michael Zaidman <michael.zaidman@gmail.com>
Subject: [PATCH 08/13] HID: ft260: uart: add modem pins control via ioctl
Date: Sun, 23 Aug 2026 00:39:36 +0300 [thread overview]
Message-ID: <20260822213941.98882-9-michael.zaidman@gmail.com> (raw)
In-Reply-To: <20260822213941.98882-1-michael.zaidman@gmail.com>
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 <michael.zaidman@gmail.com>
---
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 <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/kfifo.h>
+#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/minmax.h>
#include <linux/unaligned.h>
@@ -182,7 +183,7 @@ enum {
FT260_GPIO_DIR_OUTPUT = 0x01,
};
-/* GPIO offsets */
+/* GPIO bit masks */
enum {
FT260_GPIO_0 = (1 << 0),
FT260_GPIO_1 = (1 << 1),
@@ -215,7 +216,8 @@ enum {
FT260_GPIO_UART_MODE_1_SET = (FT260_GPIO_UART_DTR_DSR),
FT260_GPIO_UART_MODE_2_SET = (FT260_GPIO_UART_RTS_CTS),
FT260_GPIO_UART_MODE_3_SET = (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 = (FT260_GPIO_UART_MODE_3_SET),
FT260_GPIO_UART_DEFAULT = (FT260_GPIO_UART_MODE_0_SET),
FT260_GPIO_UART_MODE_1_CLR = (FT260_GPIO_UART_RX_TX |
@@ -226,6 +228,16 @@ enum {
FT260_GPIO_UART_MODES = (5),
};
+/* UART modem control GPIO offsets */
+enum {
+ FT260_GPIO_UART_DCD = (4),
+ FT260_GPIO_UART_RI = (5),
+ FT260_GPIO_UART_RTS = (7),
+ FT260_GPIO_UART_CTS = (10),
+ FT260_GPIO_UART_DTR = (11),
+ FT260_GPIO_UART_DSR = (13),
+};
+
#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | (report_id))
/* 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 |= 1 << offset;
+ if (value)
+ gpio->vals |= !!value << offset;
+ else
+ gpio->vals &= ~(1 << offset);
+ } else {
+ offset = offset - FT260_GPIO_MAX;
+ gpio->ex_dirs |= 1 << offset;
+ if (value)
+ gpio->ex_vals |= !!value << offset;
+ else
+ gpio->ex_vals &= ~(1 << offset);
+ }
}
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 offset, int value)
rep.gpio.ex_vals &= ~(1 << offset);
}
- 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);
@@ -1283,8 +1316,6 @@ static int ft260_gpio_init(struct ft260_device *dev,
char prefix[] = "ft260_";
u8 mode = cfg->chip_mode;
- hid_info(hdev, "initialize gpio chip\n");
-
dev->gpio_uart_mode[0] = (u16)FT260_GPIO_UART_MODE_0_SET;
dev->gpio_uart_mode[1] = (u16)FT260_GPIO_UART_MODE_1_SET;
dev->gpio_uart_mode[2] = (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);
dev->gc->label = label;
dev->gc->direction_input = ft260_gpio_direction_input;
@@ -1388,6 +1418,7 @@ static int ft260_get_interface_type(struct ft260_device *dev,
ft260_dbg("wakeup_int: 0x%02x\n", cfg->enable_wakeup_int);
dev->power_saving_en = cfg->power_saving_en;
+ dev->chip_mode = cfg->chip_mode;
switch (cfg->chip_mode) {
case FT260_MODE_ALL:
@@ -1405,7 +1436,6 @@ static int ft260_get_interface_type(struct ft260_device *dev,
break;
}
- dev->iface_type = ret;
return ret;
}
@@ -1912,6 +1942,78 @@ static void ft260_uart_set_termios(struct tty_struct *tty,
ft260_uart_change_speed(port, &tty->termios, NULL);
}
+static int ft260_uart_tiocmget(struct tty_struct *tty)
+{
+ struct ft260_device *port = 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 = FT260_GPIO_UART_MODE_4_SET;
+
+ if ((port->gpio_en & mask) != mask) {
+ ft260_dbg("Illegal gpio config %#02x != mask %#04x\n",
+ port->gpio_en, mask);
+ return 0;
+ }
+
+ gpios = 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 = tty->driver_data;
+ struct ft260_gpio_write_request_report rep;
+ struct hid_device *hdev = port->hdev;
+ int mask = FT260_GPIO_UART_MODE_4_SET;
+
+ if ((port->gpio_en & mask) != mask) {
+ ft260_dbg("Illegal gpio config %#02x != mask %#04x\n",
+ port->gpio_en, mask);
+ return 0;
+ }
+ rep.report = FT260_GPIO;
+ rep.gpio = 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 = 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 = rep.gpio;
+ mutex_unlock(&port->lock);
+
+ return 0;
+}
+
static int ft260_uart_install(struct tty_driver *driver, struct tty_struct *tty)
{
int idx = tty->index;
@@ -1988,6 +2090,8 @@ static const struct tty_operations ft260_uart_ops = {
.write_room = ft260_uart_write_room,
.chars_in_buffer = ft260_uart_chars_in_buffer,
.set_termios = ft260_uart_set_termios,
+ .tiocmget = ft260_uart_tiocmget,
+ .tiocmset = ft260_uart_tiocmset,
.hangup = ft260_uart_hangup,
.install = ft260_uart_install,
.cleanup = ft260_uart_cleanup,
@@ -2131,14 +2235,16 @@ static int ft260_i2c_probe(struct ft260_device *dev,
return ret;
}
- ret = ft260_gpio_init(dev, cfg);
- if (ret)
- goto err_i2c_free;
+ if (cfg->chip_mode == FT260_MODE_I2C) {
+ ret = ft260_gpio_init(dev, cfg);
+ if (ret)
+ goto err_i2c_free;
- ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
- if (ret < 0) {
- hid_err(hdev, "failed to create sysfs attrs\n");
- goto err_i2c_free;
+ ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
+ if (ret < 0) {
+ hid_err(hdev, "failed to create sysfs attrs\n");
+ goto err_i2c_free;
+ }
}
return 0;
@@ -2199,7 +2305,8 @@ static int ft260_uart_probe(struct ft260_device *dev,
cfg->uart_mode = FT260_UART_CFG_FLOW_CTRL_NONE;
- if (dev->iface_id == 0) {
+ if (dev->chip_mode & FT260_MODE_UART ||
+ dev->chip_mode == FT260_MODE_ALL) {
ret = ft260_gpio_init(dev, cfg);
if (ret)
goto err_hid_report;
@@ -2274,11 +2381,11 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
spin_lock_init(&dev->read_lock);
init_completion(&dev->wait);
- ret = ft260_get_interface_type(dev, &cfg);
- if (ret <= FT260_IFACE_NONE)
+ dev->iface_type = ft260_get_interface_type(dev, &cfg);
+ if (dev->iface_type <= FT260_IFACE_NONE)
goto err_hid_close;
- if (ret == FT260_IFACE_I2C)
+ if (dev->iface_type == FT260_IFACE_I2C)
ret = ft260_i2c_probe(dev, &cfg);
else
ret = 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 == 0)
+ if (dev->chip_mode & FT260_MODE_UART ||
+ dev->chip_mode == FT260_MODE_ALL)
sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
} else {
- sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
+ if (dev->chip_mode == FT260_MODE_I2C)
+ sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
i2c_del_adapter(&dev->adap);
kfree(dev);
}
--
2.43.0
next prev parent reply other threads:[~2026-08-22 21:40 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-22 21:39 [PATCH 00/13] HID: ft260: add UART and GPIO support, plus I2C fixes Michael Zaidman
2026-08-22 21:39 ` [PATCH 01/13] HID: ft260: add serial driver Michael Zaidman
2026-08-22 22:00 ` sashiko-bot
2026-08-25 7:49 ` Linus Walleij
2026-08-25 8:12 ` Linus Walleij
2026-08-27 19:16 ` Michael Zaidman
2026-08-22 21:39 ` [PATCH 02/13] HID: ft260: uart: bring-up fixes Michael Zaidman
2026-08-22 21:56 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 03/13] HID: ft260: add GPIO support on top of UART Michael Zaidman
2026-08-22 21:56 ` sashiko-bot
2026-08-25 7:44 ` Linus Walleij
2026-08-27 20:39 ` Michael Zaidman
2026-08-22 21:39 ` [PATCH 04/13] HID: ft260: i2c: reduce driver module loading time Michael Zaidman
2026-08-22 21:51 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 05/13] HID: ft260: i2c: silence sysfs store big-numbers Michael Zaidman
2026-08-22 21:51 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 06/13] HID: ft260: i2c: reduce bus-error message severity Michael Zaidman
2026-08-22 21:52 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 07/13] HID: ft260: uart: enable flow control Michael Zaidman
2026-08-22 21:52 ` sashiko-bot
2026-08-22 21:39 ` Michael Zaidman [this message]
2026-08-22 21:54 ` [PATCH 08/13] HID: ft260: uart: add modem pins control via ioctl sashiko-bot
2026-08-25 8:08 ` Linus Walleij
2026-08-27 22:08 ` Michael Zaidman
2026-08-22 21:39 ` [PATCH 09/13] HID: ft260: gpio: group sysfs attrs per HID interface Michael Zaidman
2026-08-22 21:54 ` sashiko-bot
2026-08-25 8:13 ` Linus Walleij
2026-08-27 20:50 ` Michael Zaidman
2026-08-22 21:39 ` [PATCH 10/13] HID: ft260: uart: fix active-low RTS/CTS/DTR/DSR polarity Michael Zaidman
2026-08-22 22:03 ` sashiko-bot
2026-08-25 8:16 ` Linus Walleij
2026-08-27 21:08 ` Michael Zaidman
2026-08-22 21:39 ` [PATCH 11/13] HID: ft260: i2c: fix large write transaction failure Michael Zaidman
2026-08-22 22:02 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 12/13] HID: ft260: workaround for TN_189 errata endpoint STALL after enumeration Michael Zaidman
2026-08-22 22:03 ` sashiko-bot
2026-08-22 21:39 ` [PATCH 13/13] HID: ft260: i2c: abort in-flight transfers with STOP before reset Michael Zaidman
2026-08-22 22:12 ` sashiko-bot
2026-08-25 8:21 ` [PATCH 00/13] HID: ft260: add UART and GPIO support, plus I2C fixes Linus Walleij
2026-08-27 13:27 ` Lee Jones
2026-08-27 18:53 ` Michael Zaidman
2026-08-27 20:51 ` Lee Jones
2026-08-27 22:25 ` Michael Zaidman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260822213941.98882-9-michael.zaidman@gmail.com \
--to=michael.zaidman@gmail.com \
--cc=bentiss@kernel.org \
--cc=brgl@kernel.org \
--cc=brunoceg1@gmail.com \
--cc=contact@christina-quast.de \
--cc=germain.hebert@ca.abb.com \
--cc=jikos@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rio@r26.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox