[PATCH 03/13] HID: ft260: add GPIO support on top of UART

From: Michael Zaidman

Date: Sat Aug 22 2026 - 17:43:00 EST


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@xxxxxxxxx/

The gpio_chip.set callback returns int to match the current gpiolib
API, based on the adaptation originally contributed by Rio Liu
<rio@xxxxxx>.

Signed-off-by: Michael Zaidman <michael.zaidman@xxxxxxxxx>
---
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.

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.

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 <linux/tty_flip.h>
#include <linux/minmax.h>
#include <linux/unaligned.h>
+#include <linux/gpio/driver.h>

#ifdef DEBUG
static int ft260_debug = 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))

/*
* Device interface configuration.
@@ -151,7 +157,69 @@ enum {
FT260_IFACE_UART
};

-#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)
+/* Multi-function pin functions */
+enum {
+ FT260_MFPIN_GPIO = 0x00,
+ FT260_MFPIN_SUSPOUT = 0x01,
+ FT260_MFPIN_PWREN = 0x02,
+ FT260_MFPIN_TX_ACTIVE = 0x03,
+ FT260_MFPIN_TX_LED = 0x04,
+ FT260_MFPIN_RX_LED = 0x05,
+ FT260_MFPIN_BCD_DET = 0x06,
+};
+
+enum {
+ FT260_GPIO_VALUE = 0x00,
+ FT260_GPIO_DIRECTION = 0x01,
+ FT260_GPIO_DIR_INPUT = 0x00,
+ FT260_GPIO_DIR_OUTPUT = 0x01,
+};
+
+/* GPIO offsets */
+enum {
+ FT260_GPIO_0 = (1 << 0),
+ FT260_GPIO_1 = (1 << 1),
+ FT260_GPIO_2 = (1 << 2),
+ FT260_GPIO_3 = (1 << 3),
+ FT260_GPIO_4 = (1 << 4),
+ FT260_GPIO_5 = (1 << 5),
+ FT260_GPIO_A = (1 << (FT260_GPIO_MAX + 0)),
+ FT260_GPIO_B = (1 << (FT260_GPIO_MAX + 1)),
+ FT260_GPIO_C = (1 << (FT260_GPIO_MAX + 2)),
+ FT260_GPIO_D = (1 << (FT260_GPIO_MAX + 3)),
+ FT260_GPIO_E = (1 << (FT260_GPIO_MAX + 4)),
+ FT260_GPIO_F = (1 << (FT260_GPIO_MAX + 5)),
+ FT260_GPIO_G = (1 << (FT260_GPIO_MAX + 6)),
+ FT260_GPIO_H = (1 << (FT260_GPIO_MAX + 7)),
+};
+
+/* GPIO groups */
+enum {
+ FT260_GPIO_WAKEUP = (FT260_GPIO_3),
+ FT260_GPIO_I2C_DEFAULT = (FT260_GPIO_0 | FT260_GPIO_1),
+ FT260_GPIO_UART_RX_TX = (FT260_GPIO_C | FT260_GPIO_D),
+ FT260_GPIO_UART_DCD_RI = (FT260_GPIO_4 | FT260_GPIO_5),
+ FT260_GPIO_UART_RTS_CTS = (FT260_GPIO_B | FT260_GPIO_E),
+ FT260_GPIO_UART_DTR_DSR = (FT260_GPIO_F | FT260_GPIO_H),
+ FT260_GPIO_UART_MODE_0_SET = (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 = (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_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 |
+ FT260_GPIO_UART_RTS_CTS),
+ FT260_GPIO_UART_MODE_2_CLR = (FT260_GPIO_UART_RX_TX |
+ FT260_GPIO_UART_DTR_DSR),
+ FT260_GPIO_UART_MODE_3_CLR = (FT260_GPIO_UART_RX_TX),
+ FT260_GPIO_UART_MODES = (5),
+};
+
+#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | (report_id))

/* Feature In reports */

@@ -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;

+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 */

struct ft260_set_system_clock_report {
@@ -223,6 +303,12 @@ struct ft260_set_uart_mode_report {
/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

+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;

+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 */

struct ft260_i2c_write_request_report {
@@ -280,32 +393,32 @@ struct ft260_configure_uart_request_report {

/* UART interface configuration */
enum {
- FT260_CFG_FLOW_CTRL_OFF = 0x00,
- FT260_CFG_FLOW_CTRL_RTS_CTS = 0x01,
- FT260_CFG_FLOW_CTRL_DTR_DSR = 0x02,
- FT260_CFG_FLOW_CTRL_XON_XOFF = 0x03,
- FT260_CFG_FLOW_CTRL_NONE = 0x04,
+ FT260_UART_CFG_FLOW_CTRL_OFF = 0x00,
+ FT260_UART_CFG_FLOW_CTRL_RTS_CTS = 0x01,
+ FT260_UART_CFG_FLOW_CTRL_DTR_DSR = 0x02,
+ FT260_UART_CFG_FLOW_CTRL_XON_XOFF = 0x03,
+ FT260_UART_CFG_FLOW_CTRL_NONE = 0x04,

- FT260_CFG_DATA_BITS_7 = 0x07,
- FT260_CFG_DATA_BITS_8 = 0x08,
+ FT260_UART_CFG_DATA_BITS_7 = 0x07,
+ FT260_UART_CFG_DATA_BITS_8 = 0x08,

- FT260_CFG_PAR_NO = 0x00,
- FT260_CFG_PAR_ODD = 0x01,
- FT260_CFG_PAR_EVEN = 0x02,
- FT260_CFG_PAR_HIGH = 0x03,
- FT260_CFG_PAR_LOW = 0x04,
+ FT260_UART_CFG_PAR_NO = 0x00,
+ FT260_UART_CFG_PAR_ODD = 0x01,
+ FT260_UART_CFG_PAR_EVEN = 0x02,
+ FT260_UART_CFG_PAR_HIGH = 0x03,
+ FT260_UART_CFG_PAR_LOW = 0x04,

- FT260_CFG_STOP_ONE_BIT = 0x00,
- FT260_CFG_STOP_TWO_BIT = 0x02,
+ FT260_UART_CFG_STOP_ONE_BIT = 0x00,
+ FT260_UART_CFG_STOP_TWO_BIT = 0x02,

- FT260_CFG_BREAKING_NO = 0x00,
- FT260_CFG_BEAKING_YES = 0x01,
+ FT260_UART_CFG_BREAKING_NO = 0x00,
+ FT260_UART_CFG_BEAKING_YES = 0x01,

- FT260_CFG_BAUD_MIN = 1200,
- FT260_CFG_BAUD_MAX = 12000000,
+ FT260_UART_CFG_BAUD_MIN = 1200,
+ FT260_UART_CFG_BAUD_MAX = 12000000,
};

-#define FT260_UART_EN_PW_SAVE_BAUD (4800)
+#define FT260_UART_EN_PW_SAVE_BAUD (4800)

#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];
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
@@ -375,8 +493,6 @@ static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
if (!buf)
return -ENOMEM;

- buf[0] = FT260_SYSTEM_SETTINGS;
-
ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
HID_REQ_SET_REPORT);

@@ -389,6 +505,7 @@ static int ft260_i2c_reset(struct hid_device *hdev)
struct ft260_set_i2c_reset_report report;
int ret;

+ report.report = FT260_SYSTEM_SETTINGS;
report.request = FT260_SET_I2C_RESET;

ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
@@ -906,6 +1023,322 @@ static const struct i2c_algorithm ft260_i2c_algo = {
.functionality = ft260_functionality,
};

+static void ft260_gpio_en_set(struct ft260_device *dev, u16 bitmap)
+{
+ dev->gpio_en |= bitmap & FT260_GPIO_MASK;
+}
+
+static void ft260_gpio_en_clr(struct ft260_device *dev, u16 bitmap)
+{
+ dev->gpio_en &= ~bitmap & FT260_GPIO_MASK;
+}
+
+static void ft260_gpio_en_update(struct hid_device *hdev, u8 req, u8 value)
+{
+ u16 bitmap;
+ struct ft260_device *dev = hid_get_drvdata(hdev);
+
+ switch (req) {
+
+ case FT260_SET_I2C_MODE:
+ bitmap = FT260_GPIO_I2C_DEFAULT;
+ break;
+ case FT260_SET_UART_MODE:
+ switch (value) {
+ case FT260_UART_CFG_FLOW_CTRL_OFF:
+ bitmap = (u16)FT260_GPIO_MASK;
+ break;
+ case FT260_UART_CFG_FLOW_CTRL_RTS_CTS:
+ bitmap = FT260_GPIO_UART_MODE_1_CLR;
+ break;
+ case FT260_UART_CFG_FLOW_CTRL_DTR_DSR:
+ bitmap = FT260_GPIO_UART_MODE_2_CLR;
+ break;
+ case FT260_UART_CFG_FLOW_CTRL_XON_XOFF:
+ case FT260_UART_CFG_FLOW_CTRL_NONE:
+ bitmap = FT260_GPIO_UART_MODE_3_CLR;
+ break;
+ default:
+ return;
+ }
+ ft260_gpio_en_clr(dev, bitmap);
+ bitmap = dev->gpio_uart_mode[value];
+ ft260_gpio_en_set(dev, bitmap);
+ goto exit;
+
+ case FT260_ENABLE_UART_DCD_RI:
+ bitmap = FT260_GPIO_UART_DCD_RI;
+ break;
+
+ case FT260_SELECT_GPIO2_FUNC:
+ bitmap = FT260_GPIO_2;
+ break;
+ case FT260_SELECT_GPIOA_FUNC:
+ bitmap = FT260_GPIO_A;
+ break;
+ case FT260_SELECT_GPIOG_FUNC:
+ bitmap = FT260_GPIO_G;
+ break;
+ default:
+ return;
+ }
+
+ if (value == 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 = 0;
+ struct ft260_gpio_write_request_report rep;
+ struct ft260_device *dev = gpiochip_get_data(gc);
+ struct hid_device *hdev = dev->hdev;
+
+ if (offset >= 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 = -EINVAL;
+ goto exit;
+ }
+
+ rep.report = FT260_GPIO;
+ rep.gpio = dev->gpio;
+
+ if (offset < FT260_GPIO_MAX) {
+ if (value)
+ rep.gpio.vals |= !!value << offset;
+ else
+ rep.gpio.vals &= ~(1 << offset);
+ } else {
+ offset = offset - FT260_GPIO_MAX;
+ if (value)
+ rep.gpio.ex_vals |= !!value << offset;
+ else
+ rep.gpio.ex_vals &= ~(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 = 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 = rep.gpio;
+ ret = 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 = gpiochip_get_data(gc);
+ struct hid_device *hdev = dev->hdev;
+
+ if (offset >= 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 = -EIO;
+ goto exit;
+ }
+
+ ret = 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 = (struct ft260_gpio_write_request_report *)&buf;
+
+ if (direction == FT260_GPIO_DIR_OUTPUT)
+ if (offset < FT260_GPIO_MAX)
+ rep->gpio.dirs |= 1 << offset;
+ else
+ rep->gpio.ex_dirs |= 1 << (offset - FT260_GPIO_MAX);
+ else
+ if (offset < FT260_GPIO_MAX)
+ rep->gpio.dirs &= ~(1 << offset);
+ else
+ rep->gpio.ex_dirs &= ~(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 = 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 = rep->gpio;
+ mutex_unlock(&dev->lock);
+
+ if (direction == 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 = gpiochip_get_data(gc);
+ struct hid_device *hdev = dev->hdev;
+
+ ret = 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 == FT260_GPIO_VALUE)
+ ret = (rep.gpio.ex_vals << FT260_GPIO_MAX) | rep.gpio.vals;
+ else
+ ret = (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 = 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 = 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 = dev->hdev;
+ 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;
+ dev->gpio_uart_mode[3] = (u16)FT260_GPIO_UART_MODE_3_SET;
+ dev->gpio_uart_mode[4] = (u16)FT260_GPIO_UART_MODE_4_SET;
+
+ if (mode == FT260_MODE_ALL)
+ mode = FT260_MODE_BOTH;
+
+ if (mode & FT260_MODE_UART)
+ dev->gpio_en |= dev->gpio_uart_mode[cfg->uart_mode];
+ else
+ dev->gpio_en |= FT260_GPIO_UART_DEFAULT;
+
+ if (!(mode & FT260_MODE_I2C))
+ dev->gpio_en |= FT260_GPIO_I2C_DEFAULT;
+
+ if (cfg->gpio2_func == FT260_MFPIN_GPIO)
+ dev->gpio_en |= FT260_GPIO_2;
+ if (cfg->enable_wakeup_int == FT260_MFPIN_GPIO)
+ dev->gpio_en |= FT260_GPIO_3;
+ if (cfg->gpioa_func == FT260_MFPIN_GPIO)
+ dev->gpio_en |= FT260_GPIO_A;
+ if (cfg->gpiog_func == FT260_MFPIN_GPIO)
+ dev->gpio_en |= FT260_GPIO_G;
+
+ hid_info(hdev, "enabled GPIOs: %04x\n", dev->gpio_en);
+
+ dev->gc = devm_kzalloc(&hdev->dev, sizeof(*dev->gc), GFP_KERNEL);
+ if (!dev->gc)
+ return -ENOMEM;
+
+ label_sz = strlen(dev_name(&hdev->dev)) + strlen(prefix) + 1;
+ label = devm_kzalloc(&hdev->dev, label_sz, GFP_KERNEL);
+ if (!label) {
+ ret = -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 = label;
+ dev->gc->direction_input = ft260_gpio_direction_input;
+ dev->gc->direction_output = ft260_gpio_direction_output;
+ dev->gc->get_direction = ft260_gpio_get_direction;
+ dev->gc->set = ft260_gpio_set;
+ dev->gc->get = ft260_gpio_get;
+ dev->gc->base = -1;
+ dev->gc->ngpio = FT260_GPIO_TOTAL;
+ dev->gc->can_sleep = true;
+ dev->gc->parent = &hdev->dev;
+
+ /* Wakeup chip */
+ (void)ft260_hid_feature_report_get(dev->hdev, FT260_CHIP_VERSION,
+ (u8 *)&ver, sizeof(ver));
+
+ ret = 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;
}

-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 = to_usb_interface(hdev->dev.parent);
- int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
int ret;
+ struct hid_device *hdev = dev->hdev;
+ struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);

- ret = ft260_get_system_config(hdev, &cfg);
+ dev->iface_id = usbif->cur_altsetting->desc.bInterfaceNumber;
+
+ ret = ft260_get_system_config(hdev, cfg);
if (ret < 0)
return ret;

- 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);

- dev->power_saving_en = cfg.power_saving_en;
+ dev->power_saving_en = cfg->power_saving_en;

- switch (cfg.chip_mode) {
+ switch (cfg->chip_mode) {
case FT260_MODE_ALL:
case FT260_MODE_BOTH:
- if (interface == 1)
+ if (dev->iface_id == 1)
ret = FT260_IFACE_UART;
else
ret = FT260_IFACE_I2C;
@@ -984,6 +1424,10 @@ static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

+static void ft260_attr_dummy_func(struct hid_device *hdev, u8 req, u16 value)
+{
+}
+
#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)

-#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 = to_hid_device(kdev); \
+ struct ft260_device *dev = hid_get_drvdata(hdev); \
type name; \
int ret; \
\
- if (!func(buf, 10, (ctype *)&name)) { \
+ if (!strtou(buf, 10, (ctype *)&name)) { \
rep.name = name; \
rep.report = id; \
rep.request = req; \
+ mutex_lock(&dev->lock); \
ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep, \
sizeof(rep)); \
- if (!ret) \
- ret = count; \
+ if (ret < 0) \
+ hid_err(hdev, "%s: failed!\n", __func__); \
+ else \
+ func(hdev, req, name); \
+ mutex_unlock(&dev->lock); \
} else { \
ret = -EINVAL; \
} \
return ret; \
}

-#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)

-#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)

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);

+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);

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);

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);

+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);

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);

static ssize_t i2c_reset_store(struct device *kdev,
@@ -1092,7 +1560,11 @@ static const struct attribute_group ft260_attr_group = {
&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_device *port,

switch (termios->c_cflag & CSIZE) {
case CS7:
- req.data_bit = FT260_CFG_DATA_BITS_7;
+ req.data_bit = 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 = FT260_CFG_DATA_BITS_8;
+ req.data_bit = FT260_UART_CFG_DATA_BITS_8;
termios->c_cflag &= ~CSIZE;
termios->c_cflag |= CS8;
break;
default:
case CS8:
- req.data_bit = FT260_CFG_DATA_BITS_8;
+ req.data_bit = FT260_UART_CFG_DATA_BITS_8;
break;
}

req.stop_bit = (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;

if (termios->c_cflag & PARENB) {
req.parity = (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 = FT260_CFG_PAR_NO;
+ req.parity = FT260_UART_CFG_PAR_NO;
}

baud = tty_termios_baud_rate(termios);
- if (baud == 0 || baud < FT260_CFG_BAUD_MIN || baud > FT260_CFG_BAUD_MAX) {
+ if (baud == 0 || baud < FT260_UART_CFG_BAUD_MIN || baud > FT260_UART_CFG_BAUD_MAX) {
struct tty_struct *tty = tty_port_tty_get(&port->port);

hid_err(hdev, "invalid baud rate %d\n", baud);
@@ -1385,9 +1857,9 @@ static int ft260_uart_change_speed(struct ft260_device *port,
put_unaligned_le32(cpu_to_le32(baud), &req.baudrate);

if (termios->c_cflag & CRTSCTS)
- req.flow_ctrl = FT260_CFG_FLOW_CTRL_RTS_CTS;
+ req.flow_ctrl = FT260_UART_CFG_FLOW_CTRL_RTS_CTS;
else
- req.flow_ctrl = FT260_CFG_FLOW_CTRL_OFF;
+ req.flow_ctrl = FT260_UART_CFG_FLOW_CTRL_OFF;

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_device *port,
req.data_bit, req.parity,
req.stop_bit, req.breaking);

- req.flow_ctrl = FT260_CFG_FLOW_CTRL_NONE;
- req.breaking = FT260_CFG_BREAKING_NO;
+ req.flow_ctrl = FT260_UART_CFG_FLOW_CTRL_NONE;
+ req.breaking = FT260_UART_CFG_BREAKING_NO;
+
+ mutex_lock(&port->lock);

ret = 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);

return ret;
}
@@ -1611,9 +2089,11 @@ static const struct tty_port_operations ft260_uart_port_ops = {

static struct tty_driver *ft260_tty_driver;

-static int ft260_i2c_probe(struct hid_device *hdev, struct ft260_device *dev)
+static int ft260_i2c_probe(struct ft260_device *dev,
+ struct ft260_get_system_status_report *cfg)
{
int ret;
+ struct hid_device *hdev = dev->hdev;

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;
}

+ 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;
}
+
return 0;

err_i2c_free:
@@ -1652,14 +2137,15 @@ static int ft260_i2c_probe(struct hid_device *hdev, struct ft260_device *dev)
return ret;
}

-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 = dev->hdev;
struct device *devt;
+ int ret;

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 = FT260_SYSTEM_SETTINGS;
req.request = FT260_SET_UART_CONFIG;
- req.flow_ctrl = FT260_CFG_FLOW_CTRL_NONE;
+ req.flow_ctrl = FT260_UART_CFG_FLOW_CTRL_NONE;
put_unaligned_le32(cpu_to_le32(9600), &req.baudrate);
- req.data_bit = FT260_CFG_DATA_BITS_8;
- req.parity = FT260_CFG_PAR_NO;
- req.stop_bit = FT260_CFG_STOP_ONE_BIT;
- req.breaking = FT260_CFG_BREAKING_NO;
+ req.data_bit = FT260_UART_CFG_DATA_BITS_8;
+ req.parity = FT260_UART_CFG_PAR_NO;
+ req.stop_bit = FT260_UART_CFG_STOP_ONE_BIT;
+ req.breaking = FT260_UART_CFG_BREAKING_NO;

ret = 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;
}

+ cfg->uart_mode = FT260_UART_CFG_FLOW_CTRL_NONE;
+
+ if (dev->iface_id == 0) {
+ ret = ft260_gpio_init(dev, cfg);
+ if (ret)
+ goto err_hid_report;
+
+ ret = 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;

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;

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 = hdev;

ret = hid_parse(hdev);
if (ret) {
@@ -1757,21 +2259,18 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
version.chip_code[0], version.chip_code[1],
version.chip_code[2], version.chip_code[3]);

- ret = ft260_get_interface_type(hdev, dev);
- if (ret <= FT260_IFACE_NONE)
- goto err_hid_close;
-
- hid_set_drvdata(hdev, dev);
- dev->hdev = hdev;
-
mutex_init(&dev->lock);
spin_lock_init(&dev->read_lock);
init_completion(&dev->wait);

+ ret = ft260_get_interface_type(dev, &cfg);
+ if (ret <= FT260_IFACE_NONE)
+ goto err_hid_close;
+
if (ret == FT260_IFACE_I2C)
- ret = ft260_i2c_probe(hdev, dev);
+ ret = ft260_i2c_probe(dev, &cfg);
else
- ret = ft260_uart_probe(hdev, dev);
+ ret = ft260_uart_probe(dev, &cfg);
if (ret)
goto err_hid_close;

@@ -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 == 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);
--
2.43.0