diff mbox series

[v4,3/3] fpga: Add fpga manager driver for ARRI Altera FPP

Message ID 20190221202506.17744-4-agust@denx.de (mailing list archive)
State New, archived
Headers show
Series Add support for ARRI FPGA configuration | expand

Commit Message

Anatolij Gustschin Feb. 21, 2019, 8:25 p.m. UTC
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

Comments

Anatolij Gustschin April 8, 2019, 5:25 p.m. UTC | #1
Hi,

On Thu, 21 Feb 2019 21:25:06 +0100
Anatolij Gustschin agust@denx.de wrote:
...
> .../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

Ping. Any comments to this driver?

Thanks,
Anatolij
Moritz Fischer April 8, 2019, 6:05 p.m. UTC | #2
Hi Anatolij,

looks mostly good, a couple of nits inline.

On Thu, Feb 21, 2019 at 09:25:06PM +0100, Anatolij Gustschin wrote:
> 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..f3055100d07e
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp
> @@ -0,0 +1,7 @@
> +What:		/sys/bus/platform/devices/ftdi-fifo-fpp-mgr.N/cfg_mode
> +Date:		Feb 2019
> +Kernel Version:	5.2
> +Contact:	Anatolij Gustschin <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 c20445b867ae..19d82163a0b2 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -50,6 +50,13 @@ config FPGA_MGR_ALTERA_CVP
>  	  FPGA manager driver support for Arria-V, Cyclone-V, Stratix-V
>  	  and Arria 10 Altera FPGAs using the CvP interface over PCIe.
>  
> +config FPGA_MGR_FTDI_FIFO_FPP
> +	tristate "Altera FPP over FT232H FIFO"
> +	depends on USB_FT232H_INTF
> +	help
> +	  FPGA manager driver support for Altera fast passive parallel
> +	  interface (FPP) over FT232H FT245 FIFO.
> +
>  config FPGA_MGR_ZYNQ_FPGA
>  	tristate "Xilinx Zynq FPGA"
>  	depends on ARCH_ZYNQ || COMPILE_TEST
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index c0dd4c82fbdb..61725d31e6d1 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_FPGA)			+= fpga-mgr.o
>  # FPGA Manager Drivers
>  obj-$(CONFIG_FPGA_MGR_ALTERA_CVP)	+= altera-cvp.o
>  obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI)	+= altera-ps-spi.o
> +obj-$(CONFIG_FPGA_MGR_FTDI_FIFO_FPP)	+= ftdi-fifo-fpp.o
>  obj-$(CONFIG_FPGA_MGR_ICE40_SPI)	+= ice40-spi.o
>  obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI)	+= machxo2-spi.o
>  obj-$(CONFIG_FPGA_MGR_SOCFPGA)		+= socfpga.o
> diff --git a/drivers/fpga/ftdi-fifo-fpp.c b/drivers/fpga/ftdi-fifo-fpp.c
> new file mode 100644
> index 000000000000..2bc72335cd70
> --- /dev/null
> +++ b/drivers/fpga/ftdi-fifo-fpp.c
> @@ -0,0 +1,594 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Altera FPGA firmware upload via FPP using FT232H Bitbang/FT245-FIFO.
> + *
> + * Copyright (C) 2017 - 2018 DENX Software Engineering
Maybe bump that to 2019 ;-)

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

You could use a constant here for the 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);

and here ...
> +	/* 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);
and here ...
> +
> +	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);

and here ...
> +	gpiod_set_value_cansleep(priv->nconfig, 1);
> +	msleep(50);
and here ...
> +	gpiod_set_value_cansleep(priv->nconfig, 0);
> +
> +	/* Wait for CONF_DONE to get low */
Does the retry number come from datasheet?
> +	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");

warn or err?

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

