]> www.infradead.org Git - users/willy/linux.git/commitdiff
spi: spi-kspi2: Add KEBA SPI controller support
authorGerhard Engleder <eg@keba.com>
Mon, 2 Dec 2024 19:40:03 +0000 (20:40 +0100)
committerMark Brown <broonie@kernel.org>
Wed, 4 Dec 2024 16:25:22 +0000 (16:25 +0000)
The KEBA SPI controller is found in the system FPGA of KEBA PLC devices.
It is used to connect the SPI flash chip of the FPGA and some SPI
devices.

It is a simple SPI controller with configurable speed. The hardware
supports only single byte transfers. There are no FIFOs or interrupts.

Signed-off-by: Gerhard Engleder <eg@keba.com>
Link: https://patch.msgid.link/20241202194003.57679-1-gerhard@engleder-embedded.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-kspi2.c [new file with mode: 0644]

index f51f9466e518ae9cb86232e9c1816fb9c2a589a9..ea8a310329274bb2701e265cd152a56fb4e0f3a7 100644 (file)
@@ -542,6 +542,18 @@ config SPI_JCORE
          This enables support for the SPI master controller in the J-Core
          synthesizable, open source SoC.
 
+config SPI_KSPI2
+       tristate "Support for KEBA SPI master type 2 hardware"
+       depends on HAS_IOMEM
+       depends on KEBA_CP500 || COMPILE_TEST
+       select AUXILIARY_BUS
+       help
+         This driver supports KEBA SPI master type 2 FPGA implementation,
+         as found on CP500 devices for example.
+
+         This driver can also be built as a module. If so, the module
+         will be called spi-kspi2.
+
 config SPI_LM70_LLP
        tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)"
        depends on PARPORT
index aea5e54de195b699c3c67196959ccbdf02b69cc1..9db7554c1864bf9b37dcf59c16eb76f5af03a7e8 100644 (file)
@@ -74,6 +74,7 @@ obj-$(CONFIG_SPI_INTEL_PCI)           += spi-intel-pci.o
 obj-$(CONFIG_SPI_INTEL_PLATFORM)       += spi-intel-platform.o
 obj-$(CONFIG_SPI_LANTIQ_SSC)           += spi-lantiq-ssc.o
 obj-$(CONFIG_SPI_JCORE)                        += spi-jcore.o
+obj-$(CONFIG_SPI_KSPI2)                        += spi-kspi2.o
 obj-$(CONFIG_SPI_LJCA)                 += spi-ljca.o
 obj-$(CONFIG_SPI_LM70_LLP)             += spi-lm70llp.o
 obj-$(CONFIG_SPI_LOONGSON_CORE)                += spi-loongson-core.o
