From patchwork Sat Aug 4 09:52:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Loic Poulain X-Patchwork-Id: 143444 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp1112092ljj; Sat, 4 Aug 2018 02:52:10 -0700 (PDT) X-Google-Smtp-Source: AAOMgpdRPuYoHOk3/Gmrqs25e1bn63m3GZft8f3qGGA9CMMSy89POOu0jA/qUdyLybX6UKtS3U/o X-Received: by 2002:a65:60cf:: with SMTP id r15-v6mr7056198pgv.41.1533376330227; Sat, 04 Aug 2018 02:52:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1533376330; cv=none; d=google.com; s=arc-20160816; b=W4wn7KevItCgDRuBYoS4PQZB03Aj3pk6rhqhlitAEdKHM18scnXMzmyCg2gzX4+d7/ 67QDlDDOdZ+pIqC9oCRbGtTJv+3WnvYHJM0fZQ+TmPMXgiUETDtNW9Si+oKdpIZcGxXz zZlt+izqiroWA9n2zOZJNrfgcoXnYi8K8NMbSzFRmlJqZP6OfayPtw0+kUSXiaTjjBbf ZC28V2fwIZqxRf37nb+3qdWzNf2/aJO6qWpmCOcC1RitbihZ7PzVfMmsi1xm/YxOtNIk yo3yYaG2VtLXekgDu675mdmJUmHWEn3blscZmVcdbgob9B3Y67PAc3IUdCLHkg5PVhhn PVnQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:message-id:date:subject:cc:to:from :dkim-signature:arc-authentication-results; bh=cDqw1tTKgIKWdUjN750Hz4xLipd2Zbtnf4mq5k+d4yQ=; b=Lv6ehG/gHbYkjhVDeLMJmBFqRXL2DE5TE1EQ1eSBMKtvQMrLx5iVP46pgo2niKqbsf l3RQtCPHHB/fK1w65qb8tvw90tBdVCtop76DD8vEqiDl31blV/Jr0CuHKoFaQ1Kclorf g4FDmNWGZXTD2YMvCfAVxnLXNbTZSjW1Yi+V4+9G32ugPzoqz4i1WkQJ8gd/EoA8ezdP kfGtLp3CMVFM+fh2zjGZCVn5KM4duXovsEc/NGc8JPE6mWKF6TqzqIVPkz9+Go05l7w5 IivtKef6wHRHnM0Q/8UAUfDy7oBJBS85kvvnezFPyjqrreae6QmI3jP1gSsTMUZeBuFC tj/A== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b=Nesk3DYK; spf=pass (google.com: best guess record for domain of linux-usb-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-usb-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 77-v6si7782109pfh.332.2018.08.04.02.52.09; Sat, 04 Aug 2018 02:52:10 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-usb-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b=Nesk3DYK; spf=pass (google.com: best guess record for domain of linux-usb-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-usb-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727096AbeHDLwR (ORCPT + 5 others); Sat, 4 Aug 2018 07:52:17 -0400 Received: from mail-wm0-f68.google.com ([74.125.82.68]:53020 "EHLO mail-wm0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726532AbeHDLwR (ORCPT ); Sat, 4 Aug 2018 07:52:17 -0400 Received: by mail-wm0-f68.google.com with SMTP id o11-v6so8706538wmh.2 for ; Sat, 04 Aug 2018 02:52:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id; bh=+9h3uvXolBmBWgQIMWhakh6i7VkqUlq9dUqQcYrhtbM=; b=Nesk3DYKE3JQ62AMlHZoDte2Mzf+zwwWOcbSy7FH24x9+znFt6MDIUgtqwssrpNs8b LTsXRX233py6V1LY854aU2k3vqS5BnssRpYjTLzNKRiA2IubEDpSxqc1/1kZz8Trpf0T 1vRYn7cBbiUMliUxlCWH/muJv4stMZZ6Fjepo= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=+9h3uvXolBmBWgQIMWhakh6i7VkqUlq9dUqQcYrhtbM=; b=fYX/ZsUqfTairEUlt5Rq4X1WXpRbb/23TN2day6RIaRYcpXFBumwKR1pvF9taGJhW5 H+zAcv2Ogx/EW9NdsvhJDpPOpZCLbuMrjxbnRZXJ9vJOi8w0rteRQo7jzQ50ajF1Pu5P BkCOLKyMAvygTiVbC28AE3ZgnUYJyJ/XsgRcQ7LUws8RGABzIJVr6ICP+2Xnz7uY3YhE OXdIkta2ncjXH6kZP0bImL0ptuNwvOLIiYlKBWMoOvhsY/fiO3J/PuYulPOh472jE2ZP 6kIUHEX/3c7Q+JxE+uUA1Efv45/VHKIFya+CaaxGA4FnWoLMhap9EscEQo+pqqy84ljf HVhw== X-Gm-Message-State: AOUpUlFBUUecP0xeHukETni5YJQenoZrcDGtFQKB7ClHm/ElvCwO7XRj aKRM4Gbrx1ggRiM7DICFSdA88A== X-Received: by 2002:a1c:545:: with SMTP id 66-v6mr7454480wmf.140.1533376325363; Sat, 04 Aug 2018 02:52:05 -0700 (PDT) Received: from lpoulain-ThinkPad-T470p.home (AToulouse-655-1-762-165.w109-220.abo.wanadoo.fr. [109.220.142.165]) by smtp.gmail.com with ESMTPSA id 71-v6sm2314092wmq.17.2018.08.04.02.52.04 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 04 Aug 2018 02:52:04 -0700 (PDT) From: Loic Poulain To: andy.shevchenko@gmail.com Cc: linux-usb@vger.kernel.org, ajaykuee@gmail.com, daniel.thompson@linaro.org, Loic Poulain Subject: [PATCH v2] USB: serial: ftdi_sio: Add support for CBUS GPIO Date: Sat, 4 Aug 2018 11:52:02 +0200 Message-Id: <1533376322-21566-1-git-send-email-loic.poulain@linaro.org> X-Mailer: git-send-email 2.7.4 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Some FTDI devices like FTX or FT232R support CBUS Bit Bang mode on CBUS pins, allowing host to control them via simple USB control transfers. To make use of a CBUS pin in Bit Bang mode, the pin must be configured to I/O mode in the FTDI EEPROM. This mode perfectly coexists with regular USB to Serial function. In this implementation, a GPIO controller is registered on FTDI probe if at least one CBUS pin is configured for I/O mode. For now, only FTX devices are supported. This patch is based on previous Stefan Agner implementation tentative on LKML [1]. [1] Message-Id: 1434838377-8042-1-git-send-email-stefan@agner.ch Signed-off-by: Loic Poulain --- v2: Use message-id for LKML reference Rework read_eeprom according to Andy's comment and return read count Remove noisy messages Comment style alignment Add defines for magic values Cannot use devm, because gpiochip is linked to the port not to the udev drivers/usb/serial/Kconfig | 9 ++ drivers/usb/serial/ftdi_sio.c | 238 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/serial/ftdi_sio.h | 83 +++++++++++++++ 3 files changed, 330 insertions(+) -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 533f127..64c9f2e 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -181,6 +181,15 @@ config USB_SERIAL_FTDI_SIO To compile this driver as a module, choose M here: the module will be called ftdi_sio. +config USB_SERIAL_FTDI_SIO_CBUS_GPIO + bool "USB FDTI CBUS GPIO support" + depends on USB_SERIAL_FTDI_SIO + depends on GPIOLIB + help + Say yes here to add support for the CBUS bit-bang mode, allowing CBUS + pins to act as GPIOs. Note that pins must first be configured for GPIO + in the device's EEPROM. The FT232R and FT-X series support this mode. + config USB_SERIAL_VISOR tristate "USB Handspring Visor / Palm m50x / Sony Clie Driver" help diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index b5cef32..8592f39 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -40,12 +41,21 @@ #include #include #include + #include "ftdi_sio.h" #include "ftdi_sio_ids.h" #define DRIVER_AUTHOR "Greg Kroah-Hartman , Bill Ryder , Kuba Ober , Andreas Mohr, Johan Hovold " #define DRIVER_DESC "USB FTDI Serial Converters Driver" +#define FTDI_CBUS_PIN_COUNT 4 + +struct ftdi_gpiochip { + struct gpio_chip gc; + struct usb_serial_port *port; + unsigned int cbus_map[FTDI_CBUS_PIN_COUNT]; + unsigned long cbus_mask; +}; struct ftdi_private { enum ftdi_chip_type chip_type; @@ -72,6 +82,8 @@ struct ftdi_private { unsigned int latency; /* latency setting in use */ unsigned short max_packet_size; struct mutex cfg_lock; /* Avoid mess by parallel calls of config ioctl() and change_speed() */ + + struct ftdi_gpiochip *fgc; }; /* struct ftdi_sio_quirk is used by devices requiring special attention. */ @@ -1528,6 +1540,224 @@ static int get_lsr_info(struct usb_serial_port *port, return 0; } +#ifdef CONFIG_USB_SERIAL_FTDI_SIO_CBUS_GPIO + +#define FTX_CBUS_MUX_EEPROM_ADDR 0x1a + +static int ftdi_read_eeprom(struct usb_device *udev, unsigned int off, + void *val, size_t bytes) +{ + unsigned int read = 0; + + if (bytes % 2) /* 16-bit word eeprom */ + bytes--; + + while (read < bytes) { + int rv; + + rv = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + FTDI_SIO_READ_EEPROM_REQUEST, + FTDI_SIO_READ_EEPROM_REQUEST_TYPE, + 0, (off + read) / 2, val + read, 2, + WDR_TIMEOUT); + if (rv < 0) + break; + + read += 2; + } + + return read; +} + +static int ftdi_set_bitmode(struct usb_serial_port *port, u8 bitmask, + u8 bitmode) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + __u16 urb_value = 0; + + urb_value = bitmode << 8 | bitmask; + + if (usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_BITMODE_REQUEST, + FTDI_SIO_SET_BITMODE_REQUEST_TYPE, + urb_value, priv->interface, NULL, 0, + WDR_SHORT_TIMEOUT) < 0) { + return -EIO; + } + + return 0; +} + +static int ftdi_read_pins(struct usb_serial_port *port, u8 *val) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned char *buf; + + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + FTDI_SIO_READ_PINS_REQUEST, + FTDI_SIO_READ_PINS_REQUEST_TYPE, + 0, priv->interface, buf, 1, WDR_TIMEOUT) < 0) { + kfree(buf); + return -EIO; + } + + *val = buf[0]; + kfree(buf); + + return 0; +} + +static int ftdi_cbus_gpio_dir_out(struct gpio_chip *gc, unsigned gpio, int val) +{ + struct ftdi_gpiochip *fgc = gpiochip_get_data(gc); + unsigned int cbus_idx = fgc->cbus_map[gpio]; + + /* + * CBUS mask is an 8-bit register controlling the direction and value + * of CBUS pins in Bit-Bang mode. cf application note AN_373/AN_232R-01. + * | bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 | + * | CBUS0 | CBUS1 | CBUS2 | CBUS3 | CBUS4 | CBUS5 | CBUS6 | CBUS7 | + * | I/O | I/O | I/O | I/O | Hi/Lo | Hi/Lo | Hi/Lo | Hi/Lo | + */ + set_bit(cbus_idx + 4, &fgc->cbus_mask); /* direction */ + set_bit(cbus_idx, &fgc->cbus_mask); /* value */ + + return ftdi_set_bitmode(fgc->port, fgc->cbus_mask, + FTDI_SIO_BITMODE_CBUS); +} + +static int ftdi_cbus_gpio_dir_in(struct gpio_chip *gc, unsigned gpio) +{ + struct ftdi_gpiochip *fgc = gpiochip_get_data(gc); + unsigned int cbus_idx = fgc->cbus_map[gpio]; + + clear_bit(cbus_idx + 4, &fgc->cbus_mask); + + return ftdi_set_bitmode(fgc->port, fgc->cbus_mask, + FTDI_SIO_BITMODE_CBUS); +} + +static int ftdi_cbus_gpio_get(struct gpio_chip *gc, unsigned gpio) +{ + struct ftdi_gpiochip *fgc = gpiochip_get_data(gc); + unsigned int cbus_idx = fgc->cbus_map[gpio]; + u8 val = 0; + int rv; + + rv = ftdi_read_pins(fgc->port, &val); + if (rv) + return rv; + + return !!(val & BIT(cbus_idx)); +} + +static void ftdi_cbus_gpio_set(struct gpio_chip *gc, unsigned gpio, int val) +{ + struct ftdi_gpiochip *fgc = gpiochip_get_data(gc); + + if (val) + set_bit(fgc->cbus_map[gpio], &fgc->cbus_mask); + else + clear_bit(fgc->cbus_map[gpio], &fgc->cbus_mask); + + ftdi_set_bitmode(fgc->port, fgc->cbus_mask, FTDI_SIO_BITMODE_CBUS); +} + +static int ftdi_register_cbus_gpiochip(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_device *udev = port->serial->dev; + struct ftdi_gpiochip *fgc; + int rv = 0; + + fgc = kzalloc(sizeof(*fgc), GFP_KERNEL); + if (!fgc) + return -ENOMEM; + + if (priv->chip_type == FTX) { + unsigned char *cbus_mux; + unsigned int i; + + /* + * Each individual CBUS pin is controlled by a separate 8-bit + * MUX control value stored in the EEPROM, starting at 0x1a: + * | 0x1a CBUS0 | 0x1b CBUS1 | 0x1c CBUS2 | 0x1d CBUS3 | ... + * cf application note AN_201. + */ + + cbus_mux = kmalloc(FTDI_CBUS_PIN_COUNT, GFP_KERNEL); + if (!cbus_mux) + return -ENOMEM; + + rv = ftdi_read_eeprom(udev, FTX_CBUS_MUX_EEPROM_ADDR, cbus_mux, + FTDI_CBUS_PIN_COUNT); + if (rv < 0) { + dev_err(&udev->dev, "Unable to read CBUS config\n"); + kfree(cbus_mux); + goto exit_release; + } + + for (i = 0; i < rv; i++) { + if (cbus_mux[i] == FTX_CBUS_MUX_IO) + fgc->cbus_map[fgc->gc.ngpio++] = i; + } + + rv = 0; + kfree(cbus_mux); + } + + if (!fgc->gc.ngpio) + goto exit_release; + + fgc->gc.label = "ftdi-cbus-gpio"; + fgc->gc.direction_input = ftdi_cbus_gpio_dir_in; + fgc->gc.direction_output = ftdi_cbus_gpio_dir_out; + fgc->gc.set = ftdi_cbus_gpio_set; + fgc->gc.get = ftdi_cbus_gpio_get; + fgc->gc.can_sleep = true; + fgc->gc.parent = &udev->dev; + fgc->gc.base = -1; + fgc->gc.owner = THIS_MODULE; + fgc->port = port; + + rv = gpiochip_add_data(&fgc->gc, fgc); + if (rv) { + dev_err(&udev->dev, "Unable to add gpiochip\n"); + goto exit_release; + } + + priv->fgc = fgc; + + dev_info(&udev->dev, + "FTDI USB GPIO controller Registered, base=%d, ngpio=%u\n", + fgc->gc.base, fgc->gc.ngpio); + + return 0; + +exit_release: + kfree(fgc); + return rv; +} + +static void ftdi_unregister_cbus_gpiochip(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct ftdi_gpiochip *fgc = priv->fgc; + + if (!fgc) + return; + + gpiochip_remove(&fgc->gc); + kfree(fgc); +} + +#endif /* CONFIG_USB_SERIAL_FTDI_SIO_CBUS_GPIO */ /* Determine type of FTDI chip based on USB config and descriptor. */ static void ftdi_determine_type(struct usb_serial_port *port) @@ -1813,6 +2043,10 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port) priv->latency = 16; write_latency_timer(port); create_sysfs_attrs(port); +#ifdef CONFIG_USB_SERIAL_FTDI_SIO_CBUS_GPIO + ftdi_register_cbus_gpiochip(port); +#endif + return 0; } @@ -1930,6 +2164,10 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port) { struct ftdi_private *priv = usb_get_serial_port_data(port); +#ifdef CONFIG_USB_SERIAL_FTDI_SIO_CBUS_GPIO + ftdi_unregister_cbus_gpiochip(port); +#endif + remove_sysfs_attrs(port); kfree(priv); diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index dcd0b6e..3bd248f 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -36,6 +36,10 @@ #define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ #define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ #define FTDI_SIO_GET_LATENCY_TIMER 10 /* Get the latency timer */ +#define FTDI_SIO_SET_BITMODE 11 /* Set the bitmode */ +#define FTDI_SIO_READ_PINS 12 /* Read pins in bitmode */ +#define FTDI_SIO_READ_EEPROM 0x90 /* Read eeprom */ +#define FTDI_SIO_WRITE_EEPROM 0x91 /* Write eeprom */ /* Interface indices for FT2232, FT2232H and FT4232H devices */ #define INTERFACE_A 1 @@ -400,6 +404,60 @@ enum ftdi_sio_baudrate { * */ + /* FTDI_SIO_READ_EEPROM */ +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_READ_EEPROM + * wValue: 0 + * wIndex: Word Index + * wLength: 2 + * Data: return data (a word) + * + */ + +/* FTDI_SIO_WRITE_EEPROM */ +#define FTDI_SIO_WRITE_EEPROM_REQUEST_TYPE 0x40 +#define FTDI_SIO_WRITE_EEPROM_REQUEST FTDI_SIO_WRITE_EEPROM +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_WRITE_EEPROM + * wValue: Data (word) + * wIndex: Word Index + * wLength: 0 + * Data: None + * + */ + + /* FTDI_SIO_SET_BITMODE */ +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_BITMODE + * wValue: [0-7] bitmask, [8-15] bitmode + * wIndex: Port + * wLength: 0 + * Data: None + * + */ + +/* FTDI_SIO_READ_PINS */ +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xC0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_READ_PINS + * wValue: 0 + * wIndex: Port + * wLength: 2 + * Data: return data (a word) + * + */ + /* FTDI_SIO_GET_MODEM_STATUS */ /* Retrieve the current value of the modem status register */ @@ -563,3 +621,28 @@ enum ftdi_sio_baudrate { * B2..7 Length of message - (not including Byte 0) * */ + +enum ftdi_ftx_cbus_mux { + FTX_CBUS_MUX_TRISTATE, + FTX_CBUS_MUX_RXLED, + FTX_CBUS_MUX_TXLED, + FTX_CBUS_MUX_TXRXLED, + FTX_CBUS_MUX_PWREN, + FTX_CBUS_MUX_SLEEP, + FTX_CBUS_MUX_DRIVE0, + FTX_CBUS_MUX_DRIVE1, + FTX_CBUS_MUX_IO, + FTX_CBUS_MUX_TXDEN, + FTX_CBUS_MUX_CLK24, + FTX_CBUS_MUX_CLK12, + FTX_CBUS_MUX_CLK6, + FTX_CBUS_MUX_BCD_CHARGER, + FTX_CBUS_MUX_BCD_CHARGER_N, + FTX_CBUS_MUX_I2C_TXE, + FTX_CBUS_MUX_I2C_RXF, + FTX_CBUS_MUX_VBUS_SENSE, + FTX_CBUS_MUX_BITBANG_WR, + FTX_CBUS_MUX_BITBANG_RD, + FTX_CBUS_MUX_TIMESTAMP, + FTX_CBUS_MUX_KEEP_AWAKE +};