diff mbox series

[v2] fpga: microsemi-spi: add Microsemi FPGA manager

Message ID 20220215115853.26491-1-i.bornyakov@metrotek.ru (mailing list archive)
State New
Headers show
Series [v2] fpga: microsemi-spi: add Microsemi FPGA manager | expand

Commit Message

Ivan Bornyakov Feb. 15, 2022, 11:58 a.m. UTC
Add support to the FPGA manager for programming Microsemi Polarfire
FPGAs over slave SPI interface.

Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
---
Changelog:
  v1 -> v2: fix printk formating

 drivers/fpga/Kconfig         |   9 +
 drivers/fpga/Makefile        |   1 +
 drivers/fpga/microsemi-spi.c | 366 +++++++++++++++++++++++++++++++++++
 3 files changed, 376 insertions(+)
 create mode 100644 drivers/fpga/microsemi-spi.c

Comments

Conor Dooley Feb. 15, 2022, 5:37 p.m. UTC | #1
Hey Ivan,
Firstly thanks for the patch(es), stumbled across them today.
As you may know Microsemi has been acquired by Microchip, so
s/microsemi/microchip/ please. This would make the correct vendor
prefix for compatible strings "microchip". While you've said this is
for the PolarFire FPGA, there is prescendent for using "mpfs" for the
PolarFire SoC FPGA in the kernel - so if you could change the uses of
"polarfire" to "mpf" that'd be great.

The current item on my own todo list is the opposite side of this,
reprogramming the FPGA via the system controller acting as a SPI
master for PolarFire SoC.
I will get back to you when I have a better idea of what (if any) code
can be made generic between both modes. In the meantime, I will get
together a setup to test SPI slave reprogramming of the PolarFire (SoC)

Thanks,
Conor <conor.dooley@microchip.com>

 > Add support to the FPGA manager for programming Microsemi Polarfire
 > FPGAs over slave SPI interface.
 >
 > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
 > ---
 > Changelog:
 >   v1 -> v2: fix printk formating
 >
 >  drivers/fpga/Kconfig         |   9 +
 >  drivers/fpga/Makefile        |   1 +
 >  drivers/fpga/microsemi-spi.c | 366 +++++++++++++++++++++++++++++++++++
 >  3 files changed, 376 insertions(+)
 >  create mode 100644 drivers/fpga/microsemi-spi.c
 >
--<snip>--
Moritz Fischer Feb. 15, 2022, 5:52 p.m. UTC | #2
Hi Conor, Ivan,

On Tue, Feb 15, 2022 at 05:37:04PM +0000, Conor Dooley wrote:
> Hey Ivan,
> Firstly thanks for the patch(es), stumbled across them today.
> As you may know Microsemi has been acquired by Microchip, so
> s/microsemi/microchip/ please. This would make the correct vendor
> prefix for compatible strings "microchip". While you've said this is
> for the PolarFire FPGA, there is prescendent for using "mpfs" for the
> PolarFire SoC FPGA in the kernel - so if you could change the uses of
> "polarfire" to "mpf" that'd be great.

I personally don't have a strong opinion on hte microchip vs microsemi
here. We have precedent with intel/altera.

> 
> The current item on my own todo list is the opposite side of this,
> reprogramming the FPGA via the system controller acting as a SPI
> master for PolarFire SoC.
> I will get back to you when I have a better idea of what (if any) code
> can be made generic between both modes. In the meantime, I will get
> together a setup to test SPI slave reprogramming of the PolarFire (SoC)
> 
> Thanks,
> Conor <conor.dooley@microchip.com>

Thanks for chiming in. Always nice to have vendors help out reviewing.
> 
> > Add support to the FPGA manager for programming Microsemi Polarfire
> > FPGAs over slave SPI interface.
> >
> > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > ---
> > Changelog:
> >   v1 -> v2: fix printk formating
> >
> >  drivers/fpga/Kconfig         |   9 +
> >  drivers/fpga/Makefile        |   1 +
> >  drivers/fpga/microsemi-spi.c | 366 +++++++++++++++++++++++++++++++++++
> >  3 files changed, 376 insertions(+)
> >  create mode 100644 drivers/fpga/microsemi-spi.c
> >
> --<snip>--

I'll take a closer look once the bot's complaints are addressed.

Thanks,
Moritz
Ivan Bornyakov Feb. 15, 2022, 6:05 p.m. UTC | #3
Hello Conor, Moritz