diff --git a/drivers/spi/spi-kspi2.c b/drivers/spi/spi-kspi2.c
new file mode 100644 (file)
index 0000000..ca73ec5
--- /dev/null
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) KEBA Industrial Automation Gmbh 2024
+ *
+ * Driver for KEBA SPI host controller type 2 FPGA IP core
+ */
+
+#include <linux/iopoll.h>
+#include <linux/misc/keba.h>
+#include <linux/spi/spi.h>
+
+#define KSPI2 "kspi2"
+
+#define KSPI2_CLK_FREQ_REG     0x03
+#define  KSPI2_CLK_FREQ_MASK   0x0f
+#define  KSPI2_CLK_FREQ_62_5M  0x0
+#define  KSPI2_CLK_FREQ_33_3M  0x1
+#define  KSPI2_CLK_FREQ_125M   0x2
+#define  KSPI2_CLK_FREQ_50M    0x3
+#define  KSPI2_CLK_FREQ_100M   0x4
+
+#define KSPI2_CONTROL_REG              0x04
+#define  KSPI2_CONTROL_CLK_DIV_MAX     0x0f
+#define  KSPI2_CONTROL_CLK_DIV_MASK    0x0f
+#define  KSPI2_CONTROL_CPHA            0x10
+#define  KSPI2_CONTROL_CPOL            0x20
+#define  KSPI2_CONTROL_CLK_MODE_MASK   0x30
+#define  KSPI2_CONTROL_INIT            KSPI2_CONTROL_CLK_DIV_MAX
+
+#define KSPI2_STATUS_REG       0x08
+#define  KSPI2_STATUS_IN_USE   0x01
+#define  KSPI2_STATUS_BUSY     0x02
+
+#define KSPI2_DATA_REG 0x0c
+
+#define KSPI2_CS_NR_REG                0x10
+#define  KSPI2_CS_NR_NONE      0xff
+
+#define KSPI2_MODE_BITS        (SPI_CPHA | SPI_CPOL)
+#define KSPI2_NUM_CS   255
+
+#define KSPI2_SPEED_HZ_MIN(kspi)       (kspi->base_speed_hz / 65536)
+#define KSPI2_SPEED_HZ_MAX(kspi)       (kspi->base_speed_hz / 2)
+
+/* timeout is 10 times the time to transfer one byte at slowest clock */
+#define KSPI2_XFER_TIMEOUT_US(kspi)    (USEC_PER_SEC / \
+                                        KSPI2_SPEED_HZ_MIN(kspi) * 8 * 10)
+
+#define KSPI2_INUSE_SLEEP_US   (2 * USEC_PER_MSEC)
+#define KSPI2_INUSE_TIMEOUT_US (10 * USEC_PER_SEC)
+
+struct kspi2 {
+       struct keba_spi_auxdev *auxdev;
+       void __iomem *base;
+       struct spi_controller *host;
+
+       u32 base_speed_hz; /* SPI base clock frequency in HZ */
+       u8 control_shadow;
+
+       struct spi_device **device;
+       int device_size;
+};
+
+static int kspi2_inuse_lock(struct kspi2 *kspi)
+{
+       u8 sts;
+       int ret;
+
+       /*
+        * The SPI controller has an IN_USE bit for locking access to the
+        * controller. This enables the use of the SPI controller by other none
+        * Linux processors.
+        *
+        * If the SPI controller is free, then the first read returns
+        * IN_USE == 0. After that the SPI controller is locked and further
+        * reads of IN_USE return 1.
+        *
+        * The SPI controller is unlocked by writing 1 into IN_USE.
+        *
+        * The IN_USE bit acts as a hardware semaphore for the SPI controller.
+        * Poll for semaphore, but sleep while polling to free the CPU.
+        */
+       ret = readb_poll_timeout(kspi->base + KSPI2_STATUS_REG,
+                                sts, (sts & KSPI2_STATUS_IN_USE) == 0,
+                                KSPI2_INUSE_SLEEP_US, KSPI2_INUSE_TIMEOUT_US);
+       if (ret != 0)
+               dev_warn(&kspi->auxdev->auxdev.dev, "%s err!\n", __func__);
+
+       return ret;
+}
+
+static void kspi2_inuse_unlock(struct kspi2 *kspi)
+{
+       /* unlock the controller by writing 1 into IN_USE */
+       iowrite8(KSPI2_STATUS_IN_USE, kspi->base + KSPI2_STATUS_REG);
+}
+
+static int kspi2_prepare_hardware(struct spi_controller *host)
+{
+       struct kspi2 *kspi = spi_controller_get_devdata(host);
+
+       /* lock hardware semaphore before actual use of controller */
+       return kspi2_inuse_lock(kspi);
+}
+
+static int kspi2_unprepare_hardware(struct spi_controller *host)
+{
+       struct kspi2 *kspi = spi_controller_get_devdata(host);
+
+       /* unlock hardware semaphore after actual use of controller */
+       kspi2_inuse_unlock(kspi);
+
+       return 0;
+}
+
+static u8 kspi2_calc_minimal_divider(struct kspi2 *kspi, u32 max_speed_hz)
+{
+       u8 div;
+
+       /*
+        * Divider values 2, 4, 8, 16, ..., 65536 are possible. They are coded
+        * as 0, 1, 2, 3, ..., 15 in the CONTROL_CLK_DIV bit.
+        */
+       for (div = 0; div < KSPI2_CONTROL_CLK_DIV_MAX; div++) {
+               if ((kspi->base_speed_hz >> (div + 1)) <= max_speed_hz)
+                       return div;
+       }
+
+       /* return divider for slowest clock if loop fails to find one */
+       return KSPI2_CONTROL_CLK_DIV_MAX;
+}
+
+static void kspi2_write_control_reg(struct kspi2 *kspi, u8 val, u8 mask)
+{
+       /* write control register only when necessary to improve performance */
+       if (val != (kspi->control_shadow & mask)) {
+               kspi->control_shadow = (kspi->control_shadow & ~mask) | val;
+               iowrite8(kspi->control_shadow, kspi->base + KSPI2_CONTROL_REG);
+       }
+}
+
+static int kspi2_txrx_byte(struct kspi2 *kspi, u8 tx, u8 *rx)
+{
+       u8 sts;
+       int ret;
+
+       /* start transfer by writing TX byte */
+       iowrite8(tx, kspi->base + KSPI2_DATA_REG);
+
+       /* wait till finished (BUSY == 0) */
+       ret = readb_poll_timeout(kspi->base + KSPI2_STATUS_REG,
+                                sts, (sts & KSPI2_STATUS_BUSY) == 0,
+                                0, KSPI2_XFER_TIMEOUT_US(kspi));
+       if (ret != 0)
+               return ret;
+
+       /* read RX byte */
+       if (rx)
+               *rx = ioread8(kspi->base + KSPI2_DATA_REG);
+
+       return 0;
+}
+
+static int kspi2_process_transfer(struct kspi2 *kspi, struct spi_transfer *t)
+{
+       u8 tx = 0;
+       u8 rx;
+       int i;
+       int ret;
+
+       for (i = 0; i < t->len; i++) {
+               if (t->tx_buf)
+                       tx = ((const u8 *)t->tx_buf)[i];
+
+               ret = kspi2_txrx_byte(kspi, tx, &rx);
+               if (ret)
+                       return ret;
+
+               if (t->rx_buf)
+                       ((u8 *)t->rx_buf)[i] = rx;
+       }
+
+       return 0;
+}
+
+static int kspi2_setup_transfer(struct kspi2 *kspi,
+                                struct spi_device *spi,
+                                struct spi_transfer *t)
+{
+       u32 max_speed_hz = spi->max_speed_hz;
+       u8 clk_div;
+
+       /*
+        * spi_device (spi) has default parameters. Some of these can be
+        * overwritten by parameters in spi_transfer (t).
+        */
+       if (t->bits_per_word && ((t->bits_per_word % 8) != 0)) {
+               dev_err(&spi->dev, "Word width %d not supported!\n",
+                       t->bits_per_word);
+
+               return -EINVAL;
+       }
+
+       if (t->speed_hz && (t->speed_hz < max_speed_hz))
+               max_speed_hz = t->speed_hz;
+
+       clk_div = kspi2_calc_minimal_divider(kspi, max_speed_hz);
+       kspi2_write_control_reg(kspi, clk_div, KSPI2_CONTROL_CLK_DIV_MASK);
+
+       return 0;
+}
+
+static int kspi2_transfer_one(struct spi_controller *host,
+                             struct spi_device *spi,
+                             struct spi_transfer *t)
+{
+       struct kspi2 *kspi = spi_controller_get_devdata(host);
+       int ret;
+
+       ret = kspi2_setup_transfer(kspi, spi, t);
+       if (ret != 0)
+               return ret;
+
+       if (t->len) {
+               ret = kspi2_process_transfer(kspi, t);
+               if (ret != 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void kspi2_set_cs(struct spi_device *spi, bool enable)
+{
+       struct spi_controller *host = spi->controller;
+       struct kspi2 *kspi = spi_controller_get_devdata(host);
+
+       /* controller is using active low chip select signals by design */
+       if (!enable)
+               iowrite8(spi_get_chipselect(spi, 0), kspi->base + KSPI2_CS_NR_REG);
+       else
+               iowrite8(KSPI2_CS_NR_NONE, kspi->base + KSPI2_CS_NR_REG);
+}
+
+static int kspi2_prepare_message(struct spi_controller *host,
+                                struct spi_message *msg)
+{
+       struct kspi2 *kspi = spi_controller_get_devdata(host);
+       struct spi_device *spi = msg->spi;
+       u8 mode = 0;
+
+       /* setup SPI clock phase and polarity */
+       if (spi->mode & SPI_CPHA)
+               mode |= KSPI2_CONTROL_CPHA;
+       if (spi->mode & SPI_CPOL)
+               mode |= KSPI2_CONTROL_CPOL;
+       kspi2_write_control_reg(kspi, mode, KSPI2_CONTROL_CLK_MODE_MASK);
+
+       return 0;
+}
+
+static int kspi2_setup(struct spi_device *spi)
+{
+       struct kspi2 *kspi = spi_controller_get_devdata(spi->controller);
+
+       /*
+        * Check only parameters. Actual setup is done in kspi2_prepare_message
+        * and directly before the SPI transfer starts.
+        */
+
+       if (spi->mode & ~KSPI2_MODE_BITS) {
+               dev_err(&spi->dev, "Mode %d not supported!\n", spi->mode);
+
+               return -EINVAL;
+       }
+
+       if ((spi->bits_per_word % 8) != 0) {
+               dev_err(&spi->dev, "Word width %d not supported!\n",
+                       spi->bits_per_word);
+
+               return -EINVAL;
+       }
+
+       if ((spi->max_speed_hz == 0) ||
+           (spi->max_speed_hz > KSPI2_SPEED_HZ_MAX(kspi)))
+               spi->max_speed_hz = KSPI2_SPEED_HZ_MAX(kspi);
+
+       if (spi->max_speed_hz < KSPI2_SPEED_HZ_MIN(kspi)) {
+               dev_err(&spi->dev, "Requested speed of %d Hz is too low!\n",
+                       spi->max_speed_hz);
+
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void kspi2_unregister_devices(struct kspi2 *kspi)
+{
+       int i;
+
+       for (i = 0; i < kspi->device_size; i++) {
+               struct spi_device *device = kspi->device[i];
+
+               if (device)
+                       spi_unregister_device(device);
+       }
+}
+
+static int kspi2_register_devices(struct kspi2 *kspi)
+{
+       struct spi_board_info *info = kspi->auxdev->info;
+       int i;
+
+       /* register all known SPI devices */
+       for (i = 0; i < kspi->auxdev->info_size; i++) {
+               struct spi_device *device = spi_new_device(kspi->host, &info[i]);
+
+               if (!device) {
+                       kspi2_unregister_devices(kspi);
+
+                       return -ENODEV;
+               }
+               kspi->device[i] = device;
+       }
+
+       return 0;
+}
+
+static void kspi2_init(struct kspi2 *kspi)
+{
+       iowrite8(KSPI2_CONTROL_INIT, kspi->base + KSPI2_CONTROL_REG);
+       kspi->control_shadow = KSPI2_CONTROL_INIT;
+
+       iowrite8(KSPI2_CS_NR_NONE, kspi->base + KSPI2_CS_NR_REG);
+}
+
+static int kspi2_probe(struct auxiliary_device *auxdev,
+                      const struct auxiliary_device_id *id)
+{
+       struct device *dev = &auxdev->dev;
+       struct spi_controller *host;
+       struct kspi2 *kspi;
+       u8 clk_reg;
+       int ret;
+
+       host = devm_spi_alloc_host(dev, sizeof(struct kspi2));
+       if (!host)
+               return -ENOMEM;
+       kspi = spi_controller_get_devdata(host);
+       kspi->auxdev = container_of(auxdev, struct keba_spi_auxdev, auxdev);
+       kspi->host = host;
+       kspi->device = devm_kcalloc(dev, kspi->auxdev->info_size,
+                                   sizeof(*kspi->device), GFP_KERNEL);
+       if (!kspi->device)
+               return -ENOMEM;
+       kspi->device_size = kspi->auxdev->info_size;
+       auxiliary_set_drvdata(auxdev, kspi);
+
+       kspi->base = devm_ioremap_resource(dev, &kspi->auxdev->io);
+       if (IS_ERR(kspi->base))
+               return PTR_ERR(kspi->base);
+
+       /* read the SPI base clock frequency */
+       clk_reg = ioread8(kspi->base + KSPI2_CLK_FREQ_REG);
+       switch (clk_reg & KSPI2_CLK_FREQ_MASK) {
+       case KSPI2_CLK_FREQ_62_5M:
+               kspi->base_speed_hz = 62500000; break;
+       case KSPI2_CLK_FREQ_33_3M:
+               kspi->base_speed_hz = 33333333; break;
+       case KSPI2_CLK_FREQ_125M:
+               kspi->base_speed_hz = 125000000; break;
+       case KSPI2_CLK_FREQ_50M:
+               kspi->base_speed_hz = 50000000; break;
+       case KSPI2_CLK_FREQ_100M:
+               kspi->base_speed_hz = 100000000; break;
+       default:
+               dev_err(dev, "Undefined SPI base clock frequency!\n");
+               return -ENODEV;
+       }
+
+       kspi2_init(kspi);
+
+       host->bus_num = -1;
+       host->num_chipselect = KSPI2_NUM_CS;
+       host->mode_bits = KSPI2_MODE_BITS;
+       host->setup = kspi2_setup;
+       host->prepare_transfer_hardware = kspi2_prepare_hardware;
+       host->unprepare_transfer_hardware = kspi2_unprepare_hardware;
+       host->prepare_message = kspi2_prepare_message;
+       host->set_cs = kspi2_set_cs;
+       host->transfer_one = kspi2_transfer_one;
+       ret = devm_spi_register_controller(dev, host);
+       if (ret) {
+               dev_err(dev, "Failed to register host (%d)!\n", ret);
+               return ret;
+       }
+
+       ret = kspi2_register_devices(kspi);
+       if (ret) {
+               dev_err(dev, "Failed to register devices (%d)!\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void kspi2_remove(struct auxiliary_device *auxdev)
+{
+       struct kspi2 *kspi = auxiliary_get_drvdata(auxdev);
+
+       kspi2_unregister_devices(kspi);
+}
+
+static const struct auxiliary_device_id kspi2_devtype_aux[] = {
+       { .name = "keba.spi" },
+       { },
+};
+MODULE_DEVICE_TABLE(auxiliary, kspi2_devtype_aux);
+
+static struct auxiliary_driver kspi2_driver_aux = {
+       .name = KSPI2,
+       .id_table = kspi2_devtype_aux,
+       .probe = kspi2_probe,
+       .remove = kspi2_remove,
+};
+module_auxiliary_driver(kspi2_driver_aux);
+
+MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
+MODULE_DESCRIPTION("KEBA SPI host controller driver");
+MODULE_LICENSE("GPL");