From patchwork Mon Nov 5 16:19:11 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anatolij Gustschin X-Patchwork-Id: 10668591 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A76421803 for ; Mon, 5 Nov 2018 16:19:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8EBEC29BDB for ; Mon, 5 Nov 2018 16:19:33 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 827C029B6B; Mon, 5 Nov 2018 16:19:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1D04229C0B for ; Mon, 5 Nov 2018 16:19:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387520AbeKFBjz (ORCPT ); Mon, 5 Nov 2018 20:39:55 -0500 Received: from mail-out.m-online.net ([212.18.0.10]:59826 "EHLO mail-out.m-online.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387420AbeKFBjy (ORCPT ); Mon, 5 Nov 2018 20:39:54 -0500 Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 42pdD71kRbz1qwfM; Mon, 5 Nov 2018 17:19:19 +0100 (CET) Received: from localhost (dynscan1.mnet-online.de [192.168.6.70]) by mail.m-online.net (Postfix) with ESMTP id 42pdD70tC4z1qql1; Mon, 5 Nov 2018 17:19:19 +0100 (CET) X-Virus-Scanned: amavisd-new at mnet-online.de Received: from mail.mnet-online.de ([192.168.8.182]) by localhost (dynscan1.mail.m-online.net [192.168.6.70]) (amavisd-new, port 10024) with ESMTP id IHlBuyR0ivvA; Mon, 5 Nov 2018 17:19:15 +0100 (CET) X-Auth-Info: toGDiLaGwEYAfOL6mRgkVh7xOb4oukQ9zSSge46fGJo= Received: from crub.agik.hopto.org (p5088702D.dip0.t-ipconnect.de [80.136.112.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.mnet-online.de (Postfix) with ESMTPSA; Mon, 5 Nov 2018 17:19:15 +0100 (CET) From: Anatolij Gustschin To: linux-usb@vger.kernel.org, linux-spi@vger.kernel.org, linux-fpga@vger.kernel.org Cc: gregkh@linuxfoundation.org, broonie@kernel.org, atull@kernel.org, mdf@kernel.org Subject: [PATCH 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Date: Mon, 5 Nov 2018 17:19:11 +0100 Message-Id: <20181105161913.27538-2-agust@denx.de> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181105161913.27538-1-agust@denx.de> References: <20181105161913.27538-1-agust@denx.de> Sender: linux-spi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-spi@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add USB interface driver for ARRI FPGA configuration devices based on FTDI FT232H chip. Depending on USB PID the driver registers different platform devices describing an FPGA configuration interface. One FPGA configuration interface type is the usual SPI bus with additional control and status GPIOs. For this interface type the FTDI MPSSE mode is utilized to support SPI bus and GPIO-L/H pins for SPI bus chipselect and SPI slave device I/O. Subsequent patch adds an MPSSE SPI controller driver for USB-SPI bus support. The actual FPGA configuration driver for SPI bus is already available in the FPGA manager framework (altera-ps-spi module). Another FPGA configuration interface type is the FT245 FIFO (connected via CPLD) with additional control and status GPIOs on FT232H ACBUS. This interface is used to configure FPGAs using Altera fast passive parallel (FPP) interface. Subsequent patch will add an FPP FPGA manager driver for it. The FTDI protocol code and prototypes was borrowed from libftdi. Signed-off-by: Anatolij Gustschin --- drivers/usb/misc/Kconfig | 9 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/ft232h-intf.c | 1468 +++++++++++++++++++++++++++++++ include/linux/usb/ft232h-intf.h | 198 +++++ 4 files changed, 1676 insertions(+) create mode 100644 drivers/usb/misc/ft232h-intf.c create mode 100644 include/linux/usb/ft232h-intf.h diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 68d2f2cd17dd..41304ac7f0e3 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,3 +275,12 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. + +config USB_FT232H_INTF + tristate "FTDI FT232H FPGA configuration interface driver" + help + Enable driver support for the FT232H based USB-GPIO/SPI/FIFO + interface adapter for FPGA configuration. Additional drivers + such as spi-ftdi-mpsse, altera-ps-spi, ftdi-fifo-fpp and + fpga-mgr must be enabled in order to use the functionality of + the device. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 109f54f5b9aa..3618d8c7bfdd 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_USB_FT232H_INTF) += ft232h-intf.o diff --git a/drivers/usb/misc/ft232h-intf.c b/drivers/usb/misc/ft232h-intf.c new file mode 100644 index 000000000000..315ce212b1b6 --- /dev/null +++ b/drivers/usb/misc/ft232h-intf.c @@ -0,0 +1,1468 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FTDI FT232H interface driver for ARRI FPGA configuration + * + * Copyright (C) 2017 DENX Software Engineering + * Anatolij Gustschin + * + * Figure: FT232H, FPGA Devices and Drivers Relationship + * + * +-------------+ + * | | + * | STRATIX V |PS-SPI FT245 FIFO & GPIO + * | +-----+ +-------------------+ + * | on Board 1 | + + | + * | | +----+---+ + * | PCIe | ADBUS&ACBUS | CPLD | + * +---+---------+ Connection Options +----+---+ + * ^ (MPSSE or FIFO&GPIO) | + * + + +------+-------+ + * altera-cvp +-----------+----------+ | FPP | + * | FT232H | | | + * | 0x0403:0x7148 | | ARRIA 10 | + * | 0x0403:0x7149 | | | + * +----------+-----------+ | on Board 2 | + * | | | + * +-----------+------------+ | PCIe | + * creates | ft232h-intf (USB misc) | +----------+---+ + * platform | bulk/ctrl xfer | ^ + * devices |ACBUS GPIO Ctrl (0x7148)| | + * below |MPSSE GPIO Ctrl (0x7149)| | + * +-------+-------+--------+ | + * | | | + * for +----+ +------+ for | + * PID 0x7149 | | PID 0x7148 | + * +---------+--------+ +-------+---------+ | + * | ftdi-mpsse-spi | | | | + * | altera-ps-spi in | |ftdi-fifo-fpp-mgr| | + * | spi_board_info | | | | + * +---------+--------+ +--------+--------+ | + * ^ ^ | + * Drivers: | | | + * + | | + * MPSSE SPI master(spi-ftdi-mpsse) | + + * ^ | | + * | + + + * altera-ps-spi ftdi-fifo-fpp altera-cvp + * FPGA Manager FPGA Manager FPGA Manager + * + * + * When using your custom USB product ID, this FT232H interface driver + * also allows to register the GPIO controller for CBUS pins or for + * MPSSE GPIO pins. Below are examples how to use the driver as CBUS- + * or MPSSE-GPIO controller. + * + * For CBUS-GPIOs add new entry with your PID to ft232h_intf_table[]: + * static const struct ft232h_intf_info ftdi_cbus_gpio_intf_info = { + * .use_cbus_gpio_ctrl = true, + * }; + * { USB_DEVICE(FTDI_VID, PID), + * .driver_info = (kernel_ulong_t)&ftdi_cbus_gpio_intf_info }, + * + * For MPSSE-GPIO add new entry with your PID to ft232h_intf_table[]: + * static const struct ft232h_intf_info ftdi_mpsse_gpio_intf_info = { + * .use_mpsse_gpio_ctrl = true, + * }; + * { USB_DEVICE(FTDI_VID, PID), + * .driver_info = (kernel_ulong_t)&ftdi_mpsse_gpio_intf_info }, + * + * With custom USB product IDs it is also possible to use FT232H SPI bus + * with different SPI slave devices attached (e.g. SPI-NOR flash chips, + * spidev, etc.). Example below shows how to add a bus with two SPI slave + * devices for your USB PID: + * + * struct spi_board_info ftdi_spi_bus_info[] = { + * { + * .modalias = "w25q32", + * .mode = SPI_MODE_0, + * .max_speed_hz = 60000000, + * .bus_num = 0, + * .chip_select = 0, // TCK/SK at ADBUS0 + * }, + * { + * .modalias = "spidev", + * .mode = SPI_MODE_0 | SPI_LSB_FIRST | SPI_CS_HIGH, + * .max_speed_hz = 30000000, + * .bus_num = 0, + * .chip_select = 5, // GPIOH0 at ACBUS0 + * }, + * }; + * + * static const struct mpsse_spi_platform_data ftdi_spi_bus_plat_data = { + * .ops = &ft232h_intf_ops, + * .spi_info = ftdi_spi_bus_info, + * .spi_info_len = ARRAY_SIZE(ftdi_spi_bus_info), + * }; + * + * static const struct ft232h_intf_info ftdi_spi_bus_intf_info = { + * .probe = ft232h_intf_spi_probe, + * .remove = ft232h_intf_spi_remove, + * .plat_data = &ftdi_spi_bus_plat_data, + * }; + * { USB_DEVICE(FTDI_VID, YOUR_PID), + * .driver_info = (kernel_ulong_t)&ftdi_spi_bus_intf_info }, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ft232h_intf_priv { + struct usb_interface *intf; + struct usb_device *udev; + struct mutex io_mutex; /* sync I/O with disconnect */ + struct mutex ops_mutex; + int bitbang_enabled; + int id; + int index; + u8 bulk_in; + u8 bulk_out; + size_t bulk_in_sz; + void *bulk_in_buf; + + const struct usb_device_id *usb_dev_id; + struct ft232h_intf_info *info; + struct platform_device *fifo_pdev; + struct platform_device *spi_pdev; + struct gpiod_lookup_table *lookup_fifo; + struct gpiod_lookup_table *lookup_cs; + + struct gpio_chip cbus_gpio; + const char *cbus_gpio_names[4]; + u8 cbus_pin_offsets[4]; + u8 cbus_mask; + u8 pinbuf[4]; + u8 eeprom[FTDI_MAX_EEPROM_SIZE]; + + struct gpio_chip mpsse_gpio; + u8 gpiol_mask; + u8 gpioh_mask; + u8 gpiol_dir; + u8 gpioh_dir; + u8 tx_buf[4]; +}; + +/* Device info struct used for device specific init. */ +struct ft232h_intf_info { + unsigned int use_cbus_gpio_ctrl; + unsigned int use_mpsse_gpio_ctrl; + int (*probe)(struct usb_interface *intf, const void *plat_data); + int (*remove)(struct usb_interface *intf); + const void *plat_data; /* optional, passed to probe() */ +}; + +static DEFINE_IDA(ftdi_devid_ida); + +/* Use baudrate calculation borrowed from libftdi */ +static int ftdi_to_clkbits(int baudrate, unsigned int clk, int clk_div, + unsigned long *encoded_divisor) +{ + static const char frac_code[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + int best_baud = 0; + int div, best_div; + + if (baudrate >= clk / clk_div) { + *encoded_divisor = 0; + best_baud = clk / clk_div; + } else if (baudrate >= clk / (clk_div + clk_div / 2)) { + *encoded_divisor = 1; + best_baud = clk / (clk_div + clk_div / 2); + } else if (baudrate >= clk / (2 * clk_div)) { + *encoded_divisor = 2; + best_baud = clk / (2 * clk_div); + } else { + /* + * Divide by 16 to have 3 fractional bits and + * one bit for rounding + */ + div = clk * 16 / clk_div / baudrate; + if (div & 1) /* Decide if to round up or down */ + best_div = div / 2 + 1; + else + best_div = div / 2; + if (best_div > 0x20000) + best_div = 0x1ffff; + best_baud = clk * 16 / clk_div / best_div; + if (best_baud & 1) /* Decide if to round up or down */ + best_baud = best_baud / 2 + 1; + else + best_baud = best_baud / 2; + *encoded_divisor = (best_div >> 3) | + (frac_code[best_div & 0x7] << 14); + } + return best_baud; +} + +#define H_CLK 120000000 +#define C_CLK 48000000 +static int ftdi_convert_baudrate(struct ft232h_intf_priv *priv, int baud, + u16 *value, u16 *index) +{ + unsigned long encoded_divisor = 0; + int best_baud = 0; + + if (baud <= 0) + return -EINVAL; + + /* + * On H Devices, use 12000000 baudrate when possible. + * We have a 14 bit divisor, a 1 bit divisor switch (10 or 16), + * three fractional bits and a 120 MHz clock. Assume AN_120 + * "Sub-integer divisors between 0 and 2 are not allowed" holds + * for DIV/10 CLK too, so /1, /1.5 and /2 can be handled the same + */ + if (baud * 10 > H_CLK / 0x3fff) { + best_baud = ftdi_to_clkbits(baud, H_CLK, 10, &encoded_divisor); + encoded_divisor |= 0x20000; /* switch on CLK/10 */ + } else { + best_baud = ftdi_to_clkbits(baud, C_CLK, 16, &encoded_divisor); + } + + if (best_baud <= 0) { + pr_err("Invalid baudrate: %d\n", best_baud); + return -EINVAL; + } + + /* Check within tolerance (about 5%) */ + if ((best_baud * 2 < baud) || + (best_baud < baud + ? (best_baud * 21 < baud * 20) + : (baud * 21 < best_baud * 20))) { + pr_err("Unsupported baudrate.\n"); + return -EINVAL; + } + + /* Split into "value" and "index" values */ + *value = (u16)(encoded_divisor & 0xffff); + *index = (u16)(((encoded_divisor >> 8) & 0xff00) | priv->index); + + dev_dbg(&priv->intf->dev, "best baud %d, v/i: %d, %d\n", + best_baud, *value, *index); + return best_baud; +} + +/** + * ftdi_ctrl_xfer - FTDI control endpoint transfer + * @intf: USB interface pointer + * @desc: pointer to descriptor struct for control transfer + * + * Return: + * Return: If successful, the number of bytes transferred. Otherwise, + * a negative error number. + */ +int ftdi_ctrl_xfer(struct usb_interface *intf, struct ctrl_desc *desc) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct usb_device *udev = priv->udev; + unsigned int pipe; + int ret; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + ret = -ENODEV; + goto exit; + } + + if (!desc->data && desc->size) + desc->data = priv->bulk_in_buf; + + if (desc->dir_out) + pipe = usb_sndctrlpipe(udev, 0); + else + pipe = usb_rcvctrlpipe(udev, 0); + + ret = usb_control_msg(udev, pipe, desc->request, desc->requesttype, + desc->value, desc->index, desc->data, desc->size, + desc->timeout); + if (ret < 0) + dev_dbg(&udev->dev, "ctrl msg failed: %d\n", ret); +exit: + mutex_unlock(&priv->io_mutex); + return ret; +} + +/** + * ftdi_bulk_xfer - FTDI bulk endpoint transfer + * @intf: USB interface pointer + * @desc: pointer to descriptor struct for bulk-in or bulk-out transfer + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of + * actual bytes transferred will be stored in the @desc->act_len field + * of the descriptor struct. + */ +int ftdi_bulk_xfer(struct usb_interface *intf, struct bulk_desc *desc) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct usb_device *udev = priv->udev; + unsigned int pipe; + int ret; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + ret = -ENODEV; + goto exit; + } + + if (desc->dir_out) + pipe = usb_sndbulkpipe(udev, priv->bulk_out); + else + pipe = usb_rcvbulkpipe(udev, priv->bulk_in); + + ret = usb_bulk_msg(udev, pipe, desc->data, desc->len, + &desc->act_len, desc->timeout); + if (ret) + dev_dbg(&udev->dev, "bulk msg failed: %d\n", ret); + +exit: + mutex_unlock(&priv->io_mutex); + return ret; +} + +/** + * ftdi_set_baudrate - set the device baud rate + * @intf: USB interface pointer + * @baudrate: baud rate value to set + * + * Return: If successful, 0. Otherwise a negative error number. + */ +int ftdi_set_baudrate(struct usb_interface *intf, int baudrate) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct ctrl_desc desc; + u16 index, value; + int ret; + + if (priv->bitbang_enabled) + baudrate *= 4; + + ret = ftdi_convert_baudrate(priv, baudrate, &value, &index); + if (ret < 0) + return ret; + + desc.dir_out = true; + desc.request = FTDI_SIO_SET_BAUDRATE_REQUEST; + desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT; + desc.value = value; + desc.index = index; + desc.data = NULL; + desc.size = 0; + desc.timeout = USB_CTRL_SET_TIMEOUT; + + ret = ftdi_ctrl_xfer(intf, &desc); + if (ret < 0) { + dev_dbg(&intf->dev, "failed to set baudrate: %d\n", ret); + return ret; + } + + return 0; +} + +/** + * ftdi_read_data - read from FTDI bulk-in endpoint + * @intf: USB interface pointer + * @buf: pointer to data buffer + * @len: length in bytes of the data to read + * + * The two modem status bytes transferred in every read will + * be removed and will not appear in the data buffer. + * + * Return: + * If successful, the number of data bytes received (can be 0). + * Otherwise, a negative error number. + */ +int ftdi_read_data(struct usb_interface *intf, void *buf, size_t len) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct bulk_desc desc; + int ret; + + desc.act_len = 0; + desc.dir_out = false; + desc.data = priv->bulk_in_buf; + /* Device sends 2 additional status bytes, read at least len + 2 */ + desc.len = min_t(size_t, len + 2, priv->bulk_in_sz); + desc.timeout = FTDI_USB_READ_TIMEOUT; + + ret = ftdi_bulk_xfer(intf, &desc); + if (ret) + return ret; + + /* Only status bytes and no data? */ + if (desc.act_len <= 2) + return 0; + + /* Skip first two status bytes */ + ret = desc.act_len - 2; + if (ret > len) + ret = len; + memcpy(buf, desc.data + 2, ret); + return ret; +} + +/** + * ftdi_write_data - write to FTDI bulk-out endpoint + * @intf: USB interface pointer + * @buf: pointer to data buffer + * @len: length in bytes of the data to send + * + * Return: + * If successful, the number of bytes transferred. Otherwise a negative + * error number. + */ +int ftdi_write_data(struct usb_interface *intf, const char *buf, size_t len) +{ + struct bulk_desc desc; + int ret; + + desc.act_len = 0; + desc.dir_out = true; + desc.data = (char *)buf; + desc.len = len; + desc.timeout = FTDI_USB_WRITE_TIMEOUT; + + ret = ftdi_bulk_xfer(intf, &desc); + if (ret < 0) + return ret; + + return desc.act_len; +} + +/** + * ftdi_set_bitmode - configure bitbang mode + * @intf: USB interface pointer + * @bitmask: line configuration bitmask + * @mode: bitbang mode to set + * + * Return: + * If successful, 0. Otherwise a negative error number. + */ +int ftdi_set_bitmode(struct usb_interface *intf, unsigned char bitmask, + unsigned char mode) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct ctrl_desc desc; + int ret; + + desc.dir_out = true; + desc.data = NULL; + desc.request = FTDI_SIO_SET_BITMODE_REQUEST; + desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT; + desc.index = 1; + desc.value = (mode << 8) | bitmask; + desc.size = 0; + desc.timeout = USB_CTRL_SET_TIMEOUT; + + ret = ftdi_ctrl_xfer(intf, &desc); + if (ret < 0) + return ret; + + switch (mode) { + case BITMODE_BITBANG: + case BITMODE_CBUS: + case BITMODE_SYNCBB: + case BITMODE_SYNCFF: + priv->bitbang_enabled = 1; + break; + case BITMODE_MPSSE: + case BITMODE_RESET: + default: + priv->bitbang_enabled = 0; + break; + } + + return 0; +} + +/** + * ftdi_disable_bitbang - disable bitbang mode + * @intf: USB interface pointer + * + * Return: + * If successful, 0. Otherwise a negative error number. + */ +int ftdi_disable_bitbang(struct usb_interface *intf) +{ + int ret; + + ret = ftdi_set_bitmode(intf, 0, BITMODE_RESET); + if (ret < 0) { + dev_dbg(&intf->dev, "disable bitbang failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int ftdi_read_eeprom(struct ft232h_intf_priv *priv) +{ + struct ctrl_desc desc; + unsigned int i; + int ret; + + desc.dir_out = false; + desc.request = FTDI_SIO_READ_EEPROM_REQUEST; + desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN; + desc.value = 0; + desc.size = 2; + desc.timeout = USB_CTRL_GET_TIMEOUT; + + for (i = 0; i < FTDI_MAX_EEPROM_SIZE / 2; i++) { + desc.index = i; + desc.data = &priv->eeprom[i * 2]; + + ret = ftdi_ctrl_xfer(priv->intf, &desc); + if (ret < 0) { + dev_dbg(&priv->intf->dev, "EEPROM read failed: %d\n", + ret); + return ret; + } + } + + print_hex_dump_debug("EEPROM: ", DUMP_PREFIX_OFFSET, 16, 1, + priv->eeprom, sizeof(priv->eeprom), 1); + return 0; +} + +/* + * ACBUS GPIO functions + */ +static const char *ftdi_acbus_names[5] = { + "ACBUS5", "ACBUS6", NULL, "ACBUS8", "ACBUS9" +}; + +static int ftdi_cbus_gpio_read_pins(struct ft232h_intf_priv *priv, + unsigned char *pins) +{ + struct gpio_chip *chip = &priv->cbus_gpio; + struct ctrl_desc desc; + int ret; + + desc.dir_out = false; + desc.request = FTDI_SIO_READ_PINS_REQUEST; + desc.requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN; + desc.value = 0; + desc.index = 1; + desc.data = &priv->pinbuf[0]; + desc.size = 1; + desc.timeout = USB_CTRL_GET_TIMEOUT; + + ret = ftdi_ctrl_xfer(priv->intf, &desc); + if (ret < 0) { + dev_dbg(chip->parent, "failed to get pin values: %d\n", ret); + return ret; + } + + *pins = priv->pinbuf[0]; + return 0; +} + +static inline void ftdi_cbus_init_gpio_data(struct ft232h_intf_priv *priv, + int gpio_num, int cbus_num) +{ + switch (cbus_num) { + case 5: + case 6: + priv->cbus_pin_offsets[gpio_num] = cbus_num - 5; + break; + case 8: + case 9: + priv->cbus_pin_offsets[gpio_num] = cbus_num - 6; + break; + default: + return; + } + + priv->cbus_gpio_names[gpio_num] = ftdi_acbus_names[cbus_num - 5]; +} + +static int ftdi_cbus_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + unsigned int offs; + int ret; + u8 pins = 0; + + ret = ftdi_cbus_gpio_read_pins(priv, &pins); + if (ret) + return ret; + + offs = priv->cbus_pin_offsets[offset]; + + return !!(pins & BIT(offs)); +} + +static void ftdi_cbus_gpio_set(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + unsigned int offs; + int ret; + + offs = priv->cbus_pin_offsets[offset]; + + if (value) + priv->cbus_mask |= BIT(offs); + else + priv->cbus_mask &= ~BIT(offs); + + ret = ftdi_set_bitmode(priv->intf, priv->cbus_mask, BITMODE_CBUS); + if (ret < 0) + dev_dbg(chip->parent, "setting pin value failed: %d\n", ret); +} + +static int ftdi_cbus_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + unsigned int offs; + + offs = priv->cbus_pin_offsets[offset]; + /* Direction bits are in the upper nibble */ + priv->cbus_mask &= ~(BIT(offs) << 4); + + return ftdi_set_bitmode(priv->intf, priv->cbus_mask, BITMODE_CBUS); +} + +static int ftdi_cbus_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + unsigned int offs; + + offs = priv->cbus_pin_offsets[offset]; + priv->cbus_mask |= BIT(offs) << 4; + + if (value) + priv->cbus_mask |= BIT(offs); + else + priv->cbus_mask &= ~BIT(offs); + + return ftdi_set_bitmode(priv->intf, priv->cbus_mask, BITMODE_CBUS); +} + +static int ft232h_intf_add_cbus_gpio(struct ft232h_intf_priv *priv) +{ + struct device *dev = &priv->intf->dev; + char **names, *label; + int ngpio = 0; + int i, ret; + u8 val; + + ret = ftdi_read_eeprom(priv); + if (ret < 0) + return ret; + + /* Check if I/O mode is enabled for supported pins 5, 6, 8, 9 */ + for (i = 5; i < 10; i++) { + val = priv->eeprom[0x18 + i / 2] >> (i % 2 ? 4 : 0); + if ((val & 0x0f) == FTDI_232H_CBUS_IOMODE) { + dev_dbg(dev, "gpio-%d @ ACBUS%d\n", + priv->cbus_gpio.ngpio, i); + priv->cbus_gpio.ngpio++; + ftdi_cbus_init_gpio_data(priv, ngpio++, i); + } + } + + if (!priv->cbus_gpio.ngpio) { + dev_warn(dev, "I/O mode disabled in EEPROM\n"); + return -ENODEV; + } + + label = devm_kasprintf(dev, GFP_KERNEL, "ftdi-cbus-gpio.%d", priv->id); + if (!label) + return -ENOMEM; + + priv->cbus_gpio.label = label; + priv->cbus_gpio.parent = dev; + priv->cbus_gpio.owner = THIS_MODULE; + priv->cbus_gpio.base = -1; + priv->cbus_gpio.can_sleep = true; + priv->cbus_gpio.set = ftdi_cbus_gpio_set; + priv->cbus_gpio.get = ftdi_cbus_gpio_get; + priv->cbus_gpio.direction_input = ftdi_cbus_gpio_direction_input; + priv->cbus_gpio.direction_output = ftdi_cbus_gpio_direction_output; + + names = devm_kcalloc(dev, priv->cbus_gpio.ngpio, sizeof(char *), + GFP_KERNEL); + if (!names) + return -ENOMEM; + + for (i = 0; i < priv->cbus_gpio.ngpio; i++) { + if (!priv->cbus_gpio_names[i]) + continue; + names[i] = devm_kasprintf(dev, GFP_KERNEL, "cbus.%d-%s", + priv->id, priv->cbus_gpio_names[i]); + if (!names[i]) + return -ENOMEM; + } + + priv->cbus_gpio.names = (const char *const *)names; + + ret = devm_gpiochip_add_data(dev, &priv->cbus_gpio, priv); + if (ret) { + dev_warn(dev, "failed to add CBUS gpiochip: %d\n", ret); + return ret; + } + + dev_info(dev, "using %d CBUS pins\n", priv->cbus_gpio.ngpio); + return 0; +} + +/* + * MPSSE CS and GPIO-L/-H support + */ +#define SET_BITS_LOW 0x80 +#define GET_BITS_LOW 0x81 +#define SET_BITS_HIGH 0x82 +#define GET_BITS_HIGH 0x83 + +static int ftdi_mpsse_get_port_pins(struct ft232h_intf_priv *priv, bool low) +{ + struct device *dev = &priv->intf->dev; + int ret, tout = 10; + u8 rxbuf[4]; + + if (low) + priv->tx_buf[0] = GET_BITS_LOW; + else + priv->tx_buf[0] = GET_BITS_HIGH; + + ret = ftdi_write_data(priv->intf, priv->tx_buf, 1); + if (ret < 0) { + dev_dbg_ratelimited(dev, "Writing port pins cmd failed: %d\n", + ret); + return ret; + } + + rxbuf[0] = 0; + do { + usleep_range(5000, 5200); + ret = ftdi_read_data(priv->intf, rxbuf, 1); + tout--; + if (!tout) { + dev_err(dev, "Timeout when getting port pins\n"); + return -ETIMEDOUT; + } + } while (ret == 0); + + if (ret < 0) + return ret; + + if (ret != 1) + return -EINVAL; + + if (low) + priv->gpiol_mask = rxbuf[0]; + else + priv->gpioh_mask = rxbuf[0]; + + return 0; +} + +static int ftdi_mpsse_set_port_pins(struct ft232h_intf_priv *priv, bool low) +{ + struct device *dev = &priv->intf->dev; + int ret; + + if (low) { + priv->tx_buf[0] = SET_BITS_LOW; + priv->tx_buf[1] = priv->gpiol_mask; + priv->tx_buf[2] = priv->gpiol_dir; + } else { + priv->tx_buf[0] = SET_BITS_HIGH; + priv->tx_buf[1] = priv->gpioh_mask; + priv->tx_buf[2] = priv->gpioh_dir; + } + + ret = ftdi_write_data(priv->intf, priv->tx_buf, 3); + if (ret < 0) { + dev_dbg_ratelimited(dev, "Failed to set GPIO pins: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ftdi_mpsse_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + int ret, val; + bool low; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + mutex_unlock(&priv->io_mutex); + return -ENODEV; + } + mutex_unlock(&priv->io_mutex); + + dev_dbg(chip->parent, "%s: offset %d\n", __func__, offset); + + low = offset < 5 ? true : false; + + mutex_lock(&priv->ops_mutex); + + ret = ftdi_mpsse_get_port_pins(priv, low); + if (ret < 0) { + mutex_unlock(&priv->ops_mutex); + return ret; + } + + if (low) + val = priv->gpiol_mask & (BIT(offset) << 3); + else + val = priv->gpioh_mask & BIT(offset - 5); + + mutex_unlock(&priv->ops_mutex); + + return !!val; +} + +static void ftdi_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + bool low; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + mutex_unlock(&priv->io_mutex); + return; + } + mutex_unlock(&priv->io_mutex); + + dev_dbg(chip->parent, "%s: offset %d, val %d\n", + __func__, offset, value); + + mutex_lock(&priv->ops_mutex); + + if (offset < 5) { + low = true; + if (value) + priv->gpiol_mask |= (BIT(offset) << 3); + else + priv->gpiol_mask &= ~(BIT(offset) << 3); + } else { + low = false; + if (value) + priv->gpioh_mask |= BIT(offset - 5); + else + priv->gpioh_mask &= ~BIT(offset - 5); + } + + ftdi_mpsse_set_port_pins(priv, low); + + mutex_unlock(&priv->ops_mutex); +} + +static int ftdi_mpsse_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + bool low; + int ret; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + mutex_unlock(&priv->io_mutex); + return -ENODEV; + } + mutex_unlock(&priv->io_mutex); + + dev_dbg(chip->parent, "%s: offset %d\n", __func__, offset); + + mutex_lock(&priv->ops_mutex); + + if (offset < 5) { + low = true; + priv->gpiol_dir &= ~(BIT(offset) << 3); + } else { + low = false; + priv->gpioh_dir &= ~BIT(offset - 5); + } + + ret = ftdi_mpsse_set_port_pins(priv, low); + + mutex_unlock(&priv->ops_mutex); + + return ret; +} + +static int ftdi_mpsse_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ft232h_intf_priv *priv = gpiochip_get_data(chip); + bool low; + int ret; + + mutex_lock(&priv->io_mutex); + if (!priv->intf) { + mutex_unlock(&priv->io_mutex); + return -ENODEV; + } + mutex_unlock(&priv->io_mutex); + + dev_dbg(chip->parent, "%s: offset %d, val %d\n", + __func__, offset, value); + + mutex_lock(&priv->ops_mutex); + + if (offset < 5) { + low = true; + priv->gpiol_dir |= BIT(offset) << 3; + + if (value) + priv->gpiol_mask |= BIT(offset) << 3; + else + priv->gpiol_mask &= ~(BIT(offset) << 3); + } else { + low = false; + priv->gpioh_dir |= BIT(offset - 5); + + if (value) + priv->gpioh_mask |= BIT(offset - 5); + else + priv->gpioh_mask &= ~BIT(offset - 5); + } + + ret = ftdi_mpsse_set_port_pins(priv, low); + + mutex_unlock(&priv->ops_mutex); + + return ret; +} + +static int ftdi_mpsse_init_pins(struct usb_interface *intf, bool low, + u8 bits, u8 direction) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + int ret; + + mutex_lock(&priv->ops_mutex); + + if (low) { + priv->gpiol_mask = bits; + priv->gpiol_dir = direction; + } else { + priv->gpioh_mask = bits; + priv->gpioh_dir = direction; + } + ret = ftdi_mpsse_set_port_pins(priv, low); + + mutex_unlock(&priv->ops_mutex); + + return ret; +} + +static int ftdi_mpsse_cfg_bus_pins(struct usb_interface *intf, + u8 dir_bits, u8 value_bits) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + int ret; + + mutex_lock(&priv->ops_mutex); + + priv->gpiol_dir &= ~7; + priv->gpiol_dir |= (dir_bits & 7); + + priv->gpiol_mask &= ~7; + priv->gpiol_mask |= (value_bits & 7); + + ret = ftdi_mpsse_set_port_pins(priv, true); + + mutex_unlock(&priv->ops_mutex); + + return ret; +} + +static int ft232h_intf_add_mpsse_gpio(struct ft232h_intf_priv *priv) +{ + struct device *dev = &priv->intf->dev; + char **names, *label; + int i, ret; + + label = devm_kasprintf(dev, GFP_KERNEL, "ftdi-mpsse-gpio.%d", priv->id); + if (!label) + return -ENOMEM; + + priv->mpsse_gpio.label = label; + priv->mpsse_gpio.parent = dev; + priv->mpsse_gpio.owner = THIS_MODULE; + priv->mpsse_gpio.base = -1; + priv->mpsse_gpio.ngpio = 13; + priv->mpsse_gpio.can_sleep = true; + priv->mpsse_gpio.set = ftdi_mpsse_gpio_set; + priv->mpsse_gpio.get = ftdi_mpsse_gpio_get; + priv->mpsse_gpio.direction_input = ftdi_mpsse_gpio_direction_input; + priv->mpsse_gpio.direction_output = ftdi_mpsse_gpio_direction_output; + + names = devm_kcalloc(dev, priv->mpsse_gpio.ngpio, sizeof(char *), + GFP_KERNEL); + if (!names) + return -ENOMEM; + + names[0] = devm_kasprintf(dev, GFP_KERNEL, "mpsse.%d-CS", priv->id); + if (!names[0]) + return -ENOMEM; + + for (i = 1; i < priv->mpsse_gpio.ngpio; i++) { + int offs; + + offs = i < 5 ? 1 : 5; + names[i] = devm_kasprintf(dev, GFP_KERNEL, + "mpsse.%d-GPIO%c%d", priv->id, + i < 5 ? 'L' : 'H', i - offs); + if (!names[i]) + return -ENOMEM; + } + + priv->mpsse_gpio.names = (const char *const *)names; + + ret = ftdi_set_bitmode(priv->intf, 0x00, BITMODE_MPSSE); + if (ret < 0) { + dev_err(dev, "Failed to set MPSSE mode\n"); + return ret; + } + + ret = devm_gpiochip_add_data(dev, &priv->mpsse_gpio, priv); + if (ret < 0) { + dev_err(dev, "Failed to add MPSSE GPIO chip: %d\n", ret); + return ret; + } + + dev_info(dev, "using %d MPSSE pins\n", priv->mpsse_gpio.ngpio); + return 0; +} + +static void ftdi_lock(struct usb_interface *intf) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + + mutex_lock(&priv->ops_mutex); +} + +static void ftdi_unlock(struct usb_interface *intf) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + + mutex_unlock(&priv->ops_mutex); +} + +static const struct ft232h_intf_ops ft232h_intf_ops = { + .ctrl_xfer = ftdi_ctrl_xfer, + .bulk_xfer = ftdi_bulk_xfer, + .read_data = ftdi_read_data, + .write_data = ftdi_write_data, + .lock = ftdi_lock, + .unlock = ftdi_unlock, + .set_bitmode = ftdi_set_bitmode, + .set_baudrate = ftdi_set_baudrate, + .disable_bitbang = ftdi_disable_bitbang, + .init_pins = ftdi_mpsse_init_pins, + .cfg_bus_pins = ftdi_mpsse_cfg_bus_pins, +}; + +/* + * FPGA config interface: FPP via FT245 FIFO + */ +#define FPP_INTF_DEVNAME "ftdi-fifo-fpp-mgr" + +static struct dev_io_desc_data fpga_cfg_fpp_dev_io[2] = { + { "nconfig", 0, GPIO_ACTIVE_LOW }, + { "conf_done", 1, GPIO_ACTIVE_HIGH }, +}; + +static const struct fifo_fpp_mgr_platform_data fpga_cfg_fpp_plat_data = { + .ops = &ft232h_intf_ops, + .io_data = fpga_cfg_fpp_dev_io, + .io_data_len = ARRAY_SIZE(fpga_cfg_fpp_dev_io), + .nconfig_num = 8, + .conf_done_num = 9, +}; + +static int ft232h_intf_fpp_probe(struct usb_interface *intf, + const void *plat_data) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + const struct fifo_fpp_mgr_platform_data *pd = plat_data; + struct device *dev = &intf->dev; + struct platform_device *pdev; + struct gpiod_lookup_table *lookup; + char *cfgdone, *ncfg, *ptr; + size_t lookup_size; + int i, ret, gpios = 0; + + dev_dbg(dev, "%s: plat_data %p\n", __func__, pd); + if (!pd) { + dev_err(dev, "%s: Missing platform data\n", __func__); + return -EINVAL; + } + + ret = ft232h_intf_add_cbus_gpio(priv); + if (ret < 0) + return ret; + + lookup_size = sizeof(*lookup) + 3 * sizeof(struct gpiod_lookup); + lookup = devm_kzalloc(dev, lookup_size, GFP_KERNEL); + if (!lookup) + return -ENOMEM; + + lookup->dev_id = devm_kasprintf(dev, GFP_KERNEL, "%s.%d", + FPP_INTF_DEVNAME, priv->id); + if (!lookup->dev_id) + return -ENOMEM; + + ncfg = devm_kasprintf(dev, GFP_KERNEL, "ACBUS%d", pd->nconfig_num); + if (!ncfg) + return -ENOMEM; + + cfgdone = devm_kasprintf(dev, GFP_KERNEL, "ACBUS%d", pd->conf_done_num); + if (!cfgdone) + return -ENOMEM; + + for (i = 0; i < priv->cbus_gpio.ngpio; i++) { + if (!priv->cbus_gpio.names[i]) + continue; + + ptr = strstr(priv->cbus_gpio.names[i], "ACBUS"); + if (!ptr) + continue; + + if (!strncmp(ptr, ncfg, 6)) { + lookup->table[0].chip_hwnum = i; + gpios++; + continue; + } + if (!strncmp(ptr, cfgdone, 6)) { + lookup->table[1].chip_hwnum = i; + gpios++; + } + } + + /* Does GPIO controller provide all needed ACBUS pins? */ + if (gpios < 2) { + dev_err(dev, "Missing control GPIOs\n"); + return -ENODEV; + } + + for (i = 0; i < pd->io_data_len; i++) { + lookup->table[i].chip_label = priv->cbus_gpio.label; + lookup->table[i].idx = 0; + lookup->table[i].con_id = pd->io_data[i].con_id; + lookup->table[i].flags = pd->io_data[i].flags; + } + + priv->lookup_fifo = lookup; + gpiod_add_lookup_table(priv->lookup_fifo); + + pdev = platform_device_register_data(dev, FPP_INTF_DEVNAME, + priv->id, pd, sizeof(*pd)); + if (IS_ERR(pdev)) { + gpiod_remove_lookup_table(priv->lookup_fifo); + return PTR_ERR(pdev); + } + + priv->fifo_pdev = pdev; + + dev_dbg(dev, "%s: fifo pdev %p\n", __func__, pdev); + return 0; +} + +static int ft232h_intf_fpp_remove(struct usb_interface *intf) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct device *dev = &intf->dev; + + dev_dbg(dev, "%s\n", __func__); + platform_device_unregister(priv->fifo_pdev); + gpiod_remove_lookup_table(priv->lookup_fifo); + return 0; +} + +/* + * FPGA config interface: PS-SPI via MPSSE + */ +#define SPI_INTF_DEVNAME "ftdi-mpsse-spi" + +struct spi_board_info fpga_cfg_spi_info[] = { + { + .modalias = "fpga-passive-serial", + .mode = SPI_MODE_0 | SPI_LSB_FIRST, + .max_speed_hz = 30000000, + .bus_num = 0, + .chip_select = 0, + }, +}; + +static struct dev_io_desc_data fpga_cfg_spi_dev_io[3] = { + { "confd", 1, GPIO_ACTIVE_HIGH }, + { "nstat", 2, GPIO_ACTIVE_LOW }, + { "nconfig", 3, GPIO_ACTIVE_LOW }, +}; + +static const struct mpsse_spi_platform_data fpga_cfg_spi_plat_data = { + .ops = &ft232h_intf_ops, + .spi_info = fpga_cfg_spi_info, + .spi_info_len = ARRAY_SIZE(fpga_cfg_spi_info), + .io_data = fpga_cfg_spi_dev_io, + .io_data_len = ARRAY_SIZE(fpga_cfg_spi_dev_io), +}; + +static struct platform_device *mpsse_dev_register(struct ft232h_intf_priv *priv, + const struct mpsse_spi_platform_data *pd) +{ + struct device *parent = &priv->intf->dev; + struct platform_device *pdev; + struct gpiod_lookup_table *lookup; + size_t lookup_size, tbl_size; + int i, ret; + + pdev = platform_device_alloc(SPI_INTF_DEVNAME, 0); + if (!pdev) + return NULL; + + pdev->dev.parent = parent; + pdev->dev.fwnode = NULL; + priv->spi_pdev = pdev; + + tbl_size = pd->spi_info_len + 1; + lookup_size = sizeof(*lookup) + tbl_size * sizeof(struct gpiod_lookup); + lookup = devm_kzalloc(parent, lookup_size, GFP_KERNEL); + if (!lookup) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < pd->spi_info_len; i++) { + dev_dbg(parent, "INFO: %s cs %d\n", + pd->spi_info[i].modalias, pd->spi_info[i].chip_select); + } + + ret = platform_device_add_data(pdev, pd, sizeof(*pd)); + if (ret) + goto err; + + pdev->id = priv->id; + + ret = ft232h_intf_add_mpsse_gpio(priv); + if (ret < 0) + goto err; + + lookup->dev_id = devm_kasprintf(parent, GFP_KERNEL, "%s.%d", + pdev->name, pdev->id); + if (!lookup->dev_id) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < pd->spi_info_len; i++) { + lookup->table[i].chip_label = priv->mpsse_gpio.label; + lookup->table[i].chip_hwnum = pd->spi_info[i].chip_select; + lookup->table[i].idx = i; + lookup->table[i].con_id = NULL; + if (pd->spi_info[i].mode & SPI_CS_HIGH) + lookup->table[i].flags = GPIO_ACTIVE_HIGH; + else + lookup->table[i].flags = GPIO_ACTIVE_LOW; + } + + priv->lookup_cs = lookup; + gpiod_add_lookup_table(priv->lookup_cs); + + ret = platform_device_add(pdev); + if (ret < 0) + goto err_add; + + dev_dbg(&pdev->dev, "%s done\n", __func__); + return pdev; + +err_add: + gpiod_remove_lookup_table(priv->lookup_cs); +err: + platform_device_put(pdev); + return ERR_PTR(ret); +} + +static int ft232h_intf_spi_probe(struct usb_interface *intf, + const void *plat_data) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct device *dev = &intf->dev; + struct platform_device *pdev; + + pdev = mpsse_dev_register(priv, plat_data); + if (IS_ERR(pdev)) { + dev_info(dev, "%s: Can't create MPSSE SPI device %ld\n", + __func__, PTR_ERR(pdev)); + return PTR_ERR(pdev); + } + + priv->spi_pdev = pdev; + return 0; +} + +static int ft232h_intf_spi_remove(struct usb_interface *intf) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + struct device *dev = &intf->dev; + + dev_dbg(dev, "%s: spi pdev %p\n", __func__, priv->spi_pdev); + gpiod_remove_lookup_table(priv->lookup_cs); + platform_device_unregister(priv->spi_pdev); + return 0; +} + +static const struct ft232h_intf_info fpga_cfg_spi_intf_info = { + .probe = ft232h_intf_spi_probe, + .remove = ft232h_intf_spi_remove, + .plat_data = &fpga_cfg_spi_plat_data, +}; + +static const struct ft232h_intf_info fpga_cfg_fifo_intf_info = { + .probe = ft232h_intf_fpp_probe, + .remove = ft232h_intf_fpp_remove, + .plat_data = &fpga_cfg_fpp_plat_data, +}; + +static const struct ft232h_intf_info ftdi_cbus_gpio_intf_info = { + .use_cbus_gpio_ctrl = true, +}; + +static const struct ft232h_intf_info ftdi_mpsse_gpio_intf_info = { + .use_mpsse_gpio_ctrl = true, +}; + +static int ft232h_intf_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct ft232h_intf_priv *priv; + struct device *dev = &intf->dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + const struct ft232h_intf_info *info; + unsigned int i; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + iface_desc = intf->cur_altsetting; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_out(endpoint)) + priv->bulk_out = endpoint->bEndpointAddress; + + if (usb_endpoint_is_bulk_in(endpoint)) { + priv->bulk_in = endpoint->bEndpointAddress; + priv->bulk_in_sz = usb_endpoint_maxp(endpoint); + } + } + + priv->usb_dev_id = id; + priv->index = 1; + priv->intf = intf; + priv->info = (struct ft232h_intf_info *)id->driver_info; + + info = priv->info; + if (!info) { + dev_info(dev, "Missing device specific driver info...\n"); + return -ENODEV; + } + + mutex_init(&priv->io_mutex); + mutex_init(&priv->ops_mutex); + usb_set_intfdata(intf, priv); + + priv->bulk_in_buf = devm_kmalloc(dev, priv->bulk_in_sz, GFP_KERNEL); + if (!priv->bulk_in_buf) + return -ENOMEM; + + priv->udev = usb_get_dev(interface_to_usbdev(intf)); + + priv->id = ida_simple_get(&ftdi_devid_ida, 0, 0, GFP_KERNEL); + if (priv->id < 0) + return priv->id; + + if (info->probe) { + ret = info->probe(intf, info->plat_data); + if (ret < 0) + goto err; + return 0; + } + + /* for simple GPIO-only devices */ + ret = -ENODEV; + if (info->use_cbus_gpio_ctrl) + ret = ft232h_intf_add_cbus_gpio(priv); + else if (info->use_mpsse_gpio_ctrl) + ret = ft232h_intf_add_mpsse_gpio(priv); + if (!ret) + return 0; +err: + ida_simple_remove(&ftdi_devid_ida, priv->id); + return ret; +} + +static void ft232h_intf_disconnect(struct usb_interface *intf) +{ + struct ft232h_intf_priv *priv = usb_get_intfdata(intf); + const struct ft232h_intf_info *info; + + info = (struct ft232h_intf_info *)priv->usb_dev_id->driver_info; + if (info && info->remove) + info->remove(intf); + + if (info->use_mpsse_gpio_ctrl) + devm_gpiochip_remove(&intf->dev, &priv->mpsse_gpio); + + if (info->use_cbus_gpio_ctrl) + devm_gpiochip_remove(&intf->dev, &priv->cbus_gpio); + + mutex_lock(&priv->io_mutex); + priv->intf = NULL; + usb_set_intfdata(intf, NULL); + mutex_unlock(&priv->io_mutex); + + usb_put_dev(priv->udev); + ida_simple_remove(&ftdi_devid_ida, priv->id); +} + +#define FTDI_VID 0x0403 +#define ARRI_FPP_INTF_PRODUCT_ID 0x7148 +#define ARRI_SPI_INTF_PRODUCT_ID 0x7149 + +static struct usb_device_id ft232h_intf_table[] = { + { USB_DEVICE(FTDI_VID, ARRI_FPP_INTF_PRODUCT_ID), + .driver_info = (kernel_ulong_t)&fpga_cfg_fifo_intf_info }, + { USB_DEVICE(FTDI_VID, ARRI_SPI_INTF_PRODUCT_ID), + .driver_info = (kernel_ulong_t)&fpga_cfg_spi_intf_info }, + {} +}; +MODULE_DEVICE_TABLE(usb, ft232h_intf_table); + +static struct usb_driver ft232h_intf_driver = { + .name = "ft232h-intf", + .id_table = ft232h_intf_table, + .probe = ft232h_intf_probe, + .disconnect = ft232h_intf_disconnect, +}; + +module_usb_driver(ft232h_intf_driver); + +MODULE_ALIAS("ft232h-intf"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_DESCRIPTION("FT232H to FPGA interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/ft232h-intf.h b/include/linux/usb/ft232h-intf.h new file mode 100644 index 000000000000..2f4b2191bd21 --- /dev/null +++ b/include/linux/usb/ft232h-intf.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Common definitions for FTDI FT232H interface device + * + * Copyright (C) 2018 DENX Software Engineering + * Anatolij Gustschin + */ + +#ifndef __LINUX_FT232H_INTF_H +#define __LINUX_FT232H_INTF_H + +/* Used FTDI USB Requests */ +#define FTDI_SIO_RESET_REQUEST 0x00 +#define FTDI_SIO_SET_BAUDRATE_REQUEST 0x03 +#define FTDI_SIO_SET_BITMODE_REQUEST 0x0B +#define FTDI_SIO_READ_PINS_REQUEST 0x0C +#define FTDI_SIO_READ_EEPROM_REQUEST 0x90 + +/* MPSSE Commands */ +#define TX_BYTES_RE_MSB 0x10 /* tx on +ve clk (rising edge) */ +#define TX_BYTES_FE_MSB 0x11 /* tx on -ve clk (falling edge) */ +#define RX_BYTES_RE_MSB 0x20 +#define RX_BYTES_FE_MSB 0x24 +#define TXF_RXR_BYTES_MSB 0x31 /* tx on -ve clk, rx on +ve */ +#define TXR_RXF_BYTES_MSB 0x34 /* tx on +ve clk, rx on -ve */ + +#define TX_BYTES_RE_LSB 0x18 /* tx on +ve clk */ +#define TX_BYTES_FE_LSB 0x19 /* tx on -ve clk */ +#define RX_BYTES_RE_LSB 0x28 +#define RX_BYTES_FE_LSB 0x2C +#define TXF_RXR_BYTES_LSB 0x39 /* tx on -ve clk, rx on +ve */ +#define TXR_RXF_BYTES_LSB 0x3C /* tx on +ve clk, rx on -ve */ + +#define LOOPBACK_ON 0x84 +#define LOOPBACK_OFF 0x85 +#define TCK_DIVISOR 0x86 +#define DIS_DIV_5 0x8A +#define EN_DIV_5 0x8B +#define EN_3_PHASE 0x8C +#define DIS_3_PHASE 0x8D +#define DIS_ADAPTIVE 0x97 + +/* For EEPROM I/O mode */ +#define FTDI_MAX_EEPROM_SIZE 256 +#define FTDI_232H_CBUS_IOMODE 0x08 + +#define FTDI_USB_READ_TIMEOUT 5000 +#define FTDI_USB_WRITE_TIMEOUT 5000 + +/* MPSSE bitbang modes (copied from libftdi) */ +enum ftdi_mpsse_mode { + BITMODE_RESET = 0x00, /* switch off bitbang mode */ + BITMODE_BITBANG = 0x01, /* asynchronous bitbang mode */ + BITMODE_MPSSE = 0x02, /* MPSSE mode, on 2232x chips */ + BITMODE_SYNCBB = 0x04, /* synchronous bitbang mode */ + BITMODE_MCU = 0x08, /* MCU Host Bus Emulation mode */ + /* CPU-style fifo mode gets set via EEPROM */ + BITMODE_OPTO = 0x10, /* Fast Opto-Isolated Serial Interface Mode */ + BITMODE_CBUS = 0x20, /* Bitbang on CBUS pins, EEPROM config needed */ + BITMODE_SYNCFF = 0x40, /* Single Channel Synchronous FIFO mode */ + BITMODE_FT1284 = 0x80, /* FT1284 mode, available on 232H chips */ +}; + +struct ctrl_desc { + unsigned int dir_out; + u8 request; + u8 requesttype; + u16 value; + u16 index; + u16 size; + void *data; + int timeout; +}; + +struct bulk_desc { + unsigned int dir_out; + void *data; + int len; + int act_len; + int timeout; +}; + +/* + * The FTDI protocol functions, mainly borrowed from libftdi + */ +int ftdi_ctrl_xfer(struct usb_interface *intf, struct ctrl_desc *desc); +int ftdi_bulk_xfer(struct usb_interface *intf, struct bulk_desc *desc); +int ftdi_read_data(struct usb_interface *intf, void *buf, size_t len); +int ftdi_write_data(struct usb_interface *intf, const char *buf, size_t len); +int ftdi_set_baudrate(struct usb_interface *intf, int baudrate); +int ftdi_set_bitmode(struct usb_interface *intf, unsigned char bitmask, + unsigned char mode); +int ftdi_disable_bitbang(struct usb_interface *intf); + +/* + * struct ft232h_intf_ops - FT232H interface operations for upper drivers + * + * @bulk_xfer: FTDI USB bulk transfer + * @ctrl_xfer: FTDI USB control transfer + * @read_data: read 'len' bytes from FTDI device to the given buffer + * @write_data: write 'len' bytes from the given buffer to the FTDI device + * @lock: lock the interface for an operation sequence. Used when multiple + * command and/or data operations must be executed in a specific order + * (when other intermediate command/data transfers may not interfere) + * @unlock: unlock the previously locked interface + * @set_bitmode: configure FTDI bit mode + * @set_baudrate: configure FTDI baudrate + * @disable_bitbang: turn off bitbang mode + * @init_pins: initialize GPIOL/GPIOH port pins in MPSSE mode + * @cfg_bus_pins: configure MPSSE SPI bus pins + * + * Common FT232H interface USB xfer and device configuration operations used + * in FIFO-FPP, MPSSE-SPI or MPSSE-I2C drivers + */ +struct ft232h_intf_ops { + int (*bulk_xfer)(struct usb_interface *intf, struct bulk_desc *desc); + int (*ctrl_xfer)(struct usb_interface *intf, struct ctrl_desc *desc); + int (*read_data)(struct usb_interface *intf, void *buf, size_t len); + int (*write_data)(struct usb_interface *intf, const char *buf, + size_t len); + void (*lock)(struct usb_interface *intf); + void (*unlock)(struct usb_interface *intf); + int (*set_bitmode)(struct usb_interface *intf, unsigned char bitmask, + unsigned char mode); + int (*set_baudrate)(struct usb_interface *intf, int baudrate); + int (*disable_bitbang)(struct usb_interface *intf); + int (*init_pins)(struct usb_interface *intf, bool low, u8 bits, u8 dir); + int (*cfg_bus_pins)(struct usb_interface *intf, u8 dir_bits, + u8 value_bits); +}; + +/* + * struct dev_io_desc_data - Descriptor of FT232H pin used by attached device + * @con_id: Name of the GPIO pin + * @idx: Index of the pin + * @flags: GPIOD flags of the pin + * + * Description of a GPIO used by device connected to FT232H + */ +struct dev_io_desc_data { + const char *con_id; + unsigned int idx; + unsigned int flags; +}; + +/* + * struct fifo_fpp_mgr_platform_data - FIFO/FPP device platform data + * @ops: USB interface operations used in FPP manager driver + * @io_data: Array with descriptors of used I/O pins + * @io_data_len: Length of io_data array + * @nconfig_num: ACBUS pin number of the NCONFIG pin + * @conf_done_num: ACBUS pin number of the CONF_DONE pin + * + * FIFO/FPP fpga manager specific platform data + */ +struct fifo_fpp_mgr_platform_data { + const struct ft232h_intf_ops *ops; + struct dev_io_desc_data *io_data; + size_t io_data_len; + int nconfig_num; + int conf_done_num; +}; + +/* + * struct mpsse_spi_platform_data - MPSSE SPI bus platform data + * @ops: USB interface operations used in MPSSE SPI controller driver + * @spi_info: Array with spi_board_info structures of attached SPI slaves + * @spi_info_len: Length of spi_info array + * @io_data: Array with descriptors of used I/O pins + * @io_data_len: Length of io_data array + * + * MPSSE SPI specific platform data describing attached SPI slaves and + * their additional I/O pins + */ +struct mpsse_spi_platform_data { + const struct ft232h_intf_ops *ops; + struct spi_board_info *spi_info; + size_t spi_info_len; + struct dev_io_desc_data *io_data; + size_t io_data_len; +}; + +/* + * Value HIGH. rate is 12000000 / ((1 + value) * 2) + */ +static inline int div_value(int rate) +{ + int r; + + if (rate >= 6000000) + return 0; + r = 6000000 / rate - 1; + if (r < 0xffff) + return r; + return 0xffff; +} + +#endif /* __LINUX_FT232H_INTF_H */