On Tue, Feb 15, 2022 at 09:52:30AM -0800, Moritz Fischer wrote:
> Hi Conor, Ivan,
> 
> On Tue, Feb 15, 2022 at 05:37:04PM +0000, Conor Dooley wrote:
> > Hey Ivan,
> > Firstly thanks for the patch(es), stumbled across them today.
> > As you may know Microsemi has been acquired by Microchip, so
> > s/microsemi/microchip/ please. This would make the correct vendor
> > prefix for compatible strings "microchip". While you've said this is
> > for the PolarFire FPGA, there is prescendent for using "mpfs" for the
> > PolarFire SoC FPGA in the kernel - so if you could change the uses of
> > "polarfire" to "mpf" that'd be great.
> 
> I personally don't have a strong opinion on hte microchip vs microsemi
> here. We have precedent with intel/altera.
> 

Me neither, so I'll do what Conor asked.

> > 
> > The current item on my own todo list is the opposite side of this,
> > reprogramming the FPGA via the system controller acting as a SPI
> > master for PolarFire SoC.
> > I will get back to you when I have a better idea of what (if any) code
> > can be made generic between both modes. In the meantime, I will get
> > together a setup to test SPI slave reprogramming of the PolarFire (SoC)
> > 
> > Thanks,
> > Conor <conor.dooley@microchip.com>
> 
> Thanks for chiming in. Always nice to have vendors help out reviewing.
>

Yeah, that's great, thanks in advance Conor.

> > > Add support to the FPGA manager for programming Microsemi Polarfire
> > > FPGAs over slave SPI interface.
> > >
> > > Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> > > ---
> > > Changelog:
> > >   v1 -> v2: fix printk formating
> > >
> > >  drivers/fpga/Kconfig         |   9 +
> > >  drivers/fpga/Makefile        |   1 +
> > >  drivers/fpga/microsemi-spi.c | 366 +++++++++++++++++++++++++++++++++++
> > >  3 files changed, 376 insertions(+)
> > >  create mode 100644 drivers/fpga/microsemi-spi.c
> > >
> > --<snip>--
> 
> I'll take a closer look once the bot's complaints are addressed.
> 
> Thanks,
> Moritz

