| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Lattice FPGA programming over slave SPI sysCONFIG interface. |
| */ |
| |
| #include <linux/of.h> |
| #include <linux/spi/spi.h> |
| |
| #include "lattice-sysconfig.h" |
| |
| static const u32 ecp5_spi_max_speed_hz = 60000000; |
| |
| static int sysconfig_spi_cmd_transfer(struct sysconfig_priv *priv, |
| const void *tx_buf, size_t tx_len, |
| void *rx_buf, size_t rx_len) |
| { |
| struct spi_device *spi = to_spi_device(priv->dev); |
| |
| return spi_write_then_read(spi, tx_buf, tx_len, rx_buf, rx_len); |
| } |
| |
| static int sysconfig_spi_bitstream_burst_init(struct sysconfig_priv *priv) |
| { |
| const u8 lsc_bitstream_burst[] = SYSCONFIG_LSC_BITSTREAM_BURST; |
| struct spi_device *spi = to_spi_device(priv->dev); |
| struct spi_transfer xfer = {}; |
| struct spi_message msg; |
| size_t buf_len; |
| void *buf; |
| int ret; |
| |
| buf_len = sizeof(lsc_bitstream_burst); |
| |
| buf = kmemdup(lsc_bitstream_burst, buf_len, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| xfer.len = buf_len; |
| xfer.tx_buf = buf; |
| xfer.cs_change = 1; |
| |
| spi_message_init_with_transfers(&msg, &xfer, 1); |
| |
| /* |
| * Lock SPI bus for exclusive usage until FPGA programming is done. |
| * SPI bus will be released in sysconfig_spi_bitstream_burst_complete(). |
| */ |
| spi_bus_lock(spi->controller); |
| |
| ret = spi_sync_locked(spi, &msg); |
| if (ret) |
| spi_bus_unlock(spi->controller); |
| |
| kfree(buf); |
| |
| return ret; |
| } |
| |
| static int sysconfig_spi_bitstream_burst_write(struct sysconfig_priv *priv, |
| const char *buf, size_t len) |
| { |
| struct spi_device *spi = to_spi_device(priv->dev); |
| struct spi_transfer xfer = { |
| .tx_buf = buf, |
| .len = len, |
| .cs_change = 1, |
| }; |
| struct spi_message msg; |
| |
| spi_message_init_with_transfers(&msg, &xfer, 1); |
| |
| return spi_sync_locked(spi, &msg); |
| } |
| |
| static int sysconfig_spi_bitstream_burst_complete(struct sysconfig_priv *priv) |
| { |
| struct spi_device *spi = to_spi_device(priv->dev); |
| |
| /* Bitstream burst write is done, release SPI bus */ |
| spi_bus_unlock(spi->controller); |
| |
| /* Toggle CS to finish bitstream write */ |
| return spi_write(spi, NULL, 0); |
| } |
| |
| static int sysconfig_spi_probe(struct spi_device *spi) |
| { |
| const struct spi_device_id *dev_id; |
| struct device *dev = &spi->dev; |
| struct sysconfig_priv *priv; |
| const u32 *spi_max_speed; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| spi_max_speed = device_get_match_data(dev); |
| if (!spi_max_speed) { |
| dev_id = spi_get_device_id(spi); |
| if (!dev_id) |
| return -ENODEV; |
| |
| spi_max_speed = (const u32 *)dev_id->driver_data; |
| } |
| |
| if (!spi_max_speed) |
| return -EINVAL; |
| |
| if (spi->max_speed_hz > *spi_max_speed) { |
| dev_err(dev, "SPI speed %u is too high, maximum speed is %u\n", |
| spi->max_speed_hz, *spi_max_speed); |
| return -EINVAL; |
| } |
| |
| priv->dev = dev; |
| priv->command_transfer = sysconfig_spi_cmd_transfer; |
| priv->bitstream_burst_write_init = sysconfig_spi_bitstream_burst_init; |
| priv->bitstream_burst_write = sysconfig_spi_bitstream_burst_write; |
| priv->bitstream_burst_write_complete = sysconfig_spi_bitstream_burst_complete; |
| |
| return sysconfig_probe(priv); |
| } |
| |
| static const struct spi_device_id sysconfig_spi_ids[] = { |
| { |
| .name = "sysconfig-ecp5", |
| .driver_data = (kernel_ulong_t)&ecp5_spi_max_speed_hz, |
| }, {}, |
| }; |
| MODULE_DEVICE_TABLE(spi, sysconfig_spi_ids); |
| |
| #if IS_ENABLED(CONFIG_OF) |
| static const struct of_device_id sysconfig_of_ids[] = { |
| { |
| .compatible = "lattice,sysconfig-ecp5", |
| .data = &ecp5_spi_max_speed_hz, |
| }, {}, |
| }; |
| MODULE_DEVICE_TABLE(of, sysconfig_of_ids); |
| #endif /* IS_ENABLED(CONFIG_OF) */ |
| |
| static struct spi_driver lattice_sysconfig_driver = { |
| .probe = sysconfig_spi_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"); |