linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add support for ARRI FPGA configuration
@ 2018-11-20  0:28 Anatolij Gustschin
  2018-11-20  0:28 ` [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Anatolij Gustschin
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-20  0:28 UTC (permalink / raw)
  To: linux-usb, linux-spi, linux-fpga, linux-kernel
  Cc: gregkh, broonie, atull, mdf

This series adds drivers and FPGA manager support required
for FT232H based ARRI FPGA configuration adapters.

Patch 1/3 adds FT232H interface driver (for ARRI USB PIDs)
implementing commonly used FTDI USB transfer operations and
ACBUS/MPSSE GPIO controllers. Depending on USB PIDs it creates
platform devices for MPSSE SPI bus with attached SPI slaves
or platform devices for ARRI FPP FPGA manager.

Patch 2/3 adds MPSSE SPI controller driver which registers
an FTDI USB-SPI bus with attached SPI slaves from description
in platform data, so existing SPI protocol drivers can be
used for these slave devices (in our case altera-ps-spi).
Via interface driver in patch 1/3 it is easily possible to add
support for other slave devices using custom USB PIDs (e.g.
spi-nor flash W25Q32 with m25p80 driver has beed used for
testing MPSSE SPI controller driver).

Patch 3/3 adds FPGA manager driver for ARRI FPP adapters for
FPGA configuration via Altera fast passive parallel interface.

Changes since v1:
  Patch 1/3
  - add myself as a maintainer for these drivers
  - update copyright line to include 2018
  - include required linux headers
  - fix gcc 7.3.0 build warnings -Wunused-const-variable=
  - make ftdi functions and spi_board_info struct static
  - use KBUILD_MODNAME in drivers struct
  - use dev_err() for error messages instead of dev_info()
  - drop not needed output about number of used MPSSE pins
    since it is constant (not configurable)
  - remove ftdi function prototypes from header ft232h-intf.h
    as these are not exported anymore
  - update comments (don't use kernel-doc format as we do not
    export symbols anymore and functions are "static")

  Patch 2/3
  - fix build breakage when building with ARCH=i386 allmodconfig
  - add checks for ops->lock/ops->unlock presence in pdata
  - update copyright line to include 2018
  - remove printing numbers in parentheses (%d)

  Patch 3/3
  - update copyright line to include 2018
  - add sysfs ABI documentation
 
Changes since initial version (MFD based):
  - don't use MFD framework
  - don't use original FT232H USB PID (it is too generic and collides
    with existing ftdi_sio driver)
  - don't add separate CBUS GPIO driver based on FT232H MFD device
  - redesign/rework the drivers as described under [4]
  - add basic FT232H interface driver under drivers/usb/misc/
  - use custom reserved USB PIDs in the USB driver for adapter devices
  - add notes about borrowed protocol code from libftdi to commit log
    and header file
  - add MPSSE SPI controller driver supporting USB-SPI bus with
    dynamically added SPI slaves from description in platform data
    (the initial attempt didn't include USB SPI bus support for PS-
     SPI FPGA configuration via USB)
  - rework FPP fpga manager driver based on new FT232H interface
    driver and extend it according to CPLD changes for additional
    support of new hardware revision B.

Anatolij Gustschin (3):
  usb: misc: add driver for FT232H based FPGA configuration devices
  spi: add FTDI MPSSE SPI controller driver
  fpga: Add fpga manager driver for ARRI Altera FPP

 .../ABI/testing/sysfs-driver-ftdi-fifo-fpp    |    7 +
 MAINTAINERS                                   |    8 +
 drivers/fpga/Kconfig                          |    7 +
 drivers/fpga/Makefile                         |    1 +
 drivers/fpga/ftdi-fifo-fpp.c                  |  594 +++++++
 drivers/spi/Kconfig                           |    7 +
 drivers/spi/Makefile                          |    1 +
 drivers/spi/spi-ftdi-mpsse.c                  |  673 ++++++++
 drivers/usb/misc/Kconfig                      |    9 +
 drivers/usb/misc/Makefile                     |    1 +
 drivers/usb/misc/ft232h-intf.c                | 1464 +++++++++++++++++
 include/linux/usb/ft232h-intf.h               |  187 +++
 12 files changed, 2959 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp
 create mode 100644 drivers/fpga/ftdi-fifo-fpp.c
 create mode 100644 drivers/spi/spi-ftdi-mpsse.c
 create mode 100644 drivers/usb/misc/ft232h-intf.c
 create mode 100644 include/linux/usb/ft232h-intf.h

-- 
2.17.1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices
  2018-11-20  0:28 [PATCH v2 0/3] Add support for ARRI FPGA configuration Anatolij Gustschin
@ 2018-11-20  0:28 ` Anatolij Gustschin
  2018-11-20  0:56   ` Trent Piepho
  2018-11-20  0:28 ` [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver Anatolij Gustschin
  2018-11-20  0:28 ` [PATCH v2 3/3] fpga: Add fpga manager driver for ARRI Altera FPP Anatolij Gustschin
  2 siblings, 1 reply; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-20  0:28 UTC (permalink / raw)
  To: linux-usb, linux-spi, linux-fpga, linux-kernel
  Cc: gregkh, broonie, atull, mdf

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 <agust@denx.de>
---
 MAINTAINERS                     |    8 +
 drivers/usb/misc/Kconfig        |    9 +
 drivers/usb/misc/Makefile       |    1 +
 drivers/usb/misc/ft232h-intf.c  | 1464 +++++++++++++++++++++++++++++++
 include/linux/usb/ft232h-intf.h |  187 ++++
 5 files changed, 1669 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 b755a89fa325..aaafa11f860f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15388,6 +15388,14 @@ S:	Maintained
 F:	Documentation/usb/acm.txt
 F:	drivers/usb/class/cdc-acm.*
 
+USB ARRI FPGA CONFIGURATION ADAPTER DRIVER
+M:	Anatolij Gustschin <agust@denx.de>
+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 <pontus.fuchs@gmail.com>
 L:	linux-wireless@vger.kernel.org
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..4f58ef552319
--- /dev/null
+++ b/drivers/usb/misc/ft232h-intf.c
@@ -0,0 +1,1464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FTDI FT232H interface driver for ARRI FPGA configuration
+ *
+ * Copyright (C) 2017 - 2018 DENX Software Engineering
+ * Anatolij Gustschin <agust@denx.de>
+ *
+ *    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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/printk.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb.h>
+#include <linux/usb/ft232h-intf.h>
+
+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.
+ */
+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 ? 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;
+	}
+
+	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 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_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)
+		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		= 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 <agust@denx.de>");
+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..f83fae050968
--- /dev/null
+++ b/include/linux/usb/ft232h-intf.h
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Common definitions for FTDI FT232H interface device
+ *
+ * Copyright (C) 2017 - 2018 DENX Software Engineering
+ * Anatolij Gustschin <agust@denx.de>
+ */
+
+#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;
+};
+
+/*
+ * 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;
+};
+
+/*
+ * 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 */
-- 
2.17.1



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver
  2018-11-20  0:28 [PATCH v2 0/3] Add support for ARRI FPGA configuration Anatolij Gustschin
  2018-11-20  0:28 ` [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Anatolij Gustschin
@ 2018-11-20  0:28 ` Anatolij Gustschin
  2018-11-21 12:42   ` Mark Brown
  2018-11-20  0:28 ` [PATCH v2 3/3] fpga: Add fpga manager driver for ARRI Altera FPP Anatolij Gustschin
  2 siblings, 1 reply; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-20  0:28 UTC (permalink / raw)
  To: linux-usb, linux-spi, linux-fpga, linux-kernel
  Cc: gregkh, broonie, atull, mdf

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 <agust@denx.de>
---
 drivers/spi/Kconfig          |   7 +
 drivers/spi/Makefile         |   1 +
 drivers/spi/spi-ftdi-mpsse.c | 673 +++++++++++++++++++++++++++++++++++
 3 files changed, 681 insertions(+)
 create mode 100644 drivers/spi/spi-ftdi-mpsse.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 7d3a5c94727e..2cb24e28c485 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -259,6 +259,13 @@ config SPI_FSL_LPSPI
 	help
 	  This enables Freescale i.MX LPSPI controllers in master mode.
 
+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 3575205c5c27..268c42c502e2 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_SPI_XILINX)		+= spi-xilinx.o
 obj-$(CONFIG_SPI_XLP)			+= spi-xlp.o
 obj-$(CONFIG_SPI_XTENSA_XTFPGA)		+= spi-xtensa-xtfpga.o
 obj-$(CONFIG_SPI_ZYNQMP_GQSPI)		+= spi-zynqmp-gqspi.o
+obj-$(CONFIG_SPI_FTDI_MPSSE)		+= spi-ftdi-mpsse.o
 
 # SPI slave protocol handlers
 obj-$(CONFIG_SPI_SLAVE_TIME)		+= spi-slave-time.o
diff --git a/drivers/spi/spi-ftdi-mpsse.c b/drivers/spi/spi-ftdi-mpsse.c
new file mode 100644
index 000000000000..a7f4cc52fa74
--- /dev/null
+++ b/drivers/spi/spi-ftdi-mpsse.c
@@ -0,0 +1,673 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FTDI FT232H MPSSE SPI controller driver
+ *
+ * Copyright (C) 2017 - 2018 DENX Software Engineering
+ * Anatolij Gustschin <agust@denx.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/usb.h>
+#include <linux/usb/ft232h-intf.h>
+
+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_master *master;
+	const struct ft232h_intf_ops *iops;
+	struct gpiod_lookup_table *lookup[13];
+	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_chipselect(struct ftdi_spi *priv, struct spi_device *spi,
+				bool value)
+{
+	int cs = spi->chip_select;
+
+	dev_dbg(&priv->master->dev, "%s: CS %d, cs mode %d, val %d\n",
+		__func__, cs, (spi->mode & SPI_CS_HIGH), value);
+
+	gpiod_set_raw_value_cansleep(priv->cs_gpios[cs], value);
+}
+
+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->master->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 spi_master *master = priv->master;
+	struct device *dev = &master->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;
+
+	if (!t->len)
+		return 0;
+
+	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_tx(struct ftdi_spi *priv, const u8 *tx_data, size_t len)
+{
+	struct spi_master *master = priv->master;
+	size_t remaining, stride;
+	int ret;
+
+	if (!len)
+		return 0;
+
+	priv->iops->lock(priv->intf);
+
+	remaining = len;
+	do {
+		stride = min_t(size_t, remaining, SZ_64K - 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_data, stride);
+
+		ret = priv->iops->write_data(priv->intf, priv->xfer_buf,
+					     stride + 3);
+		if (ret < 0) {
+			dev_dbg(&master->dev, "%s: tx failed %d\n",
+				__func__, ret);
+			goto err;
+		}
+		dev_dbg(&master->dev, "%s: %zu byte(s) done\n",
+			__func__, stride);
+		remaining -= stride;
+	} while (remaining);
+
+	ret = 0;
+err:
+	priv->iops->unlock(priv->intf);
+	return ret;
+}
+
+static int ftdi_spi_rx(struct ftdi_spi *priv, u8 *rx_data, size_t len)
+{
+	const struct ft232h_intf_ops *ops = priv->iops;
+	struct spi_master *master = priv->master;
+	size_t remaining, stride;
+	int ret, tout = 10;
+	void *rxbuf;
+
+	dev_dbg(&master->dev, "%s: CMD 0x%02x, len %zu\n",
+		__func__, priv->rx_cmd, len);
+
+	if (!len)
+		return 0;
+
+	priv->xfer_buf[0] = priv->rx_cmd;
+	priv->xfer_buf[1] = len - 1;
+	priv->xfer_buf[2] = (len - 1) >> 8;
+
+	ops->lock(priv->intf);
+
+	ret = ops->write_data(priv->intf, priv->xfer_buf, 3);
+	if (ret < 0)
+		goto err;
+
+	remaining = len;
+	rxbuf = rx_data;
+
+	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(&master->dev,
+				"Waiting for data (read : %02X), tout %d\n",
+				 ret, tout);
+			if (--tout)
+				continue;
+
+			dev_dbg(&master->dev, "read timeout...\n");
+			ret = -ETIMEDOUT;
+			goto err;
+		}
+
+		memcpy(rxbuf, priv->xfer_buf, ret);
+
+		dev_dbg(&master->dev, "%s: %d byte(s)\n", __func__, ret);
+		rxbuf += ret;
+		remaining -= ret;
+	} while (remaining);
+
+	ret = 0;
+err:
+	ops->unlock(priv->intf);
+	return ret;
+}
+
+static int ftdi_spi_transfer_one(struct spi_master *master,
+				 struct spi_message *msg)
+{
+	struct ftdi_spi *priv = spi_controller_get_devdata(master);
+	struct spi_device *spi = msg->spi;
+	struct spi_transfer *t;
+	bool keep_cs = false;
+	int ret = 0;
+
+	if (!priv)
+		return -ENODEV;
+
+	msg->actual_length = 0;
+	msg->state = NULL;
+	msg->status = 0;
+
+	if (priv->last_mode != spi->mode) {
+		u8 spi_mode = spi->mode & (SPI_CPOL | SPI_CPHA);
+		u8 pins = 0;
+
+		dev_dbg(&master->dev, "%s: MODE %d\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(&master->dev, "IO cfg failed: %d\n", ret);
+			return ret;
+		}
+		priv->last_mode = spi->mode;
+	}
+
+	dev_dbg(&master->dev, "%s: mode 0x%x, CMD RX/TX 0x%x/0x%x\n",
+		__func__, spi->mode, priv->rx_cmd, priv->tx_cmd);
+
+	ftdi_spi_chipselect(priv, spi, spi->mode & SPI_CS_HIGH);
+
+	ret = -EINVAL;
+
+	list_for_each_entry(t, &msg->transfers, transfer_list) {
+		dev_dbg(&master->dev, "%s: cs_change %d, cs %d, len %d\n",
+			__func__, t->cs_change, spi->chip_select, t->len);
+		dev_dbg(&master->dev, "%s: txb 0x%p, rxb 0x%p, bpw %d\n",
+			__func__, t->tx_buf, t->rx_buf, t->bits_per_word);
+
+		if (t->tx_buf && t->rx_buf)
+			ret = ftdi_spi_tx_rx(priv, spi, t);
+		else if (t->tx_buf)
+			ret = ftdi_spi_tx(priv, t->tx_buf, t->len);
+		else if (t->rx_buf)
+			ret = ftdi_spi_rx(priv, t->rx_buf, t->len);
+
+		dev_dbg(&master->dev, "%s: xfer ret %d\n", __func__, ret);
+		if (ret)
+			break;
+
+		msg->actual_length += t->len;
+
+		if (t->delay_usecs) {
+			u16 us = t->delay_usecs;
+
+			if (us <= 10)
+				udelay(us);
+			else
+				usleep_range(us, us + DIV_ROUND_UP(us, 10));
+		}
+
+		if (!t->cs_change)
+			continue;
+
+		/* Last transfer with cs_change set, stop keeping CS */
+		if (list_is_last(&t->transfer_list, &msg->transfers)) {
+			keep_cs = true;
+			break;
+		}
+		ftdi_spi_chipselect(priv, spi, !(spi->mode & SPI_CS_HIGH));
+		usleep_range(10, 15);
+		ftdi_spi_chipselect(priv, spi, spi->mode & SPI_CS_HIGH);
+	}
+
+	dev_dbg(&master->dev, "%s: status %d\n", __func__, ret);
+	msg->status = ret;
+	spi_finalize_current_message(master);
+
+	if (!keep_cs)
+		ftdi_spi_chipselect(priv, spi, !(spi->mode & SPI_CS_HIGH));
+
+	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_master *master, int cs)
+{
+	struct ftdi_spi *priv = spi_controller_get_devdata(master);
+	const struct mpsse_spi_platform_data *pd;
+	struct platform_device *pdev = priv->pdev;
+	struct gpiod_lookup_table *lookup;
+	size_t lookup_size;
+	char *label;
+	int i, size;
+
+	pd = pdev->dev.platform_data;
+	size = pd->io_data_len + 1;
+
+	lookup_size = sizeof(*lookup) + size * sizeof(struct gpiod_lookup);
+	lookup = devm_kzalloc(&pdev->dev, lookup_size, GFP_KERNEL);
+	if (!lookup)
+		return -ENOMEM;
+
+	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 < pd->io_data_len; i++) {
+		dev_dbg(&pdev->dev, "con_id: '%s' idx: %d, flags: 0x%x\n",
+			pd->io_data[i].con_id, pd->io_data[i].idx,
+			pd->io_data[i].flags);
+		lookup->table[i].chip_label = label;
+		lookup->table[i].chip_hwnum = pd->io_data[i].idx;
+		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[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_master *master;
+	struct ftdi_spi *priv;
+	struct gpio_desc *desc;
+	int num_cs, max_cs = 0;
+	int i, 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;
+
+	/* 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 > 12) {
+		dev_err(dev, "Invalid max CS in platform data: %d\n", max_cs);
+		return -EINVAL;
+	}
+	dev_dbg(dev, "CS count %d, max CS %d\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->transfer_one_message = 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++) {
+		int idx = pd->spi_info[i].chip_select;
+
+		dev_dbg(&pdev->dev, "CS num: %d\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 %d 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;
+		int cs;
+
+		dev_dbg(&pdev->dev, "slave: '%s', CS: %d\n",
+			pd->spi_info[i].modalias, pd->spi_info[i].chip_select);
+		ret = ftdi_spi_init_io(master, pd->spi_info[i].chip_select);
+		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 %d\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;
+	int cs = spi->chip_select;
+
+	dev_dbg(dev, "%s: remove CS %d\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_master *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 <agust@denx.de");
+MODULE_DESCRIPTION("FTDI MPSSE SPI master driver");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v2 3/3] fpga: Add fpga manager driver for ARRI Altera FPP
  2018-11-20  0:28 [PATCH v2 0/3] Add support for ARRI FPGA configuration Anatolij Gustschin
  2018-11-20  0:28 ` [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Anatolij Gustschin
  2018-11-20  0:28 ` [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver Anatolij Gustschin
@ 2018-11-20  0:28 ` Anatolij Gustschin
  2 siblings, 0 replies; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-20  0:28 UTC (permalink / raw)
  To: linux-usb, linux-spi, linux-fpga, linux-kernel
  Cc: gregkh, broonie, atull, mdf

Add FPGA manager driver for loading ARRI Altera FPGAs via fast
passive parallel (FPP) interface using FTDI FT232H chip.

Signed-off-by: Anatolij Gustschin <agust@denx.de>
---
 .../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..084e691d0983
--- /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:		Nov 2018
+Kernel Version:	4.21
+Contact:	Anatolij Gustschin <agust@denx.de>
+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 205042b441ea..584866a27c35 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -59,6 +59,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 7a2d73ba7122..8c1d714bd440 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..652a5430bdf2
--- /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 <agust@denx.de>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/ft232h-intf.h>
+
+#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));
+		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));
+		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 <agust@denx.de>");
+MODULE_DESCRIPTION("FT232H Bitbang/FT245-FIFO FPP FPGA Manager Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices
  2018-11-20  0:28 ` [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Anatolij Gustschin
@ 2018-11-20  0:56   ` Trent Piepho
  2018-11-20 14:49     ` Anatolij Gustschin
  0 siblings, 1 reply; 9+ messages in thread
From: Trent Piepho @ 2018-11-20  0:56 UTC (permalink / raw)
  To: agust, linux-spi, linux-kernel, linux-fpga, linux-usb
  Cc: gregkh, broonie, atull, mdf

On Tue, 2018-11-20 at 01:28 +0100, Anatolij Gustschin wrote:
> 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.

Is ARRI different than Arria?

> +/* Use baudrate calculation borrowed from libftdi */
> +static int ftdi_to_clkbits(int baudrate, unsigned int clk, int clk_div,

Linux uses unsigned values for clocks.  Does it make any sense to mix
the unsigned clk with signed values?  Seems like baudrate and clk_div
should also be unsigned.

> +			   unsigned long *encoded_divisor)

unsigned long is an odd choice here.  Is there any to reason to use an
unsigned long to store the result of right shifting a signed int
(best_div)?  It can't be longer than a int, but it can be negative.

> +{
> +	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;

In Linux we would write:

best_div = DIV_ROUND_UP(div, 2);

Though I think you can combine that with the above to get:

best_div = DIV_ROUND_CLOSEST(clk * 8 / clk_div, baudrate);

That what the above is trying to accomplish in a round about way

> +		if (best_div > 0x20000)
> +			best_div = 0x1ffff;

Looks like the above was probably supposed to be >=

> +		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;

Again, looks like a complicated way to round to the nearest.

> +		*encoded_divisor = (best_div >> 3) |
> +				   (frac_code[best_div & 0x7] << 14);
> +	}
> +	return best_baud;
> 

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices
  2018-11-20  0:56   ` Trent Piepho
@ 2018-11-20 14:49     ` Anatolij Gustschin
  0 siblings, 0 replies; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-20 14:49 UTC (permalink / raw)
  To: Trent Piepho
  Cc: linux-spi, linux-kernel, linux-fpga, linux-usb, gregkh, broonie,
	atull, mdf

On Tue, 20 Nov 2018 00:56:13 +0000
Trent Piepho tpiepho@impinj.com wrote:

>On Tue, 2018-11-20 at 01:28 +0100, Anatolij Gustschin wrote:
>> 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.  
>
>Is ARRI different than Arria?

yes, ARRI is a company name.

>> +/* Use baudrate calculation borrowed from libftdi */
>> +static int ftdi_to_clkbits(int baudrate, unsigned int clk, int clk_div,  
>
>Linux uses unsigned values for clocks.  Does it make any sense to mix
>the unsigned clk with signed values?  Seems like baudrate and clk_div
>should also be unsigned.

okay, will fix to unsigned.

>> +			   unsigned long *encoded_divisor)  
>
>unsigned long is an odd choice here.  Is there any to reason to use an
>unsigned long to store the result of right shifting a signed int
>(best_div)?  It can't be longer than a int, but it can be negative.

okay, I'll change that to unsigned int.

>> +{
>> +	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;  
>
>In Linux we would write:
>
>best_div = DIV_ROUND_UP(div, 2);
>
>Though I think you can combine that with the above to get:
>
>best_div = DIV_ROUND_CLOSEST(clk * 8 / clk_div, baudrate);
>
>That what the above is trying to accomplish in a round about way

will rework this, too. Thanks for suggestions.

>> +		if (best_div > 0x20000)
>> +			best_div = 0x1ffff;

>Looks like the above was probably supposed to be >=

I'll check it.

>> +		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;  
>
>Again, looks like a complicated way to round to the nearest.

will change this, too.

Thanks,
Anatolij

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver
  2018-11-20  0:28 ` [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver Anatolij Gustschin
@ 2018-11-21 12:42   ` Mark Brown
  2018-11-27  0:21     ` Anatolij Gustschin
  0 siblings, 1 reply; 9+ messages in thread
From: Mark Brown @ 2018-11-21 12:42 UTC (permalink / raw)
  To: Anatolij Gustschin
  Cc: linux-usb, linux-spi, linux-fpga, linux-kernel, gregkh, atull, mdf

[-- Attachment #1: Type: text/plain, Size: 2706 bytes --]

On Tue, Nov 20, 2018 at 01:28:20AM +0100, Anatolij Gustschin wrote:

> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -113,6 +113,7 @@ obj-$(CONFIG_SPI_XILINX)		+= spi-xilinx.o
>  obj-$(CONFIG_SPI_XLP)			+= spi-xlp.o
>  obj-$(CONFIG_SPI_XTENSA_XTFPGA)		+= spi-xtensa-xtfpga.o
>  obj-$(CONFIG_SPI_ZYNQMP_GQSPI)		+= spi-zynqmp-gqspi.o
> +obj-$(CONFIG_SPI_FTDI_MPSSE)		+= spi-ftdi-mpsse.o
>  
>  # SPI slave protocol handlers
>  obj-$(CONFIG_SPI_SLAVE_TIME)		+= spi-slave-time.o

Please keep the Makefile sorted.

> +++ b/drivers/spi/spi-ftdi-mpsse.c
> @@ -0,0 +1,673 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * FTDI FT232H MPSSE SPI controller driver

Please make the entire comment block here a C++ one so it looks more
consistent.

> +	struct gpiod_lookup_table *lookup[13];

This magic number for the size of the lookup table is not good.

> +static void ftdi_spi_chipselect(struct ftdi_spi *priv, struct spi_device *spi,
> +				bool value)
> +{
> +	int cs = spi->chip_select;
> +
> +	dev_dbg(&priv->master->dev, "%s: CS %d, cs mode %d, val %d\n",
> +		__func__, cs, (spi->mode & SPI_CS_HIGH), value);
> +
> +	gpiod_set_raw_value_cansleep(priv->cs_gpios[cs], value);
> +}

This is just a gpio chip select - can't it be handled by the core chip
select code?

> +	remaining = len;
> +	do {
> +		stride = min_t(size_t, remaining, SZ_64K - 3);

Rather than having a magic number for the buffer size it would be better
to either have a driver specific constant that's used consistently or
just use sizeof() when it's referenced in the code.  That way if the
buffer size is changed nothing will get missed.

> +		/* Last transfer with cs_change set, stop keeping CS */
> +		if (list_is_last(&t->transfer_list, &msg->transfers)) {
> +			keep_cs = true;
> +			break;
> +		}
> +		ftdi_spi_chipselect(priv, spi, !(spi->mode & SPI_CS_HIGH));
> +		usleep_range(10, 15);
> +		ftdi_spi_chipselect(priv, spi, spi->mode & SPI_CS_HIGH);

I'm not clear what this is intended to do?  It's overall not clear to me
that the driver needs to use transfer_one_message and not transfer_one,
the latter keeps more of the code in common code.

> +	/* 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 > 12) {
> +		dev_err(dev, "Invalid max CS in platform data: %d\n", max_cs);
> +		return -EINVAL;
> +	}
> +	dev_dbg(dev, "CS count %d, max CS %d\n", num_cs, max_cs);
> +	max_cs += 1; /* including CS0 */

Why not just size the array based on the platform data?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver
  2018-11-21 12:42   ` Mark Brown
@ 2018-11-27  0:21     ` Anatolij Gustschin
  2018-11-28  9:14       ` Mark Brown
  0 siblings, 1 reply; 9+ messages in thread
From: Anatolij Gustschin @ 2018-11-27  0:21 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-usb, linux-spi, linux-fpga, linux-kernel, gregkh, atull, mdf

On Wed, 21 Nov 2018 12:42:37 +0000
Mark Brown broonie@kernel.org wrote:
...
>>  obj-$(CONFIG_SPI_ZYNQMP_GQSPI)		+= spi-zynqmp-gqspi.o
>> +obj-$(CONFIG_SPI_FTDI_MPSSE)		+= spi-ftdi-mpsse.o
>>  
>>  # SPI slave protocol handlers
>>  obj-$(CONFIG_SPI_SLAVE_TIME)		+= spi-slave-time.o  
>
>Please keep the Makefile sorted.

Will fix it in v3.

>> +++ b/drivers/spi/spi-ftdi-mpsse.c
>> @@ -0,0 +1,673 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * FTDI FT232H MPSSE SPI controller driver  
>
>Please make the entire comment block here a C++ one so it looks more
>consistent.

Okay.

>> +	struct gpiod_lookup_table *lookup[13];  
>
>This magic number for the size of the lookup table is not good.

Will fix it in v3.

>> +static void ftdi_spi_chipselect(struct ftdi_spi *priv, struct spi_device *spi,
>> +				bool value)
>> +{
>> +	int cs = spi->chip_select;
>> +
>> +	dev_dbg(&priv->master->dev, "%s: CS %d, cs mode %d, val %d\n",
>> +		__func__, cs, (spi->mode & SPI_CS_HIGH), value);
>> +
>> +	gpiod_set_raw_value_cansleep(priv->cs_gpios[cs], value);
>> +}  
>
>This is just a gpio chip select - can't it be handled by the core chip
>select code?

spi core chip select code doesn't use gpio_desc based API yet.
But it can be handled using .set_cs(), I'll convert the driver
to use .set_cs().

>> +	remaining = len;
>> +	do {
>> +		stride = min_t(size_t, remaining, SZ_64K - 3);  
>
>Rather than having a magic number for the buffer size it would be better
>to either have a driver specific constant that's used consistently or
>just use sizeof() when it's referenced in the code.  That way if the
>buffer size is changed nothing will get missed.

sizeof() is better choice here, will use it in v3.

>> +		/* Last transfer with cs_change set, stop keeping CS */
>> +		if (list_is_last(&t->transfer_list, &msg->transfers)) {
>> +			keep_cs = true;
>> +			break;
>> +		}
>> +		ftdi_spi_chipselect(priv, spi, !(spi->mode & SPI_CS_HIGH));
>> +		usleep_range(10, 15);
>> +		ftdi_spi_chipselect(priv, spi, spi->mode & SPI_CS_HIGH);  
>
>I'm not clear what this is intended to do?  It's overall not clear to me
>that the driver needs to use transfer_one_message and not transfer_one,
>the latter keeps more of the code in common code.

It has been a while since I started with this driver, I don't remember
the intention of this chip select toggling here. I'll convert the
driver to use .transfer_one().

>> +	/* 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 > 12) {
>> +		dev_err(dev, "Invalid max CS in platform data: %d\n", max_cs);
>> +		return -EINVAL;
>> +	}
>> +	dev_dbg(dev, "CS count %d, max CS %d\n", num_cs, max_cs);
>> +	max_cs += 1; /* including CS0 */  
>
>Why not just size the array based on the platform data?

The driver must also support multiple SPI slaves with additional control
pins (besides SPI chip-select gpios). There are devices with not adjacent
chip-select gpios or devices with single chip-select gpio starting at
some offset. The array size is not always the number of chip-selects
or the max. chip-select, e.g.:

$ tree /sys/bus/spi/devices/
/sys/bus/spi/devices/
├── spi0.4 -> ../../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.4/2-1.2.4:1.0/ftdi-mpsse-spi.0/spi_master/spi0/spi0.4
├── spi1.0 -> ../../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.0/ftdi-mpsse-spi.1/spi_master/spi1/spi1.0
├── spi1.12 -> ../../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.0/ftdi-mpsse-spi.1/spi_master/spi1/spi1.12
├── spi1.4 -> ../../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.0/ftdi-mpsse-spi.1/spi_master/spi1/spi1.4
└── spi1.8 -> ../../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2.3/2-1.2.3:1.0/ftdi-mpsse-spi.1/spi_master/spi1/spi1.8

$ sudo cat /sys/kernel/debug/gpio
gpiochip1: GPIOs 486-498, parent: usb/2-1.2.3:1.0, ftdi-mpsse-gpio.1, can sleep:
 gpio-486 (mpsse.1-CS          |spi-cs              ) out hi ACTIVE LOW
 gpio-487 (mpsse.1-GPIOL0      |confd               ) in  hi 
 gpio-488 (mpsse.1-GPIOL1      |nstat               ) in  hi ACTIVE LOW
 gpio-489 (mpsse.1-GPIOL2      |nconfig             ) out hi ACTIVE LOW
 gpio-490 (mpsse.1-GPIOL3      |spi-cs              ) out hi ACTIVE LOW
 gpio-491 (mpsse.1-GPIOH0      |confd               ) in  hi 
 gpio-492 (mpsse.1-GPIOH1      |nstat               ) in  hi ACTIVE LOW
 gpio-493 (mpsse.1-GPIOH2      |nconfig             ) out hi ACTIVE LOW
 gpio-494 (mpsse.1-GPIOH3      |spi-cs              ) out hi ACTIVE LOW
 gpio-495 (mpsse.1-GPIOH4      |confd               ) in  hi 
 gpio-496 (mpsse.1-GPIOH5      |nstat               ) in  hi ACTIVE LOW
 gpio-497 (mpsse.1-GPIOH6      |nconfig             ) out hi ACTIVE LOW
 gpio-498 (mpsse.1-GPIOH7      |spi-cs              ) out hi 

gpiochip0: GPIOs 499-511, parent: usb/2-1.2.4:1.0, ftdi-mpsse-gpio.0, can sleep:
 gpio-499 (mpsse.0-CS          )
 gpio-500 (mpsse.0-GPIOL0      )
 gpio-501 (mpsse.0-GPIOL1      )
 gpio-502 (mpsse.0-GPIOL2      )
 gpio-503 (mpsse.0-GPIOL3      |spi-cs              ) out hi ACTIVE LOW
 gpio-504 (mpsse.0-GPIOH0      |confd               ) in  hi 
 gpio-505 (mpsse.0-GPIOH1      |nstat               ) in  hi ACTIVE LOW
 gpio-506 (mpsse.0-GPIOH2      |nconfig             ) out hi ACTIVE LOW
 gpio-507 (mpsse.0-GPIOH3      )
 gpio-508 (mpsse.0-GPIOH4      )
 gpio-509 (mpsse.0-GPIOH5      )
 gpio-510 (mpsse.0-GPIOH6      )
 gpio-511 (mpsse.0-GPIOH7      )

Thanks,
Anatolij

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver
  2018-11-27  0:21     ` Anatolij Gustschin
@ 2018-11-28  9:14       ` Mark Brown
  0 siblings, 0 replies; 9+ messages in thread
From: Mark Brown @ 2018-11-28  9:14 UTC (permalink / raw)
  To: Anatolij Gustschin
  Cc: linux-usb, linux-spi, linux-fpga, linux-kernel, gregkh, atull, mdf

[-- Attachment #1: Type: text/plain, Size: 795 bytes --]

On Tue, Nov 27, 2018 at 01:21:27AM +0100, Anatolij Gustschin wrote:
> Mark Brown broonie@kernel.org wrote:

> >> +	if (max_cs > 12) {
> >> +		dev_err(dev, "Invalid max CS in platform data: %d\n", max_cs);
> >> +		return -EINVAL;
> >> +	}
> >> +	dev_dbg(dev, "CS count %d, max CS %d\n", num_cs, max_cs);
> >> +	max_cs += 1; /* including CS0 */  

> >Why not just size the array based on the platform data?

> The driver must also support multiple SPI slaves with additional control
> pins (besides SPI chip-select gpios). There are devices with not adjacent
> chip-select gpios or devices with single chip-select gpio starting at
> some offset. The array size is not always the number of chip-selects
> or the max. chip-select, e.g.:

A few unused entries really won't matter.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2018-11-28  9:14 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-11-20  0:28 [PATCH v2 0/3] Add support for ARRI FPGA configuration Anatolij Gustschin
2018-11-20  0:28 ` [PATCH v2 1/3] usb: misc: add driver for FT232H based FPGA configuration devices Anatolij Gustschin
2018-11-20  0:56   ` Trent Piepho
2018-11-20 14:49     ` Anatolij Gustschin
2018-11-20  0:28 ` [PATCH v2 2/3] spi: add FTDI MPSSE SPI controller driver Anatolij Gustschin
2018-11-21 12:42   ` Mark Brown
2018-11-27  0:21     ` Anatolij Gustschin
2018-11-28  9:14       ` Mark Brown
2018-11-20  0:28 ` [PATCH v2 3/3] fpga: Add fpga manager driver for ARRI Altera FPP Anatolij Gustschin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).