Can you swap the two lines, reverse xmas-tree please.
> +
> +	mask = IN_CONF_DONE | IN_INIT_DONE;
> +
> +	do {
> +		ret = fpp_fpga_mgr_read_status(priv, &status);
> +		if (!ret) {
> +			/* Check conf_done/init_done status */
> +			if ((status & mask) == mask)
> +				break;
> +		}
> +	} while (--retries > 0);
> +
> +	if (!retries) {
> +		dev_warn(dev, "INIT_DONE wait timeout\n");
> +		return -ETIMEDOUT;
> +	}
> +
> +	/* Release Reset_n, keep nCONFIG high, too! */
> +	return fpp_fpga_mgr_set_data_port(priv, OUT_NCONFIG | OUT_RESET_N, 1);
> +}
> +
> +static enum fpga_mgr_states fpp_fpga_mgr_state(struct fpga_manager *mgr)
> +{
> +	return FPGA_MGR_STATE_UNKNOWN;
> +}
> +
> +static int fpp_fpga_mgr_write_init(struct fpga_manager *mgr,
> +				   struct fpga_image_info *info,
> +				   const char *buf, size_t count)
> +{
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	if (info && info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(&mgr->dev, "Partial reconfiguration not supported.\n");
> +		return -EINVAL;
> +	}
> +
> +	if (priv->ops->write_init)
> +		return priv->ops->write_init(mgr, info, buf, count);
> +
> +	return -ENODEV;
> +}
> +
> +static int fpp_fpga_mgr_write(struct fpga_manager *mgr, const char *buf,
> +			      size_t count)
> +{
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	if (priv->ops->write)
> +		return priv->ops->write(mgr, buf, count);
> +
> +	return -ENODEV;
> +}
> +
> +static int fpp_fpga_mgr_write_complete(struct fpga_manager *mgr,
> +				       struct fpga_image_info *info)
> +{
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	if (priv->ops->write_complete)
> +		return priv->ops->write_complete(mgr, info);
> +
> +	return -ENODEV;
> +}
> +
> +static struct fpp_mgr_ops fpp_mgr_bitbang_ops = {
> +	.write_init	= fpp_fpga_mgr_bitbang_write_init,
> +	.write		= fpp_fpga_mgr_bitbang_write,
> +	.write_complete	= fpp_fpga_mgr_bitbang_write_complete,
> +};
> +
> +static struct fpp_mgr_ops fpp_mgr_ft245_fifo_ops = {
> +	.write_init	= fpp_fpga_mgr_ft245_fifo_write_init,
> +	.write		= fpp_fpga_mgr_ft245_fifo_write,
> +	.write_complete	= fpp_fpga_mgr_ft245_fifo_write_complete,
> +};
> +
> +static const struct fpga_manager_ops fpp_fpga_mgr_ops = {
> +	.state		= fpp_fpga_mgr_state,
> +	.write_init	= fpp_fpga_mgr_write_init,
> +	.write		= fpp_fpga_mgr_write,
> +	.write_complete	= fpp_fpga_mgr_write_complete,
> +};
> +
> +static ssize_t cfg_mode_show(struct device *dev, struct device_attribute *attr,
> +			     char *buf)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct fpga_manager *mgr = platform_get_drvdata(pdev);
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	return snprintf(buf, PAGE_SIZE, "%s\n", priv->cfg_mode);
> +}
> +
> +static ssize_t cfg_mode_store(struct device *dev, struct device_attribute *attr,
> +			      const char *buf, size_t count)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct fpga_manager *mgr = platform_get_drvdata(pdev);
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	if (!count || count > sizeof(priv->cfg_mode))
> +		return -EINVAL;
> +
> +	if (!strncmp(buf, "fifo", 4)) {
> +		strncpy(priv->cfg_mode, buf, sizeof(priv->cfg_mode) - 1);
> +		priv->cfg_mode[4] = 0;
> +		priv->ops = &fpp_mgr_ft245_fifo_ops;
> +		gpiod_direction_output_raw(priv->conf_done, 0);
> +	} else if (!strncmp(buf, "bitbang", 7)) {
> +		strncpy(priv->cfg_mode, buf, sizeof(priv->cfg_mode) - 1);
> +		priv->cfg_mode[7] = 0;
> +		priv->ops = &fpp_mgr_bitbang_ops;
> +		gpiod_direction_input(priv->conf_done);
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(cfg_mode);
> +
> +static int fpp_fpga_mgr_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct fpp_fpga_mgr_priv *priv;
> +	struct fpga_manager *mgr;
> +	struct fifo_fpp_mgr_platform_data *pd;
> +	int ret, retries = MAX_RETRIES;
> +	char id_string[8];
> +	u8 status = 0;
> +
> +	pd = dev->platform_data;
> +	if (!pd) {
> +		dev_err(dev, "Missing platform data.\n");
> +		return -EINVAL;
> +	}
> +
> +	if (!pd->ops ||
> +	    !pd->ops->bulk_xfer || !pd->ops->ctrl_xfer ||
> +	    !pd->ops->read_data || !pd->ops->write_data ||
> +	    !pd->ops->set_bitmode || !pd->ops->set_baudrate ||
> +	    !pd->ops->disable_bitbang)
> +		return -EINVAL;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->intf = to_usb_interface(dev->parent);
> +	priv->iops = pd->ops;
> +
> +	ret = sscanf(dev_name(dev->parent), "%s", priv->usb_dev_id);
> +	if (ret != 1) {
> +		dev_err(dev, "Can't get parent device name: %d\n", ret);
> +		return -ENODEV;
> +	}
> +
> +	priv->pdev = pdev;
> +	priv->ops = &fpp_mgr_ft245_fifo_ops;
> +	strncpy(priv->cfg_mode, "fifo", sizeof(priv->cfg_mode));
> +
> +	priv->nconfig = devm_gpiod_get(dev, "nconfig", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->nconfig)) {
> +		ret = PTR_ERR(priv->nconfig);
> +		dev_err(dev, "Failed to get nconfig gpio: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->conf_done = devm_gpiod_get(dev, "conf_done", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->conf_done)) {
> +		ret = PTR_ERR(priv->conf_done);
> +		dev_err(dev, "Failed to get conf_done gpio: %d\n", ret);
> +		goto err_cfg1;
> +	}
> +
> +	priv->bulk_buf = devm_kmalloc(dev, BULK_OUT_BUF_SZ,
> +				      GFP_KERNEL | GFP_DMA32);
> +	if (!priv->bulk_buf) {
> +		ret = -ENOMEM;
> +		goto err_cfg2;
> +	}
> +
> +	ret = priv->iops->set_bitmode(priv->intf, 0xff, BITMODE_SYNCFF);
> +	if (ret)
> +		goto err_cfg2;
> +
> +	/* Read status register from CPLD */
> +	do {
> +		ret = fpp_fpga_mgr_read_status(priv, &status);
> +		if (!ret)
> +			break;
> +	} while (--retries > 0);
> +
> +	if (!retries) {
> +		ret = -ETIMEDOUT;
> +		goto err_cfg2;
> +	}
> +
> +	priv->rev = (status & IN_BOARD_REV) ? BOARD_REVB : BOARD_REVA;
> +
> +	if (priv->rev == BOARD_REVB) {
> +		priv->addr_sel = (status & IN_ADDR_SELECT) ?
> +				 ADDR_SELECT_NC : ADDR_SELECT_GND;
> +		if (priv->addr_sel == ADDR_SELECT_NC)
> +			strncpy(id_string, "right", sizeof(id_string));
> +		else
> +			strncpy(id_string, "left", sizeof(id_string));
> +	} else {
> +		priv->addr_sel = ADDR_SELECT_INVALID;
> +		strncpy(id_string, "single", sizeof(id_string));
> +	}
> +
> +	dev_info(dev, "Board Rev %d, Addr Sel %d\n", priv->rev, priv->addr_sel);
> +
> +	/* Use unique board ID and USB bus/port in FPGA manager name */
> +	snprintf(priv->fpga_mgr_name, sizeof(priv->fpga_mgr_name),
> +		 "ftdi-fpp-fpga-mgr %s %s", id_string, priv->usb_dev_id);
> +
> +	mgr = devm_fpga_mgr_create(dev, priv->fpga_mgr_name,
> +				   &fpp_fpga_mgr_ops, priv);
> +	if (!mgr)
> +		goto err_cfg2;
> +
> +	platform_set_drvdata(pdev, mgr);
> +
> +	ret = fpga_mgr_register(mgr);
> +	if (ret) {
> +		dev_err(dev, "unable to register FPGA manager\n");
> +		goto err_cfg2;
> +	}
> +
> +	ret = device_create_file(dev, &dev_attr_cfg_mode);
> +	if (ret)
> +		dev_warn(dev, "Can't create cfg_mode interface %d\n", ret);
> +
> +	return 0;
> +
> +err_cfg2:
> +	devm_gpiod_put(dev, priv->conf_done);
> +err_cfg1:
> +	devm_gpiod_put(dev, priv->nconfig);
> +	return ret;
> +}
> +
> +static int fpp_fpga_mgr_remove(struct platform_device *pdev)
> +{
> +	struct fpga_manager *mgr = platform_get_drvdata(pdev);
> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
> +
> +	device_remove_file(&pdev->dev, &dev_attr_cfg_mode);
> +	fpga_mgr_unregister(mgr);
> +	devm_gpiod_put(&pdev->dev, priv->conf_done);
> +	devm_gpiod_put(&pdev->dev, priv->nconfig);
> +	return 0;
> +}
> +
> +static struct platform_driver fpp_fpga_mgr_driver = {
> +	.driver.name	= "ftdi-fifo-fpp-mgr",
> +	.probe		= fpp_fpga_mgr_probe,
> +	.remove		= fpp_fpga_mgr_remove,
> +};
> +
> +module_platform_driver(fpp_fpga_mgr_driver);
> +
> +MODULE_ALIAS("platform:ftdi-fifo-fpp-mgr");
> +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
> +MODULE_DESCRIPTION("FT232H Bitbang/FT245-FIFO FPP FPGA Manager Driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.17.1
> 

Thanks,

Moritz
Anatolij Gustschin April 8, 2019, 7:01 p.m. UTC | #3
Hi Moritz,

On Mon, 8 Apr 2019 11:05:49 -0700
Moritz Fischer mdf@kernel.org wrote:

>Hi Anatolij,
>
>looks mostly good, a couple of nits inline.

Thanks for review.

...
>> +++ 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  
>Maybe bump that to 2019 ;-)

