1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00

gpio: add support for FTDI's MPSSE as GPIO

FTDI FT2232H is a USB to GPIO chip. Sealevel produces some devices
with this chip. FT2232H presents itself as a composite device with two
interfaces (each is an "MPSSE"). Each MPSSE has two banks (high and low)
of 8 GPIO each. I believe some MPSSE's have only one bank, but I don't
know how to identify them (I don't have any for testing) and as a result
are unsupported for the time being.

Additionally, this driver provides software polling-based interrupts for
edge detection. For the Sealevel device I have to test with, this works
well because there is hardware debouncing. From talking to Sealevel's
people, this is their preferred way to do edge detection.

Signed-off-by: Mary Strodl <mstrodl@csh.rit.edu>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Link: https://lore.kernel.org/r/20241009131131.1618329-1-mstrodl@csh.rit.edu
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
This commit is contained in:
Mary Strodl 2024-10-09 09:11:31 -04:00 committed by Bartosz Golaszewski
parent b2e861bd1e
commit c46a74ff05
3 changed files with 530 additions and 0 deletions

View file

@ -1853,6 +1853,13 @@ config GPIO_VIPERBOARD
River Tech's viperboard.h for detailed meaning River Tech's viperboard.h for detailed meaning
of the module parameters. of the module parameters.
config GPIO_MPSSE
tristate "FTDI MPSSE GPIO support"
select GPIOLIB_IRQCHIP
help
GPIO driver for FTDI's MPSSE interface. These can do input and
output. Each MPSSE provides 16 IO pins.
endmenu endmenu
menu "Virtual GPIO drivers" menu "Virtual GPIO drivers"

View file

@ -115,6 +115,7 @@ obj-$(CONFIG_GPIO_MOCKUP) += gpio-mockup.o
obj-$(CONFIG_GPIO_MOXTET) += gpio-moxtet.o obj-$(CONFIG_GPIO_MOXTET) += gpio-moxtet.o
obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o
obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o
obj-$(CONFIG_GPIO_MPSSE) += gpio-mpsse.o
obj-$(CONFIG_GPIO_MSC313) += gpio-msc313.o obj-$(CONFIG_GPIO_MSC313) += gpio-msc313.o
obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o
obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o

522
drivers/gpio/gpio-mpsse.c Normal file
View file

@ -0,0 +1,522 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* FTDI MPSSE GPIO support
*
* Based on code by Anatolij Gustschin
*
* Copyright (C) 2024 Mary Strodl <mstrodl@csh.rit.edu>
*/
#include <linux/cleanup.h>
#include <linux/gpio/driver.h>
#include <linux/mutex.h>
#include <linux/usb.h>
struct mpsse_priv {
struct gpio_chip gpio;
struct usb_device *udev; /* USB device encompassing all MPSSEs */
struct usb_interface *intf; /* USB interface for this MPSSE */
u8 intf_id; /* USB interface number for this MPSSE */
struct work_struct irq_work; /* polling work thread */
struct mutex irq_mutex; /* lock over irq_data */
atomic_t irq_type[16]; /* pin -> edge detection type */
atomic_t irq_enabled;
int id;
u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */
u8 gpio_dir[2]; /* Directions for GPIOs [L, H] */
u8 *bulk_in_buf; /* Extra recv buffer to grab status bytes */
struct usb_endpoint_descriptor *bulk_in;
struct usb_endpoint_descriptor *bulk_out;
struct mutex io_mutex; /* sync I/O with disconnect */
};
struct bulk_desc {
bool tx; /* direction of bulk transfer */
u8 *data; /* input (tx) or output (rx) */
int len; /* Length of `data` if tx, or length of */
/* Data to read if rx */
int len_actual; /* Length successfully transferred */
int timeout;
};
static const struct usb_device_id gpio_mpsse_table[] = {
{ USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, gpio_mpsse_table);
static DEFINE_IDA(gpio_mpsse_ida);
/* MPSSE commands */
#define SET_BITS_CMD 0x80
#define GET_BITS_CMD 0x81
#define SET_BITMODE_REQUEST 0x0B
#define MODE_MPSSE (2 << 8)
#define MODE_RESET 0
/* Arbitrarily decided. This could probably be much less */
#define MPSSE_WRITE_TIMEOUT 5000
#define MPSSE_READ_TIMEOUT 5000
/* 1 millisecond, also pretty arbitrary */
#define MPSSE_POLL_INTERVAL 1000
static int mpsse_bulk_xfer(struct usb_interface *intf, struct bulk_desc *desc)
{
struct mpsse_priv *priv = usb_get_intfdata(intf);
struct usb_device *udev = priv->udev;
unsigned int pipe;
int ret;
if (desc->tx)
pipe = usb_sndbulkpipe(udev, priv->bulk_out->bEndpointAddress);
else
pipe = usb_rcvbulkpipe(udev, priv->bulk_in->bEndpointAddress);
ret = usb_bulk_msg(udev, pipe, desc->data, desc->len,
&desc->len_actual, desc->timeout);
if (ret)
dev_dbg(&udev->dev, "mpsse: bulk transfer failed: %d\n", ret);
return ret;
}
static int mpsse_write(struct usb_interface *intf,
u8 *buf, size_t len)
{
int ret;
struct bulk_desc desc;
desc.len_actual = 0;
desc.tx = true;
desc.data = buf;
desc.len = len;
desc.timeout = MPSSE_WRITE_TIMEOUT;
ret = mpsse_bulk_xfer(intf, &desc);
return ret;
}
static int mpsse_read(struct usb_interface *intf, u8 *buf, size_t len)
{
int ret;
struct bulk_desc desc;
struct mpsse_priv *priv = usb_get_intfdata(intf);
desc.len_actual = 0;
desc.tx = false;
desc.data = priv->bulk_in_buf;
/* Device sends 2 additional status bytes, read len + 2 */
desc.len = min_t(size_t, len + 2, usb_endpoint_maxp(priv->bulk_in));
desc.timeout = MPSSE_READ_TIMEOUT;
ret = mpsse_bulk_xfer(intf, &desc);
if (ret)
return ret;
/* Did we get enough data? */
if (desc.len_actual < desc.len)
return -EIO;
memcpy(buf, desc.data + 2, desc.len_actual - 2);
return ret;
}
static int gpio_mpsse_set_bank(struct mpsse_priv *priv, u8 bank)
{
int ret;
u8 tx_buf[3] = {
SET_BITS_CMD | (bank << 1),
priv->gpio_outputs[bank],
priv->gpio_dir[bank],
};
ret = mpsse_write(priv->intf, tx_buf, 3);
return ret;
}
static int gpio_mpsse_get_bank(struct mpsse_priv *priv, u8 bank)
{
int ret;
u8 buf = GET_BITS_CMD | (bank << 1);
ret = mpsse_write(priv->intf, &buf, 1);
if (ret)
return ret;
ret = mpsse_read(priv->intf, &buf, 1);
if (ret)
return ret;
return buf;
}
static void gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
unsigned long i, bank, bank_mask, bank_bits;
int ret;
struct mpsse_priv *priv = gpiochip_get_data(chip);
guard(mutex)(&priv->io_mutex);
for_each_set_clump8(i, bank_mask, mask, chip->ngpio) {
bank = i / 8;
if (bank_mask) {
bank_bits = bitmap_get_value8(bits, i);
/* Zero out pins we want to change */
priv->gpio_outputs[bank] &= ~bank_mask;
/* Set pins we care about */
priv->gpio_outputs[bank] |= bank_bits & bank_mask;
ret = gpio_mpsse_set_bank(priv, bank);
if (ret)
dev_err(&priv->intf->dev,
"Couldn't set values for bank %ld!",
bank);
}
}
}
static int gpio_mpsse_get_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
unsigned long i, bank, bank_mask;
int ret;
struct mpsse_priv *priv = gpiochip_get_data(chip);
guard(mutex)(&priv->io_mutex);
for_each_set_clump8(i, bank_mask, mask, chip->ngpio) {
bank = i / 8;
if (bank_mask) {
ret = gpio_mpsse_get_bank(priv, bank);
if (ret < 0)
return ret;
bitmap_set_value8(bits, ret & bank_mask, i);
}
}
return 0;
}
static int gpio_mpsse_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
int err;
unsigned long mask = 0, bits = 0;
__set_bit(offset, &mask);
err = gpio_mpsse_get_multiple(chip, &mask, &bits);
if (err)
return err;
/* == is not guaranteed to give 1 if true */
if (bits)
return 1;
else
return 0;
}
static void gpio_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
unsigned long mask = 0, bits = 0;
__set_bit(offset, &mask);
if (value)
__set_bit(offset, &bits);
gpio_mpsse_set_multiple(chip, &mask, &bits);
}
static int gpio_mpsse_direction_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct mpsse_priv *priv = gpiochip_get_data(chip);
int bank = (offset & 8) >> 3;
int bank_offset = offset & 7;
scoped_guard(mutex, &priv->io_mutex)
priv->gpio_dir[bank] |= BIT(bank_offset);
gpio_mpsse_gpio_set(chip, offset, value);
return 0;
}
static int gpio_mpsse_direction_input(struct gpio_chip *chip,
unsigned int offset)
{
struct mpsse_priv *priv = gpiochip_get_data(chip);
int bank = (offset & 8) >> 3;
int bank_offset = offset & 7;
guard(mutex)(&priv->io_mutex);
priv->gpio_dir[bank] &= ~BIT(bank_offset);
gpio_mpsse_set_bank(priv, bank);
return 0;
}
static int gpio_mpsse_get_direction(struct gpio_chip *chip,
unsigned int offset)
{
int ret;
int bank = (offset & 8) >> 3;
int bank_offset = offset & 7;
struct mpsse_priv *priv = gpiochip_get_data(chip);
guard(mutex)(&priv->io_mutex);
/* MPSSE directions are inverted */
if (priv->gpio_dir[bank] & BIT(bank_offset))
ret = GPIO_LINE_DIRECTION_OUT;
else
ret = GPIO_LINE_DIRECTION_IN;
return ret;
}
static void gpio_mpsse_poll(struct work_struct *work)
{
unsigned long pin_mask, pin_states, flags;
int irq_enabled, offset, err, value, fire_irq,
irq, old_value[16], irq_type[16];
struct mpsse_priv *priv = container_of(work, struct mpsse_priv,
irq_work);
for (offset = 0; offset < priv->gpio.ngpio; ++offset)
old_value[offset] = -1;
while ((irq_enabled = atomic_read(&priv->irq_enabled))) {
usleep_range(MPSSE_POLL_INTERVAL, MPSSE_POLL_INTERVAL + 1000);
/* Cleanup will trigger at the end of the loop */
guard(mutex)(&priv->irq_mutex);
pin_mask = 0;
pin_states = 0;
for (offset = 0; offset < priv->gpio.ngpio; ++offset) {
irq_type[offset] = atomic_read(&priv->irq_type[offset]);
if (irq_type[offset] != IRQ_TYPE_NONE &&
irq_enabled & BIT(offset))
pin_mask |= BIT(offset);
else
old_value[offset] = -1;
}
err = gpio_mpsse_get_multiple(&priv->gpio, &pin_mask,
&pin_states);
if (err) {
dev_err_ratelimited(&priv->intf->dev,
"Error polling!\n");
continue;
}
/* Check each value */
for (offset = 0; offset < priv->gpio.ngpio; ++offset) {
if (old_value[offset] == -1)
continue;
fire_irq = 0;
value = pin_states & BIT(offset);
switch (irq_type[offset]) {
case IRQ_TYPE_EDGE_RISING:
fire_irq = value > old_value[offset];
break;
case IRQ_TYPE_EDGE_FALLING:
fire_irq = value < old_value[offset];
break;
case IRQ_TYPE_EDGE_BOTH:
fire_irq = value != old_value[offset];
break;
}
if (!fire_irq)
continue;
irq = irq_find_mapping(priv->gpio.irq.domain,
offset);
local_irq_save(flags);
generic_handle_irq(irq);
local_irq_disable();
local_irq_restore(flags);
}
/* Sync back values so we can refer to them next tick */
for (offset = 0; offset < priv->gpio.ngpio; ++offset)
if (irq_type[offset] != IRQ_TYPE_NONE &&
irq_enabled & BIT(offset))
old_value[offset] = pin_states & BIT(offset);
}
}
static int gpio_mpsse_set_irq_type(struct irq_data *irqd, unsigned int type)
{
int offset;
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
offset = irqd->hwirq;
atomic_set(&priv->irq_type[offset], type & IRQ_TYPE_EDGE_BOTH);
return 0;
}
static void gpio_mpsse_irq_disable(struct irq_data *irqd)
{
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
atomic_and(~BIT(irqd->hwirq), &priv->irq_enabled);
gpiochip_disable_irq(&priv->gpio, irqd->hwirq);
}
static void gpio_mpsse_irq_enable(struct irq_data *irqd)
{
struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd);
gpiochip_enable_irq(&priv->gpio, irqd->hwirq);
/* If no-one else was using the IRQ, enable it */
if (!atomic_fetch_or(BIT(irqd->hwirq), &priv->irq_enabled)) {
INIT_WORK(&priv->irq_work, gpio_mpsse_poll);
schedule_work(&priv->irq_work);
}
}
static const struct irq_chip gpio_mpsse_irq_chip = {
.name = "gpio-mpsse-irq",
.irq_enable = gpio_mpsse_irq_enable,
.irq_disable = gpio_mpsse_irq_disable,
.irq_set_type = gpio_mpsse_set_irq_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static void gpio_mpsse_ida_remove(void *data)
{
struct mpsse_priv *priv = data;
ida_simple_remove(&gpio_mpsse_ida, priv->id);
}
static int gpio_mpsse_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct mpsse_priv *priv;
struct device *dev;
int err;
dev = &interface->dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->udev = usb_get_dev(interface_to_usbdev(interface));
priv->intf = interface;
priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber;
priv->id = ida_simple_get(&gpio_mpsse_ida, 0, 0, GFP_KERNEL);
if (priv->id < 0)
return priv->id;
err = devm_add_action_or_reset(dev, gpio_mpsse_ida_remove, priv);
if (err)
return err;
devm_mutex_init(dev, &priv->io_mutex);
devm_mutex_init(dev, &priv->irq_mutex);
priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL,
"gpio-mpsse.%d.%d",
priv->id, priv->intf_id);
if (!priv->gpio.label)
return -ENOMEM;
priv->gpio.owner = THIS_MODULE;
priv->gpio.parent = interface->usb_dev;
priv->gpio.get_direction = gpio_mpsse_get_direction;
priv->gpio.direction_input = gpio_mpsse_direction_input;
priv->gpio.direction_output = gpio_mpsse_direction_output;
priv->gpio.get = gpio_mpsse_gpio_get;
priv->gpio.set = gpio_mpsse_gpio_set;
priv->gpio.get_multiple = gpio_mpsse_get_multiple;
priv->gpio.set_multiple = gpio_mpsse_set_multiple;
priv->gpio.base = -1;
priv->gpio.ngpio = 16;
priv->gpio.offset = priv->intf_id * priv->gpio.ngpio;
priv->gpio.can_sleep = 1;
err = usb_find_common_endpoints(interface->cur_altsetting,
&priv->bulk_in, &priv->bulk_out,
NULL, NULL);
if (err)
return err;
priv->bulk_in_buf = devm_kmalloc(dev, usb_endpoint_maxp(priv->bulk_in),
GFP_KERNEL);
if (!priv->bulk_in_buf)
return -ENOMEM;
usb_set_intfdata(interface, priv);
/* Reset mode, needed to correctly enter MPSSE mode */
err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0),
SET_BITMODE_REQUEST,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
MODE_RESET, priv->intf_id + 1, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (err)
return err;
/* Enter MPSSE mode */
err = usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0),
SET_BITMODE_REQUEST,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
MODE_MPSSE, priv->intf_id + 1, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (err)
return err;
gpio_irq_chip_set_chip(&priv->gpio.irq, &gpio_mpsse_irq_chip);
priv->gpio.irq.parent_handler = NULL;
priv->gpio.irq.num_parents = 0;
priv->gpio.irq.parents = NULL;
priv->gpio.irq.default_type = IRQ_TYPE_NONE;
priv->gpio.irq.handler = handle_simple_irq;
err = devm_gpiochip_add_data(dev, &priv->gpio, priv);
if (err)
return err;
return 0;
}
static void gpio_mpsse_disconnect(struct usb_interface *intf)
{
struct mpsse_priv *priv = usb_get_intfdata(intf);
priv->intf = NULL;
usb_set_intfdata(intf, NULL);
usb_put_dev(priv->udev);
}
static struct usb_driver gpio_mpsse_driver = {
.name = "gpio-mpsse",
.probe = gpio_mpsse_probe,
.disconnect = gpio_mpsse_disconnect,
.id_table = gpio_mpsse_table,
};
module_usb_driver(gpio_mpsse_driver);
MODULE_AUTHOR("Mary Strodl <mstrodl@csh.rit.edu>");
MODULE_DESCRIPTION("MPSSE GPIO driver");
MODULE_LICENSE("GPL");