mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
spi: add support for microchip "soft" spi controller
Introduce driver support for the Microchip FPGA CoreSPI IP. This driver supports only Motorola SPI mode and frame size of 8-bits. TI/NSC modes and wider frame sizes are not currently supported. Signed-off-by: Prajna Rajendra Kumar <prajna.rajendrakumar@microchip.com> Reviewed-by: Conor Dooley <conor.dooley@microchip.com> Acked-by: Conor Dooley <conor.dooley@microchip.com> Link: https://patch.msgid.link/20251114104545.284765-4-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
committed by
Mark Brown
parent
8ce9a2ed15
commit
059f545832
@@ -715,6 +715,15 @@ config SPI_MICROCHIP_CORE_QSPI
|
||||
PolarFire SoC.
|
||||
If built as a module, it will be called spi-microchip-core-qspi.
|
||||
|
||||
config SPI_MICROCHIP_CORE_SPI
|
||||
tristate "Microchip FPGA CoreSPI controller"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
This enables the SPI driver for Microchip FPGA CoreSPI controller.
|
||||
Say Y or M here if you want to use the "soft" controllers on
|
||||
PolarFire SoC.
|
||||
If built as a module, it will be called spi-microchip-core-spi.
|
||||
|
||||
config SPI_MT65XX
|
||||
tristate "MediaTek SPI controller"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
|
||||
@@ -87,6 +87,7 @@ obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o
|
||||
obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o
|
||||
obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o
|
||||
obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) += spi-microchip-core-qspi.o
|
||||
obj-$(CONFIG_SPI_MICROCHIP_CORE_SPI) += spi-microchip-core-spi.o
|
||||
obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o
|
||||
obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o
|
||||
obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o
|
||||
|
||||
442
drivers/spi/spi-microchip-core-spi.c
Normal file
442
drivers/spi/spi-microchip-core-spi.c
Normal file
@@ -0,0 +1,442 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0)
|
||||
//
|
||||
// Microchip CoreSPI controller driver
|
||||
//
|
||||
// Copyright (c) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
//
|
||||
// Author: Prajna Rajendra Kumar <prajna.rajendrakumar@microchip.com>
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define MCHP_CORESPI_MAX_CS (8)
|
||||
#define MCHP_CORESPI_DEFAULT_FIFO_DEPTH (4)
|
||||
#define MCHP_CORESPI_DEFAULT_MOTOROLA_MODE (3)
|
||||
|
||||
#define MCHP_CORESPI_CONTROL_ENABLE BIT(0)
|
||||
#define MCHP_CORESPI_CONTROL_MASTER BIT(1)
|
||||
#define MCHP_CORESPI_CONTROL_TX_DATA_INT BIT(3)
|
||||
#define MCHP_CORESPI_CONTROL_RX_OVER_INT BIT(4)
|
||||
#define MCHP_CORESPI_CONTROL_TX_UNDER_INT BIT(5)
|
||||
#define MCHP_CORESPI_CONTROL_FRAMEURUN BIT(6)
|
||||
#define MCHP_CORESPI_CONTROL_OENOFF BIT(7)
|
||||
|
||||
#define MCHP_CORESPI_STATUS_ACTIVE BIT(7)
|
||||
#define MCHP_CORESPI_STATUS_SSEL BIT(6)
|
||||
#define MCHP_CORESPI_STATUS_TXFIFO_UNDERFLOW BIT(5)
|
||||
#define MCHP_CORESPI_STATUS_RXFIFO_FULL BIT(4)
|
||||
#define MCHP_CORESPI_STATUS_TXFIFO_FULL BIT(3)
|
||||
#define MCHP_CORESPI_STATUS_RXFIFO_EMPTY BIT(2)
|
||||
#define MCHP_CORESPI_STATUS_DONE BIT(1)
|
||||
#define MCHP_CORESPI_STATUS_FIRSTFRAME BIT(0)
|
||||
|
||||
#define MCHP_CORESPI_INT_TXDONE BIT(0)
|
||||
#define MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW BIT(2)
|
||||
#define MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN BIT(3)
|
||||
#define MCHP_CORESPI_INT_CMDINT BIT(4)
|
||||
#define MCHP_CORESPI_INT_SSEND BIT(5)
|
||||
#define MCHP_CORESPI_INT_DATA_RX BIT(6)
|
||||
#define MCHP_CORESPI_INT_TXRFM BIT(7)
|
||||
|
||||
#define MCHP_CORESPI_CONTROL2_INTEN_TXRFMT BIT(7)
|
||||
#define MCHP_CORESPI_CONTROL2_INTEN_DATA_RX BIT(6)
|
||||
#define MCHP_CORESPI_CONTROL2_INTEN_SSEND BIT(5)
|
||||
#define MCHP_CORESPI_CONTROL2_INTEN_CMD BIT(4)
|
||||
|
||||
#define INT_ENABLE_MASK (MCHP_CORESPI_CONTROL_TX_DATA_INT | MCHP_CORESPI_CONTROL_RX_OVER_INT | \
|
||||
MCHP_CORESPI_CONTROL_TX_UNDER_INT)
|
||||
|
||||
#define MCHP_CORESPI_REG_CONTROL (0x00)
|
||||
#define MCHP_CORESPI_REG_INTCLEAR (0x04)
|
||||
#define MCHP_CORESPI_REG_RXDATA (0x08)
|
||||
#define MCHP_CORESPI_REG_TXDATA (0x0c)
|
||||
#define MCHP_CORESPI_REG_INTMASK (0X10)
|
||||
#define MCHP_CORESPI_REG_INTRAW (0X14)
|
||||
#define MCHP_CORESPI_REG_CONTROL2 (0x18)
|
||||
#define MCHP_CORESPI_REG_COMMAND (0x1c)
|
||||
#define MCHP_CORESPI_REG_STAT (0x20)
|
||||
#define MCHP_CORESPI_REG_SSEL (0x24)
|
||||
#define MCHP_CORESPI_REG_TXDATA_LAST (0X28)
|
||||
#define MCHP_CORESPI_REG_CLK_DIV (0x2c)
|
||||
|
||||
struct mchp_corespi {
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
const u8 *tx_buf;
|
||||
u8 *rx_buf;
|
||||
u32 clk_gen;
|
||||
int irq;
|
||||
int tx_len;
|
||||
int rx_len;
|
||||
u32 fifo_depth;
|
||||
};
|
||||
|
||||
static inline void mchp_corespi_disable(struct mchp_corespi *spi)
|
||||
{
|
||||
u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
|
||||
control &= ~MCHP_CORESPI_CONTROL_ENABLE;
|
||||
|
||||
writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
}
|
||||
|
||||
static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, u32 fifo_max)
|
||||
{
|
||||
for (int i = 0; i < fifo_max; i++) {
|
||||
u32 data;
|
||||
|
||||
while (readb(spi->regs + MCHP_CORESPI_REG_STAT) &
|
||||
MCHP_CORESPI_STATUS_RXFIFO_EMPTY)
|
||||
;
|
||||
|
||||
data = readb(spi->regs + MCHP_CORESPI_REG_RXDATA);
|
||||
|
||||
spi->rx_len--;
|
||||
if (!spi->rx_buf)
|
||||
continue;
|
||||
|
||||
*spi->rx_buf = data;
|
||||
|
||||
spi->rx_buf++;
|
||||
}
|
||||
}
|
||||
|
||||
static void mchp_corespi_enable_ints(struct mchp_corespi *spi)
|
||||
{
|
||||
u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
|
||||
control |= INT_ENABLE_MASK;
|
||||
writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
}
|
||||
|
||||
static void mchp_corespi_disable_ints(struct mchp_corespi *spi)
|
||||
{
|
||||
u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
|
||||
control &= ~INT_ENABLE_MASK;
|
||||
writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
}
|
||||
|
||||
static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, u32 fifo_max)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while ((i < fifo_max) &&
|
||||
!(readb(spi->regs + MCHP_CORESPI_REG_STAT) &
|
||||
MCHP_CORESPI_STATUS_TXFIFO_FULL)) {
|
||||
u32 word;
|
||||
|
||||
word = spi->tx_buf ? *spi->tx_buf : 0xaa;
|
||||
writeb(word, spi->regs + MCHP_CORESPI_REG_TXDATA);
|
||||
|
||||
if (spi->tx_buf)
|
||||
spi->tx_buf++;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
spi->tx_len -= i;
|
||||
}
|
||||
|
||||
static void mchp_corespi_set_cs(struct spi_device *spi, bool disable)
|
||||
{
|
||||
struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller);
|
||||
u32 reg;
|
||||
|
||||
reg = readb(corespi->regs + MCHP_CORESPI_REG_SSEL);
|
||||
reg &= ~BIT(spi_get_chipselect(spi, 0));
|
||||
reg |= !disable << spi_get_chipselect(spi, 0);
|
||||
|
||||
writeb(reg, corespi->regs + MCHP_CORESPI_REG_SSEL);
|
||||
}
|
||||
|
||||
static int mchp_corespi_setup(struct spi_device *spi)
|
||||
{
|
||||
u32 dev_mode = spi->mode & (SPI_CPOL | SPI_CPHA);
|
||||
|
||||
if (spi_get_csgpiod(spi, 0))
|
||||
return 0;
|
||||
|
||||
if (spi->mode & (SPI_CS_HIGH)) {
|
||||
dev_err(&spi->dev, "unable to support active-high CS in Motorola mode\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (dev_mode & ~spi->controller->mode_bits) {
|
||||
dev_err(&spi->dev, "incompatible CPOL/CPHA, must match controller's Motorola mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi *spi)
|
||||
{
|
||||
u8 control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
|
||||
/* Master mode changes require core to be disabled.*/
|
||||
control = (control & ~MCHP_CORESPI_CONTROL_ENABLE) | MCHP_CORESPI_CONTROL_MASTER;
|
||||
|
||||
writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
|
||||
mchp_corespi_enable_ints(spi);
|
||||
|
||||
control = readb(spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
control |= MCHP_CORESPI_CONTROL_ENABLE;
|
||||
|
||||
writeb(control, spi->regs + MCHP_CORESPI_REG_CONTROL);
|
||||
}
|
||||
|
||||
static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct spi_controller *host = dev_id;
|
||||
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
||||
u8 intfield = readb(spi->regs + MCHP_CORESPI_REG_INTMASK) & 0xff;
|
||||
bool finalise = false;
|
||||
|
||||
/* Interrupt line may be shared and not for us at all */
|
||||
if (intfield == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (intfield & MCHP_CORESPI_INT_TXDONE)
|
||||
writeb(MCHP_CORESPI_INT_TXDONE, spi->regs + MCHP_CORESPI_REG_INTCLEAR);
|
||||
|
||||
if (intfield & MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW) {
|
||||
writeb(MCHP_CORESPI_INT_RX_CHANNEL_OVERFLOW,
|
||||
spi->regs + MCHP_CORESPI_REG_INTCLEAR);
|
||||
finalise = true;
|
||||
dev_err(&host->dev,
|
||||
"RX OVERFLOW: rxlen: %d, txlen: %d\n",
|
||||
spi->rx_len, spi->tx_len);
|
||||
}
|
||||
|
||||
if (intfield & MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN) {
|
||||
writeb(MCHP_CORESPI_INT_TX_CHANNEL_UNDERRUN,
|
||||
spi->regs + MCHP_CORESPI_REG_INTCLEAR);
|
||||
finalise = true;
|
||||
dev_err(&host->dev,
|
||||
"TX UNDERFLOW: rxlen: %d, txlen: %d\n",
|
||||
spi->rx_len, spi->tx_len);
|
||||
}
|
||||
|
||||
if (finalise)
|
||||
spi_finalize_current_transfer(host);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mchp_corespi_set_clk_div(struct mchp_corespi *spi,
|
||||
unsigned long target_hz)
|
||||
{
|
||||
unsigned long pclk_hz, spi_hz;
|
||||
u32 clk_div;
|
||||
|
||||
/* Get peripheral clock rate */
|
||||
pclk_hz = clk_get_rate(spi->clk);
|
||||
if (!pclk_hz)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Calculate clock rate generated by SPI master
|
||||
* Formula: SPICLK = PCLK / (2 * (CLK_DIV + 1))
|
||||
*/
|
||||
clk_div = DIV_ROUND_UP(pclk_hz, 2 * target_hz) - 1;
|
||||
|
||||
if (clk_div > 0xFF)
|
||||
return -EINVAL;
|
||||
|
||||
spi_hz = pclk_hz / (2 * (clk_div + 1));
|
||||
|
||||
if (spi_hz > target_hz)
|
||||
return -EINVAL;
|
||||
|
||||
writeb(clk_div, spi->regs + MCHP_CORESPI_REG_CLK_DIV);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_corespi_transfer_one(struct spi_controller *host,
|
||||
struct spi_device *spi_dev,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
||||
int ret;
|
||||
|
||||
ret = mchp_corespi_set_clk_div(spi, (unsigned long)xfer->speed_hz);
|
||||
if (ret) {
|
||||
dev_err(&host->dev, "failed to set clock divider for target %u Hz\n",
|
||||
xfer->speed_hz);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi->tx_buf = xfer->tx_buf;
|
||||
spi->rx_buf = xfer->rx_buf;
|
||||
spi->tx_len = xfer->len;
|
||||
spi->rx_len = xfer->len;
|
||||
|
||||
while (spi->tx_len) {
|
||||
int fifo_max = min_t(int, spi->tx_len, spi->fifo_depth);
|
||||
|
||||
mchp_corespi_write_fifo(spi, fifo_max);
|
||||
mchp_corespi_read_fifo(spi, fifo_max);
|
||||
}
|
||||
|
||||
spi_finalize_current_transfer(host);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int mchp_corespi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *host;
|
||||
struct mchp_corespi *spi;
|
||||
struct resource *res;
|
||||
const char *protocol;
|
||||
u32 num_cs, mode, frame_size;
|
||||
bool assert_ssel;
|
||||
int ret = 0;
|
||||
|
||||
host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi));
|
||||
if (!host)
|
||||
return dev_err_probe(&pdev->dev, -ENOMEM,
|
||||
"unable to allocate host for SPI controller\n");
|
||||
|
||||
platform_set_drvdata(pdev, host);
|
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs))
|
||||
num_cs = MCHP_CORESPI_MAX_CS;
|
||||
|
||||
/*
|
||||
* Protocol: CFG_MODE
|
||||
* CoreSPI can be configured for Motorola, TI or NSC.
|
||||
* The current driver supports only Motorola mode.
|
||||
*/
|
||||
ret = of_property_read_string(pdev->dev.of_node, "microchip,protocol-configuration",
|
||||
&protocol);
|
||||
if (strcmp(protocol, "motorola") != 0)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||
"CoreSPI: protocol '%s' not supported by this driver\n",
|
||||
protocol);
|
||||
|
||||
/*
|
||||
* Motorola mode (0-3): CFG_MOT_MODE
|
||||
* Mode is fixed in the IP configurator.
|
||||
*/
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "microchip,motorola-mode", &mode);
|
||||
if (ret)
|
||||
mode = MCHP_CORESPI_DEFAULT_MOTOROLA_MODE;
|
||||
else if (mode > 3)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||
"invalid 'microchip,motorola-mode' value %u\n", mode);
|
||||
|
||||
/*
|
||||
* Frame size: CFG_FRAME_SIZE
|
||||
* The hardware allows frame sizes <= APB data width.
|
||||
* However, this driver currently only supports 8-bit frames.
|
||||
*/
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "microchip,frame-size", &frame_size);
|
||||
if (!ret && frame_size != 8)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||
"CoreSPI: frame size %u not supported by this driver\n",
|
||||
frame_size);
|
||||
|
||||
/*
|
||||
* SSEL: CFG_MOT_SSEL
|
||||
* CoreSPI deasserts SSEL when the TX FIFO empties.
|
||||
* To prevent CS deassertion when TX FIFO drains, the ssel-active property
|
||||
* keeps CS asserted for the full SPI transfer.
|
||||
*/
|
||||
assert_ssel = of_property_read_bool(pdev->dev.of_node, "microchip,ssel-active");
|
||||
if (!assert_ssel)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||
"hardware must enable 'microchip,ssel-active' to keep CS asserted for the SPI transfer\n");
|
||||
|
||||
spi = spi_controller_get_devdata(host);
|
||||
|
||||
host->num_chipselect = num_cs;
|
||||
host->mode_bits = mode;
|
||||
host->setup = mchp_corespi_setup;
|
||||
host->use_gpio_descriptors = true;
|
||||
host->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
|
||||
host->transfer_one = mchp_corespi_transfer_one;
|
||||
host->set_cs = mchp_corespi_set_cs;
|
||||
host->dev.of_node = pdev->dev.of_node;
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "fifo-depth", &spi->fifo_depth);
|
||||
if (ret)
|
||||
spi->fifo_depth = MCHP_CORESPI_DEFAULT_FIFO_DEPTH;
|
||||
|
||||
spi->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
||||
if (IS_ERR(spi->regs))
|
||||
return PTR_ERR(spi->regs);
|
||||
|
||||
spi->irq = platform_get_irq(pdev, 0);
|
||||
if (spi->irq < 0)
|
||||
return spi->irq;
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt,
|
||||
IRQF_SHARED, dev_name(&pdev->dev), host);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"could not request irq\n");
|
||||
|
||||
spi->clk = devm_clk_get_enabled(&pdev->dev, NULL);
|
||||
if (IS_ERR(spi->clk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk),
|
||||
"could not get clk\n");
|
||||
|
||||
mchp_corespi_init(host, spi);
|
||||
|
||||
ret = devm_spi_register_controller(&pdev->dev, host);
|
||||
if (ret) {
|
||||
mchp_corespi_disable(spi);
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"unable to register host for CoreSPI controller\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mchp_corespi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *host = platform_get_drvdata(pdev);
|
||||
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
||||
|
||||
mchp_corespi_disable_ints(spi);
|
||||
mchp_corespi_disable(spi);
|
||||
}
|
||||
|
||||
#define MICROCHIP_SPI_PM_OPS (NULL)
|
||||
|
||||
/*
|
||||
* Platform driver data structure
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id mchp_corespi_dt_ids[] = {
|
||||
{ .compatible = "microchip,corespi-rtl-v5" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mchp_corespi_dt_ids);
|
||||
#endif
|
||||
|
||||
static struct platform_driver mchp_corespi_driver = {
|
||||
.probe = mchp_corespi_probe,
|
||||
.driver = {
|
||||
.name = "microchip-corespi",
|
||||
.pm = MICROCHIP_SPI_PM_OPS,
|
||||
.of_match_table = of_match_ptr(mchp_corespi_dt_ids),
|
||||
},
|
||||
.remove = mchp_corespi_remove,
|
||||
};
|
||||
module_platform_driver(mchp_corespi_driver);
|
||||
MODULE_DESCRIPTION("Microchip CoreSPI controller driver");
|
||||
MODULE_AUTHOR("Prajna Rajendra Kumar <prajna.rajendrakumar@microchip.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user