I'll update it in next version.

...
>> +	gpiod_set_raw_value_cansleep(priv->nconfig, 1);
>> +	gpiod_set_raw_value_cansleep(priv->conf_done, 0);
>> +	msleep(50);  
>
>You could use a constant here for the 50 ...

Do you mean something like below?

  const unsigned int wait_50ms = 50;
  ...
  msleep(wait_50ms);

...
>and here ...
>> +	gpiod_set_value_cansleep(priv->nconfig, 1);
>> +	msleep(50);  
>and here ...
>> +	gpiod_set_value_cansleep(priv->nconfig, 0);
>> +
>> +	/* Wait for CONF_DONE to get low */  
>Does the retry number come from datasheet?

No, it is an empirical value.

...
>> +	if (!retries) {
>> +		dev_warn(dev, "CONF_DONE wait timeout\n");  
>
>warn or err?

err fits better here, I'll update to dev_err().

...
>> +	struct fpp_fpga_mgr_priv *priv = mgr->priv;
>> +	struct device *dev = &priv->pdev->dev;
>> +	int retries = MAX_RETRIES;
>> +	int ret;
>> +	u8 mask, status;  
>
>Can you swap the two lines, reverse xmas-tree please.

Will do in v5.

Thanks,
Anatolij
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp b/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp
new file mode 100644
index 000000000000..f3055100d07e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-ftdi-fifo-fpp
@@ -0,0 +1,7 @@ 
+What:		/sys/bus/platform/devices/ftdi-fifo-fpp-mgr.N/cfg_mode
+Date:		Feb 2019
+Kernel Version:	5.2
+Contact:	Anatolij Gustschin <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 c20445b867ae..19d82163a0b2 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -50,6 +50,13 @@  config FPGA_MGR_ALTERA_CVP
 	  FPGA manager driver support for Arria-V, Cyclone-V, Stratix-V
 	  and Arria 10 Altera FPGAs using the CvP interface over PCIe.
 
+config FPGA_MGR_FTDI_FIFO_FPP
+	tristate "Altera FPP over FT232H FIFO"
+	depends on USB_FT232H_INTF
+	help
+	  FPGA manager driver support for Altera fast passive parallel
+	  interface (FPP) over FT232H FT245 FIFO.
+
 config FPGA_MGR_ZYNQ_FPGA
 	tristate "Xilinx Zynq FPGA"
 	depends on ARCH_ZYNQ || COMPILE_TEST
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index c0dd4c82fbdb..61725d31e6d1 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -9,6 +9,7 @@  obj-$(CONFIG_FPGA)			+= fpga-mgr.o
 # FPGA Manager Drivers
 obj-$(CONFIG_FPGA_MGR_ALTERA_CVP)	+= altera-cvp.o
 obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI)	+= altera-ps-spi.o
