154 lines
3.8 KiB
C
154 lines
3.8 KiB
C
// 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");
|