From patchwork Wed Aug 1 15:46:24 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Loic Poulain X-Patchwork-Id: 143301 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp1030764ljj; Wed, 1 Aug 2018 08:46:32 -0700 (PDT) X-Google-Smtp-Source: AAOMgpckJkjSh7S7XDYnvUTDYeUoE5QB4G+btt5meCqGWFIdk8A3XiHqlDpj7ZBswr+PpVpYl5Ca X-Received: by 2002:a17:902:20e9:: with SMTP id v38-v6mr25432940plg.107.1533138391874; Wed, 01 Aug 2018 08:46:31 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1533138391; cv=none; d=google.com; s=arc-20160816; b=Tr+Xb+7sgIXbgHDpl7r6h1MrDHQC1Rqx93XKbfzt0izxDTinbAN1LY+61PEQHe5zNF 6yLWGEr8Ptui5JXgn1gK8z/GFNsJ/QSKPthS3vm+GfQHG5pB29M9jwmUeg8DKBFyLfUy 3A3P2dSVni5Jh3Tjsmtj34Tgeg9JZCTc85V3k/5kGHvIzOk56/qadfuJ6wItuanZFZG4 OBXq/aYRDKhLMBpo2C2CCK/W9eBwjXMgsg0/PMOo30Kdrp0C2K0kUGhA8rV24nUOjHPh zUlNKzCCwbwD8al/yuA0ljM9UzHl2kweexiHQSV6osatMBw5YJSI+SPP0Fxu5+Z0ZIXK iJSQ== 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=1v4bdj9xbfvpogfL3a8bLeIf+R+ee8OmSEdSiOn5Y2E=; b=NqMzN7/dgbRsVn8ihF8VdTV39d+tEN8M4aoYObdu8yfEfxgX9bCSz/9hp6PHj4nb0E 8LpDNSk/x6cXuoBVjD7CSrmLgIF9g4qU2a8JFji1fidAQ2PFo3TmWC0uGaIP/BC1XXHb WdCMDGoA4HRhmRxUXrv15VfVO/4vMhKyjeHioMMY6uZhkKgqrQiN7ee06IuEoA0kOtk/ mA78UJzA3u0IYQDxIEBpG1Qu83dqgxLu3D1+nKVtsTPm3TjtRTyAejao7sYwbxOe5IfQ uPF4grV4nVbb7Vv5mjl7mqBuZVgXwlgdkOUanjhjd/lwsRKcOiGXxzjksNx6jbCnmeXg JZlA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b=B8xpUdIa; 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 cb1-v6si16504251plb.128.2018.08.01.08.46.31; Wed, 01 Aug 2018 08:46:31 -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=B8xpUdIa; 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 S2389697AbeHARct (ORCPT + 5 others); Wed, 1 Aug 2018 13:32:49 -0400 Received: from mail-wm0-f65.google.com ([74.125.82.65]:37782 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2389553AbeHARct (ORCPT ); Wed, 1 Aug 2018 13:32:49 -0400 Received: by mail-wm0-f65.google.com with SMTP id n11-v6so7574379wmc.2 for ; Wed, 01 Aug 2018 08:46:29 -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=/dWBK6x85mU5t9QKd453GXJOLP4mhvJxHPZupvSHpgQ=; b=B8xpUdIa1pvOicCVwQL80NFoF/Dn06jr8BQ4HJNKBg98YaoBHw1sE4/N00VLaKZ00A bOOzD0U17sG3nhod6qaSdsdQ+fnQSt9Y8+xLza/Uri+8YuuwCHcmZy57Y9AhUSHNDkaG pe4fL0S1/QGBD8PUd+lzdRIgz0RrWU9zWcvyE= 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=/dWBK6x85mU5t9QKd453GXJOLP4mhvJxHPZupvSHpgQ=; b=BAngZvXDbCtvjb9MZJXK2T0E15bzSpyCxKiqZ5aSRSC4Wa1qb/lWVACBB1FWDUAptG XJ0k/6mbyGglKHxp/9hXo8s2+nA5Dh4aWFwQWkGjmM8Pp0MgokAVEfJBlAdDFiis9m49 fxsCWxPxse4vJFfMoruapQQEcSaOAg6KyNEhwtkxM4mGCs40UN/OWOQE9Nym1uAIejlg XjPggrcH2DWxNV73/GZlBLWXzV68+/LBF0d6W1qTPpMTQKJUTLI+6stFh8NPhy8wgX9n DzpLiv2oJE4SVTtyADh3JeXpfvBya/7NOREmlek3PlBOXL48jfAWM+/uUbV7jIHNLj50 q6lg== X-Gm-Message-State: AOUpUlH3J5AhoDVQ/VaHmT3pgkRiJVycbeA7Kpxze2cWQTSOODIZlVVj 8sgZHaWEz0AbZD825o/L9qFtvg== X-Received: by 2002:a1c:8f50:: with SMTP id r77-v6mr3272978wmd.44.1533138388328; Wed, 01 Aug 2018 08:46:28 -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 k3-v6sm5908179wme.44.2018.08.01.08.46.26 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 01 Aug 2018 08:46:27 -0700 (PDT) From: Loic Poulain To: johan@kernel.org Cc: linux-usb@vger.kernel.org, andy.shevchenko@gmail.com, ajaykuee@gmail.com, daniel.thompson@linaro.org, Loic Poulain Subject: [PATCH] USB: serial: ftdi_sio: Add support for CBUS GPIO Date: Wed, 1 Aug 2018 17:46:24 +0200 Message-Id: <1533138384-11258-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 ([PATCH 0/2] FTDI CBUS GPIO support). Signed-off-by: Loic Poulain --- drivers/usb/serial/Kconfig | 9 ++ drivers/usb/serial/ftdi_sio.c | 222 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/serial/ftdi_sio.h | 83 ++++++++++++++++ 3 files changed, 314 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..3cfb5fd 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,19 @@ #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" +struct ftdi_gpiochip { + struct gpio_chip gc; + unsigned int cbus_map[4]; + unsigned long cbus_mask; + struct usb_serial_port *port; +}; struct ftdi_private { enum ftdi_chip_type chip_type; @@ -72,6 +80,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 +1538,211 @@ static int get_lsr_info(struct usb_serial_port *port, return 0; } +#ifdef CONFIG_USB_SERIAL_FTDI_SIO_CBUS_GPIO + +static int ftdi_read_eeprom(struct usb_device *udev, unsigned int off, + void *val, size_t bytes) +{ + if (bytes % 2) /* 16-bit eeprom */ + return -EINVAL; + + while (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 / 2, val, 2, WDR_TIMEOUT); + if (rv < 0) + return rv; + + off += 2; + val += 2; + bytes -= 2; + } + + return 0; +} + +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_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_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]; + + 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_get(struct gpio_chip *gc, unsigned gpio) +{ + struct ftdi_gpiochip *fgc = gpiochip_get_data(gc); + struct usb_device *udev = fgc->port->serial->dev; + unsigned int cbus_idx = fgc->cbus_map[gpio]; + u8 val = 0; + int rv; + + rv = ftdi_read_pins(fgc->port, &val); + if (rv) + dev_err(&udev->dev, "Unable to read CBUS GPIO pins, %d\n", 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); + struct usb_device *udev = fgc->port->serial->dev; + int rv; + + if (val) + set_bit(fgc->cbus_map[gpio], &fgc->cbus_mask); + else + clear_bit(fgc->cbus_map[gpio], &fgc->cbus_mask); + + rv = ftdi_set_bitmode(fgc->port, fgc->cbus_mask, FTDI_SIO_BITMODE_CBUS); + if (rv) + dev_err(&udev->dev, "Unable set CBUS Bit-Bang mode, %d\n", rv); +} + +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; + + fgc = kzalloc(sizeof(*fgc), GFP_KERNEL); + if (!fgc) + return -ENOMEM; + + if (priv->chip_type == FTX) { + unsigned char *cbus_mux; + unsigned int i; + + cbus_mux = kmalloc(4, GFP_KERNEL); + if (!cbus_mux) + return -ENOMEM; + + /* CBUS pins are individually configurable via 8-bit MUX control + * value, living at 0x1a for CBUS0. cf application note AN_201. + */ + rv = ftdi_read_eeprom(udev, 0x1a, cbus_mux, 4); + if (rv) { + dev_err(&udev->dev, "Unable to read CBUS config\n"); + kfree(cbus_mux); + return -EIO; + } + + for (i = 0; i < 4; i++) { + if (cbus_mux[i] == FTX_CBUS_MUX_IO) + fgc->cbus_map[fgc->gc.ngpio++] = i; + } + + kfree(cbus_mux); + } + + if (!fgc->gc.ngpio) { + kfree(fgc); + return 0; + } + + 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"); + kfree(fgc); + return rv; + } + + 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; +} + +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) { + 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 +2028,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 +2149,9 @@ 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 +};