Thanks in advance Moritz.
Conor Dooley Feb. 16, 2022, 12:55 p.m. UTC | #4
On 15/02/2022 11:58, Ivan Bornyakov wrote:
> Add support to the FPGA manager for programming Microsemi Polarfire
> FPGAs over slave SPI interface.
> 
> Signed-off-by: Ivan Bornyakov <i.bornyakov@metrotek.ru>
> ---
> Changelog:
>    v1 -> v2: fix printk formating
> 
>   drivers/fpga/Kconfig         |   9 +
>   drivers/fpga/Makefile        |   1 +
>   drivers/fpga/microsemi-spi.c | 366 +++++++++++++++++++++++++++++++++++
>   3 files changed, 376 insertions(+)
>   create mode 100644 drivers/fpga/microsemi-spi.c
> 
> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
> index 991b3f361ec9..25c2631a387c 100644
> --- a/drivers/fpga/Kconfig
> +++ b/drivers/fpga/Kconfig
> @@ -243,4 +243,13 @@ config FPGA_MGR_VERSAL_FPGA
>   	  configure the programmable logic(PL).
>   
>   	  To compile this as a module, choose M here.
> +
> +config FPGA_MGR_MICROSEMI_SPI
> +	tristate "Microsemi FPGA manager"
> +	depends on SPI
> +	select CRC_CCITT
> +	help
> +	  FPGA manager driver support for Microsemi Polarfire FPGAs
> +	  programming over slave SPI interface.
> +
>   endif # FPGA
> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
> index 0bff783d1b61..8b3d818546a6 100644
> --- a/drivers/fpga/Makefile
> +++ b/drivers/fpga/Makefile
> @@ -19,6 +19,7 @@ obj-$(CONFIG_FPGA_MGR_XILINX_SPI)	+= xilinx-spi.o
>   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_MICROSEMI_SPI)	+= microsemi-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/microsemi-spi.c b/drivers/fpga/microsemi-spi.c
> new file mode 100644
> index 000000000000..facbc8f600be
> --- /dev/null
> +++ b/drivers/fpga/microsemi-spi.c
> @@ -0,0 +1,366 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Microsemi Polarfire FPGA programming over slave SPI interface.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/of_device.h>
> +#include <linux/fpga/fpga-mgr.h>
> +#include <linux/delay.h>
> +#include <linux/crc-ccitt.h>
> +
> +#define	SPI_ISC_ENABLE		0x0B
> +#define	SPI_ISC_DISABLE		0x0C
> +#define	SPI_READ_STATUS		0x00
> +#define	SPI_READ_DATA		0x01
> +#define	SPI_FRAME_INIT		0xAE
> +#define	SPI_FRAME		0xEE
> +#define	SPI_PRG_MODE		0x01
> +#define	SPI_RELEASE		0x23
> +
> +#define	SPI_FRAME_SIZE	16
> +
> +#define	HEADER_SIZE_OFFSET		24
> +#define	DATA_SIZE_OFFSET		55
> +
> +#define	LOOKUP_TABLE_RECORD_SIZE	9
> +#define	LOOKUP_TABLE_BLOCK_ID_OFFSET	0
> +#define	LOOKUP_TABLE_BLOCK_START_OFFSET	1
> +
> +#define	COMPONENTS_SIZE_ID	5
> +#define	BITSTREAM_ID		8
> +
> +#define	BITS_PER_COMPONENT_SIZE	22
> +
> +#define	STATUS_POLL_TIMEOUT_MS	1000
> +#define	STATUS_BUSY		BIT(0)
> +#define	STATUS_READY		BIT(1)
> +#define	STATUS_SPI_VIOLATION	BIT(2)
> +#define	STATUS_SPI_ERROR	BIT(3)
> +
> +struct microsemi_fpga_priv {
> +	struct spi_device *spi;
> +	bool program_mode;
> +};
> +
> +static enum fpga_mgr_states microsemi_fpga_ops_state(struct fpga_manager *mgr)
> +{
> +	struct microsemi_fpga_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	bool program_mode = priv->program_mode;
> +	ssize_t status;
> +
> +	status = spi_w8r8(spi, SPI_READ_STATUS);
> +
> +	if (!program_mode && !status)
> +		return FPGA_MGR_STATE_OPERATING;
> +
> +	return FPGA_MGR_STATE_UNKNOWN;
> +}
> +
> +static int poll_status_not_busy(struct spi_device *spi, u8 mask)
> +{
> +	ssize_t status, timeout = STATUS_POLL_TIMEOUT_MS;
> +
> +	while (timeout--) {
> +		status = spi_w8r8(spi, SPI_READ_STATUS);
> +		if (status < 0)
> +			return status;
> +
> +		if (mask) {
> +			if (!(status & STATUS_BUSY) && (status & mask))
> +				return status;
> +		} else {
> +			if (!(status & STATUS_BUSY))
> +				return status;
> +		}
> +
> +		mdelay(1);
> +	}
> +
> +	return -EBUSY;
> +}
> +
> +static int microsemi_spi_write(struct spi_device *spi, const void *buf,
> +			       size_t buf_size)
> +{
> +	int status = poll_status_not_busy(spi, 0);
> +
> +	if (status < 0)
> +		return status;
> +
> +	return spi_write(spi, buf, buf_size);
> +}
> +
> +static int microsemi_spi_write_then_read(struct spi_device *spi,
> +					 const void *txbuf, size_t txbuf_size,
> +					 void *rxbuf, size_t rxbuf_size)
> +{
> +	const u8 read_command[] = { SPI_READ_DATA };
> +	int ret;
> +
> +	ret = microsemi_spi_write(spi, txbuf, txbuf_size);
> +	if (ret)
> +		return ret;
> +
> +	ret = poll_status_not_busy(spi, STATUS_READY);
> +	if (ret < 0)
> +		return ret;
> +
> +	return spi_write_then_read(spi, read_command, sizeof(read_command),
> +				   rxbuf, rxbuf_size);
> +}
> +
> +static int microsemi_fpga_ops_write_init(struct fpga_manager *mgr,
> +					 struct fpga_image_info *info,
> +					 const char *buf, size_t count)
> +{
> +	const u8 isc_en_command[] = { SPI_ISC_ENABLE };
> +	const u8 program_mode[] = { SPI_FRAME_INIT, SPI_PRG_MODE };
> +	struct microsemi_fpga_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	u32 isc_ret;
> +	int ret;
> +
> +	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
> +		dev_err(dev, "Partial reconfiguration is not supported\n");
> +
> +		return -EOPNOTSUPP;
> +	}
> +
> +	ret = microsemi_spi_write_then_read(spi, isc_en_command,
> +					    sizeof(isc_en_command),
> +					    &isc_ret, sizeof(isc_ret));
> +	if (ret || isc_ret) {
> +		dev_err(dev, "Failed to enable ISC: %d\n", ret ? ret : isc_ret);
> +
> +		return -EFAULT;
> +	}
> +
> +	ret = microsemi_spi_write(spi, program_mode, sizeof(program_mode));
> +	if (ret) {
> +		dev_err(dev, "Failed to enter program mode: %d\n", ret);
> +
> +		return ret;
> +	}
> +
> +	priv->program_mode = true;
> +
> +	return 0;
> +}
> +
> +static ssize_t lookup_block_start(int id, const char *buf, size_t buf_size)
> +{
> +	u8 header_size, blocks_num, block_id;
> +	u32 block_start, i;
> +
> +	header_size = *(buf + HEADER_SIZE_OFFSET);
> +
> +	if (header_size > buf_size)
> +		return -EFAULT;
> +
> +	blocks_num = *(buf + header_size - 1);
> +
> +	if (header_size + blocks_num * LOOKUP_TABLE_RECORD_SIZE > buf_size)
> +		return -EFAULT;
> +
> +	for (i = 0; i < blocks_num; i++) {
> +		block_id = *(buf + header_size + LOOKUP_TABLE_RECORD_SIZE * i +
> +			     LOOKUP_TABLE_BLOCK_ID_OFFSET);
> +
> +		if (block_id == id) {
> +			memcpy(&block_start,
> +			       buf + header_size +
> +			       LOOKUP_TABLE_RECORD_SIZE * i +
> +			       LOOKUP_TABLE_BLOCK_START_OFFSET,
> +			       sizeof(block_start));
> +
> +			return le32_to_cpu(block_start);
> +		}
> +	}
> +
> +	return -EFAULT;
> +}
> +
> +static ssize_t parse_bitstream_size(const char *buf, size_t buf_size)
> +{
> +	ssize_t	bitstream_size = 0, components_size_start = 0,
> +		component_size_byte_num, component_size_byte_off, i;
> +	u16 components_num;
> +	u32 component_size;
> +
> +	memcpy(&components_num, buf + DATA_SIZE_OFFSET, sizeof(components_num));
> +	components_num = le16_to_cpu(components_num);
> +
> +	components_size_start = lookup_block_start(COMPONENTS_SIZE_ID, buf,
> +						   buf_size);
> +	if (components_size_start < 0)
> +		return components_size_start;
> +
> +	if (components_size_start +
> +	    DIV_ROUND_UP(components_num * BITS_PER_COMPONENT_SIZE,
> +			 BITS_PER_BYTE) > buf_size)
> +		return -EFAULT;
> +
> +	for (i = 0; i < components_num; i++) {
> +		component_size_byte_num =
> +			(i * BITS_PER_COMPONENT_SIZE) / BITS_PER_BYTE;
> +		component_size_byte_off =
> +			(i * BITS_PER_COMPONENT_SIZE) % BITS_PER_BYTE;
> +
> +		memcpy(&component_size,
> +		       buf + components_size_start + component_size_byte_num,
> +		       sizeof(component_size));
> +		component_size = le32_to_cpu(component_size);
> +		component_size >>= component_size_byte_off;
> +		component_size &= GENMASK(BITS_PER_COMPONENT_SIZE - 1, 0);
> +
> +		bitstream_size += component_size;
> +	}
> +
> +	return bitstream_size;
> +}
> +
> +static int microsemi_fpga_ops_write(struct fpga_manager *mgr, const char *buf,
> +				    size_t count)
> +{
> +	ssize_t bitstream_start = 0, bitstream_size;
> +	struct microsemi_fpga_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	u8 tmp_buf[SPI_FRAME_SIZE + 1];
> +	int ret, i;
> +
> +	if (crc_ccitt(0, buf, count)) {
> +		dev_err(dev, "CRC error\n");
> +
> +		return -EINVAL;
> +	}
> +
> +	bitstream_start = lookup_block_start(BITSTREAM_ID, buf, count);
> +	if (bitstream_start < 0) {
> +		dev_err(dev, "Failed to find bitstream start %zd\n",
> +			bitstream_start);
> +
> +		return bitstream_start;
> +	}
> +
> +	bitstream_size = parse_bitstream_size(buf, count);
> +	if (bitstream_size < 0) {
> +		dev_err(dev, "Failed to parse bitstream size %zd\n",
> +			bitstream_size);
> +
> +		return bitstream_size;
> +	}
> +
> +	if (bitstream_start + bitstream_size * SPI_FRAME_SIZE > count) {
> +		dev_err(dev,
> +			"Bitstram outruns firmware. Bitstream start %zd, bitstream size %zd, firmware size %zu\n",
> +			bitstream_start, bitstream_size * SPI_FRAME_SIZE, count);
> +
> +		return -EFAULT;
> +	}
> +
> +	for (i = 0; i < bitstream_size; i++) {
> +		tmp_buf[0] = SPI_FRAME;
> +		memcpy(tmp_buf + 1, buf + bitstream_start + i * SPI_FRAME_SIZE,
> +		       SPI_FRAME_SIZE);
> +
> +		ret = microsemi_spi_write(spi, tmp_buf, sizeof(tmp_buf));
> +		if (ret) {
> +			dev_err(dev,
> +				"Failed to write bitstream frame number %d of %zd\n",
> +				i, bitstream_size);
> +
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int microsemi_fpga_ops_write_complete(struct fpga_manager *mgr,
> +					     struct fpga_image_info *info)
> +{
> +	const u8 isc_dis_command[] = { SPI_ISC_DISABLE };
> +	const u8 release_command[] = { SPI_RELEASE };
> +	struct microsemi_fpga_priv *priv = mgr->priv;
> +	struct spi_device *spi = priv->spi;
> +	struct device *dev = &mgr->dev;
> +	int ret;
> +
> +	ret = microsemi_spi_write(spi, isc_dis_command,
> +				  sizeof(isc_dis_command));
> +	if (ret) {
> +		dev_err(dev, "Failed to disable ISC: %d\n", ret);
> +
> +		return ret;
> +	}
> +
> +	mdelay(1);
> +
> +	ret = microsemi_spi_write(spi, release_command,
> +				  sizeof(release_command));
> +	if (ret) {
> +		dev_err(dev, "Failed to exit program mode: %d\n", ret);
> +
> +		return ret;
> +	}
> +
> +	priv->program_mode = false;
> +
> +	return 0;
> +}
> +
> +static const struct fpga_manager_ops microsemi_fpga_ops = {
> +	.state = microsemi_fpga_ops_state,
> +	.write_init = microsemi_fpga_ops_write_init,
> +	.write = microsemi_fpga_ops_write,
> +	.write_complete = microsemi_fpga_ops_write_complete,
> +};
> +
> +static int microsemi_fpga_probe(struct spi_device *spi)
> +{
> +	struct microsemi_fpga_priv *priv;
> +	struct device *dev = &spi->dev;
> +	struct fpga_manager *mgr;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->spi = spi;
> +
> +	mgr = devm_fpga_mgr_register(dev, "Microsemi FPGA Manager",
> +				     &microsemi_fpga_ops, priv);
Something else quick that I noticed today is that this string, plus the
.name strings, compatible string etc should indicate that this is spi 
slave programming mode as opposed to the other possible ones (jtag/iap)
> +
> +	return PTR_ERR_OR_ZERO(mgr);
> +}
> +
> +static const struct spi_device_id microsemi_fpga_spi_ids[] = {
> +	{ .name = "polarfire-fpga-mgr", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(spi, microsemi_fpga_spi_ids);
> +
> +static const struct of_device_id microsemi_fpga_of_ids[] = {
> +	{ .compatible = "mscc,polarfire-fpga-mgr" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, microsemi_fpga_of_ids);
> +
> +static struct spi_driver microsemi_fpga_driver = {
> +	.probe = microsemi_fpga_probe,
> +	.id_table = microsemi_fpga_spi_ids,
> +	.driver = {
> +		.name = "microsemi_fpga_manager",
> +		.of_match_table = of_match_ptr(microsemi_fpga_of_ids),
> +	},
> +};
> +
> +module_spi_driver(microsemi_fpga_driver);
> +
> +MODULE_DESCRIPTION("Microsemi FPGA Manager");
> +MODULE_LICENSE("GPL");
diff mbox series

Patch

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 991b3f361ec9..25c2631a387c 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -243,4 +243,13 @@  config FPGA_MGR_VERSAL_FPGA
 	  configure the programmable logic(PL).
 
 	  To compile this as a module, choose M here.
+
+config FPGA_MGR_MICROSEMI_SPI
+	tristate "Microsemi FPGA manager"
+	depends on SPI
+	select CRC_CCITT
+	help
+	  FPGA manager driver support for Microsemi Polarfire FPGAs
+	  programming over slave SPI interface.
+
 endif # FPGA
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 0bff783d1b61..8b3d818546a6 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -19,6 +19,7 @@  obj-$(CONFIG_FPGA_MGR_XILINX_SPI)	+= xilinx-spi.o
 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_MICROSEMI_SPI)	+= microsemi-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/microsemi-spi.c b/drivers/fpga/microsemi-spi.c
new file mode 100644
index 000000000000..facbc8f600be
--- /dev/null
+++ b/drivers/fpga/microsemi-spi.c
@@ -0,0 +1,366 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microsemi Polarfire FPGA programming over slave SPI interface.
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of_device.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/delay.h>
+#include <linux/crc-ccitt.h>
+
+#define	SPI_ISC_ENABLE		0x0B
+#define	SPI_ISC_DISABLE		0x0C
+#define	SPI_READ_STATUS		0x00
+#define	SPI_READ_DATA		0x01
+#define	SPI_FRAME_INIT		0xAE
+#define	SPI_FRAME		0xEE
+#define	SPI_PRG_MODE		0x01
+#define	SPI_RELEASE		0x23
+
+#define	SPI_FRAME_SIZE	16
+
+#define	HEADER_SIZE_OFFSET		24
+#define	DATA_SIZE_OFFSET		55
+
+#define	LOOKUP_TABLE_RECORD_SIZE	9
+#define	LOOKUP_TABLE_BLOCK_ID_OFFSET	0
+#define	LOOKUP_TABLE_BLOCK_START_OFFSET	1
+
+#define	COMPONENTS_SIZE_ID	5
+#define	BITSTREAM_ID		8
+
+#define	BITS_PER_COMPONENT_SIZE	22
+
+#define	STATUS_POLL_TIMEOUT_MS	1000
+#define	STATUS_BUSY		BIT(0)
+#define	STATUS_READY		BIT(1)
+#define	STATUS_SPI_VIOLATION	BIT(2)
+#define	STATUS_SPI_ERROR	BIT(3)
+
+struct microsemi_fpga_priv {
+	struct spi_device *spi;
+	bool program_mode;
+};
+
+static enum fpga_mgr_states microsemi_fpga_ops_state(struct fpga_manager *mgr)
+{
+	struct microsemi_fpga_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	bool program_mode = priv->program_mode;
+	ssize_t status;
+
+	status = spi_w8r8(spi, SPI_READ_STATUS);
+
+	if (!program_mode && !status)
+		return FPGA_MGR_STATE_OPERATING;
+
+	return FPGA_MGR_STATE_UNKNOWN;
+}
+
+static int poll_status_not_busy(struct spi_device *spi, u8 mask)
+{
+	ssize_t status, timeout = STATUS_POLL_TIMEOUT_MS;
+
+	while (timeout--) {
+		status = spi_w8r8(spi, SPI_READ_STATUS);
+		if (status < 0)
+			return status;
+
+		if (mask) {
+			if (!(status & STATUS_BUSY) && (status & mask))
+				return status;
+		} else {
+			if (!(status & STATUS_BUSY))
+				return status;
+		}
+
+		mdelay(1);
+	}
+
+	return -EBUSY;
+}
+
+static int microsemi_spi_write(struct spi_device *spi, const void *buf,
+			       size_t buf_size)
+{
+	int status = poll_status_not_busy(spi, 0);
+
+	if (status < 0)
+		return status;
+
+	return spi_write(spi, buf, buf_size);
+}
+
+static int microsemi_spi_write_then_read(struct spi_device *spi,
+					 const void *txbuf, size_t txbuf_size,
+					 void *rxbuf, size_t rxbuf_size)
+{
+	const u8 read_command[] = { SPI_READ_DATA };
+	int ret;
+
+	ret = microsemi_spi_write(spi, txbuf, txbuf_size);
+	if (ret)
+		return ret;
+
+	ret = poll_status_not_busy(spi, STATUS_READY);
+	if (ret < 0)
+		return ret;
+
+	return spi_write_then_read(spi, read_command, sizeof(read_command),
+				   rxbuf, rxbuf_size);
+}
+
+static int microsemi_fpga_ops_write_init(struct fpga_manager *mgr,
+					 struct fpga_image_info *info,
+					 const char *buf, size_t count)
+{
+	const u8 isc_en_command[] = { SPI_ISC_ENABLE };
+	const u8 program_mode[] = { SPI_FRAME_INIT, SPI_PRG_MODE };
+	struct microsemi_fpga_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &mgr->dev;
+	u32 isc_ret;
+	int ret;
+
+	if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
+		dev_err(dev, "Partial reconfiguration is not supported\n");
+
+		return -EOPNOTSUPP;
+	}
+
+	ret = microsemi_spi_write_then_read(spi, isc_en_command,
+					    sizeof(isc_en_command),
+					    &isc_ret, sizeof(isc_ret));
+	if (ret || isc_ret) {
+		dev_err(dev, "Failed to enable ISC: %d\n", ret ? ret : isc_ret);
+
+		return -EFAULT;
+	}
+
+	ret = microsemi_spi_write(spi, program_mode, sizeof(program_mode));
+	if (ret) {
+		dev_err(dev, "Failed to enter program mode: %d\n", ret);
+
+		return ret;
+	}
+
+	priv->program_mode = true;
+
+	return 0;
+}
+
+static ssize_t lookup_block_start(int id, const char *buf, size_t buf_size)
+{
+	u8 header_size, blocks_num, block_id;
+	u32 block_start, i;
+
+	header_size = *(buf + HEADER_SIZE_OFFSET);
+
+	if (header_size > buf_size)
+		return -EFAULT;
+
+	blocks_num = *(buf + header_size - 1);
+
+	if (header_size + blocks_num * LOOKUP_TABLE_RECORD_SIZE > buf_size)
+		return -EFAULT;
+
+	for (i = 0; i < blocks_num; i++) {
+		block_id = *(buf + header_size + LOOKUP_TABLE_RECORD_SIZE * i +
+			     LOOKUP_TABLE_BLOCK_ID_OFFSET);
+
+		if (block_id == id) {
+			memcpy(&block_start,
+			       buf + header_size +
+			       LOOKUP_TABLE_RECORD_SIZE * i +
+			       LOOKUP_TABLE_BLOCK_START_OFFSET,
+			       sizeof(block_start));
+
+			return le32_to_cpu(block_start);
+		}
+	}
+
+	return -EFAULT;
+}
+
+static ssize_t parse_bitstream_size(const char *buf, size_t buf_size)
+{
+	ssize_t	bitstream_size = 0, components_size_start = 0,
+		component_size_byte_num, component_size_byte_off, i;
+	u16 components_num;
+	u32 component_size;
+
+	memcpy(&components_num, buf + DATA_SIZE_OFFSET, sizeof(components_num));
+	components_num = le16_to_cpu(components_num);
+
+	components_size_start = lookup_block_start(COMPONENTS_SIZE_ID, buf,
+						   buf_size);
+	if (components_size_start < 0)
+		return components_size_start;
+
+	if (components_size_start +
+	    DIV_ROUND_UP(components_num * BITS_PER_COMPONENT_SIZE,
+			 BITS_PER_BYTE) > buf_size)
+		return -EFAULT;
+
+	for (i = 0; i < components_num; i++) {
+		component_size_byte_num =
+			(i * BITS_PER_COMPONENT_SIZE) / BITS_PER_BYTE;
+		component_size_byte_off =
+			(i * BITS_PER_COMPONENT_SIZE) % BITS_PER_BYTE;
+
+		memcpy(&component_size,
+		       buf + components_size_start + component_size_byte_num,
+		       sizeof(component_size));
+		component_size = le32_to_cpu(component_size);
+		component_size >>= component_size_byte_off;
+		component_size &= GENMASK(BITS_PER_COMPONENT_SIZE - 1, 0);
+
+		bitstream_size += component_size;
+	}
+
+	return bitstream_size;
+}
+
+static int microsemi_fpga_ops_write(struct fpga_manager *mgr, const char *buf,
+				    size_t count)
+{
+	ssize_t bitstream_start = 0, bitstream_size;
+	struct microsemi_fpga_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &mgr->dev;
+	u8 tmp_buf[SPI_FRAME_SIZE + 1];
+	int ret, i;
+
+	if (crc_ccitt(0, buf, count)) {
+		dev_err(dev, "CRC error\n");
+
+		return -EINVAL;
+	}
+
+	bitstream_start = lookup_block_start(BITSTREAM_ID, buf, count);
+	if (bitstream_start < 0) {
+		dev_err(dev, "Failed to find bitstream start %zd\n",
+			bitstream_start);
+
+		return bitstream_start;
+	}
+
+	bitstream_size = parse_bitstream_size(buf, count);
+	if (bitstream_size < 0) {
+		dev_err(dev, "Failed to parse bitstream size %zd\n",
+			bitstream_size);
+
+		return bitstream_size;
+	}
+
+	if (bitstream_start + bitstream_size * SPI_FRAME_SIZE > count) {
+		dev_err(dev,
+			"Bitstram outruns firmware. Bitstream start %zd, bitstream size %zd, firmware size %zu\n",
+			bitstream_start, bitstream_size * SPI_FRAME_SIZE, count);
+
+		return -EFAULT;
+	}
+
+	for (i = 0; i < bitstream_size; i++) {
+		tmp_buf[0] = SPI_FRAME;
+		memcpy(tmp_buf + 1, buf + bitstream_start + i * SPI_FRAME_SIZE,
+		       SPI_FRAME_SIZE);
+
+		ret = microsemi_spi_write(spi, tmp_buf, sizeof(tmp_buf));
+		if (ret) {
+			dev_err(dev,
+				"Failed to write bitstream frame number %d of %zd\n",
+				i, bitstream_size);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int microsemi_fpga_ops_write_complete(struct fpga_manager *mgr,
+					     struct fpga_image_info *info)
+{
+	const u8 isc_dis_command[] = { SPI_ISC_DISABLE };
+	const u8 release_command[] = { SPI_RELEASE };
+	struct microsemi_fpga_priv *priv = mgr->priv;
+	struct spi_device *spi = priv->spi;
+	struct device *dev = &mgr->dev;
+	int ret;
+
+	ret = microsemi_spi_write(spi, isc_dis_command,
+				  sizeof(isc_dis_command));
+	if (ret) {
+		dev_err(dev, "Failed to disable ISC: %d\n", ret);
+
+		return ret;
+	}
+
+	mdelay(1);
+
+	ret = microsemi_spi_write(spi, release_command,
+				  sizeof(release_command));
+	if (ret) {
+		dev_err(dev, "Failed to exit program mode: %d\n", ret);
+
+		return ret;
+	}
+
+	priv->program_mode = false;
+
+	return 0;
+}
+
+static const struct fpga_manager_ops microsemi_fpga_ops = {
+	.state = microsemi_fpga_ops_state,
+	.write_init = microsemi_fpga_ops_write_init,
+	.write = microsemi_fpga_ops_write,
+	.write_complete = microsemi_fpga_ops_write_complete,
+};
+
+static int microsemi_fpga_probe(struct spi_device *spi)
+{
+	struct microsemi_fpga_priv *priv;
+	struct device *dev = &spi->dev;
+	struct fpga_manager *mgr;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->spi = spi;
+
+	mgr = devm_fpga_mgr_register(dev, "Microsemi FPGA Manager",
+				     &microsemi_fpga_ops, priv);
+
+	return PTR_ERR_OR_ZERO(mgr);
+}
+
+static const struct spi_device_id microsemi_fpga_spi_ids[] = {
+	{ .name = "polarfire-fpga-mgr", },
+	{},
+};
+MODULE_DEVICE_TABLE(spi, microsemi_fpga_spi_ids);
+
+static const struct of_device_id microsemi_fpga_of_ids[] = {
+	{ .compatible = "mscc,polarfire-fpga-mgr" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, microsemi_fpga_of_ids);
+
+static struct spi_driver microsemi_fpga_driver = {
+	.probe = microsemi_fpga_probe,
+	.id_table = microsemi_fpga_spi_ids,
+	.driver = {
+		.name = "microsemi_fpga_manager",
+		.of_match_table = of_match_ptr(microsemi_fpga_of_ids),
+	},
+};
+
+module_spi_driver(microsemi_fpga_driver);
+
+MODULE_DESCRIPTION("Microsemi FPGA Manager");
+MODULE_LICENSE("GPL");