diff mbox series

[v8,1/2] fpga: lattice-sysconfig-spi: add Lattice sysCONFIG FPGA manager

Message ID 20220825112433.14583-2-i.bornyakov@metrotek.ru (mailing list archive)
State New
Headers show
Series Lattice sysCONFIG SPI FPGA manager | expand

Commit Message

Ivan Bornyakov Aug. 25, 2022, 11:24 a.m. UTC
Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
FPGAs over slave SPI sysCONFIG interface.

Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
---
 drivers/fpga/Kconfig                 |   7 +
 drivers/fpga/Makefile                |   1 +
 drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
 3 files changed, 638 insertions(+)
 create mode 100644 drivers/fpga/lattice-sysconfig-spi.c

Comments

Ivan Bornyakov Aug. 26, 2022, 8:16 a.m. UTC | #1
On Thu, Aug 25, 2022 at 02:24:32PM +0300, Ivan Bornyakov wrote:
> Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> FPGAs over slave SPI sysCONFIG interface.
> 
> Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> ---
>  drivers/fpga/Kconfig                 |   7 +
>  drivers/fpga/Makefile                |   1 +
>  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
>  3 files changed, 638 insertions(+)
>  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> 
> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> index 6c416955da53..991d9d976dca 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
>  	  programming over slave SPI interface with .dat formatted
>  	  bitstream image.
>  
> +config FPGA_MGR_LATTICE_SPI
> +	tristate "Lattice sysCONFIG SPI FPGA manager"
> +	depends on SPI
> +	help
> +	  FPGA manager driver support for Lattice FPGAs programming over slave
> +	  SPI sysCONFIG interface.
> +
>  endif # FPGA
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index 42ae8b58abce..115dba916024 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
>  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
>  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
>  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
>  
> diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> new file mode 100644
> index 000000000000..145b5b27b88d
> --- /dev/null
> +++ b/drivers/fpga/lattice-sysconfig-spi.c
> @@ -0,0 +1,630 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/fpga/fpga-mgr.h>
> +#include <linux/of_device.h>
> +#include <linux/spi/spi.h>
> +
> +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> +
> +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> +
> +#define	SYSCONFIG_POLL_RETRIES		100000
> +
> +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> +#define	ECP5_ISC_ERASE_OPERAND		0x01
> +
> +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> +#define	MACHXO2_PAGE_SIZE		16
> +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> +
> +struct sysconfig_priv {
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	struct spi_device *spi;
> +	u8 isc_enable_operand;
> +	u8 isc_erase_operand;
> +};
> +
> +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	u8 busy;
> +
> +	while (retries--) {
> +		ret = spi_write_then_read(data->spi,
> +					  lsc_check_busy, sizeof(lsc_check_busy),
> +					  &busy, sizeof(busy));
> +		if (ret)
> +			return ret;
> +
> +		if (!busy)
> +			return 0;
> +
> +		usleep_range(50, 100);
> +	}
> +
> +	return -EBUSY;
> +}
> +
> +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> +	__be32 device_status;
> +	int ret;
> +
> +	ret = spi_write_then_read(data->spi,
> +				  lsc_read_status, sizeof(lsc_read_status),
> +				  &device_status, sizeof(device_status));
> +	if (ret)
> +		return ret;
> +
> +	*status = be32_to_cpu(device_status);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	int ret;
> +
> +	ret = sysconfig_poll_busy(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_read_status(data, status);
> +}
> +
> +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> +{
> +	int value, retries = SYSCONFIG_POLL_RETRIES;
> +
> +	while (retries--) {
> +		value = gpiod_get_value(gpio);
> +		if (value < 0)
> +			return value;
> +
> +		if ((!is_active && !value) || (is_active && value))
> +			return 0;
> +
> +		ndelay(10);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int sysconfig_refresh(struct sysconfig_priv *data)
> +{
> +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> +	int ret;
> +
> +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(4000, 8000);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> +{
> +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> +	u32 status;
> +	int ret;
> +
> +	isc_enable[1] = data->isc_enable_operand;
> +
> +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> +{
> +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> +	u32 status;
> +	int ret;
> +
> +	isc_erase[1] = data->isc_erase_operand;
> +
> +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_init(struct sysconfig_priv *data)
> +{
> +	int ret;
> +
> +	ret = sysconfig_isc_enable(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_isc_erase(data);
> +}
> +
> +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> +
> +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> +}
> +
> +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> +	struct spi_transfer xfer = {
> +		.tx_buf = lsc_bitstream_burst,
> +		.len = sizeof(lsc_bitstream_burst),
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +
> +	return spi_sync_locked(data->spi, &msg);
> +}
> +
> +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> +{
> +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> +	u32 status;
> +	int ret;
> +
> +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status & SYSCONFIG_STATUS_DONE)
> +		return 0;
> +
> +	return -EFAULT;
> +}
> +
> +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> +{
> +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> +
> +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> +}
> +
> +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +
> +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> +						   FPGA_MGR_STATE_UNKNOWN;
> +}
> +
> +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> +			       struct fpga_image_info *info,
> +			       const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	program = priv->program;
> +	init = priv->init;
> +	done = priv->done;
> +
> +	/* Enter init mode */
> +	gpiod_set_value(program, 1);
> +
> +	ret = sysconfig_poll_gpio(init, true);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(done, false);
> +
> +	if (ret) {
> +		dev_err(dev, "Failed to go to init mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter program mode */
> +	gpiod_set_value(program, 0);
> +
> +	ret = sysconfig_poll_gpio(init, false);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to program mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret) {
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +		return ret;
> +	}
> +
> +	/*
> +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> +	 */
> +	spi_bus_lock(spi->controller);
> +
> +	/* Prepare for bitstream burst write */
> +	ret = sysconfig_lsc_bitstream_burst(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> +		spi_bus_unlock(spi->controller);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct spi_transfer xfer = {
> +		.tx_buf = buf,
> +		.len = count,
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +	int ret;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +	ret = spi_sync_locked(spi, &msg);
> +	if (ret)
> +		spi_bus_unlock(spi->controller);
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> +				   struct fpga_image_info *info)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	/* Bitstream burst write is done, release SPI bus */
> +	spi_bus_unlock(spi->controller);
> +
> +	/* Toggle CS and wait for bitstream write to finish */
> +	ret = spi_write(spi, NULL, 0);
> +	if (!ret)
> +		ret = sysconfig_poll_busy(priv);
> +
> +	if (ret) {
> +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> +		return ret;
> +	}
> +
> +	/* Exit ISC mode */
> +	ret = sysconfig_isc_disable(priv);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(priv->done, true);
> +
> +	if (ret)
> +		dev_err(dev, "Failed to finish ISC\n");
> +
> +	return ret;
> +}
> +
> +static const struct fpga_manager_ops ecp5_fpga_ops = {
> +	.state = ecp5_ops_state,
> +	.write_init = ecp5_ops_write_init,
> +	.write = ecp5_ops_write,
> +	.write_complete = ecp5_ops_write_complete,
> +};
> +
> +static int ecp5_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +	int ret;
> +
> +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> +
> +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> +	if (IS_ERR(priv->done)) {
> +		ret = PTR_ERR(priv->done);
> +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> +	if (IS_ERR(priv->init)) {
> +		ret = PTR_ERR(priv->init);
> +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->program)) {
> +		ret = PTR_ERR(priv->program);
> +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> +				     &ecp5_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	u32 status;
> +	int ret;
> +
> +	ret = sysconfig_read_status(priv, &status);
> +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> +		return FPGA_MGR_STATE_UNKNOWN;
> +
> +	return FPGA_MGR_STATE_OPERATING;
> +}
> +
> +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> +				  struct fpga_image_info *info,
> +				  const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret)
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +
> +	return ret;
> +}
> +
> +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	struct spi_transfer xfers[2] = {
> +		{
> +			.tx_buf = lsc_progincr,
> +			.len = sizeof(lsc_progincr),
> +		}, {
> +			.len = MACHXO2_PAGE_SIZE,
> +		},
> +	};
> +	size_t i;
> +	int ret;
> +
> +	if (count % MACHXO2_PAGE_SIZE) {
> +		dev_err(dev, "Malformed payload.\n");
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> +		xfers[1].tx_buf = buf + i;
> +
> +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> +		if (!ret)
> +			ret = sysconfig_poll_busy(priv);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void machxo2_cleanup(struct sysconfig_priv *data)
> +{
> +	sysconfig_isc_erase(data);
> +	sysconfig_refresh(data);
> +}
> +
> +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> +				      struct fpga_image_info *info)
> +{
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	u32 status;
> +
> +	ret = sysconfig_isc_prog_done(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> +		goto fail;
> +	}
> +
> +	ret = sysconfig_isc_disable(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to disable Configuration Interface\n");
> +		goto fail;
> +	}
> +
> +	while (retries--) {
> +		ret = sysconfig_refresh(priv);
> +		if (!ret)
> +			ret = sysconfig_read_status(priv, &status);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to refresh\n");
> +			break;
> +		}
> +
> +		if (status & SYSCONFIG_STATUS_DONE &&
> +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> +		    !(status & SYSCONFIG_STATUS_ERR))
> +			return 0;
> +	}
> +
> +fail:
> +	machxo2_cleanup(priv);
> +
> +	return -EFAULT;
> +}
> +
> +static const struct fpga_manager_ops machxo2_fpga_ops = {
> +	.state = machxo2_ops_state,
> +	.write_init = machxo2_ops_write_init,
> +	.write = machxo2_ops_write,
> +	.write_complete = machxo2_ops_write_complete,
> +};
> +
> +static int machxo2_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +
> +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> +				     &machxo2_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> +
> +static int sysconfig_probe(struct spi_device *spi)
> +{
> +	const struct spi_device_id *dev_id;
> +	lattice_fpga_probe_func probe_func;
> +	struct device *dev = &spi->dev;
> +	struct sysconfig_priv *priv;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->spi = spi;
> +
> +	probe_func = of_device_get_match_data(&spi->dev);
> +	if (!probe_func) {
> +		dev_id = spi_get_device_id(spi);
> +		if (!dev_id)
> +			return -ENODEV;
> +
> +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> +	}
> +
> +	if (!probe_func)
> +		return -EINVAL;
> +
> +	return probe_func(priv);
> +}
> +
> +static const struct spi_device_id sysconfig_spi_ids[] = {
> +	{
> +		.name = "ecp5-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)ecp5_probe,
> +	}, {
> +		.name = "machxo2-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)machxo2_probe,
> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> +
> +#if IS_ENABLED(CONFIG_OF)
> +static const struct of_device_id sysconfig_of_ids[] = {
> +	{
> +		.compatible = "lattice,ecp5-fpga-mgr",
> +		.data = ecp5_probe,
> +	}, {
> +		.compatible = "lattice,machxo2-fpga-mgr",
> +		.data = machxo2_probe
> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> +#endif /* IS_ENABLED(CONFIG_OF) */
> +
> +static struct spi_driver lattice_sysconfig_driver = {
> +	.probe = sysconfig_probe,
> +	.id_table = sysconfig_spi_ids,
> +	.driver = {
> +		.name = "lattice_sysconfig_spi_fpga_mgr",
> +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> +	},
> +};
> +
> +module_spi_driver(lattice_sysconfig_driver);
> +
> +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> +MODULE_LICENSE("GPL");
> -- 
> 2.37.2
> 

Cc: Johannes Zink <j.zink@pengutronix.de>
Ivan Bornyakov Aug. 26, 2022, 10:57 a.m. UTC | #2
On Thu, Aug 25, 2022 at 02:24:32PM +0300, Ivan Bornyakov wrote:
> Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> FPGAs over slave SPI sysCONFIG interface.
> 
> Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> ---
>  drivers/fpga/Kconfig                 |   7 +
>  drivers/fpga/Makefile                |   1 +
>  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
>  3 files changed, 638 insertions(+)
>  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> 
> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> index 6c416955da53..991d9d976dca 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
>  	  programming over slave SPI interface with .dat formatted
>  	  bitstream image.
>  
> +config FPGA_MGR_LATTICE_SPI
> +	tristate "Lattice sysCONFIG SPI FPGA manager"
> +	depends on SPI
> +	help
> +	  FPGA manager driver support for Lattice FPGAs programming over slave
> +	  SPI sysCONFIG interface.
> +
>  endif # FPGA
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index 42ae8b58abce..115dba916024 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
>  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
>  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
>  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
>  
> diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> new file mode 100644
> index 000000000000..145b5b27b88d
> --- /dev/null
> +++ b/drivers/fpga/lattice-sysconfig-spi.c
> @@ -0,0 +1,630 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/fpga/fpga-mgr.h>
> +#include <linux/of_device.h>
> +#include <linux/spi/spi.h>
> +
> +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> +
> +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> +
> +#define	SYSCONFIG_POLL_RETRIES		100000
> +
> +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> +#define	ECP5_ISC_ERASE_OPERAND		0x01
> +
> +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> +#define	MACHXO2_PAGE_SIZE		16
> +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> +
> +struct sysconfig_priv {
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	struct spi_device *spi;
> +	u8 isc_enable_operand;
> +	u8 isc_erase_operand;
> +};

As Johannes Zink working on adding I2C to MachXO2, I am thinking how
about we add to struct sysconfig_priv a callback

int (*sysconfig_transfer)(struct sysconfig_priv,
			  const void *tx_buf, size_t tx_len,
			  void *rx_buf, size_t rx_len)

For SPI it would be defined like this:

static int sysconfig_spi_transfer(struct sysconfig_priv *data,
				  const void *tx_buf, size_t tx_len,
				  void *rx_buf, size_t rx_len)
{
	if (!rx_buf)
		return spi_write(data->spi, tx_buf, tx_size);
	
	return spi_write_then_read(data->spi, tx_buf, tx_size, rx_buf, rx_size);
}

And later in sysconfig_ commands we would call this callback.

Would it be a good enough starting base for Johannes to add I2C interface
for MachXO2?

> +
> +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	u8 busy;
> +
> +	while (retries--) {
> +		ret = spi_write_then_read(data->spi,
> +					  lsc_check_busy, sizeof(lsc_check_busy),
> +					  &busy, sizeof(busy));
> +		if (ret)
> +			return ret;
> +
> +		if (!busy)
> +			return 0;
> +
> +		usleep_range(50, 100);
> +	}
> +
> +	return -EBUSY;
> +}
> +
> +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> +	__be32 device_status;
> +	int ret;
> +
> +	ret = spi_write_then_read(data->spi,
> +				  lsc_read_status, sizeof(lsc_read_status),
> +				  &device_status, sizeof(device_status));
> +	if (ret)
> +		return ret;
> +
> +	*status = be32_to_cpu(device_status);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	int ret;
> +
> +	ret = sysconfig_poll_busy(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_read_status(data, status);
> +}
> +
> +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> +{
> +	int value, retries = SYSCONFIG_POLL_RETRIES;
> +
> +	while (retries--) {
> +		value = gpiod_get_value(gpio);
> +		if (value < 0)
> +			return value;
> +
> +		if ((!is_active && !value) || (is_active && value))
> +			return 0;
> +
> +		ndelay(10);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int sysconfig_refresh(struct sysconfig_priv *data)
> +{
> +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> +	int ret;
> +
> +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(4000, 8000);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> +{
> +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> +	u32 status;
> +	int ret;
> +
> +	isc_enable[1] = data->isc_enable_operand;
> +
> +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}

Quirks for I2C enable and refresh commands are 3-bytes instead of
4-bytes for SPI can be added here. Just check which of data->spi or
data->i2c is not NULL.

> +
> +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> +{
> +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> +	u32 status;
> +	int ret;
> +
> +	isc_erase[1] = data->isc_erase_operand;
> +
> +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_init(struct sysconfig_priv *data)
> +{
> +	int ret;
> +
> +	ret = sysconfig_isc_enable(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_isc_erase(data);
> +}
> +
> +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> +
> +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> +}
> +
> +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> +	struct spi_transfer xfer = {
> +		.tx_buf = lsc_bitstream_burst,
> +		.len = sizeof(lsc_bitstream_burst),
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +
> +	return spi_sync_locked(data->spi, &msg);
> +}
> +
> +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> +{
> +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> +	u32 status;
> +	int ret;
> +
> +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status & SYSCONFIG_STATUS_DONE)
> +		return 0;
> +
> +	return -EFAULT;
> +}
> +
> +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> +{
> +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> +
> +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> +}
> +
> +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +
> +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> +						   FPGA_MGR_STATE_UNKNOWN;
> +}
> +
> +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> +			       struct fpga_image_info *info,
> +			       const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	program = priv->program;
> +	init = priv->init;
> +	done = priv->done;
> +
> +	/* Enter init mode */
> +	gpiod_set_value(program, 1);
> +
> +	ret = sysconfig_poll_gpio(init, true);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(done, false);
> +
> +	if (ret) {
> +		dev_err(dev, "Failed to go to init mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter program mode */
> +	gpiod_set_value(program, 0);
> +
> +	ret = sysconfig_poll_gpio(init, false);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to program mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret) {
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +		return ret;
> +	}
> +
> +	/*
> +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> +	 */
> +	spi_bus_lock(spi->controller);
> +
> +	/* Prepare for bitstream burst write */
> +	ret = sysconfig_lsc_bitstream_burst(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> +		spi_bus_unlock(spi->controller);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct spi_transfer xfer = {
> +		.tx_buf = buf,
> +		.len = count,
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +	int ret;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +	ret = spi_sync_locked(spi, &msg);
> +	if (ret)
> +		spi_bus_unlock(spi->controller);
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> +				   struct fpga_image_info *info)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	/* Bitstream burst write is done, release SPI bus */
> +	spi_bus_unlock(spi->controller);
> +
> +	/* Toggle CS and wait for bitstream write to finish */
> +	ret = spi_write(spi, NULL, 0);
> +	if (!ret)
> +		ret = sysconfig_poll_busy(priv);
> +
> +	if (ret) {
> +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> +		return ret;
> +	}
> +
> +	/* Exit ISC mode */
> +	ret = sysconfig_isc_disable(priv);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(priv->done, true);
> +
> +	if (ret)
> +		dev_err(dev, "Failed to finish ISC\n");
> +
> +	return ret;
> +}
> +
> +static const struct fpga_manager_ops ecp5_fpga_ops = {
> +	.state = ecp5_ops_state,
> +	.write_init = ecp5_ops_write_init,
> +	.write = ecp5_ops_write,
> +	.write_complete = ecp5_ops_write_complete,
> +};
> +
> +static int ecp5_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +	int ret;
> +
> +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> +
> +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> +	if (IS_ERR(priv->done)) {
> +		ret = PTR_ERR(priv->done);
> +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> +	if (IS_ERR(priv->init)) {
> +		ret = PTR_ERR(priv->init);
> +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->program)) {
> +		ret = PTR_ERR(priv->program);
> +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> +				     &ecp5_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	u32 status;
> +	int ret;
> +
> +	ret = sysconfig_read_status(priv, &status);
> +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> +		return FPGA_MGR_STATE_UNKNOWN;
> +
> +	return FPGA_MGR_STATE_OPERATING;
> +}
> +
> +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> +				  struct fpga_image_info *info,
> +				  const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret)
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +
> +	return ret;
> +}
> +
> +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	struct spi_transfer xfers[2] = {
> +		{
> +			.tx_buf = lsc_progincr,
> +			.len = sizeof(lsc_progincr),
> +		}, {
> +			.len = MACHXO2_PAGE_SIZE,
> +		},
> +	};
> +	size_t i;
> +	int ret;
> +
> +	if (count % MACHXO2_PAGE_SIZE) {
> +		dev_err(dev, "Malformed payload.\n");
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> +		xfers[1].tx_buf = buf + i;
> +
> +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> +		if (!ret)
> +			ret = sysconfig_poll_busy(priv);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void machxo2_cleanup(struct sysconfig_priv *data)
> +{
> +	sysconfig_isc_erase(data);
> +	sysconfig_refresh(data);
> +}
> +
> +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> +				      struct fpga_image_info *info)
> +{
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	u32 status;
> +
> +	ret = sysconfig_isc_prog_done(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> +		goto fail;
> +	}
> +
> +	ret = sysconfig_isc_disable(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to disable Configuration Interface\n");
> +		goto fail;
> +	}
> +
> +	while (retries--) {
> +		ret = sysconfig_refresh(priv);
> +		if (!ret)
> +			ret = sysconfig_read_status(priv, &status);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to refresh\n");
> +			break;
> +		}
> +
> +		if (status & SYSCONFIG_STATUS_DONE &&
> +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> +		    !(status & SYSCONFIG_STATUS_ERR))
> +			return 0;
> +	}
> +
> +fail:
> +	machxo2_cleanup(priv);
> +
> +	return -EFAULT;
> +}
> +
> +static const struct fpga_manager_ops machxo2_fpga_ops = {
> +	.state = machxo2_ops_state,
> +	.write_init = machxo2_ops_write_init,
> +	.write = machxo2_ops_write,
> +	.write_complete = machxo2_ops_write_complete,
> +};
> +
> +static int machxo2_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +
> +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> +				     &machxo2_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> +
> +static int sysconfig_probe(struct spi_device *spi)
> +{
> +	const struct spi_device_id *dev_id;
> +	lattice_fpga_probe_func probe_func;
> +	struct device *dev = &spi->dev;
> +	struct sysconfig_priv *priv;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->spi = spi;
> +
> +	probe_func = of_device_get_match_data(&spi->dev);
> +	if (!probe_func) {
> +		dev_id = spi_get_device_id(spi);
> +		if (!dev_id)
> +			return -ENODEV;
> +
> +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> +	}
> +
> +	if (!probe_func)
> +		return -EINVAL;
> +
> +	return probe_func(priv);
> +}
> +
> +static const struct spi_device_id sysconfig_spi_ids[] = {
> +	{
> +		.name = "ecp5-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)ecp5_probe,
> +	}, {
> +		.name = "machxo2-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)machxo2_probe,
> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> +
> +#if IS_ENABLED(CONFIG_OF)
> +static const struct of_device_id sysconfig_of_ids[] = {
> +	{
> +		.compatible = "lattice,ecp5-fpga-mgr",
> +		.data = ecp5_probe,
> +	}, {
> +		.compatible = "lattice,machxo2-fpga-mgr",
> +		.data = machxo2_probe
> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> +#endif /* IS_ENABLED(CONFIG_OF) */
> +
> +static struct spi_driver lattice_sysconfig_driver = {
> +	.probe = sysconfig_probe,
> +	.id_table = sysconfig_spi_ids,
> +	.driver = {
> +		.name = "lattice_sysconfig_spi_fpga_mgr",
> +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> +	},
> +};
> +
> +module_spi_driver(lattice_sysconfig_driver);
> +
> +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> +MODULE_LICENSE("GPL");
> -- 
> 2.37.2
>
Ivan Bornyakov Aug. 26, 2022, 1:18 p.m. UTC | #3
On Fri, Aug 26, 2022 at 01:57:21PM +0300, Ivan Bornyakov wrote:
> On Thu, Aug 25, 2022 at 02:24:32PM +0300, Ivan Bornyakov wrote:
> > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > FPGAs over slave SPI sysCONFIG interface.
> > 
> > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > ---
> >
> > ... snip ...
> >
> > +
> > +struct sysconfig_priv {
> > +	struct gpio_desc *program;
> > +	struct gpio_desc *init;
> > +	struct gpio_desc *done;
> > +	struct spi_device *spi;
> > +	u8 isc_enable_operand;
> > +	u8 isc_erase_operand;
> > +};
> 
> As Johannes Zink working on adding I2C to MachXO2, I am thinking how
> about we add to struct sysconfig_priv a callback
> 
> int (*sysconfig_transfer)(struct sysconfig_priv,
> 			  const void *tx_buf, size_t tx_len,
> 			  void *rx_buf, size_t rx_len)
> 
> For SPI it would be defined like this:
> 
> static int sysconfig_spi_transfer(struct sysconfig_priv *data,
> 				  const void *tx_buf, size_t tx_len,
> 				  void *rx_buf, size_t rx_len)
> {
> 	if (!rx_buf)
> 		return spi_write(data->spi, tx_buf, tx_size);
> 	
> 	return spi_write_then_read(data->spi, tx_buf, tx_size, rx_buf, rx_size);
> }
> 
> And later in sysconfig_ commands we would call this callback.
> 
> Would it be a good enough starting base for Johannes to add I2C interface
> for MachXO2?
> 
> >
> > ... snip ...
> >
> > +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> > +{
> > +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> > +	u32 status;
> > +	int ret;
> > +
> > +	isc_enable[1] = data->isc_enable_operand;
> > +
> > +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = sysconfig_poll_status(data, &status);
> > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > +		return ret ? : -EFAULT;
> > +
> > +	return 0;
> > +}
> 
> Quirks for I2C enable and refresh commands are 3-bytes instead of
> 4-bytes for SPI can be added here. Just check which of data->spi or
> data->i2c is not NULL.
> 

Actually, here is a patch for your tinkering.

I guess, what you need to do for MachXO2 I2C support is:
1) implement sysconfig_transfer callback for I2C. Something like this:

static int sysconfig_i2c_transfer(struct sysconfig_priv *data,
				  const void *tx_buf, size_t tx_len,
				  void *rx_buf, size_t rx_len)
{
	struct i2c_client *i2c = data->i2c;
	struct i2c_msg msg[] = {
		{
			.addr = i2c->addr,
			.flags = 0,
			.buf = tx_buf,
			.len = tx_len,
		}, {
			.addr = i2c->addr,
			.flags = I2C_M_RD,
			.buf = rx_buf,
			.len = rx_len,
		}
	};

	if (!rx_buf)
		return i2c_master_send(i2c, tx_buf, tx_len);

	return i2c_transfer(i2c->adapter, msg, ARRAY_SIZE(msg));
}

2) add quirks for 3-byte transfer to sysconfig_refresh() and
   sysconfig_isc_enable()
3) add probe routine
4) add separate fpga_manager_ops->write() for I2C
   state(), write_init() and write_complete() can be reused



---
 drivers/fpga/lattice-sysconfig-spi.c | 46 ++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 13 deletions(-)

diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
index 145b5b27b88d..c2caf613d2f0 100644
--- a/drivers/fpga/lattice-sysconfig-spi.c
+++ b/drivers/fpga/lattice-sysconfig-spi.c
@@ -41,8 +41,21 @@ struct sysconfig_priv {
 	struct spi_device *spi;
 	u8 isc_enable_operand;
 	u8 isc_erase_operand;
+	int (*sysconfig_transfer)(struct sysconfig_priv *data,
+				  const void *tx_buf, size_t tx_len,
+				  void *rx_buf, size_t rx_len);
 };
 
+static int sysconfig_spi_transfer(struct sysconfig_priv *data,
+				  const void *tx_buf, size_t tx_len,
+				  void *rx_buf, size_t rx_len)
+{
+	if (!rx_buf)
+		return spi_write(data->spi, tx_buf, tx_len);
+
+	return spi_write_then_read(data->spi, tx_buf, tx_len, rx_buf, rx_len);
+}
+
 static int sysconfig_poll_busy(struct sysconfig_priv *data)
 {
 	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
@@ -50,9 +63,9 @@ static int sysconfig_poll_busy(struct sysconfig_priv *data)
 	u8 busy;
 
 	while (retries--) {
-		ret = spi_write_then_read(data->spi,
-					  lsc_check_busy, sizeof(lsc_check_busy),
-					  &busy, sizeof(busy));
+		ret = data->sysconfig_transfer(data, lsc_check_busy,
+					       sizeof(lsc_check_busy),
+					       &busy, sizeof(busy));
 		if (ret)
 			return ret;
 
@@ -71,9 +84,9 @@ static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
 	__be32 device_status;
 	int ret;
 
-	ret = spi_write_then_read(data->spi,
-				  lsc_read_status, sizeof(lsc_read_status),
-				  &device_status, sizeof(device_status));
+	ret = data->sysconfig_transfer(data, lsc_read_status,
+				       sizeof(lsc_read_status),
+				       &device_status, sizeof(device_status));
 	if (ret)
 		return ret;
 
@@ -116,7 +129,8 @@ static int sysconfig_refresh(struct sysconfig_priv *data)
 	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
 	int ret;
 
-	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
+	ret = data->sysconfig_transfer(data, lsc_refresh, sizeof(lsc_refresh),
+				       NULL, 0);
 	if (ret)
 		return ret;
 
@@ -133,7 +147,8 @@ static int sysconfig_isc_enable(struct sysconfig_priv *data)
 
 	isc_enable[1] = data->isc_enable_operand;
 
-	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
+	ret = data->sysconfig_transfer(data, isc_enable, sizeof(isc_enable),
+				       NULL, 0);
 	if (ret)
 		return ret;
 
@@ -152,7 +167,8 @@ static int sysconfig_isc_erase(struct sysconfig_priv *data)
 
 	isc_erase[1] = data->isc_erase_operand;
 
-	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
+	ret = data->sysconfig_transfer(data, isc_erase, sizeof(isc_erase),
+				       NULL, 0);
 	if (ret)
 		return ret;
 
@@ -178,7 +194,8 @@ static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
 {
 	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
 
-	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
+	return data->sysconfig_transfer(data, lsc_init_addr,
+					sizeof(lsc_init_addr), NULL, 0);
 }
 
 static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
@@ -202,7 +219,8 @@ static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
 	u32 status;
 	int ret;
 
-	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
+	ret = data->sysconfig_transfer(data, isc_prog_done,
+				       sizeof(isc_prog_done), NULL, 0);
 	if (ret)
 		return ret;
 
@@ -220,7 +238,8 @@ static int sysconfig_isc_disable(struct sysconfig_priv *data)
 {
 	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
 
-	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
+	return data->sysconfig_transfer(data, isc_disable, sizeof(isc_disable),
+					NULL, 0);
 }
 
 static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
@@ -336,7 +355,7 @@ static int ecp5_ops_write_complete(struct fpga_manager *mgr,
 	spi_bus_unlock(spi->controller);
 
 	/* Toggle CS and wait for bitstream write to finish */
-	ret = spi_write(spi, NULL, 0);
+	ret = priv->sysconfig_transfer(priv, NULL, 0, NULL, 0);
 	if (!ret)
 		ret = sysconfig_poll_busy(priv);
 
@@ -575,6 +594,7 @@ static int sysconfig_probe(struct spi_device *spi)
 		return -ENOMEM;
 
 	priv->spi = spi;
+	priv->sysconfig_transfer = sysconfig_spi_transfer;
 
 	probe_func = of_device_get_match_data(&spi->dev);
 	if (!probe_func) {
Xu Yilun Aug. 29, 2022, 7:25 a.m. UTC | #4
On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> FPGAs over slave SPI sysCONFIG interface.
> 
> Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> ---
>  drivers/fpga/Kconfig                 |   7 +
>  drivers/fpga/Makefile                |   1 +
>  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
>  3 files changed, 638 insertions(+)
>  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> 
> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> index 6c416955da53..991d9d976dca 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
>  	  programming over slave SPI interface with .dat formatted
>  	  bitstream image.
>  
> +config FPGA_MGR_LATTICE_SPI
> +	tristate "Lattice sysCONFIG SPI FPGA manager"
> +	depends on SPI
> +	help
> +	  FPGA manager driver support for Lattice FPGAs programming over slave
> +	  SPI sysCONFIG interface.
> +
>  endif # FPGA
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index 42ae8b58abce..115dba916024 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
>  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
>  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
>  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
>  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
>  
> diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> new file mode 100644
> index 000000000000..145b5b27b88d
> --- /dev/null
> +++ b/drivers/fpga/lattice-sysconfig-spi.c
> @@ -0,0 +1,630 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/fpga/fpga-mgr.h>
> +#include <linux/of_device.h>
> +#include <linux/spi/spi.h>
> +
> +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> +
> +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> +
> +#define	SYSCONFIG_POLL_RETRIES		100000
> +
> +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> +#define	ECP5_ISC_ERASE_OPERAND		0x01
> +
> +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> +#define	MACHXO2_PAGE_SIZE		16
> +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> +#define	MACHXO2_ISC_ERASE_OPERAND	0x04

You need to deliver the meaning of each operand, rather than just point
out this is for machxo2, that is for esp5. I assume the sysCONFIG will
not deliberately design different commands with the exact same
functionality for different boards.

We should have a set of common configurations with finer granularity that
board specific fpga manager device could select from, rather than create
more and more similar macros for each new board.

Back to the 2 operands, what's the meaning of 0x1 & 0x4 for erase, also
0x0 & 0x8 for enable?

> +
> +struct sysconfig_priv {
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	struct spi_device *spi;
> +	u8 isc_enable_operand;
> +	u8 isc_erase_operand;
> +};
> +
> +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	u8 busy;
> +
> +	while (retries--) {
> +		ret = spi_write_then_read(data->spi,
> +					  lsc_check_busy, sizeof(lsc_check_busy),
> +					  &busy, sizeof(busy));
> +		if (ret)
> +			return ret;
> +
> +		if (!busy)
> +			return 0;
> +
> +		usleep_range(50, 100);
> +	}
> +
> +	return -EBUSY;
> +}
> +
> +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> +	__be32 device_status;
> +	int ret;
> +
> +	ret = spi_write_then_read(data->spi,
> +				  lsc_read_status, sizeof(lsc_read_status),
> +				  &device_status, sizeof(device_status));
> +	if (ret)
> +		return ret;
> +
> +	*status = be32_to_cpu(device_status);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> +{
> +	int ret;
> +
> +	ret = sysconfig_poll_busy(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_read_status(data, status);
> +}
> +
> +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> +{
> +	int value, retries = SYSCONFIG_POLL_RETRIES;
> +
> +	while (retries--) {
> +		value = gpiod_get_value(gpio);
> +		if (value < 0)
> +			return value;
> +
> +		if ((!is_active && !value) || (is_active && value))
> +			return 0;
> +
> +		ndelay(10);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int sysconfig_refresh(struct sysconfig_priv *data)
> +{
> +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> +	int ret;
> +
> +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(4000, 8000);
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> +{
> +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> +	u32 status;
> +	int ret;
> +
> +	isc_enable[1] = data->isc_enable_operand;
> +
> +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> +{
> +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> +	u32 status;
> +	int ret;
> +
> +	isc_erase[1] = data->isc_erase_operand;
> +
> +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> +		return ret ? : -EFAULT;
> +
> +	return 0;
> +}
> +
> +static int sysconfig_isc_init(struct sysconfig_priv *data)
> +{
> +	int ret;
> +
> +	ret = sysconfig_isc_enable(data);
> +	if (ret)
> +		return ret;
> +
> +	return sysconfig_isc_erase(data);
> +}
> +
> +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> +
> +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> +}
> +
> +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> +{
> +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> +	struct spi_transfer xfer = {
> +		.tx_buf = lsc_bitstream_burst,
> +		.len = sizeof(lsc_bitstream_burst),
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +
> +	return spi_sync_locked(data->spi, &msg);
> +}
> +
> +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> +{
> +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> +	u32 status;
> +	int ret;
> +
> +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> +	if (ret)
> +		return ret;
> +
> +	ret = sysconfig_poll_status(data, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status & SYSCONFIG_STATUS_DONE)
> +		return 0;
> +
> +	return -EFAULT;
> +}
> +
> +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> +{
> +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> +
> +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> +}
> +
> +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +
> +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> +						   FPGA_MGR_STATE_UNKNOWN;
> +}
> +
> +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> +			       struct fpga_image_info *info,
> +			       const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	struct gpio_desc *program;
> +	struct gpio_desc *init;
> +	struct gpio_desc *done;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	program = priv->program;
> +	init = priv->init;
> +	done = priv->done;
> +
> +	/* Enter init mode */
> +	gpiod_set_value(program, 1);

Same concern, provide gpio or command options for all board specific
fpga manager to select. 

I'll not list each features one by one below.

some more comments below.

> +
> +	ret = sysconfig_poll_gpio(init, true);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(done, false);
> +
> +	if (ret) {
> +		dev_err(dev, "Failed to go to init mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter program mode */
> +	gpiod_set_value(program, 0);
> +
> +	ret = sysconfig_poll_gpio(init, false);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to program mode\n");
> +		return ret;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret) {
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +		return ret;
> +	}
> +
> +	/*
> +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> +	 */
> +	spi_bus_lock(spi->controller);
> +
> +	/* Prepare for bitstream burst write */
> +	ret = sysconfig_lsc_bitstream_burst(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> +		spi_bus_unlock(spi->controller);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct spi_transfer xfer = {
> +		.tx_buf = buf,
> +		.len = count,
> +		.cs_change = 1,
> +	};
> +	struct spi_message msg;
> +	int ret;
> +
> +	spi_message_init_with_transfers(&msg, &xfer, 1);
> +	ret = spi_sync_locked(spi, &msg);
> +	if (ret)
> +		spi_bus_unlock(spi->controller);
> +
> +	return ret;
> +}
> +
> +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> +				   struct fpga_image_info *info)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	/* Bitstream burst write is done, release SPI bus */
> +	spi_bus_unlock(spi->controller);
> +
> +	/* Toggle CS and wait for bitstream write to finish */
> +	ret = spi_write(spi, NULL, 0);
> +	if (!ret)
> +		ret = sysconfig_poll_busy(priv);
> +
> +	if (ret) {
> +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> +		return ret;
> +	}
> +
> +	/* Exit ISC mode */
> +	ret = sysconfig_isc_disable(priv);
> +	if (!ret)
> +		ret = sysconfig_poll_gpio(priv->done, true);
> +
> +	if (ret)
> +		dev_err(dev, "Failed to finish ISC\n");
> +
> +	return ret;
> +}
> +
> +static const struct fpga_manager_ops ecp5_fpga_ops = {
> +	.state = ecp5_ops_state,
> +	.write_init = ecp5_ops_write_init,
> +	.write = ecp5_ops_write,
> +	.write_complete = ecp5_ops_write_complete,
> +};
> +
> +static int ecp5_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +	int ret;
> +
> +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> +
> +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> +	if (IS_ERR(priv->done)) {
> +		ret = PTR_ERR(priv->done);
> +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> +	if (IS_ERR(priv->init)) {
> +		ret = PTR_ERR(priv->init);
> +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->program)) {
> +		ret = PTR_ERR(priv->program);
> +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> +		return ret;
> +	}
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> +				     &ecp5_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	u32 status;
> +	int ret;
> +
> +	ret = sysconfig_read_status(priv, &status);
> +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> +		return FPGA_MGR_STATE_UNKNOWN;
> +
> +	return FPGA_MGR_STATE_OPERATING;
> +}
> +
> +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> +				  struct fpga_image_info *info,
> +				  const char *buf, size_t count)
> +{
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	/* Enter ISC mode */
> +	ret = sysconfig_isc_init(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to go to ISC mode\n");
> +		return ret;
> +	}
> +
> +	/* Initialize the Address Shift Register */
> +	ret = sysconfig_lsc_init_addr(priv);
> +	if (ret)
> +		dev_err(dev,
> +			"Failed to initialize the Address Shift Register\n");
> +
> +	return ret;
> +}
> +
> +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> +{
> +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	struct spi_transfer xfers[2] = {
> +		{
> +			.tx_buf = lsc_progincr,
> +			.len = sizeof(lsc_progincr),
> +		}, {
> +			.len = MACHXO2_PAGE_SIZE,
> +		},
> +	};
> +	size_t i;
> +	int ret;
> +
> +	if (count % MACHXO2_PAGE_SIZE) {
> +		dev_err(dev, "Malformed payload.\n");
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> +		xfers[1].tx_buf = buf + i;
> +
> +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> +		if (!ret)
> +			ret = sysconfig_poll_busy(priv);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void machxo2_cleanup(struct sysconfig_priv *data)
> +{
> +	sysconfig_isc_erase(data);
> +	sysconfig_refresh(data);
> +}
> +
> +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> +				      struct fpga_image_info *info)
> +{
> +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> +	struct sysconfig_priv *priv = mgr->priv;
> +	struct device *dev = &mgr->dev;
> +	u32 status;
> +
> +	ret = sysconfig_isc_prog_done(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> +		goto fail;
> +	}
> +
> +	ret = sysconfig_isc_disable(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to disable Configuration Interface\n");
> +		goto fail;
> +	}
> +
> +	while (retries--) {
> +		ret = sysconfig_refresh(priv);
> +		if (!ret)
> +			ret = sysconfig_read_status(priv, &status);
> +
> +		if (ret) {
> +			dev_err(dev, "Failed to refresh\n");
> +			break;
> +		}
> +
> +		if (status & SYSCONFIG_STATUS_DONE &&
> +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> +		    !(status & SYSCONFIG_STATUS_ERR))
> +			return 0;
> +	}
> +
> +fail:
> +	machxo2_cleanup(priv);
> +
> +	return -EFAULT;
> +}
> +
> +static const struct fpga_manager_ops machxo2_fpga_ops = {
> +	.state = machxo2_ops_state,
> +	.write_init = machxo2_ops_write_init,
> +	.write = machxo2_ops_write,
> +	.write_complete = machxo2_ops_write_complete,
> +};
> +
> +static int machxo2_probe(struct sysconfig_priv *priv)
> +{
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +
> +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> +		return -EINVAL;
> +	}
> +
> +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> +
> +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> +				     &machxo2_fpga_ops, priv);
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> +
> +static int sysconfig_probe(struct spi_device *spi)
> +{
> +	const struct spi_device_id *dev_id;
> +	lattice_fpga_probe_func probe_func;
> +	struct device *dev = &spi->dev;
> +	struct sysconfig_priv *priv;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->spi = spi;
> +
> +	probe_func = of_device_get_match_data(&spi->dev);
> +	if (!probe_func) {
> +		dev_id = spi_get_device_id(spi);
> +		if (!dev_id)
> +			return -ENODEV;
> +
> +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> +	}
> +
> +	if (!probe_func)
> +		return -EINVAL;
> +
> +	return probe_func(priv);
> +}
> +
> +static const struct spi_device_id sysconfig_spi_ids[] = {
> +	{
> +		.name = "ecp5-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)ecp5_probe,
> +	}, {
> +		.name = "machxo2-fpga-mgr",
> +		.driver_data = (kernel_ulong_t)machxo2_probe,

Putting the whole probe flow in driver_data is the same as providing 2
drivers. The purpose is not to put all the code in one file.

Thanks,
Yilun

> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> +
> +#if IS_ENABLED(CONFIG_OF)
> +static const struct of_device_id sysconfig_of_ids[] = {
> +	{
> +		.compatible = "lattice,ecp5-fpga-mgr",
> +		.data = ecp5_probe,
> +	}, {
> +		.compatible = "lattice,machxo2-fpga-mgr",
> +		.data = machxo2_probe
> +	}, {},
> +};
> +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> +#endif /* IS_ENABLED(CONFIG_OF) */
> +
> +static struct spi_driver lattice_sysconfig_driver = {
> +	.probe = sysconfig_probe,
> +	.id_table = sysconfig_spi_ids,
> +	.driver = {
> +		.name = "lattice_sysconfig_spi_fpga_mgr",
> +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> +	},
> +};
> +
> +module_spi_driver(lattice_sysconfig_driver);
> +
> +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> +MODULE_LICENSE("GPL");
> -- 
> 2.37.2
> 
>
Ivan Bornyakov Aug. 29, 2022, 8:27 a.m. UTC | #5
On Mon, Aug 29, 2022 at 03:25:41PM +0800, Xu Yilun wrote:
> On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > FPGAs over slave SPI sysCONFIG interface.
> > 
> > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > ---
> >  drivers/fpga/Kconfig                 |   7 +
> >  drivers/fpga/Makefile                |   1 +
> >  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
> >  3 files changed, 638 insertions(+)
> >  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> > 
> > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> > index 6c416955da53..991d9d976dca 100644
> > --- a/drivers/fpga/Kconfig
> > +++ b/drivers/fpga/Kconfig
> > @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
> >  	  programming over slave SPI interface with .dat formatted
> >  	  bitstream image.
> >  
> > +config FPGA_MGR_LATTICE_SPI
> > +	tristate "Lattice sysCONFIG SPI FPGA manager"
> > +	depends on SPI
> > +	help
> > +	  FPGA manager driver support for Lattice FPGAs programming over slave
> > +	  SPI sysCONFIG interface.
> > +
> >  endif # FPGA
> > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> > index 42ae8b58abce..115dba916024 100644
> > --- a/drivers/fpga/Makefile
> > +++ b/drivers/fpga/Makefile
> > @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
> >  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
> >  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
> >  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> > +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
> >  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
> >  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
> >  
> > diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> > new file mode 100644
> > index 000000000000..145b5b27b88d
> > --- /dev/null
> > +++ b/drivers/fpga/lattice-sysconfig-spi.c
> > @@ -0,0 +1,630 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/fpga/fpga-mgr.h>
> > +#include <linux/of_device.h>
> > +#include <linux/spi/spi.h>
> > +
> > +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> > +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> > +
> > +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> > +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> > +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> > +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> > +
> > +#define	SYSCONFIG_POLL_RETRIES		100000
> > +
> > +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> > +#define	ECP5_ISC_ERASE_OPERAND		0x01
> > +
> > +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> > +#define	MACHXO2_PAGE_SIZE		16
> > +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> > +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> 
> You need to deliver the meaning of each operand, rather than just point
> out this is for machxo2, that is for esp5. I assume the sysCONFIG will
> not deliberately design different commands with the exact same
> functionality for different boards.
> 
> We should have a set of common configurations with finer granularity that
> board specific fpga manager device could select from, rather than create
> more and more similar macros for each new board.
> 
> Back to the 2 operands, what's the meaning of 0x1 & 0x4 for erase,

0x1 - erase SRAM, 0x4 - erase FLASH. I'll redefine them properly in v9

> also 0x0 & 0x8 for enable?
> 

Their meaning is not documented. It's just 0x08 for MachXO2 and 0x00 for
ECP5.

> > +
> > +struct sysconfig_priv {
> > +	struct gpio_desc *program;
> > +	struct gpio_desc *init;
> > +	struct gpio_desc *done;
> > +	struct spi_device *spi;
> > +	u8 isc_enable_operand;
> > +	u8 isc_erase_operand;
> > +};
> > +
> > +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> > +{
> > +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > +	u8 busy;
> > +
> > +	while (retries--) {
> > +		ret = spi_write_then_read(data->spi,
> > +					  lsc_check_busy, sizeof(lsc_check_busy),
> > +					  &busy, sizeof(busy));
> > +		if (ret)
> > +			return ret;
> > +
> > +		if (!busy)
> > +			return 0;
> > +
> > +		usleep_range(50, 100);
> > +	}
> > +
> > +	return -EBUSY;
> > +}
> > +
> > +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> > +{
> > +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> > +	__be32 device_status;
> > +	int ret;
> > +
> > +	ret = spi_write_then_read(data->spi,
> > +				  lsc_read_status, sizeof(lsc_read_status),
> > +				  &device_status, sizeof(device_status));
> > +	if (ret)
> > +		return ret;
> > +
> > +	*status = be32_to_cpu(device_status);
> > +
> > +	return 0;
> > +}
> > +
> > +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> > +{
> > +	int ret;
> > +
> > +	ret = sysconfig_poll_busy(data);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return sysconfig_read_status(data, status);
> > +}
> > +
> > +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> > +{
> > +	int value, retries = SYSCONFIG_POLL_RETRIES;
> > +
> > +	while (retries--) {
> > +		value = gpiod_get_value(gpio);
> > +		if (value < 0)
> > +			return value;
> > +
> > +		if ((!is_active && !value) || (is_active && value))
> > +			return 0;
> > +
> > +		ndelay(10);
> > +	}
> > +
> > +	return -ETIMEDOUT;
> > +}
> > +
> > +static int sysconfig_refresh(struct sysconfig_priv *data)
> > +{
> > +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> > +	int ret;
> > +
> > +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> > +	if (ret)
> > +		return ret;
> > +
> > +	usleep_range(4000, 8000);
> > +
> > +	return 0;
> > +}
> > +
> > +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> > +{
> > +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> > +	u32 status;
> > +	int ret;
> > +
> > +	isc_enable[1] = data->isc_enable_operand;
> > +
> > +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = sysconfig_poll_status(data, &status);
> > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > +		return ret ? : -EFAULT;
> > +
> > +	return 0;
> > +}
> > +
> > +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> > +{
> > +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> > +	u32 status;
> > +	int ret;
> > +
> > +	isc_erase[1] = data->isc_erase_operand;
> > +
> > +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = sysconfig_poll_status(data, &status);
> > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > +		return ret ? : -EFAULT;
> > +
> > +	return 0;
> > +}
> > +
> > +static int sysconfig_isc_init(struct sysconfig_priv *data)
> > +{
> > +	int ret;
> > +
> > +	ret = sysconfig_isc_enable(data);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return sysconfig_isc_erase(data);
> > +}
> > +
> > +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> > +{
> > +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> > +
> > +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> > +}
> > +
> > +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> > +{
> > +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> > +	struct spi_transfer xfer = {
> > +		.tx_buf = lsc_bitstream_burst,
> > +		.len = sizeof(lsc_bitstream_burst),
> > +		.cs_change = 1,
> > +	};
> > +	struct spi_message msg;
> > +
> > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > +
> > +	return spi_sync_locked(data->spi, &msg);
> > +}
> > +
> > +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> > +{
> > +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> > +	u32 status;
> > +	int ret;
> > +
> > +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = sysconfig_poll_status(data, &status);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (status & SYSCONFIG_STATUS_DONE)
> > +		return 0;
> > +
> > +	return -EFAULT;
> > +}
> > +
> > +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> > +{
> > +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> > +
> > +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> > +}
> > +
> > +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +
> > +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> > +						   FPGA_MGR_STATE_UNKNOWN;
> > +}
> > +
> > +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> > +			       struct fpga_image_info *info,
> > +			       const char *buf, size_t count)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct spi_device *spi = priv->spi;
> > +	struct device *dev = &mgr->dev;
> > +	struct gpio_desc *program;
> > +	struct gpio_desc *init;
> > +	struct gpio_desc *done;
> > +	int ret;
> > +
> > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > +		return -EOPNOTSUPP;
> > +	}
> > +
> > +	program = priv->program;
> > +	init = priv->init;
> > +	done = priv->done;
> > +
> > +	/* Enter init mode */
> > +	gpiod_set_value(program, 1);
> 
> Same concern, provide gpio or command options for all board specific
> fpga manager to select. 
> 
> I'll not list each features one by one below.
> 
> some more comments below.
> 
> > +
> > +	ret = sysconfig_poll_gpio(init, true);
> > +	if (!ret)
> > +		ret = sysconfig_poll_gpio(done, false);
> > +
> > +	if (ret) {
> > +		dev_err(dev, "Failed to go to init mode\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Enter program mode */
> > +	gpiod_set_value(program, 0);
> > +
> > +	ret = sysconfig_poll_gpio(init, false);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to go to program mode\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Enter ISC mode */
> > +	ret = sysconfig_isc_init(priv);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to go to ISC mode\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Initialize the Address Shift Register */
> > +	ret = sysconfig_lsc_init_addr(priv);
> > +	if (ret) {
> > +		dev_err(dev,
> > +			"Failed to initialize the Address Shift Register\n");
> > +		return ret;
> > +	}
> > +
> > +	/*
> > +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> > +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> > +	 */
> > +	spi_bus_lock(spi->controller);
> > +
> > +	/* Prepare for bitstream burst write */
> > +	ret = sysconfig_lsc_bitstream_burst(priv);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> > +		spi_bus_unlock(spi->controller);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct spi_device *spi = priv->spi;
> > +	struct spi_transfer xfer = {
> > +		.tx_buf = buf,
> > +		.len = count,
> > +		.cs_change = 1,
> > +	};
> > +	struct spi_message msg;
> > +	int ret;
> > +
> > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > +	ret = spi_sync_locked(spi, &msg);
> > +	if (ret)
> > +		spi_bus_unlock(spi->controller);
> > +
> > +	return ret;
> > +}
> > +
> > +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> > +				   struct fpga_image_info *info)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct spi_device *spi = priv->spi;
> > +	struct device *dev = &mgr->dev;
> > +	int ret;
> > +
> > +	/* Bitstream burst write is done, release SPI bus */
> > +	spi_bus_unlock(spi->controller);
> > +
> > +	/* Toggle CS and wait for bitstream write to finish */
> > +	ret = spi_write(spi, NULL, 0);
> > +	if (!ret)
> > +		ret = sysconfig_poll_busy(priv);
> > +
> > +	if (ret) {
> > +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Exit ISC mode */
> > +	ret = sysconfig_isc_disable(priv);
> > +	if (!ret)
> > +		ret = sysconfig_poll_gpio(priv->done, true);
> > +
> > +	if (ret)
> > +		dev_err(dev, "Failed to finish ISC\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct fpga_manager_ops ecp5_fpga_ops = {
> > +	.state = ecp5_ops_state,
> > +	.write_init = ecp5_ops_write_init,
> > +	.write = ecp5_ops_write,
> > +	.write_complete = ecp5_ops_write_complete,
> > +};
> > +
> > +static int ecp5_probe(struct sysconfig_priv *priv)
> > +{
> > +	struct spi_device *spi = priv->spi;
> > +	struct device *dev = &spi->dev;
> > +	struct fpga_manager *mgr;
> > +	int ret;
> > +
> > +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> > +		return -EINVAL;
> > +	}
> > +
> > +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> > +
> > +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> > +	if (IS_ERR(priv->done)) {
> > +		ret = PTR_ERR(priv->done);
> > +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> > +	if (IS_ERR(priv->init)) {
> > +		ret = PTR_ERR(priv->init);
> > +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> > +	if (IS_ERR(priv->program)) {
> > +		ret = PTR_ERR(priv->program);
> > +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> > +				     &ecp5_fpga_ops, priv);
> > +
> > +	return PTR_ERR_OR_ZERO(mgr);
> > +}
> > +
> > +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	u32 status;
> > +	int ret;
> > +
> > +	ret = sysconfig_read_status(priv, &status);
> > +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> > +		return FPGA_MGR_STATE_UNKNOWN;
> > +
> > +	return FPGA_MGR_STATE_OPERATING;
> > +}
> > +
> > +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> > +				  struct fpga_image_info *info,
> > +				  const char *buf, size_t count)
> > +{
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct device *dev = &mgr->dev;
> > +	int ret;
> > +
> > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > +		return -EOPNOTSUPP;
> > +	}
> > +
> > +	/* Enter ISC mode */
> > +	ret = sysconfig_isc_init(priv);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to go to ISC mode\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Initialize the Address Shift Register */
> > +	ret = sysconfig_lsc_init_addr(priv);
> > +	if (ret)
> > +		dev_err(dev,
> > +			"Failed to initialize the Address Shift Register\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > +{
> > +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct device *dev = &mgr->dev;
> > +	struct spi_transfer xfers[2] = {
> > +		{
> > +			.tx_buf = lsc_progincr,
> > +			.len = sizeof(lsc_progincr),
> > +		}, {
> > +			.len = MACHXO2_PAGE_SIZE,
> > +		},
> > +	};
> > +	size_t i;
> > +	int ret;
> > +
> > +	if (count % MACHXO2_PAGE_SIZE) {
> > +		dev_err(dev, "Malformed payload.\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> > +		xfers[1].tx_buf = buf + i;
> > +
> > +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> > +		if (!ret)
> > +			ret = sysconfig_poll_busy(priv);
> > +
> > +		if (ret) {
> > +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> > +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void machxo2_cleanup(struct sysconfig_priv *data)
> > +{
> > +	sysconfig_isc_erase(data);
> > +	sysconfig_refresh(data);
> > +}
> > +
> > +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> > +				      struct fpga_image_info *info)
> > +{
> > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > +	struct sysconfig_priv *priv = mgr->priv;
> > +	struct device *dev = &mgr->dev;
> > +	u32 status;
> > +
> > +	ret = sysconfig_isc_prog_done(priv);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> > +		goto fail;
> > +	}
> > +
> > +	ret = sysconfig_isc_disable(priv);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to disable Configuration Interface\n");
> > +		goto fail;
> > +	}
> > +
> > +	while (retries--) {
> > +		ret = sysconfig_refresh(priv);
> > +		if (!ret)
> > +			ret = sysconfig_read_status(priv, &status);
> > +
> > +		if (ret) {
> > +			dev_err(dev, "Failed to refresh\n");
> > +			break;
> > +		}
> > +
> > +		if (status & SYSCONFIG_STATUS_DONE &&
> > +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> > +		    !(status & SYSCONFIG_STATUS_ERR))
> > +			return 0;
> > +	}
> > +
> > +fail:
> > +	machxo2_cleanup(priv);
> > +
> > +	return -EFAULT;
> > +}
> > +
> > +static const struct fpga_manager_ops machxo2_fpga_ops = {
> > +	.state = machxo2_ops_state,
> > +	.write_init = machxo2_ops_write_init,
> > +	.write = machxo2_ops_write,
> > +	.write_complete = machxo2_ops_write_complete,
> > +};
> > +
> > +static int machxo2_probe(struct sysconfig_priv *priv)
> > +{
> > +	struct spi_device *spi = priv->spi;
> > +	struct device *dev = &spi->dev;
> > +	struct fpga_manager *mgr;
> > +
> > +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> > +		return -EINVAL;
> > +	}
> > +
> > +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> > +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> > +
> > +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> > +				     &machxo2_fpga_ops, priv);
> > +
> > +	return PTR_ERR_OR_ZERO(mgr);
> > +}
> > +
> > +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> > +
> > +static int sysconfig_probe(struct spi_device *spi)
> > +{
> > +	const struct spi_device_id *dev_id;
> > +	lattice_fpga_probe_func probe_func;
> > +	struct device *dev = &spi->dev;
> > +	struct sysconfig_priv *priv;
> > +
> > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	priv->spi = spi;
> > +
> > +	probe_func = of_device_get_match_data(&spi->dev);
> > +	if (!probe_func) {
> > +		dev_id = spi_get_device_id(spi);
> > +		if (!dev_id)
> > +			return -ENODEV;
> > +
> > +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> > +	}
> > +
> > +	if (!probe_func)
> > +		return -EINVAL;
> > +
> > +	return probe_func(priv);
> > +}
> > +
> > +static const struct spi_device_id sysconfig_spi_ids[] = {
> > +	{
> > +		.name = "ecp5-fpga-mgr",
> > +		.driver_data = (kernel_ulong_t)ecp5_probe,
> > +	}, {
> > +		.name = "machxo2-fpga-mgr",
> > +		.driver_data = (kernel_ulong_t)machxo2_probe,
> 
> Putting the whole probe flow in driver_data is the same as providing 2
> drivers. The purpose is not to put all the code in one file.
> 

Sorry, I don't understand what you suggest. Separate file for each
specific FPGA?

> Thanks,
> Yilun
> 
> > +	}, {},
> > +};
> > +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> > +
> > +#if IS_ENABLED(CONFIG_OF)
> > +static const struct of_device_id sysconfig_of_ids[] = {
> > +	{
> > +		.compatible = "lattice,ecp5-fpga-mgr",
> > +		.data = ecp5_probe,
> > +	}, {
> > +		.compatible = "lattice,machxo2-fpga-mgr",
> > +		.data = machxo2_probe
> > +	}, {},
> > +};
> > +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> > +#endif /* IS_ENABLED(CONFIG_OF) */
> > +
> > +static struct spi_driver lattice_sysconfig_driver = {
> > +	.probe = sysconfig_probe,
> > +	.id_table = sysconfig_spi_ids,
> > +	.driver = {
> > +		.name = "lattice_sysconfig_spi_fpga_mgr",
> > +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> > +	},
> > +};
> > +
> > +module_spi_driver(lattice_sysconfig_driver);
> > +
> > +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> > +MODULE_LICENSE("GPL");
> > -- 
> > 2.37.2
> > 
> >
Ivan Bornyakov Aug. 29, 2022, 9:16 a.m. UTC | #6
On Mon, Aug 29, 2022 at 11:27:40AM +0300, Ivan Bornyakov wrote:
> On Mon, Aug 29, 2022 at 03:25:41PM +0800, Xu Yilun wrote:
> > On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> > > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > > FPGAs over slave SPI sysCONFIG interface.
> > > 
> > > ... snip ...
> > >
> > > +
> > > +static const struct spi_device_id sysconfig_spi_ids[] = {
> > > +	{
> > > +		.name = "ecp5-fpga-mgr",
> > > +		.driver_data = (kernel_ulong_t)ecp5_probe,
> > > +	}, {
> > > +		.name = "machxo2-fpga-mgr",
> > > +		.driver_data = (kernel_ulong_t)machxo2_probe,
> > 
> > Putting the whole probe flow in driver_data is the same as providing 2
> > drivers. The purpose is not to put all the code in one file.
> > 
> 
> Sorry, I don't understand what you suggest. Separate file for each
> specific FPGA?
> 

In v9 I was about to split this to sysconfig.c with sysCONFIG interface
port functions independent of port type + sysconfig-spi.c with functions
specific for SPI port. In this approach there is opportunity for
Johannes to add sysconfig-i2c.c with MachXO2's I2C port while reusing
fpga_manager_ops->state(), fpga_manager_ops->write_init() and
fpga_manager_ops->write_complete().

IDs structs array in sysconfig-spi.c still contains data with FPGA
specific probe, though.
Xu Yilun Aug. 29, 2022, 10:34 a.m. UTC | #7
On 2022-08-29 at 11:27:40 +0300, Ivan Bornyakov wrote:
> On Mon, Aug 29, 2022 at 03:25:41PM +0800, Xu Yilun wrote:
> > On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> > > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > > FPGAs over slave SPI sysCONFIG interface.
> > > 
> > > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > > ---
> > >  drivers/fpga/Kconfig                 |   7 +
> > >  drivers/fpga/Makefile                |   1 +
> > >  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
> > >  3 files changed, 638 insertions(+)
> > >  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> > > 
> > > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> > > index 6c416955da53..991d9d976dca 100644
> > > --- a/drivers/fpga/Kconfig
> > > +++ b/drivers/fpga/Kconfig
> > > @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
> > >  	  programming over slave SPI interface with .dat formatted
> > >  	  bitstream image.
> > >  
> > > +config FPGA_MGR_LATTICE_SPI
> > > +	tristate "Lattice sysCONFIG SPI FPGA manager"
> > > +	depends on SPI
> > > +	help
> > > +	  FPGA manager driver support for Lattice FPGAs programming over slave
> > > +	  SPI sysCONFIG interface.
> > > +
> > >  endif # FPGA
> > > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> > > index 42ae8b58abce..115dba916024 100644
> > > --- a/drivers/fpga/Makefile
> > > +++ b/drivers/fpga/Makefile
> > > @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
> > >  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
> > >  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
> > >  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> > > +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
> > >  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
> > >  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
> > >  
> > > diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> > > new file mode 100644
> > > index 000000000000..145b5b27b88d
> > > --- /dev/null
> > > +++ b/drivers/fpga/lattice-sysconfig-spi.c
> > > @@ -0,0 +1,630 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> > > + */
> > > +
> > > +#include <linux/delay.h>
> > > +#include <linux/fpga/fpga-mgr.h>
> > > +#include <linux/of_device.h>
> > > +#include <linux/spi/spi.h>
> > > +
> > > +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> > > +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> > > +
> > > +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> > > +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> > > +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> > > +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> > > +
> > > +#define	SYSCONFIG_POLL_RETRIES		100000
> > > +
> > > +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> > > +#define	ECP5_ISC_ERASE_OPERAND		0x01
> > > +
> > > +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> > > +#define	MACHXO2_PAGE_SIZE		16
> > > +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> > > +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> > 
> > You need to deliver the meaning of each operand, rather than just point
> > out this is for machxo2, that is for esp5. I assume the sysCONFIG will
> > not deliberately design different commands with the exact same
> > functionality for different boards.
> > 
> > We should have a set of common configurations with finer granularity that
> > board specific fpga manager device could select from, rather than create
> > more and more similar macros for each new board.
> > 
> > Back to the 2 operands, what's the meaning of 0x1 & 0x4 for erase,
> 
> 0x1 - erase SRAM, 0x4 - erase FLASH. I'll redefine them properly in v9
> 
> > also 0x0 & 0x8 for enable?
> > 
> 
> Their meaning is not documented. It's just 0x08 for MachXO2 and 0x00 for
> ECP5.
> 
> > > +
> > > +struct sysconfig_priv {
> > > +	struct gpio_desc *program;
> > > +	struct gpio_desc *init;
> > > +	struct gpio_desc *done;
> > > +	struct spi_device *spi;
> > > +	u8 isc_enable_operand;
> > > +	u8 isc_erase_operand;
> > > +};
> > > +
> > > +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> > > +{
> > > +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > +	u8 busy;
> > > +
> > > +	while (retries--) {
> > > +		ret = spi_write_then_read(data->spi,
> > > +					  lsc_check_busy, sizeof(lsc_check_busy),
> > > +					  &busy, sizeof(busy));
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		if (!busy)
> > > +			return 0;
> > > +
> > > +		usleep_range(50, 100);
> > > +	}
> > > +
> > > +	return -EBUSY;
> > > +}
> > > +
> > > +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> > > +{
> > > +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> > > +	__be32 device_status;
> > > +	int ret;
> > > +
> > > +	ret = spi_write_then_read(data->spi,
> > > +				  lsc_read_status, sizeof(lsc_read_status),
> > > +				  &device_status, sizeof(device_status));
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	*status = be32_to_cpu(device_status);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> > > +{
> > > +	int ret;
> > > +
> > > +	ret = sysconfig_poll_busy(data);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	return sysconfig_read_status(data, status);
> > > +}
> > > +
> > > +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> > > +{
> > > +	int value, retries = SYSCONFIG_POLL_RETRIES;
> > > +
> > > +	while (retries--) {
> > > +		value = gpiod_get_value(gpio);
> > > +		if (value < 0)
> > > +			return value;
> > > +
> > > +		if ((!is_active && !value) || (is_active && value))
> > > +			return 0;
> > > +
> > > +		ndelay(10);
> > > +	}
> > > +
> > > +	return -ETIMEDOUT;
> > > +}
> > > +
> > > +static int sysconfig_refresh(struct sysconfig_priv *data)
> > > +{
> > > +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> > > +	int ret;
> > > +
> > > +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	usleep_range(4000, 8000);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> > > +{
> > > +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> > > +	u32 status;
> > > +	int ret;
> > > +
> > > +	isc_enable[1] = data->isc_enable_operand;
> > > +
> > > +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	ret = sysconfig_poll_status(data, &status);
> > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > +		return ret ? : -EFAULT;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> > > +{
> > > +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> > > +	u32 status;
> > > +	int ret;
> > > +
> > > +	isc_erase[1] = data->isc_erase_operand;
> > > +
> > > +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	ret = sysconfig_poll_status(data, &status);
> > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > +		return ret ? : -EFAULT;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int sysconfig_isc_init(struct sysconfig_priv *data)
> > > +{
> > > +	int ret;
> > > +
> > > +	ret = sysconfig_isc_enable(data);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	return sysconfig_isc_erase(data);
> > > +}
> > > +
> > > +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> > > +{
> > > +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> > > +
> > > +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> > > +}
> > > +
> > > +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> > > +{
> > > +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> > > +	struct spi_transfer xfer = {
> > > +		.tx_buf = lsc_bitstream_burst,
> > > +		.len = sizeof(lsc_bitstream_burst),
> > > +		.cs_change = 1,
> > > +	};
> > > +	struct spi_message msg;
> > > +
> > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > +
> > > +	return spi_sync_locked(data->spi, &msg);
> > > +}
> > > +
> > > +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> > > +{
> > > +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> > > +	u32 status;
> > > +	int ret;
> > > +
> > > +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	ret = sysconfig_poll_status(data, &status);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	if (status & SYSCONFIG_STATUS_DONE)
> > > +		return 0;
> > > +
> > > +	return -EFAULT;
> > > +}
> > > +
> > > +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> > > +{
> > > +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> > > +
> > > +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> > > +}
> > > +
> > > +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +
> > > +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> > > +						   FPGA_MGR_STATE_UNKNOWN;
> > > +}
> > > +
> > > +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> > > +			       struct fpga_image_info *info,
> > > +			       const char *buf, size_t count)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct spi_device *spi = priv->spi;
> > > +	struct device *dev = &mgr->dev;
> > > +	struct gpio_desc *program;
> > > +	struct gpio_desc *init;
> > > +	struct gpio_desc *done;
> > > +	int ret;
> > > +
> > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > +		return -EOPNOTSUPP;
> > > +	}
> > > +
> > > +	program = priv->program;
> > > +	init = priv->init;
> > > +	done = priv->done;
> > > +
> > > +	/* Enter init mode */
> > > +	gpiod_set_value(program, 1);
> > 
> > Same concern, provide gpio or command options for all board specific
> > fpga manager to select. 
> > 
> > I'll not list each features one by one below.
> > 
> > some more comments below.
> > 
> > > +
> > > +	ret = sysconfig_poll_gpio(init, true);
> > > +	if (!ret)
> > > +		ret = sysconfig_poll_gpio(done, false);
> > > +
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to go to init mode\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Enter program mode */
> > > +	gpiod_set_value(program, 0);
> > > +
> > > +	ret = sysconfig_poll_gpio(init, false);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to go to program mode\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Enter ISC mode */
> > > +	ret = sysconfig_isc_init(priv);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Initialize the Address Shift Register */
> > > +	ret = sysconfig_lsc_init_addr(priv);
> > > +	if (ret) {
> > > +		dev_err(dev,
> > > +			"Failed to initialize the Address Shift Register\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/*
> > > +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> > > +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> > > +	 */
> > > +	spi_bus_lock(spi->controller);
> > > +
> > > +	/* Prepare for bitstream burst write */
> > > +	ret = sysconfig_lsc_bitstream_burst(priv);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> > > +		spi_bus_unlock(spi->controller);
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct spi_device *spi = priv->spi;
> > > +	struct spi_transfer xfer = {
> > > +		.tx_buf = buf,
> > > +		.len = count,
> > > +		.cs_change = 1,
> > > +	};
> > > +	struct spi_message msg;
> > > +	int ret;
> > > +
> > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > +	ret = spi_sync_locked(spi, &msg);
> > > +	if (ret)
> > > +		spi_bus_unlock(spi->controller);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> > > +				   struct fpga_image_info *info)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct spi_device *spi = priv->spi;
> > > +	struct device *dev = &mgr->dev;
> > > +	int ret;
> > > +
> > > +	/* Bitstream burst write is done, release SPI bus */
> > > +	spi_bus_unlock(spi->controller);
> > > +
> > > +	/* Toggle CS and wait for bitstream write to finish */
> > > +	ret = spi_write(spi, NULL, 0);
> > > +	if (!ret)
> > > +		ret = sysconfig_poll_busy(priv);
> > > +
> > > +	if (ret) {
> > > +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Exit ISC mode */
> > > +	ret = sysconfig_isc_disable(priv);
> > > +	if (!ret)
> > > +		ret = sysconfig_poll_gpio(priv->done, true);
> > > +
> > > +	if (ret)
> > > +		dev_err(dev, "Failed to finish ISC\n");
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static const struct fpga_manager_ops ecp5_fpga_ops = {
> > > +	.state = ecp5_ops_state,
> > > +	.write_init = ecp5_ops_write_init,
> > > +	.write = ecp5_ops_write,
> > > +	.write_complete = ecp5_ops_write_complete,
> > > +};
> > > +
> > > +static int ecp5_probe(struct sysconfig_priv *priv)
> > > +{
> > > +	struct spi_device *spi = priv->spi;
> > > +	struct device *dev = &spi->dev;
> > > +	struct fpga_manager *mgr;
> > > +	int ret;
> > > +
> > > +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> > > +
> > > +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> > > +	if (IS_ERR(priv->done)) {
> > > +		ret = PTR_ERR(priv->done);
> > > +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> > > +	if (IS_ERR(priv->init)) {
> > > +		ret = PTR_ERR(priv->init);
> > > +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> > > +	if (IS_ERR(priv->program)) {
> > > +		ret = PTR_ERR(priv->program);
> > > +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> > > +				     &ecp5_fpga_ops, priv);
> > > +
> > > +	return PTR_ERR_OR_ZERO(mgr);
> > > +}
> > > +
> > > +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	u32 status;
> > > +	int ret;
> > > +
> > > +	ret = sysconfig_read_status(priv, &status);
> > > +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> > > +		return FPGA_MGR_STATE_UNKNOWN;
> > > +
> > > +	return FPGA_MGR_STATE_OPERATING;
> > > +}
> > > +
> > > +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> > > +				  struct fpga_image_info *info,
> > > +				  const char *buf, size_t count)
> > > +{
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct device *dev = &mgr->dev;
> > > +	int ret;
> > > +
> > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > +		return -EOPNOTSUPP;
> > > +	}
> > > +
> > > +	/* Enter ISC mode */
> > > +	ret = sysconfig_isc_init(priv);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Initialize the Address Shift Register */
> > > +	ret = sysconfig_lsc_init_addr(priv);
> > > +	if (ret)
> > > +		dev_err(dev,
> > > +			"Failed to initialize the Address Shift Register\n");
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > +{
> > > +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct device *dev = &mgr->dev;
> > > +	struct spi_transfer xfers[2] = {
> > > +		{
> > > +			.tx_buf = lsc_progincr,
> > > +			.len = sizeof(lsc_progincr),
> > > +		}, {
> > > +			.len = MACHXO2_PAGE_SIZE,
> > > +		},
> > > +	};
> > > +	size_t i;
> > > +	int ret;
> > > +
> > > +	if (count % MACHXO2_PAGE_SIZE) {
> > > +		dev_err(dev, "Malformed payload.\n");
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> > > +		xfers[1].tx_buf = buf + i;
> > > +
> > > +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> > > +		if (!ret)
> > > +			ret = sysconfig_poll_busy(priv);
> > > +
> > > +		if (ret) {
> > > +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> > > +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void machxo2_cleanup(struct sysconfig_priv *data)
> > > +{
> > > +	sysconfig_isc_erase(data);
> > > +	sysconfig_refresh(data);
> > > +}
> > > +
> > > +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> > > +				      struct fpga_image_info *info)
> > > +{
> > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > +	struct sysconfig_priv *priv = mgr->priv;
> > > +	struct device *dev = &mgr->dev;
> > > +	u32 status;
> > > +
> > > +	ret = sysconfig_isc_prog_done(priv);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> > > +		goto fail;
> > > +	}
> > > +
> > > +	ret = sysconfig_isc_disable(priv);
> > > +	if (ret) {
> > > +		dev_err(dev, "Failed to disable Configuration Interface\n");
> > > +		goto fail;
> > > +	}
> > > +
> > > +	while (retries--) {
> > > +		ret = sysconfig_refresh(priv);
> > > +		if (!ret)
> > > +			ret = sysconfig_read_status(priv, &status);
> > > +
> > > +		if (ret) {
> > > +			dev_err(dev, "Failed to refresh\n");
> > > +			break;
> > > +		}
> > > +
> > > +		if (status & SYSCONFIG_STATUS_DONE &&
> > > +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> > > +		    !(status & SYSCONFIG_STATUS_ERR))
> > > +			return 0;
> > > +	}
> > > +
> > > +fail:
> > > +	machxo2_cleanup(priv);
> > > +
> > > +	return -EFAULT;
> > > +}
> > > +
> > > +static const struct fpga_manager_ops machxo2_fpga_ops = {
> > > +	.state = machxo2_ops_state,
> > > +	.write_init = machxo2_ops_write_init,
> > > +	.write = machxo2_ops_write,
> > > +	.write_complete = machxo2_ops_write_complete,
> > > +};
> > > +
> > > +static int machxo2_probe(struct sysconfig_priv *priv)
> > > +{
> > > +	struct spi_device *spi = priv->spi;
> > > +	struct device *dev = &spi->dev;
> > > +	struct fpga_manager *mgr;
> > > +
> > > +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> > > +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> > > +
> > > +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> > > +				     &machxo2_fpga_ops, priv);
> > > +
> > > +	return PTR_ERR_OR_ZERO(mgr);
> > > +}
> > > +
> > > +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> > > +
> > > +static int sysconfig_probe(struct spi_device *spi)
> > > +{
> > > +	const struct spi_device_id *dev_id;
> > > +	lattice_fpga_probe_func probe_func;
> > > +	struct device *dev = &spi->dev;
> > > +	struct sysconfig_priv *priv;
> > > +
> > > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > > +	if (!priv)
> > > +		return -ENOMEM;
> > > +
> > > +	priv->spi = spi;
> > > +
> > > +	probe_func = of_device_get_match_data(&spi->dev);
> > > +	if (!probe_func) {
> > > +		dev_id = spi_get_device_id(spi);
> > > +		if (!dev_id)
> > > +			return -ENODEV;
> > > +
> > > +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> > > +	}
> > > +
> > > +	if (!probe_func)
> > > +		return -EINVAL;
> > > +
> > > +	return probe_func(priv);
> > > +}
> > > +
> > > +static const struct spi_device_id sysconfig_spi_ids[] = {
> > > +	{
> > > +		.name = "ecp5-fpga-mgr",
> > > +		.driver_data = (kernel_ulong_t)ecp5_probe,
> > > +	}, {
> > > +		.name = "machxo2-fpga-mgr",
> > > +		.driver_data = (kernel_ulong_t)machxo2_probe,
> > 
> > Putting the whole probe flow in driver_data is the same as providing 2
> > drivers. The purpose is not to put all the code in one file.
> > 
> 
> Sorry, I don't understand what you suggest. Separate file for each
> specific FPGA?

Sorry, didn't make it clear. I feel like there are still 2 board
specific drivers although they are now in one file, they have
separate probes, fpga manager ops, different spi APIs ...

What I expect is, the driver operates the HW according to the configured
properties specified in sysCONFIG spec, such as having PROGRAMN gpio pin?
burst? having backup flash? ...

Thanks,
Yilun

> 
> > Thanks,
> > Yilun
> > 
> > > +	}, {},
> > > +};
> > > +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> > > +
> > > +#if IS_ENABLED(CONFIG_OF)
> > > +static const struct of_device_id sysconfig_of_ids[] = {
> > > +	{
> > > +		.compatible = "lattice,ecp5-fpga-mgr",
> > > +		.data = ecp5_probe,
> > > +	}, {
> > > +		.compatible = "lattice,machxo2-fpga-mgr",
> > > +		.data = machxo2_probe
> > > +	}, {},
> > > +};
> > > +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> > > +#endif /* IS_ENABLED(CONFIG_OF) */
> > > +
> > > +static struct spi_driver lattice_sysconfig_driver = {
> > > +	.probe = sysconfig_probe,
> > > +	.id_table = sysconfig_spi_ids,
> > > +	.driver = {
> > > +		.name = "lattice_sysconfig_spi_fpga_mgr",
> > > +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> > > +	},
> > > +};
> > > +
> > > +module_spi_driver(lattice_sysconfig_driver);
> > > +
> > > +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> > > +MODULE_LICENSE("GPL");
> > > -- 
> > > 2.37.2
> > > 
> > > 
>
Ivan Bornyakov Aug. 29, 2022, 11:04 a.m. UTC | #8
On Mon, Aug 29, 2022 at 06:34:44PM +0800, Xu Yilun wrote:
> On 2022-08-29 at 11:27:40 +0300, Ivan Bornyakov wrote:
> > On Mon, Aug 29, 2022 at 03:25:41PM +0800, Xu Yilun wrote:
> > > On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> > > > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > > > FPGAs over slave SPI sysCONFIG interface.
> > > > 
> > > > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > > > ---
> > > >  drivers/fpga/Kconfig                 |   7 +
> > > >  drivers/fpga/Makefile                |   1 +
> > > >  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
> > > >  3 files changed, 638 insertions(+)
> > > >  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> > > > 
> > > > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> > > > index 6c416955da53..991d9d976dca 100644
> > > > --- a/drivers/fpga/Kconfig
> > > > +++ b/drivers/fpga/Kconfig
> > > > @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
> > > >  	  programming over slave SPI interface with .dat formatted
> > > >  	  bitstream image.
> > > >  
> > > > +config FPGA_MGR_LATTICE_SPI
> > > > +	tristate "Lattice sysCONFIG SPI FPGA manager"
> > > > +	depends on SPI
> > > > +	help
> > > > +	  FPGA manager driver support for Lattice FPGAs programming over slave
> > > > +	  SPI sysCONFIG interface.
> > > > +
> > > >  endif # FPGA
> > > > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> > > > index 42ae8b58abce..115dba916024 100644
> > > > --- a/drivers/fpga/Makefile
> > > > +++ b/drivers/fpga/Makefile
> > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
> > > >  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
> > > >  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
> > > >  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> > > > +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
> > > >  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
> > > >  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
> > > >  
> > > > diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> > > > new file mode 100644
> > > > index 000000000000..145b5b27b88d
> > > > --- /dev/null
> > > > +++ b/drivers/fpga/lattice-sysconfig-spi.c
> > > > @@ -0,0 +1,630 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> > > > + */
> > > > +
> > > > +#include <linux/delay.h>
> > > > +#include <linux/fpga/fpga-mgr.h>
> > > > +#include <linux/of_device.h>
> > > > +#include <linux/spi/spi.h>
> > > > +
> > > > +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> > > > +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> > > > +
> > > > +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> > > > +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> > > > +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> > > > +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> > > > +
> > > > +#define	SYSCONFIG_POLL_RETRIES		100000
> > > > +
> > > > +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> > > > +#define	ECP5_ISC_ERASE_OPERAND		0x01
> > > > +
> > > > +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> > > > +#define	MACHXO2_PAGE_SIZE		16
> > > > +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> > > > +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> > > 
> > > You need to deliver the meaning of each operand, rather than just point
> > > out this is for machxo2, that is for esp5. I assume the sysCONFIG will
> > > not deliberately design different commands with the exact same
> > > functionality for different boards.
> > > 
> > > We should have a set of common configurations with finer granularity that
> > > board specific fpga manager device could select from, rather than create
> > > more and more similar macros for each new board.
> > > 
> > > Back to the 2 operands, what's the meaning of 0x1 & 0x4 for erase,
> > 
> > 0x1 - erase SRAM, 0x4 - erase FLASH. I'll redefine them properly in v9
> > 
> > > also 0x0 & 0x8 for enable?
> > > 
> > 
> > Their meaning is not documented. It's just 0x08 for MachXO2 and 0x00 for
> > ECP5.
> > 
> > > > +
> > > > +struct sysconfig_priv {
> > > > +	struct gpio_desc *program;
> > > > +	struct gpio_desc *init;
> > > > +	struct gpio_desc *done;
> > > > +	struct spi_device *spi;
> > > > +	u8 isc_enable_operand;
> > > > +	u8 isc_erase_operand;
> > > > +};
> > > > +
> > > > +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> > > > +{
> > > > +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> > > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > > +	u8 busy;
> > > > +
> > > > +	while (retries--) {
> > > > +		ret = spi_write_then_read(data->spi,
> > > > +					  lsc_check_busy, sizeof(lsc_check_busy),
> > > > +					  &busy, sizeof(busy));
> > > > +		if (ret)
> > > > +			return ret;
> > > > +
> > > > +		if (!busy)
> > > > +			return 0;
> > > > +
> > > > +		usleep_range(50, 100);
> > > > +	}
> > > > +
> > > > +	return -EBUSY;
> > > > +}
> > > > +
> > > > +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> > > > +{
> > > > +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> > > > +	__be32 device_status;
> > > > +	int ret;
> > > > +
> > > > +	ret = spi_write_then_read(data->spi,
> > > > +				  lsc_read_status, sizeof(lsc_read_status),
> > > > +				  &device_status, sizeof(device_status));
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	*status = be32_to_cpu(device_status);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	ret = sysconfig_poll_busy(data);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	return sysconfig_read_status(data, status);
> > > > +}
> > > > +
> > > > +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> > > > +{
> > > > +	int value, retries = SYSCONFIG_POLL_RETRIES;
> > > > +
> > > > +	while (retries--) {
> > > > +		value = gpiod_get_value(gpio);
> > > > +		if (value < 0)
> > > > +			return value;
> > > > +
> > > > +		if ((!is_active && !value) || (is_active && value))
> > > > +			return 0;
> > > > +
> > > > +		ndelay(10);
> > > > +	}
> > > > +
> > > > +	return -ETIMEDOUT;
> > > > +}
> > > > +
> > > > +static int sysconfig_refresh(struct sysconfig_priv *data)
> > > > +{
> > > > +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> > > > +	int ret;
> > > > +
> > > > +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	usleep_range(4000, 8000);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> > > > +{
> > > > +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> > > > +	u32 status;
> > > > +	int ret;
> > > > +
> > > > +	isc_enable[1] = data->isc_enable_operand;
> > > > +
> > > > +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	ret = sysconfig_poll_status(data, &status);
> > > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > > +		return ret ? : -EFAULT;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> > > > +{
> > > > +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> > > > +	u32 status;
> > > > +	int ret;
> > > > +
> > > > +	isc_erase[1] = data->isc_erase_operand;
> > > > +
> > > > +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	ret = sysconfig_poll_status(data, &status);
> > > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > > +		return ret ? : -EFAULT;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int sysconfig_isc_init(struct sysconfig_priv *data)
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	ret = sysconfig_isc_enable(data);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	return sysconfig_isc_erase(data);
> > > > +}
> > > > +
> > > > +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> > > > +{
> > > > +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> > > > +
> > > > +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> > > > +}
> > > > +
> > > > +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> > > > +{
> > > > +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> > > > +	struct spi_transfer xfer = {
> > > > +		.tx_buf = lsc_bitstream_burst,
> > > > +		.len = sizeof(lsc_bitstream_burst),
> > > > +		.cs_change = 1,
> > > > +	};
> > > > +	struct spi_message msg;
> > > > +
> > > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > > +
> > > > +	return spi_sync_locked(data->spi, &msg);
> > > > +}
> > > > +
> > > > +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> > > > +{
> > > > +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> > > > +	u32 status;
> > > > +	int ret;
> > > > +
> > > > +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	ret = sysconfig_poll_status(data, &status);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	if (status & SYSCONFIG_STATUS_DONE)
> > > > +		return 0;
> > > > +
> > > > +	return -EFAULT;
> > > > +}
> > > > +
> > > > +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> > > > +{
> > > > +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> > > > +
> > > > +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> > > > +}
> > > > +
> > > > +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +
> > > > +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> > > > +						   FPGA_MGR_STATE_UNKNOWN;
> > > > +}
> > > > +
> > > > +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> > > > +			       struct fpga_image_info *info,
> > > > +			       const char *buf, size_t count)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct spi_device *spi = priv->spi;
> > > > +	struct device *dev = &mgr->dev;
> > > > +	struct gpio_desc *program;
> > > > +	struct gpio_desc *init;
> > > > +	struct gpio_desc *done;
> > > > +	int ret;
> > > > +
> > > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > > +		return -EOPNOTSUPP;
> > > > +	}
> > > > +
> > > > +	program = priv->program;
> > > > +	init = priv->init;
> > > > +	done = priv->done;
> > > > +
> > > > +	/* Enter init mode */
> > > > +	gpiod_set_value(program, 1);
> > > 
> > > Same concern, provide gpio or command options for all board specific
> > > fpga manager to select. 
> > > 
> > > I'll not list each features one by one below.
> > > 
> > > some more comments below.
> > > 
> > > > +
> > > > +	ret = sysconfig_poll_gpio(init, true);
> > > > +	if (!ret)
> > > > +		ret = sysconfig_poll_gpio(done, false);
> > > > +
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to go to init mode\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Enter program mode */
> > > > +	gpiod_set_value(program, 0);
> > > > +
> > > > +	ret = sysconfig_poll_gpio(init, false);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to go to program mode\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Enter ISC mode */
> > > > +	ret = sysconfig_isc_init(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Initialize the Address Shift Register */
> > > > +	ret = sysconfig_lsc_init_addr(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev,
> > > > +			"Failed to initialize the Address Shift Register\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/*
> > > > +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> > > > +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> > > > +	 */
> > > > +	spi_bus_lock(spi->controller);
> > > > +
> > > > +	/* Prepare for bitstream burst write */
> > > > +	ret = sysconfig_lsc_bitstream_burst(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> > > > +		spi_bus_unlock(spi->controller);
> > > > +	}
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct spi_device *spi = priv->spi;
> > > > +	struct spi_transfer xfer = {
> > > > +		.tx_buf = buf,
> > > > +		.len = count,
> > > > +		.cs_change = 1,
> > > > +	};
> > > > +	struct spi_message msg;
> > > > +	int ret;
> > > > +
> > > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > > +	ret = spi_sync_locked(spi, &msg);
> > > > +	if (ret)
> > > > +		spi_bus_unlock(spi->controller);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> > > > +				   struct fpga_image_info *info)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct spi_device *spi = priv->spi;
> > > > +	struct device *dev = &mgr->dev;
> > > > +	int ret;
> > > > +
> > > > +	/* Bitstream burst write is done, release SPI bus */
> > > > +	spi_bus_unlock(spi->controller);
> > > > +
> > > > +	/* Toggle CS and wait for bitstream write to finish */
> > > > +	ret = spi_write(spi, NULL, 0);
> > > > +	if (!ret)
> > > > +		ret = sysconfig_poll_busy(priv);
> > > > +
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Exit ISC mode */
> > > > +	ret = sysconfig_isc_disable(priv);
> > > > +	if (!ret)
> > > > +		ret = sysconfig_poll_gpio(priv->done, true);
> > > > +
> > > > +	if (ret)
> > > > +		dev_err(dev, "Failed to finish ISC\n");
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static const struct fpga_manager_ops ecp5_fpga_ops = {
> > > > +	.state = ecp5_ops_state,
> > > > +	.write_init = ecp5_ops_write_init,
> > > > +	.write = ecp5_ops_write,
> > > > +	.write_complete = ecp5_ops_write_complete,
> > > > +};
> > > > +
> > > > +static int ecp5_probe(struct sysconfig_priv *priv)
> > > > +{
> > > > +	struct spi_device *spi = priv->spi;
> > > > +	struct device *dev = &spi->dev;
> > > > +	struct fpga_manager *mgr;
> > > > +	int ret;
> > > > +
> > > > +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> > > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > > +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> > > > +
> > > > +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> > > > +	if (IS_ERR(priv->done)) {
> > > > +		ret = PTR_ERR(priv->done);
> > > > +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> > > > +	if (IS_ERR(priv->init)) {
> > > > +		ret = PTR_ERR(priv->init);
> > > > +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> > > > +	if (IS_ERR(priv->program)) {
> > > > +		ret = PTR_ERR(priv->program);
> > > > +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> > > > +				     &ecp5_fpga_ops, priv);
> > > > +
> > > > +	return PTR_ERR_OR_ZERO(mgr);
> > > > +}
> > > > +
> > > > +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	u32 status;
> > > > +	int ret;
> > > > +
> > > > +	ret = sysconfig_read_status(priv, &status);
> > > > +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> > > > +		return FPGA_MGR_STATE_UNKNOWN;
> > > > +
> > > > +	return FPGA_MGR_STATE_OPERATING;
> > > > +}
> > > > +
> > > > +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> > > > +				  struct fpga_image_info *info,
> > > > +				  const char *buf, size_t count)
> > > > +{
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct device *dev = &mgr->dev;
> > > > +	int ret;
> > > > +
> > > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > > +		return -EOPNOTSUPP;
> > > > +	}
> > > > +
> > > > +	/* Enter ISC mode */
> > > > +	ret = sysconfig_isc_init(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Initialize the Address Shift Register */
> > > > +	ret = sysconfig_lsc_init_addr(priv);
> > > > +	if (ret)
> > > > +		dev_err(dev,
> > > > +			"Failed to initialize the Address Shift Register\n");
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > > +{
> > > > +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct device *dev = &mgr->dev;
> > > > +	struct spi_transfer xfers[2] = {
> > > > +		{
> > > > +			.tx_buf = lsc_progincr,
> > > > +			.len = sizeof(lsc_progincr),
> > > > +		}, {
> > > > +			.len = MACHXO2_PAGE_SIZE,
> > > > +		},
> > > > +	};
> > > > +	size_t i;
> > > > +	int ret;
> > > > +
> > > > +	if (count % MACHXO2_PAGE_SIZE) {
> > > > +		dev_err(dev, "Malformed payload.\n");
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> > > > +		xfers[1].tx_buf = buf + i;
> > > > +
> > > > +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> > > > +		if (!ret)
> > > > +			ret = sysconfig_poll_busy(priv);
> > > > +
> > > > +		if (ret) {
> > > > +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> > > > +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void machxo2_cleanup(struct sysconfig_priv *data)
> > > > +{
> > > > +	sysconfig_isc_erase(data);
> > > > +	sysconfig_refresh(data);
> > > > +}
> > > > +
> > > > +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> > > > +				      struct fpga_image_info *info)
> > > > +{
> > > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > +	struct device *dev = &mgr->dev;
> > > > +	u32 status;
> > > > +
> > > > +	ret = sysconfig_isc_prog_done(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> > > > +		goto fail;
> > > > +	}
> > > > +
> > > > +	ret = sysconfig_isc_disable(priv);
> > > > +	if (ret) {
> > > > +		dev_err(dev, "Failed to disable Configuration Interface\n");
> > > > +		goto fail;
> > > > +	}
> > > > +
> > > > +	while (retries--) {
> > > > +		ret = sysconfig_refresh(priv);
> > > > +		if (!ret)
> > > > +			ret = sysconfig_read_status(priv, &status);
> > > > +
> > > > +		if (ret) {
> > > > +			dev_err(dev, "Failed to refresh\n");
> > > > +			break;
> > > > +		}
> > > > +
> > > > +		if (status & SYSCONFIG_STATUS_DONE &&
> > > > +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> > > > +		    !(status & SYSCONFIG_STATUS_ERR))
> > > > +			return 0;
> > > > +	}
> > > > +
> > > > +fail:
> > > > +	machxo2_cleanup(priv);
> > > > +
> > > > +	return -EFAULT;
> > > > +}
> > > > +
> > > > +static const struct fpga_manager_ops machxo2_fpga_ops = {
> > > > +	.state = machxo2_ops_state,
> > > > +	.write_init = machxo2_ops_write_init,
> > > > +	.write = machxo2_ops_write,
> > > > +	.write_complete = machxo2_ops_write_complete,
> > > > +};
> > > > +
> > > > +static int machxo2_probe(struct sysconfig_priv *priv)
> > > > +{
> > > > +	struct spi_device *spi = priv->spi;
> > > > +	struct device *dev = &spi->dev;
> > > > +	struct fpga_manager *mgr;
> > > > +
> > > > +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> > > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > > +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> > > > +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> > > > +
> > > > +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> > > > +				     &machxo2_fpga_ops, priv);
> > > > +
> > > > +	return PTR_ERR_OR_ZERO(mgr);
> > > > +}
> > > > +
> > > > +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> > > > +
> > > > +static int sysconfig_probe(struct spi_device *spi)
> > > > +{
> > > > +	const struct spi_device_id *dev_id;
> > > > +	lattice_fpga_probe_func probe_func;
> > > > +	struct device *dev = &spi->dev;
> > > > +	struct sysconfig_priv *priv;
> > > > +
> > > > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > > > +	if (!priv)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	priv->spi = spi;
> > > > +
> > > > +	probe_func = of_device_get_match_data(&spi->dev);
> > > > +	if (!probe_func) {
> > > > +		dev_id = spi_get_device_id(spi);
> > > > +		if (!dev_id)
> > > > +			return -ENODEV;
> > > > +
> > > > +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> > > > +	}
> > > > +
> > > > +	if (!probe_func)
> > > > +		return -EINVAL;
> > > > +
> > > > +	return probe_func(priv);
> > > > +}
> > > > +
> > > > +static const struct spi_device_id sysconfig_spi_ids[] = {
> > > > +	{
> > > > +		.name = "ecp5-fpga-mgr",
> > > > +		.driver_data = (kernel_ulong_t)ecp5_probe,
> > > > +	}, {
> > > > +		.name = "machxo2-fpga-mgr",
> > > > +		.driver_data = (kernel_ulong_t)machxo2_probe,
> > > 
> > > Putting the whole probe flow in driver_data is the same as providing 2
> > > drivers. The purpose is not to put all the code in one file.
> > > 
> > 
> > Sorry, I don't understand what you suggest. Separate file for each
> > specific FPGA?
> 
> Sorry, didn't make it clear. I feel like there are still 2 board
> specific drivers although they are now in one file, they have
> separate probes, fpga manager ops, different spi APIs ...
> 
> What I expect is, the driver operates the HW according to the configured
> properties specified in sysCONFIG spec, such as having PROGRAMN gpio pin?
> burst? having backup flash? ...

Ok, got it. But since I have only ECP5 this will be extremely error
prone for other FPGAs.

> 
> Thanks,
> Yilun
> 
> > 
> > > Thanks,
> > > Yilun
> > > 
> > > > +	}, {},
> > > > +};
> > > > +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> > > > +
> > > > +#if IS_ENABLED(CONFIG_OF)
> > > > +static const struct of_device_id sysconfig_of_ids[] = {
> > > > +	{
> > > > +		.compatible = "lattice,ecp5-fpga-mgr",
> > > > +		.data = ecp5_probe,
> > > > +	}, {
> > > > +		.compatible = "lattice,machxo2-fpga-mgr",
> > > > +		.data = machxo2_probe
> > > > +	}, {},
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> > > > +#endif /* IS_ENABLED(CONFIG_OF) */
> > > > +
> > > > +static struct spi_driver lattice_sysconfig_driver = {
> > > > +	.probe = sysconfig_probe,
> > > > +	.id_table = sysconfig_spi_ids,
> > > > +	.driver = {
> > > > +		.name = "lattice_sysconfig_spi_fpga_mgr",
> > > > +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> > > > +	},
> > > > +};
> > > > +
> > > > +module_spi_driver(lattice_sysconfig_driver);
> > > > +
> > > > +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> > > > +MODULE_LICENSE("GPL");
> > > > -- 
> > > > 2.37.2
> > > > 
> > > > 
> >
Ivan Bornyakov Aug. 29, 2022, 11:10 a.m. UTC | #9
On Mon, Aug 29, 2022 at 02:04:10PM +0300, Ivan Bornyakov wrote:
> On Mon, Aug 29, 2022 at 06:34:44PM +0800, Xu Yilun wrote:
> > On 2022-08-29 at 11:27:40 +0300, Ivan Bornyakov wrote:
> > > On Mon, Aug 29, 2022 at 03:25:41PM +0800, Xu Yilun wrote:
> > > > On 2022-08-25 at 14:24:32 +0300, Ivan Bornyakov wrote:
> > > > > Add support to the FPGA manager for programming Lattice ECP5 and MachXO2
> > > > > FPGAs over slave SPI sysCONFIG interface.
> > > > > 
> > > > > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > > > > ---
> > > > >  drivers/fpga/Kconfig                 |   7 +
> > > > >  drivers/fpga/Makefile                |   1 +
> > > > >  drivers/fpga/lattice-sysconfig-spi.c | 630 +++++++++++++++++++++++++++
> > > > >  3 files changed, 638 insertions(+)
> > > > >  create mode 100644 drivers/fpga/lattice-sysconfig-spi.c
> > > > > 
> > > > > diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> > > > > index 6c416955da53..991d9d976dca 100644
> > > > > --- a/drivers/fpga/Kconfig
> > > > > +++ b/drivers/fpga/Kconfig
> > > > > @@ -263,4 +263,11 @@ config FPGA_MGR_MICROCHIP_SPI
> > > > >  	  programming over slave SPI interface with .dat formatted
> > > > >  	  bitstream image.
> > > > >  
> > > > > +config FPGA_MGR_LATTICE_SPI
> > > > > +	tristate "Lattice sysCONFIG SPI FPGA manager"
> > > > > +	depends on SPI
> > > > > +	help
> > > > > +	  FPGA manager driver support for Lattice FPGAs programming over slave
> > > > > +	  SPI sysCONFIG interface.
> > > > > +
> > > > >  endif # FPGA
> > > > > diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> > > > > index 42ae8b58abce..115dba916024 100644
> > > > > --- a/drivers/fpga/Makefile
> > > > > +++ b/drivers/fpga/Makefile
> > > > > @@ -20,6 +20,7 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
> > > > >  obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
> > > > >  obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
> > > > >  obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
> > > > > +obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
> > > > >  obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
> > > > >  obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
> > > > >  
> > > > > diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
> > > > > new file mode 100644
> > > > > index 000000000000..145b5b27b88d
> > > > > --- /dev/null
> > > > > +++ b/drivers/fpga/lattice-sysconfig-spi.c
> > > > > @@ -0,0 +1,630 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > +/*
> > > > > + * Lattice FPGA programming over slave SPI sysCONFIG interface.
> > > > > + */
> > > > > +
> > > > > +#include <linux/delay.h>
> > > > > +#include <linux/fpga/fpga-mgr.h>
> > > > > +#include <linux/of_device.h>
> > > > > +#include <linux/spi/spi.h>
> > > > > +
> > > > > +#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
> > > > > +#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
> > > > > +
> > > > > +#define	SYSCONFIG_STATUS_DONE		BIT(8)
> > > > > +#define	SYSCONFIG_STATUS_BUSY		BIT(12)
> > > > > +#define	SYSCONFIG_STATUS_FAIL		BIT(13)
> > > > > +#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
> > > > > +
> > > > > +#define	SYSCONFIG_POLL_RETRIES		100000
> > > > > +
> > > > > +#define	ECP5_SPI_MAX_SPEED_HZ		60000000
> > > > > +#define	ECP5_ISC_ERASE_OPERAND		0x01
> > > > > +
> > > > > +#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
> > > > > +#define	MACHXO2_PAGE_SIZE		16
> > > > > +#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
> > > > > +#define	MACHXO2_ISC_ERASE_OPERAND	0x04
> > > > 
> > > > You need to deliver the meaning of each operand, rather than just point
> > > > out this is for machxo2, that is for esp5. I assume the sysCONFIG will
> > > > not deliberately design different commands with the exact same
> > > > functionality for different boards.
> > > > 
> > > > We should have a set of common configurations with finer granularity that
> > > > board specific fpga manager device could select from, rather than create
> > > > more and more similar macros for each new board.
> > > > 
> > > > Back to the 2 operands, what's the meaning of 0x1 & 0x4 for erase,
> > > 
> > > 0x1 - erase SRAM, 0x4 - erase FLASH. I'll redefine them properly in v9
> > > 
> > > > also 0x0 & 0x8 for enable?
> > > > 
> > > 
> > > Their meaning is not documented. It's just 0x08 for MachXO2 and 0x00 for
> > > ECP5.
> > > 
> > > > > +
> > > > > +struct sysconfig_priv {
> > > > > +	struct gpio_desc *program;
> > > > > +	struct gpio_desc *init;
> > > > > +	struct gpio_desc *done;
> > > > > +	struct spi_device *spi;
> > > > > +	u8 isc_enable_operand;
> > > > > +	u8 isc_erase_operand;
> > > > > +};
> > > > > +
> > > > > +static int sysconfig_poll_busy(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
> > > > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > > > +	u8 busy;
> > > > > +
> > > > > +	while (retries--) {
> > > > > +		ret = spi_write_then_read(data->spi,
> > > > > +					  lsc_check_busy, sizeof(lsc_check_busy),
> > > > > +					  &busy, sizeof(busy));
> > > > > +		if (ret)
> > > > > +			return ret;
> > > > > +
> > > > > +		if (!busy)
> > > > > +			return 0;
> > > > > +
> > > > > +		usleep_range(50, 100);
> > > > > +	}
> > > > > +
> > > > > +	return -EBUSY;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
> > > > > +{
> > > > > +	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
> > > > > +	__be32 device_status;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = spi_write_then_read(data->spi,
> > > > > +				  lsc_read_status, sizeof(lsc_read_status),
> > > > > +				  &device_status, sizeof(device_status));
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	*status = be32_to_cpu(device_status);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
> > > > > +{
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = sysconfig_poll_busy(data);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	return sysconfig_read_status(data, status);
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
> > > > > +{
> > > > > +	int value, retries = SYSCONFIG_POLL_RETRIES;
> > > > > +
> > > > > +	while (retries--) {
> > > > > +		value = gpiod_get_value(gpio);
> > > > > +		if (value < 0)
> > > > > +			return value;
> > > > > +
> > > > > +		if ((!is_active && !value) || (is_active && value))
> > > > > +			return 0;
> > > > > +
> > > > > +		ndelay(10);
> > > > > +	}
> > > > > +
> > > > > +	return -ETIMEDOUT;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_refresh(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	usleep_range(4000, 8000);
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_isc_enable(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
> > > > > +	u32 status;
> > > > > +	int ret;
> > > > > +
> > > > > +	isc_enable[1] = data->isc_enable_operand;
> > > > > +
> > > > > +	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	ret = sysconfig_poll_status(data, &status);
> > > > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > > > +		return ret ? : -EFAULT;
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_isc_erase(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
> > > > > +	u32 status;
> > > > > +	int ret;
> > > > > +
> > > > > +	isc_erase[1] = data->isc_erase_operand;
> > > > > +
> > > > > +	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	ret = sysconfig_poll_status(data, &status);
> > > > > +	if (ret || (status & SYSCONFIG_STATUS_FAIL))
> > > > > +		return ret ? : -EFAULT;
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_isc_init(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = sysconfig_isc_enable(data);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	return sysconfig_isc_erase(data);
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
> > > > > +
> > > > > +	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
> > > > > +	struct spi_transfer xfer = {
> > > > > +		.tx_buf = lsc_bitstream_burst,
> > > > > +		.len = sizeof(lsc_bitstream_burst),
> > > > > +		.cs_change = 1,
> > > > > +	};
> > > > > +	struct spi_message msg;
> > > > > +
> > > > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > > > +
> > > > > +	return spi_sync_locked(data->spi, &msg);
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
> > > > > +	u32 status;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	ret = sysconfig_poll_status(data, &status);
> > > > > +	if (ret)
> > > > > +		return ret;
> > > > > +
> > > > > +	if (status & SYSCONFIG_STATUS_DONE)
> > > > > +		return 0;
> > > > > +
> > > > > +	return -EFAULT;
> > > > > +}
> > > > > +
> > > > > +static int sysconfig_isc_disable(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
> > > > > +
> > > > > +	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
> > > > > +}
> > > > > +
> > > > > +static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +
> > > > > +	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
> > > > > +						   FPGA_MGR_STATE_UNKNOWN;
> > > > > +}
> > > > > +
> > > > > +static int ecp5_ops_write_init(struct fpga_manager *mgr,
> > > > > +			       struct fpga_image_info *info,
> > > > > +			       const char *buf, size_t count)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct spi_device *spi = priv->spi;
> > > > > +	struct device *dev = &mgr->dev;
> > > > > +	struct gpio_desc *program;
> > > > > +	struct gpio_desc *init;
> > > > > +	struct gpio_desc *done;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > > > +		return -EOPNOTSUPP;
> > > > > +	}
> > > > > +
> > > > > +	program = priv->program;
> > > > > +	init = priv->init;
> > > > > +	done = priv->done;
> > > > > +
> > > > > +	/* Enter init mode */
> > > > > +	gpiod_set_value(program, 1);
> > > > 
> > > > Same concern, provide gpio or command options for all board specific
> > > > fpga manager to select. 
> > > > 
> > > > I'll not list each features one by one below.
> > > > 
> > > > some more comments below.
> > > > 
> > > > > +
> > > > > +	ret = sysconfig_poll_gpio(init, true);
> > > > > +	if (!ret)
> > > > > +		ret = sysconfig_poll_gpio(done, false);
> > > > > +
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to go to init mode\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/* Enter program mode */
> > > > > +	gpiod_set_value(program, 0);
> > > > > +
> > > > > +	ret = sysconfig_poll_gpio(init, false);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to go to program mode\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/* Enter ISC mode */
> > > > > +	ret = sysconfig_isc_init(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/* Initialize the Address Shift Register */
> > > > > +	ret = sysconfig_lsc_init_addr(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev,
> > > > > +			"Failed to initialize the Address Shift Register\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/*
> > > > > +	 * Lock SPI bus for exclusive usage until FPGA programming is done.
> > > > > +	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
> > > > > +	 */
> > > > > +	spi_bus_lock(spi->controller);
> > > > > +
> > > > > +	/* Prepare for bitstream burst write */
> > > > > +	ret = sysconfig_lsc_bitstream_burst(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to prepare for bitstream burst write\n");
> > > > > +		spi_bus_unlock(spi->controller);
> > > > > +	}
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct spi_device *spi = priv->spi;
> > > > > +	struct spi_transfer xfer = {
> > > > > +		.tx_buf = buf,
> > > > > +		.len = count,
> > > > > +		.cs_change = 1,
> > > > > +	};
> > > > > +	struct spi_message msg;
> > > > > +	int ret;
> > > > > +
> > > > > +	spi_message_init_with_transfers(&msg, &xfer, 1);
> > > > > +	ret = spi_sync_locked(spi, &msg);
> > > > > +	if (ret)
> > > > > +		spi_bus_unlock(spi->controller);
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static int ecp5_ops_write_complete(struct fpga_manager *mgr,
> > > > > +				   struct fpga_image_info *info)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct spi_device *spi = priv->spi;
> > > > > +	struct device *dev = &mgr->dev;
> > > > > +	int ret;
> > > > > +
> > > > > +	/* Bitstream burst write is done, release SPI bus */
> > > > > +	spi_bus_unlock(spi->controller);
> > > > > +
> > > > > +	/* Toggle CS and wait for bitstream write to finish */
> > > > > +	ret = spi_write(spi, NULL, 0);
> > > > > +	if (!ret)
> > > > > +		ret = sysconfig_poll_busy(priv);
> > > > > +
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Error while waiting bitstream write to finish\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/* Exit ISC mode */
> > > > > +	ret = sysconfig_isc_disable(priv);
> > > > > +	if (!ret)
> > > > > +		ret = sysconfig_poll_gpio(priv->done, true);
> > > > > +
> > > > > +	if (ret)
> > > > > +		dev_err(dev, "Failed to finish ISC\n");
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static const struct fpga_manager_ops ecp5_fpga_ops = {
> > > > > +	.state = ecp5_ops_state,
> > > > > +	.write_init = ecp5_ops_write_init,
> > > > > +	.write = ecp5_ops_write,
> > > > > +	.write_complete = ecp5_ops_write_complete,
> > > > > +};
> > > > > +
> > > > > +static int ecp5_probe(struct sysconfig_priv *priv)
> > > > > +{
> > > > > +	struct spi_device *spi = priv->spi;
> > > > > +	struct device *dev = &spi->dev;
> > > > > +	struct fpga_manager *mgr;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
> > > > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > > > +			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
> > > > > +		return -EINVAL;
> > > > > +	}
> > > > > +
> > > > > +	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
> > > > > +
> > > > > +	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
> > > > > +	if (IS_ERR(priv->done)) {
> > > > > +		ret = PTR_ERR(priv->done);
> > > > > +		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
> > > > > +	if (IS_ERR(priv->init)) {
> > > > > +		ret = PTR_ERR(priv->init);
> > > > > +		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
> > > > > +	if (IS_ERR(priv->program)) {
> > > > > +		ret = PTR_ERR(priv->program);
> > > > > +		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
> > > > > +				     &ecp5_fpga_ops, priv);
> > > > > +
> > > > > +	return PTR_ERR_OR_ZERO(mgr);
> > > > > +}
> > > > > +
> > > > > +static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	u32 status;
> > > > > +	int ret;
> > > > > +
> > > > > +	ret = sysconfig_read_status(priv, &status);
> > > > > +	if (ret || !(status & SYSCONFIG_STATUS_DONE))
> > > > > +		return FPGA_MGR_STATE_UNKNOWN;
> > > > > +
> > > > > +	return FPGA_MGR_STATE_OPERATING;
> > > > > +}
> > > > > +
> > > > > +static int machxo2_ops_write_init(struct fpga_manager *mgr,
> > > > > +				  struct fpga_image_info *info,
> > > > > +				  const char *buf, size_t count)
> > > > > +{
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct device *dev = &mgr->dev;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> > > > > +		dev_err(dev, "Partial reconfiguration is not supported\n");
> > > > > +		return -EOPNOTSUPP;
> > > > > +	}
> > > > > +
> > > > > +	/* Enter ISC mode */
> > > > > +	ret = sysconfig_isc_init(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to go to ISC mode\n");
> > > > > +		return ret;
> > > > > +	}
> > > > > +
> > > > > +	/* Initialize the Address Shift Register */
> > > > > +	ret = sysconfig_lsc_init_addr(priv);
> > > > > +	if (ret)
> > > > > +		dev_err(dev,
> > > > > +			"Failed to initialize the Address Shift Register\n");
> > > > > +
> > > > > +	return ret;
> > > > > +}
> > > > > +
> > > > > +static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
> > > > > +{
> > > > > +	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct device *dev = &mgr->dev;
> > > > > +	struct spi_transfer xfers[2] = {
> > > > > +		{
> > > > > +			.tx_buf = lsc_progincr,
> > > > > +			.len = sizeof(lsc_progincr),
> > > > > +		}, {
> > > > > +			.len = MACHXO2_PAGE_SIZE,
> > > > > +		},
> > > > > +	};
> > > > > +	size_t i;
> > > > > +	int ret;
> > > > > +
> > > > > +	if (count % MACHXO2_PAGE_SIZE) {
> > > > > +		dev_err(dev, "Malformed payload.\n");
> > > > > +		return -EINVAL;
> > > > > +	}
> > > > > +
> > > > > +	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
> > > > > +		xfers[1].tx_buf = buf + i;
> > > > > +
> > > > > +		ret = spi_sync_transfer(priv->spi, xfers, 2);
> > > > > +		if (!ret)
> > > > > +			ret = sysconfig_poll_busy(priv);
> > > > > +
> > > > > +		if (ret) {
> > > > > +			dev_err(dev, "Failed to write frame %zu of %zu\n",
> > > > > +				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
> > > > > +			return ret;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	return 0;
> > > > > +}
> > > > > +
> > > > > +static void machxo2_cleanup(struct sysconfig_priv *data)
> > > > > +{
> > > > > +	sysconfig_isc_erase(data);
> > > > > +	sysconfig_refresh(data);
> > > > > +}
> > > > > +
> > > > > +static int machxo2_ops_write_complete(struct fpga_manager *mgr,
> > > > > +				      struct fpga_image_info *info)
> > > > > +{
> > > > > +	int ret, retries = SYSCONFIG_POLL_RETRIES;
> > > > > +	struct sysconfig_priv *priv = mgr->priv;
> > > > > +	struct device *dev = &mgr->dev;
> > > > > +	u32 status;
> > > > > +
> > > > > +	ret = sysconfig_isc_prog_done(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to enable Self-Download Mode\n");
> > > > > +		goto fail;
> > > > > +	}
> > > > > +
> > > > > +	ret = sysconfig_isc_disable(priv);
> > > > > +	if (ret) {
> > > > > +		dev_err(dev, "Failed to disable Configuration Interface\n");
> > > > > +		goto fail;
> > > > > +	}
> > > > > +
> > > > > +	while (retries--) {
> > > > > +		ret = sysconfig_refresh(priv);
> > > > > +		if (!ret)
> > > > > +			ret = sysconfig_read_status(priv, &status);
> > > > > +
> > > > > +		if (ret) {
> > > > > +			dev_err(dev, "Failed to refresh\n");
> > > > > +			break;
> > > > > +		}
> > > > > +
> > > > > +		if (status & SYSCONFIG_STATUS_DONE &&
> > > > > +		    !(status & SYSCONFIG_STATUS_BUSY) &&
> > > > > +		    !(status & SYSCONFIG_STATUS_ERR))
> > > > > +			return 0;
> > > > > +	}
> > > > > +
> > > > > +fail:
> > > > > +	machxo2_cleanup(priv);
> > > > > +
> > > > > +	return -EFAULT;
> > > > > +}
> > > > > +
> > > > > +static const struct fpga_manager_ops machxo2_fpga_ops = {
> > > > > +	.state = machxo2_ops_state,
> > > > > +	.write_init = machxo2_ops_write_init,
> > > > > +	.write = machxo2_ops_write,
> > > > > +	.write_complete = machxo2_ops_write_complete,
> > > > > +};
> > > > > +
> > > > > +static int machxo2_probe(struct sysconfig_priv *priv)
> > > > > +{
> > > > > +	struct spi_device *spi = priv->spi;
> > > > > +	struct device *dev = &spi->dev;
> > > > > +	struct fpga_manager *mgr;
> > > > > +
> > > > > +	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
> > > > > +		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
> > > > > +			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
> > > > > +		return -EINVAL;
> > > > > +	}
> > > > > +
> > > > > +	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
> > > > > +	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
> > > > > +
> > > > > +	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
> > > > > +				     &machxo2_fpga_ops, priv);
> > > > > +
> > > > > +	return PTR_ERR_OR_ZERO(mgr);
> > > > > +}
> > > > > +
> > > > > +typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
> > > > > +
> > > > > +static int sysconfig_probe(struct spi_device *spi)
> > > > > +{
> > > > > +	const struct spi_device_id *dev_id;
> > > > > +	lattice_fpga_probe_func probe_func;
> > > > > +	struct device *dev = &spi->dev;
> > > > > +	struct sysconfig_priv *priv;
> > > > > +
> > > > > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > > > > +	if (!priv)
> > > > > +		return -ENOMEM;
> > > > > +
> > > > > +	priv->spi = spi;
> > > > > +
> > > > > +	probe_func = of_device_get_match_data(&spi->dev);
> > > > > +	if (!probe_func) {
> > > > > +		dev_id = spi_get_device_id(spi);
> > > > > +		if (!dev_id)
> > > > > +			return -ENODEV;
> > > > > +
> > > > > +		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
> > > > > +	}
> > > > > +
> > > > > +	if (!probe_func)
> > > > > +		return -EINVAL;
> > > > > +
> > > > > +	return probe_func(priv);
> > > > > +}
> > > > > +
> > > > > +static const struct spi_device_id sysconfig_spi_ids[] = {
> > > > > +	{
> > > > > +		.name = "ecp5-fpga-mgr",
> > > > > +		.driver_data = (kernel_ulong_t)ecp5_probe,
> > > > > +	}, {
> > > > > +		.name = "machxo2-fpga-mgr",
> > > > > +		.driver_data = (kernel_ulong_t)machxo2_probe,
> > > > 
> > > > Putting the whole probe flow in driver_data is the same as providing 2
> > > > drivers. The purpose is not to put all the code in one file.
> > > > 
> > > 
> > > Sorry, I don't understand what you suggest. Separate file for each
> > > specific FPGA?
> > 
> > Sorry, didn't make it clear. I feel like there are still 2 board
> > specific drivers although they are now in one file, they have
> > separate probes, fpga manager ops, different spi APIs ...
> > 
> > What I expect is, the driver operates the HW according to the configured
> > properties specified in sysCONFIG spec, such as having PROGRAMN gpio pin?
> > burst? having backup flash? ...
> 
> Ok, got it. But since I have only ECP5 this will be extremely error
> prone for other FPGAs.
> 

Also there is no single sysCONFIG spec, but each specific FPGA have its
sysCONFIG description in its datasheet. Thus this path might be a
slippery slope.

> > 
> > Thanks,
> > Yilun
> > 
> > > 
> > > > Thanks,
> > > > Yilun
> > > > 
> > > > > +	}, {},
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
> > > > > +
> > > > > +#if IS_ENABLED(CONFIG_OF)
> > > > > +static const struct of_device_id sysconfig_of_ids[] = {
> > > > > +	{
> > > > > +		.compatible = "lattice,ecp5-fpga-mgr",
> > > > > +		.data = ecp5_probe,
> > > > > +	}, {
> > > > > +		.compatible = "lattice,machxo2-fpga-mgr",
> > > > > +		.data = machxo2_probe
> > > > > +	}, {},
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
> > > > > +#endif /* IS_ENABLED(CONFIG_OF) */
> > > > > +
> > > > > +static struct spi_driver lattice_sysconfig_driver = {
> > > > > +	.probe = sysconfig_probe,
> > > > > +	.id_table = sysconfig_spi_ids,
> > > > > +	.driver = {
> > > > > +		.name = "lattice_sysconfig_spi_fpga_mgr",
> > > > > +		.of_match_table = of_match_ptr(sysconfig_of_ids),
> > > > > +	},
> > > > > +};
> > > > > +
> > > > > +module_spi_driver(lattice_sysconfig_driver);
> > > > > +
> > > > > +MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
> > > > > +MODULE_LICENSE("GPL");
> > > > > -- 
> > > > > 2.37.2
> > > > > 
> > > > > 
> > >
diff mbox series

Patch

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 6c416955da53..991d9d976dca 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -263,4 +263,11 @@  config FPGA_MGR_MICROCHIP_SPI
 	  programming over slave SPI interface with .dat formatted
 	  bitstream image.
 
+config FPGA_MGR_LATTICE_SPI
+	tristate "Lattice sysCONFIG SPI FPGA manager"
+	depends on SPI
+	help
+	  FPGA manager driver support for Lattice FPGAs programming over slave
+	  SPI sysCONFIG interface.
+
 endif # FPGA
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 42ae8b58abce..115dba916024 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -20,6 +20,7 @@  obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA)	+= zynq-fpga.o
 obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA)	+= zynqmp-fpga.o
 obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA)	+= versal-fpga.o
 obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI)	+= microchip-spi.o
+obj-$(CONFIG_FPGA_MGR_LATTICE_SPI)	+= lattice-sysconfig-spi.o
 obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
 obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
 
diff --git a/drivers/fpga/lattice-sysconfig-spi.c b/drivers/fpga/lattice-sysconfig-spi.c
new file mode 100644
index 000000000000..145b5b27b88d
--- /dev/null
+++ b/drivers/fpga/lattice-sysconfig-spi.c
@@ -0,0 +1,630 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lattice FPGA programming over slave SPI sysCONFIG interface.
+ */
+
+#include <linux/delay.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+
+#define	SYSCONFIG_ISC_ENABLE		{0xC6, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_ISC_DISABLE		{0x26, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_ISC_ERASE		{0x0E, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_ISC_PROGRAM_DONE	{0x5E, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_READ_STATUS	{0x3C, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_CHECK_BUSY	{0xF0, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_REFRESH		{0x79, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_INIT_ADDR		{0x46, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_BITSTREAM_BURST	{0x7a, 0x00, 0x00, 0x00}
+#define	SYSCONFIG_LSC_PROG_INCR_NV	{0x70, 0x00, 0x00, 0x01}
+
+#define	SYSCONFIG_STATUS_DONE		BIT(8)
+#define	SYSCONFIG_STATUS_BUSY		BIT(12)
+#define	SYSCONFIG_STATUS_FAIL		BIT(13)
+#define	SYSCONFIG_STATUS_ERR		(BIT(23) | BIT(24) | BIT(25))
+
+#define	SYSCONFIG_POLL_RETRIES		100000
+
+#define	ECP5_SPI_MAX_SPEED_HZ		60000000
+#define	ECP5_ISC_ERASE_OPERAND		0x01
+
+#define	MACHXO2_SPI_MAX_SPEED_HZ	66000000
+#define	MACHXO2_PAGE_SIZE		16
+#define	MACHXO2_ISC_ENABLE_OPERAND	0x08
+#define	MACHXO2_ISC_ERASE_OPERAND	0x04
+
+struct sysconfig_priv {
+	struct gpio_desc *program;
+	struct gpio_desc *init;
+	struct gpio_desc *done;
+	struct spi_device *spi;
+	u8 isc_enable_operand;
+	u8 isc_erase_operand;
+};
+
+static int sysconfig_poll_busy(struct sysconfig_priv *data)
+{
+	const u8 lsc_check_busy[] = SYSCONFIG_LSC_CHECK_BUSY;
+	int ret, retries = SYSCONFIG_POLL_RETRIES;
+	u8 busy;
+
+	while (retries--) {
+		ret = spi_write_then_read(data->spi,
+					  lsc_check_busy, sizeof(lsc_check_busy),
+					  &busy, sizeof(busy));
+		if (ret)
+			return ret;
+
+		if (!busy)
+			return 0;
+
+		usleep_range(50, 100);
+	}
+
+	return -EBUSY;
+}
+
+static int sysconfig_read_status(struct sysconfig_priv *data, u32 *status)
+{
+	const u8 lsc_read_status[] = SYSCONFIG_LSC_READ_STATUS;
+	__be32 device_status;
+	int ret;
+
+	ret = spi_write_then_read(data->spi,
+				  lsc_read_status, sizeof(lsc_read_status),
+				  &device_status, sizeof(device_status));
+	if (ret)
+		return ret;
+
+	*status = be32_to_cpu(device_status);
+
+	return 0;
+}
+
+static int sysconfig_poll_status(struct sysconfig_priv *data, u32 *status)
+{
+	int ret;
+
+	ret = sysconfig_poll_busy(data);
+	if (ret)
+		return ret;
+
+	return sysconfig_read_status(data, status);
+}
+
+static int sysconfig_poll_gpio(struct gpio_desc *gpio, bool is_active)
+{
+	int value, retries = SYSCONFIG_POLL_RETRIES;
+
+	while (retries--) {
+		value = gpiod_get_value(gpio);
+		if (value < 0)
+			return value;
+
+		if ((!is_active && !value) || (is_active && value))
+			return 0;
+
+		ndelay(10);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int sysconfig_refresh(struct sysconfig_priv *data)
+{
+	static const u8 lsc_refresh[] = SYSCONFIG_LSC_REFRESH;
+	int ret;
+
+	ret = spi_write(data->spi, lsc_refresh, sizeof(lsc_refresh));
+	if (ret)
+		return ret;
+
+	usleep_range(4000, 8000);
+
+	return 0;
+}
+
+static int sysconfig_isc_enable(struct sysconfig_priv *data)
+{
+	u8 isc_enable[] = SYSCONFIG_ISC_ENABLE;
+	u32 status;
+	int ret;
+
+	isc_enable[1] = data->isc_enable_operand;
+
+	ret = spi_write(data->spi, isc_enable, sizeof(isc_enable));
+	if (ret)
+		return ret;
+
+	ret = sysconfig_poll_status(data, &status);
+	if (ret || (status & SYSCONFIG_STATUS_FAIL))
+		return ret ? : -EFAULT;
+
+	return 0;
+}
+
+static int sysconfig_isc_erase(struct sysconfig_priv *data)
+{
+	u8 isc_erase[] = SYSCONFIG_ISC_ERASE;
+	u32 status;
+	int ret;
+
+	isc_erase[1] = data->isc_erase_operand;
+
+	ret = spi_write(data->spi, isc_erase, sizeof(isc_erase));
+	if (ret)
+		return ret;
+
+	ret = sysconfig_poll_status(data, &status);
+	if (ret || (status & SYSCONFIG_STATUS_FAIL))
+		return ret ? : -EFAULT;
+
+	return 0;
+}
+
+static int sysconfig_isc_init(struct sysconfig_priv *data)
+{
+	int ret;
+
+	ret = sysconfig_isc_enable(data);
+	if (ret)
+		return ret;
+
+	return sysconfig_isc_erase(data);
+}
+
+static int sysconfig_lsc_init_addr(struct sysconfig_priv *data)
+{
+	const u8 lsc_init_addr[] = SYSCONFIG_LSC_INIT_ADDR;
+
+	return spi_write(data->spi, lsc_init_addr, sizeof(lsc_init_addr));
+}
+
+static int sysconfig_lsc_bitstream_burst(struct sysconfig_priv *data)
+{
+	const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST;
+	struct spi_transfer xfer = {
+		.tx_buf = lsc_bitstream_burst,
+		.len = sizeof(lsc_bitstream_burst),
+		.cs_change = 1,
+	};
+	struct spi_message msg;
+
+	spi_message_init_with_transfers(&msg, &xfer, 1);
+
+	return spi_sync_locked(data->spi, &msg);
+}
+
+static int sysconfig_isc_prog_done(struct sysconfig_priv *data)
+{
+	const u8 isc_prog_done[] = SYSCONFIG_ISC_PROGRAM_DONE;
+	u32 status;
+	int ret;
+
+	ret = spi_write(data->spi, isc_prog_done, sizeof(isc_prog_done));
+	if (ret)
+		return ret;
+
+	ret = sysconfig_poll_status(data, &status);
+	if (ret)
+		return ret;
+
+	if (status & SYSCONFIG_STATUS_DONE)
+		return 0;
+
+	return -EFAULT;
+}
+
+static int sysconfig_isc_disable(struct sysconfig_priv *data)
+{
+	const u8 isc_disable[] = SYSCONFIG_ISC_DISABLE;
+
+	return spi_write(data->spi, isc_disable, sizeof(isc_disable));
+}
+
+static enum fpga_mgr_states ecp5_ops_state(struct fpga_manager *mgr)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+
+	return (gpiod_get_value(priv->done) > 0) ? FPGA_MGR_STATE_OPERATING :
+						   FPGA_MGR_STATE_UNKNOWN;
+}
+
+static int ecp5_ops_write_init(struct fpga_manager *mgr,
+			       struct fpga_image_info *info,
+			       const char *buf, size_t count)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &mgr->dev;
+	struct gpio_desc *program;
+	struct gpio_desc *init;
+	struct gpio_desc *done;
+	int ret;
+
+	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
+		dev_err(dev, "Partial reconfiguration is not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	program = priv->program;
+	init = priv->init;
+	done = priv->done;
+
+	/* Enter init mode */
+	gpiod_set_value(program, 1);
+
+	ret = sysconfig_poll_gpio(init, true);
+	if (!ret)
+		ret = sysconfig_poll_gpio(done, false);
+
+	if (ret) {
+		dev_err(dev, "Failed to go to init mode\n");
+		return ret;
+	}
+
+	/* Enter program mode */
+	gpiod_set_value(program, 0);
+
+	ret = sysconfig_poll_gpio(init, false);
+	if (ret) {
+		dev_err(dev, "Failed to go to program mode\n");
+		return ret;
+	}
+
+	/* Enter ISC mode */
+	ret = sysconfig_isc_init(priv);
+	if (ret) {
+		dev_err(dev, "Failed to go to ISC mode\n");
+		return ret;
+	}
+
+	/* Initialize the Address Shift Register */
+	ret = sysconfig_lsc_init_addr(priv);
+	if (ret) {
+		dev_err(dev,
+			"Failed to initialize the Address Shift Register\n");
+		return ret;
+	}
+
+	/*
+	 * Lock SPI bus for exclusive usage until FPGA programming is done.
+	 * SPI bus will be released in ecp5_ops_write_complete() or on error.
+	 */
+	spi_bus_lock(spi->controller);
+
+	/* Prepare for bitstream burst write */
+	ret = sysconfig_lsc_bitstream_burst(priv);
+	if (ret) {
+		dev_err(dev, "Failed to prepare for bitstream burst write\n");
+		spi_bus_unlock(spi->controller);
+	}
+
+	return ret;
+}
+
+static int ecp5_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct spi_transfer xfer = {
+		.tx_buf = buf,
+		.len = count,
+		.cs_change = 1,
+	};
+	struct spi_message msg;
+	int ret;
+
+	spi_message_init_with_transfers(&msg, &xfer, 1);
+	ret = spi_sync_locked(spi, &msg);
+	if (ret)
+		spi_bus_unlock(spi->controller);
+
+	return ret;
+}
+
+static int ecp5_ops_write_complete(struct fpga_manager *mgr,
+				   struct fpga_image_info *info)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &mgr->dev;
+	int ret;
+
+	/* Bitstream burst write is done, release SPI bus */
+	spi_bus_unlock(spi->controller);
+
+	/* Toggle CS and wait for bitstream write to finish */
+	ret = spi_write(spi, NULL, 0);
+	if (!ret)
+		ret = sysconfig_poll_busy(priv);
+
+	if (ret) {
+		dev_err(dev, "Error while waiting bitstream write to finish\n");
+		return ret;
+	}
+
+	/* Exit ISC mode */
+	ret = sysconfig_isc_disable(priv);
+	if (!ret)
+		ret = sysconfig_poll_gpio(priv->done, true);
+
+	if (ret)
+		dev_err(dev, "Failed to finish ISC\n");
+
+	return ret;
+}
+
+static const struct fpga_manager_ops ecp5_fpga_ops = {
+	.state = ecp5_ops_state,
+	.write_init = ecp5_ops_write_init,
+	.write = ecp5_ops_write,
+	.write_complete = ecp5_ops_write_complete,
+};
+
+static int ecp5_probe(struct sysconfig_priv *priv)
+{
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &spi->dev;
+	struct fpga_manager *mgr;
+	int ret;
+
+	if (spi->max_speed_hz > ECP5_SPI_MAX_SPEED_HZ) {
+		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
+			spi->max_speed_hz, ECP5_SPI_MAX_SPEED_HZ);
+		return -EINVAL;
+	}
+
+	priv->isc_erase_operand = ECP5_ISC_ERASE_OPERAND;
+
+	priv->done = devm_gpiod_get(dev, "done", GPIOD_IN);
+	if (IS_ERR(priv->done)) {
+		ret = PTR_ERR(priv->done);
+		dev_err(dev, "Failed to get DONE GPIO: %d\n", ret);
+		return ret;
+	}
+
+	priv->init = devm_gpiod_get(dev, "init", GPIOD_IN);
+	if (IS_ERR(priv->init)) {
+		ret = PTR_ERR(priv->init);
+		dev_err(dev, "Failed to get INIT GPIO: %d\n", ret);
+		return ret;
+	}
+
+	priv->program = devm_gpiod_get(dev, "program", GPIOD_OUT_LOW);
+	if (IS_ERR(priv->program)) {
+		ret = PTR_ERR(priv->program);
+		dev_err(dev, "Failed to get PROGRAM GPIO: %d\n", ret);
+		return ret;
+	}
+
+	mgr = devm_fpga_mgr_register(dev, "Lattice ECP5 SPI FPGA Manager",
+				     &ecp5_fpga_ops, priv);
+
+	return PTR_ERR_OR_ZERO(mgr);
+}
+
+static enum fpga_mgr_states machxo2_ops_state(struct fpga_manager *mgr)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+	u32 status;
+	int ret;
+
+	ret = sysconfig_read_status(priv, &status);
+	if (ret || !(status & SYSCONFIG_STATUS_DONE))
+		return FPGA_MGR_STATE_UNKNOWN;
+
+	return FPGA_MGR_STATE_OPERATING;
+}
+
+static int machxo2_ops_write_init(struct fpga_manager *mgr,
+				  struct fpga_image_info *info,
+				  const char *buf, size_t count)
+{
+	struct sysconfig_priv *priv = mgr->priv;
+	struct device *dev = &mgr->dev;
+	int ret;
+
+	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
+		dev_err(dev, "Partial reconfiguration is not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* Enter ISC mode */
+	ret = sysconfig_isc_init(priv);
+	if (ret) {
+		dev_err(dev, "Failed to go to ISC mode\n");
+		return ret;
+	}
+
+	/* Initialize the Address Shift Register */
+	ret = sysconfig_lsc_init_addr(priv);
+	if (ret)
+		dev_err(dev,
+			"Failed to initialize the Address Shift Register\n");
+
+	return ret;
+}
+
+static int machxo2_ops_write(struct fpga_manager *mgr, const char *buf, size_t count)
+{
+	const u8 lsc_progincr[] = SYSCONFIG_LSC_PROG_INCR_NV;
+	struct sysconfig_priv *priv = mgr->priv;
+	struct device *dev = &mgr->dev;
+	struct spi_transfer xfers[2] = {
+		{
+			.tx_buf = lsc_progincr,
+			.len = sizeof(lsc_progincr),
+		}, {
+			.len = MACHXO2_PAGE_SIZE,
+		},
+	};
+	size_t i;
+	int ret;
+
+	if (count % MACHXO2_PAGE_SIZE) {
+		dev_err(dev, "Malformed payload.\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) {
+		xfers[1].tx_buf = buf + i;
+
+		ret = spi_sync_transfer(priv->spi, xfers, 2);
+		if (!ret)
+			ret = sysconfig_poll_busy(priv);
+
+		if (ret) {
+			dev_err(dev, "Failed to write frame %zu of %zu\n",
+				i / MACHXO2_PAGE_SIZE, count / MACHXO2_PAGE_SIZE);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void machxo2_cleanup(struct sysconfig_priv *data)
+{
+	sysconfig_isc_erase(data);
+	sysconfig_refresh(data);
+}
+
+static int machxo2_ops_write_complete(struct fpga_manager *mgr,
+				      struct fpga_image_info *info)
+{
+	int ret, retries = SYSCONFIG_POLL_RETRIES;
+	struct sysconfig_priv *priv = mgr->priv;
+	struct device *dev = &mgr->dev;
+	u32 status;
+
+	ret = sysconfig_isc_prog_done(priv);
+	if (ret) {
+		dev_err(dev, "Failed to enable Self-Download Mode\n");
+		goto fail;
+	}
+
+	ret = sysconfig_isc_disable(priv);
+	if (ret) {
+		dev_err(dev, "Failed to disable Configuration Interface\n");
+		goto fail;
+	}
+
+	while (retries--) {
+		ret = sysconfig_refresh(priv);
+		if (!ret)
+			ret = sysconfig_read_status(priv, &status);
+
+		if (ret) {
+			dev_err(dev, "Failed to refresh\n");
+			break;
+		}
+
+		if (status & SYSCONFIG_STATUS_DONE &&
+		    !(status & SYSCONFIG_STATUS_BUSY) &&
+		    !(status & SYSCONFIG_STATUS_ERR))
+			return 0;
+	}
+
+fail:
+	machxo2_cleanup(priv);
+
+	return -EFAULT;
+}
+
+static const struct fpga_manager_ops machxo2_fpga_ops = {
+	.state = machxo2_ops_state,
+	.write_init = machxo2_ops_write_init,
+	.write = machxo2_ops_write,
+	.write_complete = machxo2_ops_write_complete,
+};
+
+static int machxo2_probe(struct sysconfig_priv *priv)
+{
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &spi->dev;
+	struct fpga_manager *mgr;
+
+	if (spi->max_speed_hz > MACHXO2_SPI_MAX_SPEED_HZ) {
+		dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n",
+			spi->max_speed_hz, MACHXO2_SPI_MAX_SPEED_HZ);
+		return -EINVAL;
+	}
+
+	priv->isc_enable_operand = MACHXO2_ISC_ENABLE_OPERAND;
+	priv->isc_erase_operand = MACHXO2_ISC_ERASE_OPERAND;
+
+	mgr = devm_fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager",
+				     &machxo2_fpga_ops, priv);
+
+	return PTR_ERR_OR_ZERO(mgr);
+}
+
+typedef int (*lattice_fpga_probe_func)(struct sysconfig_priv *);
+
+static int sysconfig_probe(struct spi_device *spi)
+{
+	const struct spi_device_id *dev_id;
+	lattice_fpga_probe_func probe_func;
+	struct device *dev = &spi->dev;
+	struct sysconfig_priv *priv;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->spi = spi;
+
+	probe_func = of_device_get_match_data(&spi->dev);
+	if (!probe_func) {
+		dev_id = spi_get_device_id(spi);
+		if (!dev_id)
+			return -ENODEV;
+
+		probe_func = (lattice_fpga_probe_func)dev_id->driver_data;
+	}
+
+	if (!probe_func)
+		return -EINVAL;
+
+	return probe_func(priv);
+}
+
+static const struct spi_device_id sysconfig_spi_ids[] = {
+	{
+		.name = "ecp5-fpga-mgr",
+		.driver_data = (kernel_ulong_t)ecp5_probe,
+	}, {
+		.name = "machxo2-fpga-mgr",
+		.driver_data = (kernel_ulong_t)machxo2_probe,
+	}, {},
+};
+MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids);
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id sysconfig_of_ids[] = {
+	{
+		.compatible = "lattice,ecp5-fpga-mgr",
+		.data = ecp5_probe,
+	}, {
+		.compatible = "lattice,machxo2-fpga-mgr",
+		.data = machxo2_probe
+	}, {},
+};
+MODULE_DEVICE_TABLE(of, sysconfig_of_ids);
+#endif /* IS_ENABLED(CONFIG_OF) */
+
+static struct spi_driver lattice_sysconfig_driver = {
+	.probe = sysconfig_probe,
+	.id_table = sysconfig_spi_ids,
+	.driver = {
+		.name = "lattice_sysconfig_spi_fpga_mgr",
+		.of_match_table = of_match_ptr(sysconfig_of_ids),
+	},
+};
+
+module_spi_driver(lattice_sysconfig_driver);
+
+MODULE_DESCRIPTION("Lattice sysCONFIG Slave SPI FPGA Manager");
+MODULE_LICENSE("GPL");