summaryrefslogtreecommitdiff
path: root/drivers/spi/spi-mpfs.c
diff options
context:
space:
mode:
authorPrajna Rajendra Kumar <prajna.rajendrakumar@microchip.com>2025-11-14 10:45:43 +0000
committerMark Brown <broonie@kernel.org>2025-11-14 13:54:41 +0000
commit71c814e98696f2cd53e9e6cef7501c2d667d4c5a (patch)
tree266990043edeacf9d4318ef6b78d50511acbcbce /drivers/spi/spi-mpfs.c
parente9a6fb0bcdd7609be6969112f3fbfcce3b1d4a7c (diff)
spi: microchip: rename driver file and internal identifiers
The spi-microchip-core.c driver provides support for the Microchip PolarFire SoC (MPFS) "hard" SPI controller. It was originally named "core" with the expectation that it might also cover Microchip's CoreSPI "soft" IP, but that never materialized. The CoreSPI IP cannot be supported by this driver because its register layout differs substantially from the MPFS SPI controller. In practice most of the code would need to be replaced to handle those differences so keeping the drivers separate is the simpler approach. The file and internal symbols are renamed to reflect MPFS support and to free up "spi-microchip-core.c" for CoreSPI driver. Fixes: 9ac8d17694b6 ("spi: add support for microchip fpga spi controllers") Signed-off-by: Prajna Rajendra Kumar <prajna.rajendrakumar@microchip.com> Acked-by: Conor Dooley <conor.dooley@microchip.com> Link: https://patch.msgid.link/20251114104545.284765-2-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'drivers/spi/spi-mpfs.c')
-rw-r--r--drivers/spi/spi-mpfs.c626
1 files changed, 626 insertions, 0 deletions
diff --git a/drivers/spi/spi-mpfs.c b/drivers/spi/spi-mpfs.c
new file mode 100644
index 000000000000..9a14d1732a15
--- /dev/null
+++ b/drivers/spi/spi-mpfs.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: (GPL-2.0)
+/*
+ * Microchip CoreSPI SPI controller driver
+ *
+ * Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Daire McNamara <daire.mcnamara@microchip.com>
+ * Author: Conor Dooley <conor.dooley@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 MAX_LEN (0xffff)
+#define MAX_CS (1)
+#define DEFAULT_FRAMESIZE (8)
+#define FIFO_DEPTH (32)
+#define CLK_GEN_MODE1_MAX (255)
+#define CLK_GEN_MODE0_MAX (15)
+#define CLK_GEN_MIN (0)
+#define MODE_X_MASK_SHIFT (24)
+
+#define CONTROL_ENABLE BIT(0)
+#define CONTROL_MASTER BIT(1)
+#define CONTROL_RX_DATA_INT BIT(4)
+#define CONTROL_TX_DATA_INT BIT(5)
+#define CONTROL_RX_OVER_INT BIT(6)
+#define CONTROL_TX_UNDER_INT BIT(7)
+#define CONTROL_SPO BIT(24)
+#define CONTROL_SPH BIT(25)
+#define CONTROL_SPS BIT(26)
+#define CONTROL_FRAMEURUN BIT(27)
+#define CONTROL_CLKMODE BIT(28)
+#define CONTROL_BIGFIFO BIT(29)
+#define CONTROL_OENOFF BIT(30)
+#define CONTROL_RESET BIT(31)
+
+#define CONTROL_MODE_MASK GENMASK(3, 2)
+#define MOTOROLA_MODE (0)
+#define CONTROL_FRAMECNT_MASK GENMASK(23, 8)
+#define CONTROL_FRAMECNT_SHIFT (8)
+
+#define STATUS_ACTIVE BIT(14)
+#define STATUS_SSEL BIT(13)
+#define STATUS_FRAMESTART BIT(12)
+#define STATUS_TXFIFO_EMPTY_NEXT_READ BIT(11)
+#define STATUS_TXFIFO_EMPTY BIT(10)
+#define STATUS_TXFIFO_FULL_NEXT_WRITE BIT(9)
+#define STATUS_TXFIFO_FULL BIT(8)
+#define STATUS_RXFIFO_EMPTY_NEXT_READ BIT(7)
+#define STATUS_RXFIFO_EMPTY BIT(6)
+#define STATUS_RXFIFO_FULL_NEXT_WRITE BIT(5)
+#define STATUS_RXFIFO_FULL BIT(4)
+#define STATUS_TX_UNDERRUN BIT(3)
+#define STATUS_RX_OVERFLOW BIT(2)
+#define STATUS_RXDAT_RXED BIT(1)
+#define STATUS_TXDAT_SENT BIT(0)
+
+#define INT_TXDONE BIT(0)
+#define INT_RXRDY BIT(1)
+#define INT_RX_CHANNEL_OVERFLOW BIT(2)
+#define INT_TX_CHANNEL_UNDERRUN BIT(3)
+
+#define INT_ENABLE_MASK (CONTROL_RX_OVER_INT | CONTROL_TX_UNDER_INT)
+
+#define REG_CONTROL (0x00)
+#define REG_FRAME_SIZE (0x04)
+#define FRAME_SIZE_MASK GENMASK(5, 0)
+#define REG_STATUS (0x08)
+#define REG_INT_CLEAR (0x0c)
+#define REG_RX_DATA (0x10)
+#define REG_TX_DATA (0x14)
+#define REG_CLK_GEN (0x18)
+#define REG_SLAVE_SELECT (0x1c)
+#define SSEL_MASK GENMASK(7, 0)
+#define SSEL_DIRECT BIT(8)
+#define SSELOUT_SHIFT 9
+#define SSELOUT BIT(SSELOUT_SHIFT)
+#define REG_MIS (0x20)
+#define REG_RIS (0x24)
+#define REG_CONTROL2 (0x28)
+#define REG_COMMAND (0x2c)
+#define COMMAND_CLRFRAMECNT BIT(4)
+#define COMMAND_TXFIFORST BIT(3)
+#define COMMAND_RXFIFORST BIT(2)
+#define REG_PKTSIZE (0x30)
+#define REG_CMD_SIZE (0x34)
+#define REG_HWSTATUS (0x38)
+#define REG_STAT8 (0x3c)
+#define REG_CTRL2 (0x48)
+#define REG_FRAMESUP (0x50)
+
+struct mpfs_spi {
+ void __iomem *regs;
+ struct clk *clk;
+ const u8 *tx_buf;
+ u8 *rx_buf;
+ u32 clk_gen; /* divider for spi output clock generated by the controller */
+ u32 clk_mode;
+ u32 pending_slave_select;
+ int irq;
+ int tx_len;
+ int rx_len;
+ int n_bytes;
+};
+
+static inline u32 mpfs_spi_read(struct mpfs_spi *spi, unsigned int reg)
+{
+ return readl(spi->regs + reg);
+}
+
+static inline void mpfs_spi_write(struct mpfs_spi *spi, unsigned int reg, u32 val)
+{
+ writel(val, spi->regs + reg);
+}
+
+static inline void mpfs_spi_disable(struct mpfs_spi *spi)
+{
+ u32 control = mpfs_spi_read(spi, REG_CONTROL);
+
+ control &= ~CONTROL_ENABLE;
+
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static inline void mpfs_spi_read_fifo(struct mpfs_spi *spi, int fifo_max)
+{
+ for (int i = 0; i < fifo_max; i++) {
+ u32 data;
+
+ while (mpfs_spi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY)
+ ;
+
+ data = mpfs_spi_read(spi, REG_RX_DATA);
+
+ spi->rx_len -= spi->n_bytes;
+
+ if (!spi->rx_buf)
+ continue;
+
+ if (spi->n_bytes == 4)
+ *((u32 *)spi->rx_buf) = data;
+ else if (spi->n_bytes == 2)
+ *((u16 *)spi->rx_buf) = data;
+ else
+ *spi->rx_buf = data;
+
+ spi->rx_buf += spi->n_bytes;
+ }
+}
+
+static void mpfs_spi_enable_ints(struct mpfs_spi *spi)
+{
+ u32 control = mpfs_spi_read(spi, REG_CONTROL);
+
+ control |= INT_ENABLE_MASK;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static void mpfs_spi_disable_ints(struct mpfs_spi *spi)
+{
+ u32 control = mpfs_spi_read(spi, REG_CONTROL);
+
+ control &= ~INT_ENABLE_MASK;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static inline void mpfs_spi_set_xfer_size(struct mpfs_spi *spi, int len)
+{
+ u32 control;
+ u32 lenpart;
+ u32 frames = mpfs_spi_read(spi, REG_FRAMESUP);
+
+ /*
+ * Writing to FRAMECNT in REG_CONTROL will reset the frame count, taking
+ * a shortcut requires an explicit clear.
+ */
+ if (frames == len) {
+ mpfs_spi_write(spi, REG_COMMAND, COMMAND_CLRFRAMECNT);
+ return;
+ }
+
+ /*
+ * The lower 16 bits of the frame count are stored in the control reg
+ * for legacy reasons, but the upper 16 written to a different register:
+ * FRAMESUP. While both the upper and lower bits can be *READ* from the
+ * FRAMESUP register, writing to the lower 16 bits is (supposedly) a NOP.
+ *
+ * The driver used to disable the controller while modifying the frame
+ * count, and mask off the lower 16 bits of len while writing to
+ * FRAMES_UP. When the driver was changed to disable the controller as
+ * infrequently as possible, it was discovered that the logic of
+ * lenpart = len & 0xffff_0000
+ * write(REG_FRAMESUP, lenpart)
+ * would actually write zeros into the lower 16 bits on an mpfs250t-es,
+ * despite documentation stating these bits were read-only.
+ * Writing len unmasked into FRAMES_UP ensures those bits aren't zeroed
+ * on an mpfs250t-es and will be a NOP for the lower 16 bits on hardware
+ * that matches the documentation.
+ */
+ lenpart = len & 0xffff;
+ control = mpfs_spi_read(spi, REG_CONTROL);
+ control &= ~CONTROL_FRAMECNT_MASK;
+ control |= lenpart << CONTROL_FRAMECNT_SHIFT;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+ mpfs_spi_write(spi, REG_FRAMESUP, len);
+}
+
+static inline void mpfs_spi_write_fifo(struct mpfs_spi *spi, int fifo_max)
+{
+ int i = 0;
+
+ mpfs_spi_set_xfer_size(spi, fifo_max);
+
+ while ((i < fifo_max) && !(mpfs_spi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) {
+ u32 word;
+
+ if (spi->n_bytes == 4)
+ word = spi->tx_buf ? *((u32 *)spi->tx_buf) : 0xaa;
+ else if (spi->n_bytes == 2)
+ word = spi->tx_buf ? *((u16 *)spi->tx_buf) : 0xaa;
+ else
+ word = spi->tx_buf ? *spi->tx_buf : 0xaa;
+
+ mpfs_spi_write(spi, REG_TX_DATA, word);
+ if (spi->tx_buf)
+ spi->tx_buf += spi->n_bytes;
+ i++;
+ }
+
+ spi->tx_len -= i * spi->n_bytes;
+}
+
+static inline void mpfs_spi_set_framesize(struct mpfs_spi *spi, int bt)
+{
+ u32 frame_size = mpfs_spi_read(spi, REG_FRAME_SIZE);
+ u32 control;
+
+ if ((frame_size & FRAME_SIZE_MASK) == bt)
+ return;
+
+ /*
+ * Disable the SPI controller. Writes to the frame size have
+ * no effect when the controller is enabled.
+ */
+ control = mpfs_spi_read(spi, REG_CONTROL);
+ control &= ~CONTROL_ENABLE;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+
+ mpfs_spi_write(spi, REG_FRAME_SIZE, bt);
+
+ control |= CONTROL_ENABLE;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static void mpfs_spi_set_cs(struct spi_device *spi, bool disable)
+{
+ u32 reg;
+ struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller);
+
+ reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT);
+ reg &= ~BIT(spi_get_chipselect(spi, 0));
+ reg |= !disable << spi_get_chipselect(spi, 0);
+ mspi->pending_slave_select = reg;
+
+ /*
+ * Only deassert chip select immediately. Writing to some registers
+ * requires the controller to be disabled, which results in the
+ * output pins being tristated and can cause the SCLK and MOSI lines
+ * to transition. Therefore asserting the chip select is deferred
+ * until just before writing to the TX FIFO, to ensure the device
+ * doesn't see any spurious clock transitions whilst CS is enabled.
+ */
+ if (((spi->mode & SPI_CS_HIGH) == 0) == disable)
+ mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg);
+}
+
+static int mpfs_spi_setup(struct spi_device *spi)
+{
+ struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller);
+ u32 reg;
+
+ if (spi_is_csgpiod(spi))
+ return 0;
+
+ /*
+ * Active high targets need to be specifically set to their inactive
+ * states during probe by adding them to the "control group" & thus
+ * driving their select line low.
+ */
+ if (spi->mode & SPI_CS_HIGH) {
+ reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT);
+ reg |= BIT(spi_get_chipselect(spi, 0));
+ mspi->pending_slave_select = reg;
+ mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg);
+ }
+ return 0;
+}
+
+static void mpfs_spi_init(struct spi_controller *host, struct mpfs_spi *spi)
+{
+ unsigned long clk_hz;
+ u32 control = mpfs_spi_read(spi, REG_CONTROL);
+
+ control &= ~CONTROL_ENABLE;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+
+ control |= CONTROL_MASTER;
+ control &= ~CONTROL_MODE_MASK;
+ control |= MOTOROLA_MODE;
+
+ /*
+ * The controller must be configured so that it doesn't remove Chip
+ * Select until the entire message has been transferred, even if at
+ * some points TX FIFO becomes empty.
+ *
+ * BIGFIFO mode is also enabled, which sets the fifo depth to 32 frames
+ * for the 8 bit transfers that this driver uses.
+ */
+ control |= CONTROL_SPS | CONTROL_BIGFIFO;
+
+ mpfs_spi_write(spi, REG_CONTROL, control);
+
+ mpfs_spi_set_framesize(spi, DEFAULT_FRAMESIZE);
+
+ /* max. possible spi clock rate is the apb clock rate */
+ clk_hz = clk_get_rate(spi->clk);
+ host->max_speed_hz = clk_hz;
+
+ mpfs_spi_enable_ints(spi);
+
+ /*
+ * It is required to enable direct mode, otherwise control over the chip
+ * select is relinquished to the hardware. SSELOUT is enabled too so we
+ * can deal with active high targets.
+ */
+ spi->pending_slave_select = SSELOUT | SSEL_DIRECT;
+ mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select);
+
+ control = mpfs_spi_read(spi, REG_CONTROL);
+
+ control &= ~CONTROL_RESET;
+ control |= CONTROL_ENABLE;
+
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static inline void mpfs_spi_set_clk_gen(struct mpfs_spi *spi)
+{
+ u32 control;
+
+ control = mpfs_spi_read(spi, REG_CONTROL);
+ if (spi->clk_mode)
+ control |= CONTROL_CLKMODE;
+ else
+ control &= ~CONTROL_CLKMODE;
+
+ mpfs_spi_write(spi, REG_CLK_GEN, spi->clk_gen);
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static inline void mpfs_spi_set_mode(struct mpfs_spi *spi, unsigned int mode)
+{
+ u32 mode_val;
+ u32 control = mpfs_spi_read(spi, REG_CONTROL);
+
+ switch (mode & SPI_MODE_X_MASK) {
+ case SPI_MODE_0:
+ mode_val = 0;
+ break;
+ case SPI_MODE_1:
+ mode_val = CONTROL_SPH;
+ break;
+ case SPI_MODE_2:
+ mode_val = CONTROL_SPO;
+ break;
+ case SPI_MODE_3:
+ mode_val = CONTROL_SPH | CONTROL_SPO;
+ break;
+ }
+
+ /*
+ * Disable the SPI controller. Writes to the frame protocol have
+ * no effect when the controller is enabled.
+ */
+
+ control &= ~CONTROL_ENABLE;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+
+ control &= ~(SPI_MODE_X_MASK << MODE_X_MASK_SHIFT);
+ control |= mode_val;
+
+ mpfs_spi_write(spi, REG_CONTROL, control);
+
+ control |= CONTROL_ENABLE;
+ mpfs_spi_write(spi, REG_CONTROL, control);
+}
+
+static irqreturn_t mpfs_spi_interrupt(int irq, void *dev_id)
+{
+ struct spi_controller *host = dev_id;
+ struct mpfs_spi *spi = spi_controller_get_devdata(host);
+ u32 intfield = mpfs_spi_read(spi, REG_MIS) & 0xf;
+ bool finalise = false;
+
+ /* Interrupt line may be shared and not for us at all */
+ if (intfield == 0)
+ return IRQ_NONE;
+
+ if (intfield & INT_RX_CHANNEL_OVERFLOW) {
+ mpfs_spi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW);
+ finalise = true;
+ dev_err(&host->dev,
+ "%s: RX OVERFLOW: rxlen: %d, txlen: %d\n", __func__,
+ spi->rx_len, spi->tx_len);
+ }
+
+ if (intfield & INT_TX_CHANNEL_UNDERRUN) {
+ mpfs_spi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN);
+ finalise = true;
+ dev_err(&host->dev,
+ "%s: TX UNDERFLOW: rxlen: %d, txlen: %d\n", __func__,
+ spi->rx_len, spi->tx_len);
+ }
+
+ if (finalise)
+ spi_finalize_current_transfer(host);
+
+ return IRQ_HANDLED;
+}
+
+static int mpfs_spi_calculate_clkgen(struct mpfs_spi *spi,
+ unsigned long target_hz)
+{
+ unsigned long clk_hz, spi_hz, clk_gen;
+
+ clk_hz = clk_get_rate(spi->clk);
+ if (!clk_hz)
+ return -EINVAL;
+ spi_hz = min(target_hz, clk_hz);
+
+ /*
+ * There are two possible clock modes for the controller generated
+ * clock's division ratio:
+ * CLK_MODE = 0: 1 / (2^(CLK_GEN + 1)) where CLK_GEN = 0 to 15.
+ * CLK_MODE = 1: 1 / (2 * CLK_GEN + 1) where CLK_GEN = 0 to 255.
+ * First try mode 1, fall back to 0 and if we have tried both modes and
+ * we /still/ can't get a good setting, we then throw the toys out of
+ * the pram and give up
+ * clk_gen is the register name for the clock divider on MPFS.
+ */
+ clk_gen = DIV_ROUND_UP(clk_hz, 2 * spi_hz) - 1;
+ if (clk_gen > CLK_GEN_MODE1_MAX || clk_gen <= CLK_GEN_MIN) {
+ clk_gen = DIV_ROUND_UP(clk_hz, spi_hz);
+ clk_gen = fls(clk_gen) - 1;
+
+ if (clk_gen > CLK_GEN_MODE0_MAX)
+ return -EINVAL;
+
+ spi->clk_mode = 0;
+ } else {
+ spi->clk_mode = 1;
+ }
+
+ spi->clk_gen = clk_gen;
+ return 0;
+}
+
+static int mpfs_spi_transfer_one(struct spi_controller *host,
+ struct spi_device *spi_dev,
+ struct spi_transfer *xfer)
+{
+ struct mpfs_spi *spi = spi_controller_get_devdata(host);
+ int ret;
+
+ ret = mpfs_spi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz);
+ if (ret) {
+ dev_err(&host->dev, "failed to set clk_gen for target %u Hz\n", xfer->speed_hz);
+ return ret;
+ }
+
+ mpfs_spi_set_clk_gen(spi);
+
+ spi->tx_buf = xfer->tx_buf;
+ spi->rx_buf = xfer->rx_buf;
+ spi->tx_len = xfer->len;
+ spi->rx_len = xfer->len;
+ spi->n_bytes = roundup_pow_of_two(DIV_ROUND_UP(xfer->bits_per_word, BITS_PER_BYTE));
+
+ mpfs_spi_set_framesize(spi, xfer->bits_per_word);
+
+ mpfs_spi_write(spi, REG_COMMAND, COMMAND_RXFIFORST | COMMAND_TXFIFORST);
+
+ mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select);
+
+ while (spi->tx_len) {
+ int fifo_max = DIV_ROUND_UP(min(spi->tx_len, FIFO_DEPTH), spi->n_bytes);
+
+ mpfs_spi_write_fifo(spi, fifo_max);
+ mpfs_spi_read_fifo(spi, fifo_max);
+ }
+
+ spi_finalize_current_transfer(host);
+ return 1;
+}
+
+static int mpfs_spi_prepare_message(struct spi_controller *host,
+ struct spi_message *msg)
+{
+ struct spi_device *spi_dev = msg->spi;
+ struct mpfs_spi *spi = spi_controller_get_devdata(host);
+
+ mpfs_spi_set_mode(spi, spi_dev->mode);
+
+ return 0;
+}
+
+static int mpfs_spi_probe(struct platform_device *pdev)
+{
+ struct spi_controller *host;
+ struct mpfs_spi *spi;
+ struct resource *res;
+ u32 num_cs;
+ 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 = MAX_CS;
+
+ host->num_chipselect = num_cs;
+ host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+ host->use_gpio_descriptors = true;
+ host->setup = mpfs_spi_setup;
+ host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
+ host->transfer_one = mpfs_spi_transfer_one;
+ host->prepare_message = mpfs_spi_prepare_message;
+ host->set_cs = mpfs_spi_set_cs;
+ host->dev.of_node = pdev->dev.of_node;
+
+ spi = spi_controller_get_devdata(host);
+
+ 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, mpfs_spi_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");
+
+ mpfs_spi_init(host, spi);
+
+ ret = devm_spi_register_controller(&pdev->dev, host);
+ if (ret) {
+ mpfs_spi_disable(spi);
+ return dev_err_probe(&pdev->dev, ret,
+ "unable to register host for SPI controller\n");
+ }
+
+ dev_info(&pdev->dev, "Registered SPI controller %d\n", host->bus_num);
+
+ return 0;
+}
+
+static void mpfs_spi_remove(struct platform_device *pdev)
+{
+ struct spi_controller *host = platform_get_drvdata(pdev);
+ struct mpfs_spi *spi = spi_controller_get_devdata(host);
+
+ mpfs_spi_disable_ints(spi);
+ mpfs_spi_disable(spi);
+}
+
+#define MICROCHIP_SPI_PM_OPS (NULL)
+
+/*
+ * Platform driver data structure
+ */
+
+#if defined(CONFIG_OF)
+static const struct of_device_id mpfs_spi_dt_ids[] = {
+ { .compatible = "microchip,mpfs-spi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mpfs_spi_dt_ids);
+#endif
+
+static struct platform_driver mpfs_spi_driver = {
+ .probe = mpfs_spi_probe,
+ .driver = {
+ .name = "microchip-spi",
+ .pm = MICROCHIP_SPI_PM_OPS,
+ .of_match_table = of_match_ptr(mpfs_spi_dt_ids),
+ },
+ .remove = mpfs_spi_remove,
+};
+module_platform_driver(mpfs_spi_driver);
+MODULE_DESCRIPTION("Microchip coreSPI SPI controller driver");
+MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>");
+MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
+MODULE_LICENSE("GPL");