From patchwork Thu Feb 21 20:25:04 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anatolij Gustschin X-Patchwork-Id: 10824757 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 0B60C15AC for ; Thu, 21 Feb 2019 20:25:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F007A31C5F for ; Thu, 21 Feb 2019 20:25:32 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E3BA831D18; Thu, 21 Feb 2019 20:25:32 +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 87B5931D03 for ; Thu, 21 Feb 2019 20:25:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726074AbfBUUZY (ORCPT ); Thu, 21 Feb 2019 15:25:24 -0500 Received: from mail-out.m-online.net ([212.18.0.9]:40661 "EHLO mail-out.m-online.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726075AbfBUUZT (ORCPT ); Thu, 21 Feb 2019 15:25:19 -0500 Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 4455Yz1HRbz1qxQ5; Thu, 21 Feb 2019 21:25:11 +0100 (CET) Received: from localhost (dynscan1.mnet-online.de [192.168.6.70]) by mail.m-online.net (Postfix) with ESMTP id 4455Yz0VDrz1qswn; Thu, 21 Feb 2019 21:25:11 +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 ie4-QGS8udEJ; Thu, 21 Feb 2019 21:25:07 +0100 (CET) X-Auth-Info: yetTfUJFsOxCTAaJHYKCOfuLdxN7udPVqFy1xFdehqo= Received: from localhost.localdomain (p54833194.dip0.t-ipconnect.de [84.131.49.148]) (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; Thu, 21 Feb 2019 21:25:07 +0100 (CET) From: Anatolij Gustschin To: linux-usb@vger.kernel.org, linux-spi@vger.kernel.org, linux-fpga@vger.kernel.org, linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, broonie@kernel.org, atull@kernel.org, mdf@kernel.org Subject: [PATCH v4 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Date: Thu, 21 Feb 2019 21:25:04 +0100 Message-Id: <20190221202506.17744-2-agust@denx.de> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190221202506.17744-1-agust@denx.de> References: <20190221202506.17744-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 --- MAINTAINERS | 8 + drivers/usb/misc/Kconfig | 10 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/ft232h-intf.c | 1467 +++++++++++++++++++++++++++++++ include/linux/usb/ft232h-intf.h | 206 +++++ 5 files changed, 1692 insertions(+) create mode 100644 drivers/usb/misc/ft232h-intf.c create mode 100644 include/linux/usb/ft232h-intf.h diff --git a/MAINTAINERS b/MAINTAINERS index 8b53eb3db271..2a489b63d138 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15968,6 +15968,14 @@ S: Maintained F: Documentation/usb/acm.txt F: drivers/usb/class/cdc-acm.* +USB ARRI FPGA CONFIGURATION ADAPTER DRIVER +M: Anatolij Gustschin +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/misc/ft232h-intf.c +F: drivers/spi/spi-ftdi-mpsse.c +F: include/linux/usb/ft232h-intf.h + USB AR5523 WIRELESS DRIVER M: Pontus Fuchs L: linux-wireless@vger.kernel.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index be04c117fe80..439efc543bb3 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -276,3 +276,13 @@ 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" + depends on GPIOLIB + 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..af935effb019 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_USB_CYTHERM) += cytherm.o obj-$(CONFIG_USB_EMI26) += emi26.o obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o +obj-$(CONFIG_USB_FT232H_INTF) += ft232h-intf.o obj-$(CONFIG_USB_FTDI_ELAN) += ftdi-elan.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o diff --git a/drivers/usb/misc/ft232h-intf.c b/drivers/usb/misc/ft232h-intf.c new file mode 100644 index 000000000000..6f99257b9fbd --- /dev/null +++ b/drivers/usb/misc/ft232h-intf.c @@ -0,0 +1,1467 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FTDI FT232H interface driver for ARRI FPGA configuration + * + * Copyright (C) 2017 - 2018 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: + * + * static 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 +#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 unsigned int ftdi_to_clkbits(unsigned int baudrate, unsigned int clk, + unsigned int clk_div, + unsigned int *encoded_divisor) +{ + static const char frac_code[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + unsigned int best_baud = 0; + unsigned int 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 + */ + best_div = DIV_ROUND_CLOSEST(clk * 8 / clk_div, baudrate); + + if (best_div > 0x20000) + best_div = 0x1ffff; + + best_baud = DIV_ROUND_CLOSEST(clk * 8 / clk_div, best_div); + + *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 int encoded_divisor = 0; + unsigned int best_baud; + + 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) { + pr_err("Invalid baudrate: %d\n", 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 %u, 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. + */ +static 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. + */ +static 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. + */ +static 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. + */ +static 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. + */ +static 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. + */ +static 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. + */ +static 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; + + 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 = FTDI_MPSSE_GPIOS; + 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; + } + + 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" + +static struct dev_io_desc_data fpga_cfg_spi_dev_io[] = { + { "confd", 1, GPIO_ACTIVE_HIGH }, + { "nstat", 2, GPIO_ACTIVE_LOW }, + { "nconfig", 3, GPIO_ACTIVE_LOW }, +}; + +static const struct mpsse_spi_dev_data fpga_spi_dev_data[] = { + { + .magic = FTDI_MPSSE_IO_DESC_MAGIC, + .desc = fpga_cfg_spi_dev_io, + .desc_len = ARRAY_SIZE(fpga_cfg_spi_dev_io), + }, +}; + +static 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, + .platform_data = fpga_spi_dev_data, + }, +}; + +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), +}; + +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_err(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 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_err(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) + gpiochip_remove(&priv->mpsse_gpio); + + if (info->use_cbus_gpio_ctrl) + gpiochip_remove(&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 = KBUILD_MODNAME, + .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..7e6857263db2 --- /dev/null +++ b/include/linux/usb/ft232h-intf.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Common definitions for FTDI FT232H interface device + * + * Copyright (C) 2017 - 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 + +/* Total number of MPSSE GPIOs: 4x GPIOL, 8x GPIOH, 1x CS on ADBUS3 */ +#define FTDI_MPSSE_GPIOS 13 + +/* 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; +}; + +/* + * 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. Many of them are like FTDI + * protocol functions, which I mainly borrowed from libftdi + */ +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; +}; + +#define FTDI_MPSSE_IO_DESC_MAGIC 0x5345494F +/* + * struct mpsse_spi_dev_data - MPSSE SPI device platform data + * @magic: Special # indicating that this is a I/O descriptor struct + * @io_data: Array with descriptors of used I/O pins + * @io_data_len: Length of io_data array + * + * MPSSE SPI slave specific platform data describing additional + * I/O pins (if any) of attached SPI slave. It is supposed to be + * passed via .platform_data of spi_board_info struct. + * To differentiate between MPSSE I/O descriptor data and other + * driver-specific platform data we use FTDI_MPSSE_IO_DESC_MAGIC + * in the header of this struct + */ +struct mpsse_spi_dev_data { + u32 magic; + struct dev_io_desc_data *desc; + size_t desc_len; +}; + +/* + * 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 + * + * MPSSE SPI bus specific platform data describing attached SPI slaves + * and optionally their additional I/O pins (.platform_data of spi_info) + */ +struct mpsse_spi_platform_data { + const struct ft232h_intf_ops *ops; + struct spi_board_info *spi_info; + size_t spi_info_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 */ From patchwork Thu Feb 21 20:25:05 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anatolij Gustschin X-Patchwork-Id: 10824761 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 CEADF15AC for ; Thu, 21 Feb 2019 20:25:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C294631D2F for ; Thu, 21 Feb 2019 20:25:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B705C31D33; Thu, 21 Feb 2019 20:25:36 +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 9698431D03 for ; Thu, 21 Feb 2019 20:25:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726640AbfBUUZS (ORCPT ); Thu, 21 Feb 2019 15:25:18 -0500 Received: from mail-out.m-online.net ([212.18.0.9]:43473 "EHLO mail-out.m-online.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726074AbfBUUZR (ORCPT ); Thu, 21 Feb 2019 15:25:17 -0500 Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 4455Z11y5fz1qxPv; Thu, 21 Feb 2019 21:25:13 +0100 (CET) Received: from localhost (dynscan1.mnet-online.de [192.168.6.70]) by mail.m-online.net (Postfix) with ESMTP id 4455Z11CC7z1qswm; Thu, 21 Feb 2019 21:25:13 +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 i91KSpMyLt8u; Thu, 21 Feb 2019 21:25:11 +0100 (CET) X-Auth-Info: RyTe3YqE72ITZbPIX8xfJFtD4vir7o3IICDhtIzqTww= Received: from localhost.localdomain (p54833194.dip0.t-ipconnect.de [84.131.49.148]) (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; Thu, 21 Feb 2019 21:25:11 +0100 (CET) From: Anatolij Gustschin To: linux-usb@vger.kernel.org, linux-spi@vger.kernel.org, linux-fpga@vger.kernel.org, linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, broonie@kernel.org, atull@kernel.org, mdf@kernel.org Subject: [PATCH v4 2/3] spi: add FTDI MPSSE SPI controller driver Date: Thu, 21 Feb 2019 21:25:05 +0100 Message-Id: <20190221202506.17744-3-agust@denx.de> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190221202506.17744-1-agust@denx.de> References: <20190221202506.17744-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 SPI bus controller driver for FTDI MPSSE mode. This driver is supposed to be used together with the FT232H interface driver for FPGA configuration in drivers/usb/misc/ft232h-intf.c which adds an mpsse spi platform device describing USB SPI bus with attached SPI slave devices. Signed-off-by: Anatolij Gustschin --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-ftdi-mpsse.c | 651 +++++++++++++++++++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 drivers/spi/spi-ftdi-mpsse.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f761655e2a36..38015574e2dc 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -289,6 +289,13 @@ config SPI_NXP_FLEXSPI This controller does not support generic SPI messages and only supports the high-level SPI memory interface. +config SPI_FTDI_MPSSE + tristate "FTDI MPSSE SPI controller" + depends on USB_FT232H_INTF || COMPILE_TEST + help + FT232H supports SPI in MPSSE mode. This driver provides MPSSE + SPI controller in master mode. + config SPI_GPIO tristate "GPIO-based bitbanging SPI Master" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index d8fc03c9faa2..2a1fd2c4d2bd 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_SPI_FSL_ESPI) += spi-fsl-espi.o obj-$(CONFIG_SPI_FSL_LPSPI) += spi-fsl-lpspi.o obj-$(CONFIG_SPI_FSL_QUADSPI) += spi-fsl-qspi.o obj-$(CONFIG_SPI_FSL_SPI) += spi-fsl-spi.o +obj-$(CONFIG_SPI_FTDI_MPSSE) += spi-ftdi-mpsse.o obj-$(CONFIG_SPI_GPIO) += spi-gpio.o obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o obj-$(CONFIG_SPI_IMX) += spi-imx.o diff --git a/drivers/spi/spi-ftdi-mpsse.c b/drivers/spi/spi-ftdi-mpsse.c new file mode 100644 index 000000000000..ae8442eb4052 --- /dev/null +++ b/drivers/spi/spi-ftdi-mpsse.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// FTDI FT232H MPSSE SPI controller driver +// +// Copyright (C) 2017 - 2018 DENX Software Engineering +// Anatolij Gustschin +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum gpiol { + MPSSE_SK = BIT(0), + MPSSE_DO = BIT(1), + MPSSE_DI = BIT(2), + MPSSE_CS = BIT(3), +}; + +struct ftdi_spi { + struct platform_device *pdev; + struct usb_interface *intf; + struct spi_controller *master; + const struct ft232h_intf_ops *iops; + struct gpiod_lookup_table *lookup[FTDI_MPSSE_GPIOS]; + struct gpio_desc **cs_gpios; + + u8 txrx_cmd; + u8 rx_cmd; + u8 tx_cmd; + u8 xfer_buf[SZ_64K]; + u16 last_mode; +}; + +static void ftdi_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct ftdi_spi *priv = spi_controller_get_devdata(spi->master); + u16 cs = spi->chip_select; + + dev_dbg(&priv->pdev->dev, "%s: CS %u, cs mode %d, val %d\n", + __func__, cs, (spi->mode & SPI_CS_HIGH), enable); + + gpiod_set_raw_value_cansleep(priv->cs_gpios[cs], enable); +} + +static inline u8 ftdi_spi_txrx_byte_cmd(struct spi_device *spi) +{ + u8 mode = spi->mode & (SPI_CPOL | SPI_CPHA); + u8 cmd; + + if (spi->mode & SPI_LSB_FIRST) { + switch (mode) { + case SPI_MODE_0: + case SPI_MODE_1: + cmd = TXF_RXR_BYTES_LSB; + break; + case SPI_MODE_2: + case SPI_MODE_3: + cmd = TXR_RXF_BYTES_LSB; + break; + } + } else { + switch (mode) { + case SPI_MODE_0: + case SPI_MODE_1: + cmd = TXF_RXR_BYTES_MSB; + break; + case SPI_MODE_2: + case SPI_MODE_3: + cmd = TXR_RXF_BYTES_MSB; + break; + } + } + return cmd; +} + +static inline int ftdi_spi_loopback_cfg(struct ftdi_spi *priv, int on) +{ + int ret; + + priv->xfer_buf[0] = on ? LOOPBACK_ON : LOOPBACK_OFF; + + ret = priv->iops->write_data(priv->intf, priv->xfer_buf, 1); + if (ret < 0) + dev_warn(&priv->pdev->dev, "loopback %d failed\n", on); + return ret; +} + +static int ftdi_spi_tx_rx(struct ftdi_spi *priv, struct spi_device *spi, + struct spi_transfer *t) +{ + const struct ft232h_intf_ops *ops = priv->iops; + struct device *dev = &priv->pdev->dev; + void *rx_offs; + const void *tx_offs; + size_t remaining, stride; + size_t rx_stride; + int ret, tout = 10; + const u8 *tx_data = t->tx_buf; + u8 *rx_data = t->rx_buf; + + ops->lock(priv->intf); + + if (spi->mode & SPI_LOOP) { + ret = ftdi_spi_loopback_cfg(priv, 1); + if (ret < 0) + goto err; + } + + remaining = t->len; + rx_offs = rx_data; + tx_offs = tx_data; + + while (remaining) { + stride = min_t(size_t, remaining, SZ_512 - 3); + + priv->xfer_buf[0] = priv->txrx_cmd; + priv->xfer_buf[1] = stride - 1; + priv->xfer_buf[2] = (stride - 1) >> 8; + memcpy(&priv->xfer_buf[3], tx_offs, stride); + print_hex_dump_debug("WR: ", DUMP_PREFIX_OFFSET, 16, 1, + priv->xfer_buf, stride + 3, 1); + + ret = ops->write_data(priv->intf, priv->xfer_buf, stride + 3); + if (ret < 0) { + dev_err(dev, "%s: xfer failed %d\n", __func__, ret); + goto fail; + } + dev_dbg(dev, "%s: WR %zu byte(s), TXRX CMD 0x%02x\n", + __func__, stride, priv->txrx_cmd); + + rx_stride = min_t(size_t, stride, SZ_512); + + ret = ops->read_data(priv->intf, priv->xfer_buf, rx_stride); + while (ret == 0) { + /* If no data yet, wait and repeat */ + usleep_range(5000, 5100); + ret = ops->read_data(priv->intf, priv->xfer_buf, + rx_stride); + dev_dbg(dev, "Waiting data ready, read: %d\n", ret); + if (!--tout) { + dev_err(dev, "Read timeout\n"); + ret = -ETIMEDOUT; + goto fail; + } + } + + if (ret < 0) + goto fail; + + print_hex_dump_debug("RD: ", DUMP_PREFIX_OFFSET, 16, 1, + priv->xfer_buf, rx_stride, 1); + memcpy(rx_offs, priv->xfer_buf, ret); + rx_offs += ret; + + remaining -= stride; + tx_offs += stride; + dev_dbg(dev, "%s: WR remains %zu\n", __func__, remaining); + } + + ret = 0; + +fail: + if (spi->mode & SPI_LOOP) + ftdi_spi_loopback_cfg(priv, 0); + +err: + ops->unlock(priv->intf); + return ret; +} + +static int ftdi_spi_push_buf(struct ftdi_spi *priv, const void *buf, size_t len) +{ + size_t bytesleft = len; + int ret; + + do { + ret = priv->iops->write_data(priv->intf, buf, bytesleft); + if (ret < 0) + return ret; + + buf += ret; + bytesleft -= ret; + } while (bytesleft); + + return len; +} + +static int ftdi_spi_tx(struct ftdi_spi *priv, struct spi_transfer *xfer) +{ + const void *tx_offs; + size_t remaining, stride; + int ret; + + priv->iops->lock(priv->intf); + + tx_offs = xfer->tx_buf; + remaining = xfer->len; + + do { + stride = min_t(size_t, remaining, sizeof(priv->xfer_buf) - 3); + + priv->xfer_buf[0] = priv->tx_cmd; + priv->xfer_buf[1] = stride - 1; + priv->xfer_buf[2] = (stride - 1) >> 8; + + memcpy(&priv->xfer_buf[3], tx_offs, stride); + + ret = ftdi_spi_push_buf(priv, priv->xfer_buf, stride + 3); + if (ret < 0) { + dev_dbg(&priv->pdev->dev, "%s: tx failed %d\n", + __func__, ret); + goto err; + } + dev_dbg(&priv->pdev->dev, "%s: %zu byte(s) done\n", + __func__, stride); + remaining -= stride; + tx_offs += stride; + } while (remaining); + + ret = 0; +err: + priv->iops->unlock(priv->intf); + return ret; +} + +static int ftdi_spi_rx(struct ftdi_spi *priv, struct spi_transfer *xfer) +{ + const struct ft232h_intf_ops *ops = priv->iops; + struct device *dev = &priv->pdev->dev; + size_t remaining, stride; + int ret, tout = 10; + void *rx_offs; + + dev_dbg(dev, "%s: CMD 0x%02x, len %u\n", + __func__, priv->rx_cmd, xfer->len); + + priv->xfer_buf[0] = priv->rx_cmd; + priv->xfer_buf[1] = xfer->len - 1; + priv->xfer_buf[2] = (xfer->len - 1) >> 8; + + ops->lock(priv->intf); + + ret = ops->write_data(priv->intf, priv->xfer_buf, 3); + if (ret < 0) + goto err; + + remaining = xfer->len; + rx_offs = xfer->rx_buf; + + do { + stride = min_t(size_t, remaining, SZ_512); + + ret = ops->read_data(priv->intf, priv->xfer_buf, stride); + if (ret < 0) + goto err; + + if (!ret) { + dev_dbg(dev, "Waiting for data (read: %02X), tout %d\n", + ret, tout); + if (--tout) + continue; + + dev_dbg(dev, "read timeout...\n"); + ret = -ETIMEDOUT; + goto err; + } + + memcpy(rx_offs, priv->xfer_buf, ret); + + dev_dbg(dev, "%s: %d byte(s)\n", __func__, ret); + rx_offs += ret; + remaining -= ret; + } while (remaining); + + ret = 0; +err: + ops->unlock(priv->intf); + return ret; +} + +static int ftdi_spi_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct ftdi_spi *priv = spi_controller_get_devdata(ctlr); + struct device *dev = &priv->pdev->dev; + int ret = 0; + + if (!xfer->len) + return 0; + + if (priv->last_mode != spi->mode) { + u8 spi_mode = spi->mode & (SPI_CPOL | SPI_CPHA); + u8 pins = 0; + + dev_dbg(dev, "%s: MODE 0x%x\n", __func__, spi->mode); + + if (spi->mode & SPI_LSB_FIRST) { + switch (spi_mode) { + case SPI_MODE_0: + case SPI_MODE_3: + priv->tx_cmd = TX_BYTES_FE_LSB; + priv->rx_cmd = RX_BYTES_RE_LSB; + break; + case SPI_MODE_1: + case SPI_MODE_2: + priv->tx_cmd = TX_BYTES_RE_LSB; + priv->rx_cmd = RX_BYTES_FE_LSB; + break; + } + } else { + switch (spi_mode) { + case SPI_MODE_0: + case SPI_MODE_3: + priv->tx_cmd = TX_BYTES_FE_MSB; + priv->rx_cmd = RX_BYTES_RE_MSB; + break; + case SPI_MODE_1: + case SPI_MODE_2: + priv->tx_cmd = TX_BYTES_RE_MSB; + priv->rx_cmd = RX_BYTES_FE_MSB; + break; + } + } + + priv->txrx_cmd = ftdi_spi_txrx_byte_cmd(spi); + + switch (spi_mode) { + case SPI_MODE_2: + case SPI_MODE_3: + pins |= MPSSE_SK; + break; + } + + ret = priv->iops->cfg_bus_pins(priv->intf, + MPSSE_SK | MPSSE_DO, pins); + if (ret < 0) { + dev_err(dev, "IO cfg failed: %d\n", ret); + return ret; + } + priv->last_mode = spi->mode; + } + + dev_dbg(dev, "%s: mode 0x%x, CMD RX/TX 0x%x/0x%x\n", + __func__, spi->mode, priv->rx_cmd, priv->tx_cmd); + + if (xfer->tx_buf && xfer->rx_buf) + ret = ftdi_spi_tx_rx(priv, spi, xfer); + else if (xfer->tx_buf) + ret = ftdi_spi_tx(priv, xfer); + else if (xfer->rx_buf) + ret = ftdi_spi_rx(priv, xfer); + + dev_dbg(dev, "%s: xfer ret %d\n", __func__, ret); + + spi_finalize_current_transfer(ctlr); + return ret; +} + +static int ftdi_mpsse_init(struct ftdi_spi *priv) +{ + struct platform_device *pdev = priv->pdev; + int ret; + + dev_dbg(&pdev->dev, "MPSSE init\n"); + + /* Setup and send off the Hi-Speed specific commands for the FTx232H */ + priv->xfer_buf[0] = DIS_DIV_5; /* Use 60MHz master clock */ + priv->xfer_buf[1] = DIS_ADAPTIVE; /* Turn off adaptive clocking */ + priv->xfer_buf[2] = DIS_3_PHASE; /* Disable three-phase clocking */ + + priv->iops->lock(priv->intf); + + ret = priv->iops->write_data(priv->intf, priv->xfer_buf, 3); + if (ret < 0) { + dev_err(&pdev->dev, "Clk cfg failed: %d\n", ret); + priv->iops->unlock(priv->intf); + return ret; + } + + priv->xfer_buf[0] = TCK_DIVISOR; + priv->xfer_buf[1] = div_value(60000000); + priv->xfer_buf[2] = div_value(60000000) >> 8; + dev_dbg(&pdev->dev, "TCK_DIVISOR: 0x%04x 0x%04x\n", + priv->xfer_buf[1], priv->xfer_buf[2]); + + ret = priv->iops->write_data(priv->intf, priv->xfer_buf, 3); + if (ret < 0) { + dev_err(&pdev->dev, "Clk cfg failed: %d\n", ret); + priv->iops->unlock(priv->intf); + return ret; + } + + priv->iops->unlock(priv->intf); + + ret = priv->iops->cfg_bus_pins(priv->intf, MPSSE_SK | MPSSE_DO, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Can't init SPI bus pins: %d\n", ret); + return ret; + } + + return 0; +} + +static int ftdi_spi_init_io(struct spi_controller *master, unsigned int dev_idx) +{ + struct ftdi_spi *priv = spi_controller_get_devdata(master); + struct platform_device *pdev = priv->pdev; + const struct mpsse_spi_platform_data *pd; + const struct mpsse_spi_dev_data *data; + struct gpiod_lookup_table *lookup; + size_t lookup_size, size; + char *label; + unsigned int i; + u16 cs; + + pd = pdev->dev.platform_data; + + data = pd->spi_info[dev_idx].platform_data; + if (!data || data->magic != FTDI_MPSSE_IO_DESC_MAGIC) + return 0; + + size = data->desc_len + 1; + + lookup_size = sizeof(*lookup) + size * sizeof(struct gpiod_lookup); + lookup = devm_kzalloc(&pdev->dev, lookup_size, GFP_KERNEL); + if (!lookup) + return -ENOMEM; + + cs = pd->spi_info[dev_idx].chip_select; + + lookup->dev_id = devm_kasprintf(&pdev->dev, GFP_KERNEL, "spi%d.%d", + master->bus_num, cs); + if (!lookup->dev_id) { + devm_kfree(&pdev->dev, lookup); + return -ENOMEM; + } + dev_dbg(&pdev->dev, "LOOKUP ID '%s'\n", lookup->dev_id); + + label = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ftdi-mpsse-gpio.%d", + pdev->id); + if (!label) { + devm_kfree(&pdev->dev, (void *)lookup->dev_id); + devm_kfree(&pdev->dev, lookup); + return -ENOMEM; + } + + for (i = 0; i < data->desc_len; i++) { + dev_dbg(&pdev->dev, "con_id: '%s' idx: %d, flags: 0x%x\n", + data->desc[i].con_id, data->desc[i].idx, + data->desc[i].flags); + lookup->table[i].chip_label = label; + lookup->table[i].chip_hwnum = data->desc[i].idx; + lookup->table[i].idx = 0; + lookup->table[i].con_id = data->desc[i].con_id; + lookup->table[i].flags = data->desc[i].flags; + } + + priv->lookup[cs] = lookup; + gpiod_add_lookup_table(lookup); + return 0; +} + +static int ftdi_spi_probe(struct platform_device *pdev) +{ + const struct mpsse_spi_platform_data *pd; + struct device *dev = &pdev->dev; + struct spi_controller *master; + struct ftdi_spi *priv; + struct gpio_desc *desc; + u16 num_cs, max_cs = 0; + unsigned int i; + int ret; + + pd = dev->platform_data; + if (!pd) { + dev_err(dev, "Missing platform data.\n"); + return -EINVAL; + } + + if (!pd->ops || + !pd->ops->read_data || !pd->ops->write_data || + !pd->ops->lock || !pd->ops->unlock || + !pd->ops->set_bitmode || !pd->ops->set_baudrate || + !pd->ops->disable_bitbang || !pd->ops->cfg_bus_pins) + return -EINVAL; + + if (pd->spi_info_len > FTDI_MPSSE_GPIOS) + return -EINVAL; + + /* Find max. slave chipselect number */ + num_cs = pd->spi_info_len; + for (i = 0; i < num_cs; i++) { + if (max_cs < pd->spi_info[i].chip_select) + max_cs = pd->spi_info[i].chip_select; + } + + if (max_cs > FTDI_MPSSE_GPIOS - 1) { + dev_err(dev, "Invalid max CS in platform data: %u\n", max_cs); + return -EINVAL; + } + dev_dbg(dev, "CS count %u, max CS %u\n", num_cs, max_cs); + max_cs += 1; /* including CS0 */ + + master = spi_alloc_master(&pdev->dev, sizeof(*priv)); + if (!master) + return -ENOMEM; + + platform_set_drvdata(pdev, master); + + priv = spi_controller_get_devdata(master); + priv->master = master; + priv->pdev = pdev; + priv->intf = to_usb_interface(dev->parent); + priv->iops = pd->ops; + + master->bus_num = -1; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | + SPI_CS_HIGH | SPI_LSB_FIRST; + master->num_chipselect = max_cs; + master->min_speed_hz = 450; + master->max_speed_hz = 30000000; + master->bits_per_word_mask = SPI_BPW_MASK(8); + master->set_cs = ftdi_spi_set_cs; + master->transfer_one = ftdi_spi_transfer_one; + master->auto_runtime_pm = false; + + priv->cs_gpios = devm_kcalloc(&master->dev, max_cs, sizeof(desc), + GFP_KERNEL); + if (!priv->cs_gpios) { + spi_controller_put(master); + return -ENOMEM; + } + + for (i = 0; i < num_cs; i++) { + unsigned int idx = pd->spi_info[i].chip_select; + + dev_dbg(&pdev->dev, "CS num: %u\n", idx); + desc = devm_gpiod_get_index(&priv->pdev->dev, "spi-cs", + i, GPIOD_OUT_LOW); + if (IS_ERR(desc)) { + ret = PTR_ERR(desc); + dev_err(&pdev->dev, "CS %u gpiod err: %d\n", i, ret); + continue; + } + priv->cs_gpios[idx] = desc; + } + + ret = spi_register_controller(master); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register spi master\n"); + spi_controller_put(master); + return ret; + } + + ret = priv->iops->set_bitmode(priv->intf, 0x00, BITMODE_MPSSE); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set MPSSE mode\n"); + goto err; + } + + priv->last_mode = 0xffff; + + ret = ftdi_mpsse_init(priv); + if (ret < 0) { + dev_err(&pdev->dev, "MPSSE init failed\n"); + goto err; + } + + for (i = 0; i < pd->spi_info_len; i++) { + struct spi_device *sdev; + u16 cs; + + dev_dbg(&pdev->dev, "slave: '%s', CS: %u\n", + pd->spi_info[i].modalias, pd->spi_info[i].chip_select); + + ret = ftdi_spi_init_io(master, i); + if (ret < 0) { + dev_warn(&pdev->dev, "Can't add slave IO: %d\n", ret); + continue; + } + sdev = spi_new_device(master, &pd->spi_info[i]); + if (!sdev) { + cs = pd->spi_info[i].chip_select; + dev_warn(&pdev->dev, "Can't add slave '%s', CS %u\n", + pd->spi_info[i].modalias, cs); + if (priv->lookup[cs]) { + gpiod_remove_lookup_table(priv->lookup[cs]); + priv->lookup[cs] = NULL; + } + } + } + + return 0; +err: + platform_set_drvdata(pdev, NULL); + spi_unregister_controller(master); + return ret; +} + +static int ftdi_spi_slave_release(struct device *dev, void *data) +{ + struct spi_device *spi = to_spi_device(dev); + struct ftdi_spi *priv = data; + u16 cs = spi->chip_select; + + dev_dbg(dev, "%s: remove CS %u\n", __func__, cs); + spi_unregister_device(to_spi_device(dev)); + + if (priv->lookup[cs]) + gpiod_remove_lookup_table(priv->lookup[cs]); + return 0; +} + +static int ftdi_spi_remove(struct platform_device *pdev) +{ + struct spi_controller *master; + struct ftdi_spi *priv; + + master = platform_get_drvdata(pdev); + priv = spi_controller_get_devdata(master); + + device_for_each_child(&master->dev, priv, ftdi_spi_slave_release); + + spi_unregister_controller(master); + return 0; +} + +static struct platform_driver ftdi_spi_driver = { + .driver.name = "ftdi-mpsse-spi", + .probe = ftdi_spi_probe, + .remove = ftdi_spi_remove, +}; +module_platform_driver(ftdi_spi_driver); + +MODULE_ALIAS("platform:ftdi-mpsse-spi"); +MODULE_AUTHOR("Anatolij Gustschin X-Patchwork-Id: 10824747 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 2DDAE15AC for ; Thu, 21 Feb 2019 20:25:20 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1E98331C5F for ; Thu, 21 Feb 2019 20:25:20 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 1317A31CE3; Thu, 21 Feb 2019 20:25:20 +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=ham 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 EA32B31CAC for ; Thu, 21 Feb 2019 20:25:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726626AbfBUUZS (ORCPT ); Thu, 21 Feb 2019 15:25:18 -0500 Received: from mail-out.m-online.net ([212.18.0.10]:36985 "EHLO mail-out.m-online.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726625AbfBUUZS (ORCPT ); Thu, 21 Feb 2019 15:25:18 -0500 Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 4455Z32Gylz1qxK0; Thu, 21 Feb 2019 21:25:15 +0100 (CET) Received: from localhost (dynscan1.mnet-online.de [192.168.6.70]) by mail.m-online.net (Postfix) with ESMTP id 4455Z31sjPz1qswm; Thu, 21 Feb 2019 21:25:15 +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 po57tAK0rmMj; Thu, 21 Feb 2019 21:25:13 +0100 (CET) X-Auth-Info: HV9r+s9eq7+4YRsmf1iu2PrSI3yg4fcj+cVEIePOoHM= Received: from localhost.localdomain (p54833194.dip0.t-ipconnect.de [84.131.49.148]) (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; Thu, 21 Feb 2019 21:25:13 +0100 (CET) From: Anatolij Gustschin To: linux-usb@vger.kernel.org, linux-spi@vger.kernel.org, linux-fpga@vger.kernel.org, linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, broonie@kernel.org, atull@kernel.org, mdf@kernel.org Subject: [PATCH v4 3/3] fpga: Add fpga manager driver for ARRI Altera FPP Date: Thu, 21 Feb 2019 21:25:06 +0100 Message-Id: <20190221202506.17744-4-agust@denx.de> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190221202506.17744-1-agust@denx.de> References: <20190221202506.17744-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 FPGA manager driver for loading ARRI Altera FPGAs via fast passive parallel (FPP) interface using FTDI FT232H chip. Signed-off-by: Anatolij Gustschin --- .../ABI/testing/sysfs-driver-ftdi-fifo-fpp | 7 + drivers/fpga/Kconfig | 7 + drivers/fpga/Makefile | 1 + drivers/fpga/ftdi-fifo-fpp.c | 594 ++++++++++++++++++ 4 files changed, 609 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp create mode 100644 drivers/fpga/ftdi-fifo-fpp.c diff --git a/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp b/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp new file mode 100644 index 000000000000..f3055100d07e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp @@ -0,0 +1,7 @@ +What: /sys/bus/platform/devices/ftdi-fifo-fpp-mgr.N/cfg_mode +Date: Feb 2019 +Kernel Version: 5.2 +Contact: Anatolij Gustschin +Description: + Contains either "fifo" or "bitbang" and controls if fifo + of bitbang configuration mode is used in the driver. diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index c20445b867ae..19d82163a0b2 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -50,6 +50,13 @@ config FPGA_MGR_ALTERA_CVP FPGA manager driver support for Arria-V, Cyclone-V, Stratix-V and Arria 10 Altera FPGAs using the CvP interface over PCIe. +config FPGA_MGR_FTDI_FIFO_FPP + tristate "Altera FPP over FT232H FIFO" + depends on USB_FT232H_INTF + help + FPGA manager driver support for Altera fast passive parallel + interface (FPP) over FT232H FT245 FIFO. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index c0dd4c82fbdb..61725d31e6d1 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_ALTERA_CVP) += altera-cvp.o obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI) += altera-ps-spi.o +obj-$(CONFIG_FPGA_MGR_FTDI_FIFO_FPP) += ftdi-fifo-fpp.o obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI) += machxo2-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o diff --git a/drivers/fpga/ftdi-fifo-fpp.c b/drivers/fpga/ftdi-fifo-fpp.c new file mode 100644 index 000000000000..2bc72335cd70 --- /dev/null +++ b/drivers/fpga/ftdi-fifo-fpp.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Altera FPGA firmware upload via FPP using FT232H Bitbang/FT245-FIFO. + * + * Copyright (C) 2017 - 2018 DENX Software Engineering + * Anatolij Gustschin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BULK_OUT_BUF_SZ SZ_1M +#define MAX_RETRIES 10 + +/* + * With logic of CPLD we can write the state of nConfig pin and + * read back the state of some pins (conf_done, init_done, nStatus). + * Status header and bit assignment in data register on CPLD. + */ +#define INPUT_HEADER_0 0xA5 +#define INPUT_HEADER_1 0x5A +#define IN_CONF_DONE BIT(0) +#define IN_INIT_DONE BIT(1) +#define IN_ADDR_SELECT BIT(4) +#define IN_BOARD_REV BIT(5) +#define OUT_NCONFIG BIT(0) +#define OUT_RESET_N BIT(1) + +enum fpp_board_rev { + BOARD_REVA = 1, + BOARD_REVB = 2, +}; + +enum fpp_addr_sel { + ADDR_SELECT_INVALID, + ADDR_SELECT_GND, + ADDR_SELECT_NC +}; + +struct fpp_mgr_ops { + int (*write_init)(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count); + int (*write)(struct fpga_manager *mgr, const char *buf, size_t count); + int (*write_complete)(struct fpga_manager *mgr, + struct fpga_image_info *info); +}; + +struct fpp_fpga_mgr_priv { + struct platform_device *pdev; + struct usb_interface *intf; + const struct ft232h_intf_ops *iops; + struct fpga_manager *mgr; + struct fpp_mgr_ops *ops; + struct gpio_desc *nconfig; + struct gpio_desc *conf_done; + char cfg_mode[8]; + u8 out_data_port; + int index; + void *bulk_buf; + char usb_dev_id[32]; + char fpga_mgr_name[64]; + enum fpp_board_rev rev; + enum fpp_addr_sel addr_sel; +}; + +static int fpp_fpga_mgr_set_data_port(struct fpp_fpga_mgr_priv *priv, + u8 bitmask, u8 value) +{ + struct device *dev = &priv->pdev->dev; + struct bulk_desc desc; + u8 *data; + int ret; + + /* + * With CPLD connected (in FT245 FIFO mode) we use ACBUS8&9 + * pins to switch between data and command mode: + * ACBUS8&9 == 0, 0 --> normal mode (data communication) + * ACBUS8&9 == 1, 0 --> command mode + */ + gpiod_set_raw_value_cansleep(priv->nconfig, 1); + gpiod_set_raw_value_cansleep(priv->conf_done, 0); + msleep(50); + + /* Write commands to CPLD */ + ret = priv->iops->set_bitmode(priv->intf, 0x00, BITMODE_SYNCFF); + if (ret) + return ret; + + if (value) + priv->out_data_port |= bitmask; + else + priv->out_data_port &= ~bitmask; + + data = priv->bulk_buf; + *data = priv->out_data_port; + + desc.dir_out = true; + desc.act_len = 0; + desc.len = 1; + desc.data = data; + desc.timeout = FTDI_USB_WRITE_TIMEOUT; + + ret = priv->iops->bulk_xfer(priv->intf, &desc); + if (ret) { + dev_err(dev, "Writing in SYNCFF mode failed: %d\n", ret); + return ret; + } + + msleep(50); + /* Switch back to data mode with ACBUS8&9 back to low */ + gpiod_set_raw_value_cansleep(priv->nconfig, 0); + gpiod_set_raw_value_cansleep(priv->conf_done, 0); + msleep(50); + + return 0; +} + +static int fpp_fpga_mgr_bitbang_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + struct device *dev = &priv->pdev->dev; + int retries = MAX_RETRIES; + int ret; + + gpiod_set_value_cansleep(priv->nconfig, 0); + msleep(50); + gpiod_set_value_cansleep(priv->nconfig, 1); + msleep(50); + gpiod_set_value_cansleep(priv->nconfig, 0); + + /* Wait for CONF_DONE to get low */ + do { + msleep(50); + + ret = gpiod_get_value_cansleep(priv->conf_done); + if (ret < 0) { + dev_err(dev, "Failed to get CONF_DONE pin: %d\n", ret); + return ret; + } + + if (!ret) + break; + } while (--retries > 0); + + if (!retries) { + dev_warn(dev, "CONF_DONE low wait timeout\n"); + return -ETIMEDOUT; + } + + ret = priv->iops->set_bitmode(priv->intf, 0xff, BITMODE_BITBANG); + if (ret < 0) + return ret; + + /* Set max. working baud rate (for hardware without CPLD) */ + return priv->iops->set_baudrate(priv->intf, 700000); +} + +static int fpp_fpga_mgr_bitbang_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + struct bulk_desc desc; + size_t blk_sz; + int ret; + + desc.data = priv->bulk_buf; + desc.dir_out = true; + desc.timeout = FTDI_USB_WRITE_TIMEOUT; + + while (count) { + blk_sz = min_t(size_t, count, BULK_OUT_BUF_SZ); + memcpy(priv->bulk_buf, buf, blk_sz); + desc.act_len = 0; + desc.len = blk_sz; + ret = priv->iops->bulk_xfer(priv->intf, &desc); + if (ret < 0) + return ret; + + buf += desc.act_len; + count -= desc.act_len; + } + + return 0; +} + +static int fpp_fpga_mgr_bitbang_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + struct device *dev = &priv->pdev->dev; + int retries = MAX_RETRIES; + int ret; + + /* Wait for CONF_DONE to get high */ + do { + msleep(50); + + ret = gpiod_get_value_cansleep(priv->conf_done); + if (ret < 0) + return ret; + + if (ret) + break; + } while (--retries > 0); + + if (!retries) { + dev_warn(dev, "CONF_DONE wait timeout\n"); + return -ETIMEDOUT; + } + + priv->iops->disable_bitbang(priv->intf); + return 0; +} + +static inline bool status_hdr_is_valid(u8 *buf) +{ + return buf[0] == INPUT_HEADER_0 && buf[1] == INPUT_HEADER_1; +} + +static int fpp_fpga_mgr_read_status(struct fpp_fpga_mgr_priv *priv, u8 *status) +{ + struct device *dev = &priv->pdev->dev; + u8 *inbuf = priv->bulk_buf; + int retries = MAX_RETRIES; + int ret; + + if (!status) + return -EINVAL; + + /* Wait until CPLD sends valid header and status register */ + do { + ret = priv->iops->read_data(priv->intf, inbuf, 64); + if (ret < 0) { + dev_err(dev, "Can't read status data: %d\n", ret); + return ret; + } + + /* Check input buffer header */ + if (ret >= 4 && status_hdr_is_valid(inbuf)) { + *status = inbuf[2]; + return 0; + } + + /* Wait and read back status again */ + msleep(100); /* CPLD sends status every 100ms */ + } while (--retries > 0); + + dev_warn(dev, "Timeout when reading status\n"); + return -ETIMEDOUT; +} + +static int fpp_fpga_mgr_ft245_fifo_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + struct device *dev = &priv->pdev->dev; + int retries = MAX_RETRIES; + int ret; + u8 status; + + gpiod_direction_output_raw(priv->conf_done, 0); + + /* Set/reset nConfig via commands to CPLD */ + ret = fpp_fpga_mgr_set_data_port(priv, OUT_NCONFIG, 1); + if (ret) + return ret; + ret = fpp_fpga_mgr_set_data_port(priv, OUT_NCONFIG, 0); + if (ret) + return ret; + ret = fpp_fpga_mgr_set_data_port(priv, OUT_NCONFIG, 1); + if (ret) + return ret; + + /* In FT245 FIFO mode we need sync FIFO mode to talk to FPGA */ + ret = priv->iops->set_bitmode(priv->intf, 0xff, BITMODE_SYNCFF); + if (ret) + return ret; + + /* Wait until FPGA is ready for loading (conf_done zero) or timeout */ + do { + ret = fpp_fpga_mgr_read_status(priv, &status); + if (!ret) { + /* Check conf_done status */ + if ((status & IN_CONF_DONE) == 0) + break; + } + } while (--retries > 0); + + if (!retries) { + dev_warn(dev, "CONF_DONE wait timeout\n"); + return -ETIMEDOUT; + } + + /* Configure for max. baud rate (3MHz * 4 in bitbang mode) */ + return priv->iops->set_baudrate(priv->intf, 3000000); +} + +static int fpp_fpga_mgr_ft245_fifo_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + return fpp_fpga_mgr_bitbang_write(mgr, buf, count); +} + +static int fpp_fpga_mgr_ft245_fifo_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + struct device *dev = &priv->pdev->dev; + int retries = MAX_RETRIES; + int ret; + u8 mask, status; + + mask = IN_CONF_DONE | IN_INIT_DONE; + + do { + ret = fpp_fpga_mgr_read_status(priv, &status); + if (!ret) { + /* Check conf_done/init_done status */ + if ((status & mask) == mask) + break; + } + } while (--retries > 0); + + if (!retries) { + dev_warn(dev, "INIT_DONE wait timeout\n"); + return -ETIMEDOUT; + } + + /* Release Reset_n, keep nCONFIG high, too! */ + return fpp_fpga_mgr_set_data_port(priv, OUT_NCONFIG | OUT_RESET_N, 1); +} + +static enum fpga_mgr_states fpp_fpga_mgr_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int fpp_fpga_mgr_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + if (info && info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + if (priv->ops->write_init) + return priv->ops->write_init(mgr, info, buf, count); + + return -ENODEV; +} + +static int fpp_fpga_mgr_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + if (priv->ops->write) + return priv->ops->write(mgr, buf, count); + + return -ENODEV; +} + +static int fpp_fpga_mgr_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + if (priv->ops->write_complete) + return priv->ops->write_complete(mgr, info); + + return -ENODEV; +} + +static struct fpp_mgr_ops fpp_mgr_bitbang_ops = { + .write_init = fpp_fpga_mgr_bitbang_write_init, + .write = fpp_fpga_mgr_bitbang_write, + .write_complete = fpp_fpga_mgr_bitbang_write_complete, +}; + +static struct fpp_mgr_ops fpp_mgr_ft245_fifo_ops = { + .write_init = fpp_fpga_mgr_ft245_fifo_write_init, + .write = fpp_fpga_mgr_ft245_fifo_write, + .write_complete = fpp_fpga_mgr_ft245_fifo_write_complete, +}; + +static const struct fpga_manager_ops fpp_fpga_mgr_ops = { + .state = fpp_fpga_mgr_state, + .write_init = fpp_fpga_mgr_write_init, + .write = fpp_fpga_mgr_write, + .write_complete = fpp_fpga_mgr_write_complete, +}; + +static ssize_t cfg_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + return snprintf(buf, PAGE_SIZE, "%s\n", priv->cfg_mode); +} + +static ssize_t cfg_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + if (!count || count > sizeof(priv->cfg_mode)) + return -EINVAL; + + if (!strncmp(buf, "fifo", 4)) { + strncpy(priv->cfg_mode, buf, sizeof(priv->cfg_mode) - 1); + priv->cfg_mode[4] = 0; + priv->ops = &fpp_mgr_ft245_fifo_ops; + gpiod_direction_output_raw(priv->conf_done, 0); + } else if (!strncmp(buf, "bitbang", 7)) { + strncpy(priv->cfg_mode, buf, sizeof(priv->cfg_mode) - 1); + priv->cfg_mode[7] = 0; + priv->ops = &fpp_mgr_bitbang_ops; + gpiod_direction_input(priv->conf_done); + } else { + return -EINVAL; + } + + return count; +} + +static DEVICE_ATTR_RW(cfg_mode); + +static int fpp_fpga_mgr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fpp_fpga_mgr_priv *priv; + struct fpga_manager *mgr; + struct fifo_fpp_mgr_platform_data *pd; + int ret, retries = MAX_RETRIES; + char id_string[8]; + u8 status = 0; + + pd = dev->platform_data; + if (!pd) { + dev_err(dev, "Missing platform data.\n"); + return -EINVAL; + } + + if (!pd->ops || + !pd->ops->bulk_xfer || !pd->ops->ctrl_xfer || + !pd->ops->read_data || !pd->ops->write_data || + !pd->ops->set_bitmode || !pd->ops->set_baudrate || + !pd->ops->disable_bitbang) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->intf = to_usb_interface(dev->parent); + priv->iops = pd->ops; + + ret = sscanf(dev_name(dev->parent), "%s", priv->usb_dev_id); + if (ret != 1) { + dev_err(dev, "Can't get parent device name: %d\n", ret); + return -ENODEV; + } + + priv->pdev = pdev; + priv->ops = &fpp_mgr_ft245_fifo_ops; + strncpy(priv->cfg_mode, "fifo", sizeof(priv->cfg_mode)); + + priv->nconfig = devm_gpiod_get(dev, "nconfig", GPIOD_OUT_LOW); + if (IS_ERR(priv->nconfig)) { + ret = PTR_ERR(priv->nconfig); + dev_err(dev, "Failed to get nconfig gpio: %d\n", ret); + return ret; + } + + priv->conf_done = devm_gpiod_get(dev, "conf_done", GPIOD_OUT_LOW); + if (IS_ERR(priv->conf_done)) { + ret = PTR_ERR(priv->conf_done); + dev_err(dev, "Failed to get conf_done gpio: %d\n", ret); + goto err_cfg1; + } + + priv->bulk_buf = devm_kmalloc(dev, BULK_OUT_BUF_SZ, + GFP_KERNEL | GFP_DMA32); + if (!priv->bulk_buf) { + ret = -ENOMEM; + goto err_cfg2; + } + + ret = priv->iops->set_bitmode(priv->intf, 0xff, BITMODE_SYNCFF); + if (ret) + goto err_cfg2; + + /* Read status register from CPLD */ + do { + ret = fpp_fpga_mgr_read_status(priv, &status); + if (!ret) + break; + } while (--retries > 0); + + if (!retries) { + ret = -ETIMEDOUT; + goto err_cfg2; + } + + priv->rev = (status & IN_BOARD_REV) ? BOARD_REVB : BOARD_REVA; + + if (priv->rev == BOARD_REVB) { + priv->addr_sel = (status & IN_ADDR_SELECT) ? + ADDR_SELECT_NC : ADDR_SELECT_GND; + if (priv->addr_sel == ADDR_SELECT_NC) + strncpy(id_string, "right", sizeof(id_string)); + else + strncpy(id_string, "left", sizeof(id_string)); + } else { + priv->addr_sel = ADDR_SELECT_INVALID; + strncpy(id_string, "single", sizeof(id_string)); + } + + dev_info(dev, "Board Rev %d, Addr Sel %d\n", priv->rev, priv->addr_sel); + + /* Use unique board ID and USB bus/port in FPGA manager name */ + snprintf(priv->fpga_mgr_name, sizeof(priv->fpga_mgr_name), + "ftdi-fpp-fpga-mgr %s %s", id_string, priv->usb_dev_id); + + mgr = devm_fpga_mgr_create(dev, priv->fpga_mgr_name, + &fpp_fpga_mgr_ops, priv); + if (!mgr) + goto err_cfg2; + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) { + dev_err(dev, "unable to register FPGA manager\n"); + goto err_cfg2; + } + + ret = device_create_file(dev, &dev_attr_cfg_mode); + if (ret) + dev_warn(dev, "Can't create cfg_mode interface %d\n", ret); + + return 0; + +err_cfg2: + devm_gpiod_put(dev, priv->conf_done); +err_cfg1: + devm_gpiod_put(dev, priv->nconfig); + return ret; +} + +static int fpp_fpga_mgr_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct fpp_fpga_mgr_priv *priv = mgr->priv; + + device_remove_file(&pdev->dev, &dev_attr_cfg_mode); + fpga_mgr_unregister(mgr); + devm_gpiod_put(&pdev->dev, priv->conf_done); + devm_gpiod_put(&pdev->dev, priv->nconfig); + return 0; +} + +static struct platform_driver fpp_fpga_mgr_driver = { + .driver.name = "ftdi-fifo-fpp-mgr", + .probe = fpp_fpga_mgr_probe, + .remove = fpp_fpga_mgr_remove, +}; + +module_platform_driver(fpp_fpga_mgr_driver); + +MODULE_ALIAS("platform:ftdi-fifo-fpp-mgr"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_DESCRIPTION("FT232H Bitbang/FT245-FIFO FPP FPGA Manager Driver"); +MODULE_LICENSE("GPL v2");