+obj-$(CONFIG_FPGA_MGR_FTDI_FIFO_FPP)	+= ftdi-fifo-fpp.o
 obj-$(CONFIG_FPGA_MGR_ICE40_SPI)	+= ice40-spi.o
 obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI)	+= machxo2-spi.o
 obj-$(CONFIG_FPGA_MGR_SOCFPGA)		+= socfpga.o
diff --git a/drivers/fpga/ftdi-fifo-fpp.c b/drivers/fpga/ftdi-fifo-fpp.c
new file mode 100644
index 000000000000..2bc72335cd70
--- /dev/null
+++ b/drivers/fpga/ftdi-fifo-fpp.c
@@ -0,0 +1,594 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Altera FPGA firmware upload via FPP using FT232H Bitbang/FT245-FIFO.
+ *
+ * Copyright (C) 2017 - 2018 DENX Software Engineering
+ * Anatolij Gustschin <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) - 1);
+		priv->cfg_mode[4] = 0;
+		priv->ops = &fpp_mgr_ft245_fifo_ops;
+		gpiod_direction_output_raw(priv->conf_done, 0);
+	} else if (!strncmp(buf, "bitbang", 7)) {
+		strncpy(priv->cfg_mode, buf, sizeof(priv->cfg_mode) - 1);
+		priv->cfg_mode[7] = 0;
+		priv->ops = &fpp_mgr_bitbang_ops;
+		gpiod_direction_input(priv->conf_done);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(cfg_mode);
+
+static int fpp_fpga_mgr_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct fpp_fpga_mgr_priv *priv;
+	struct fpga_manager *mgr;
+	struct fifo_fpp_mgr_platform_data *pd;
+	int ret, retries = MAX_RETRIES;
+	char id_string[8];
+	u8 status = 0;
+
+	pd = dev->platform_data;
+	if (!pd) {
+		dev_err(dev, "Missing platform data.\n");
+		return -EINVAL;
+	}
+
+	if (!pd->ops ||
+	    !pd->ops->bulk_xfer || !pd->ops->ctrl_xfer ||
+	    !pd->ops->read_data || !pd->ops->write_data ||
+	    !pd->ops->set_bitmode || !pd->ops->set_baudrate ||
+	    !pd->ops->disable_bitbang)
+		return -EINVAL;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->intf = to_usb_interface(dev->parent);
+	priv->iops = pd->ops;
+
+	ret = sscanf(dev_name(dev->parent), "%s", priv->usb_dev_id);
+	if (ret != 1) {
+		dev_err(dev, "Can't get parent device name: %d\n", ret);
+		return -ENODEV;
+	}
+
+	priv->pdev = pdev;
+	priv->ops = &fpp_mgr_ft245_fifo_ops;
+	strncpy(priv->cfg_mode, "fifo", sizeof(priv->cfg_mode));
+
+	priv->nconfig = devm_gpiod_get(dev, "nconfig", GPIOD_OUT_LOW);
+	if (IS_ERR(priv->nconfig)) {
+		ret = PTR_ERR(priv->nconfig);
+		dev_err(dev, "Failed to get nconfig gpio: %d\n", ret);
+		return ret;
+	}
+
+	priv->conf_done = devm_gpiod_get(dev, "conf_done", GPIOD_OUT_LOW);
+	if (IS_ERR(priv->conf_done)) {
+		ret = PTR_ERR(priv->conf_done);
+		dev_err(dev, "Failed to get conf_done gpio: %d\n", ret);
+		goto err_cfg1;
+	}
+
+	priv->bulk_buf = devm_kmalloc(dev, BULK_OUT_BUF_SZ,
+				      GFP_KERNEL | GFP_DMA32);
+	if (!priv->bulk_buf) {
+		ret = -ENOMEM;
+		goto err_cfg2;
+	}
+
+	ret = priv->iops->set_bitmode(priv->intf, 0xff, BITMODE_SYNCFF);
+	if (ret)
+		goto err_cfg2;
+
+	/* Read status register from CPLD */
+	do {
+		ret = fpp_fpga_mgr_read_status(priv, &status);
+		if (!ret)
+			break;
+	} while (--retries > 0);
+
+	if (!retries) {
+		ret = -ETIMEDOUT;
+		goto err_cfg2;
+	}
+
+	priv->rev = (status & IN_BOARD_REV) ? BOARD_REVB : BOARD_REVA;
+
+	if (priv->rev == BOARD_REVB) {
+		priv->addr_sel = (status & IN_ADDR_SELECT) ?
+				 ADDR_SELECT_NC : ADDR_SELECT_GND;
+		if (priv->addr_sel == ADDR_SELECT_NC)
+			strncpy(id_string, "right", sizeof(id_string));
+		else
+			strncpy(id_string, "left", sizeof(id_string));
+	} else {
+		priv->addr_sel = ADDR_SELECT_INVALID;
+		strncpy(id_string, "single", sizeof(id_string));
+	}
+
+	dev_info(dev, "Board Rev %d, Addr Sel %d\n", priv->rev, priv->addr_sel);
+
+	/* Use unique board ID and USB bus/port in FPGA manager name */
+	snprintf(priv->fpga_mgr_name, sizeof(priv->fpga_mgr_name),
+		 "ftdi-fpp-fpga-mgr %s %s", id_string, priv->usb_dev_id);
+
+	mgr = devm_fpga_mgr_create(dev, priv->fpga_mgr_name,
+				   &fpp_fpga_mgr_ops, priv);
+	if (!mgr)
+		goto err_cfg2;
+
+	platform_set_drvdata(pdev, mgr);
+
+	ret = fpga_mgr_register(mgr);
+	if (ret) {
+		dev_err(dev, "unable to register FPGA manager\n");
+		goto err_cfg2;
+	}
+
+	ret = device_create_file(dev, &dev_attr_cfg_mode);
+	if (ret)
+		dev_warn(dev, "Can't create cfg_mode interface %d\n", ret);
+
+	return 0;
+
+err_cfg2:
+	devm_gpiod_put(dev, priv->conf_done);
+err_cfg1:
+	devm_gpiod_put(dev, priv->nconfig);
+	return ret;
+}
+
+static int fpp_fpga_mgr_remove(struct platform_device *pdev)
+{
+	struct fpga_manager *mgr = platform_get_drvdata(pdev);
+	struct fpp_fpga_mgr_priv *priv = mgr->priv;
+
+	device_remove_file(&pdev->dev, &dev_attr_cfg_mode);
+	fpga_mgr_unregister(mgr);
+	devm_gpiod_put(&pdev->dev, priv->conf_done);
+	devm_gpiod_put(&pdev->dev, priv->nconfig);
+	return 0;
+}
+
+static struct platform_driver fpp_fpga_mgr_driver = {
+	.driver.name	= "ftdi-fifo-fpp-mgr",
+	.probe		= fpp_fpga_mgr_probe,
+	.remove		= fpp_fpga_mgr_remove,
+};
+
+module_platform_driver(fpp_fpga_mgr_driver);
+
+MODULE_ALIAS("platform:ftdi-fifo-fpp-mgr");
+MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
+MODULE_DESCRIPTION("FT232H Bitbang/FT245-FIFO FPP FPGA Manager Driver");
+MODULE_LICENSE("GPL v2");