spi: nuvoton: add NPCM PSPI controller driver
authorJim Liu <jim.t90615@gmail.com>
Tue, 31 May 2022 10:14:02 +0000 (18:14 +0800)
committerJagan Teki <jagan@amarulasolutions.com>
Thu, 27 Oct 2022 07:11:36 +0000 (12:41 +0530)
Add Nuvoton NPCM BMC Peripheral SPI controller driver.
NPCM750 include two general-purpose SPI interface.

Signed-off-by: Jim Liu <JJLIU0@nuvoton.com>
Reviewed-by: Jagan Teki <jagan@amarulasolutions.com>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/npcm_pspi.c [new file with mode: 0644]

index 2f12081..fcf67de 100644 (file)
@@ -321,6 +321,11 @@ config NPCM_FIU_SPI
          This enables support for the Flash Interface Unit SPI controller
          in master mode.
 
+config NPCM_PSPI
+       bool "PSPI driver for Nuvoton NPCM SoC"
+       help
+         PSPI driver for NPCM SoC
+
 config NXP_FSPI
        bool "NXP FlexSPI driver"
        depends on SPI_MEM
index 50ba435..bf1ffd2 100644 (file)
@@ -52,6 +52,7 @@ obj-$(CONFIG_MVEBU_A3700_SPI) += mvebu_a3700_spi.o
 obj-$(CONFIG_MXC_SPI) += mxc_spi.o
 obj-$(CONFIG_MXS_SPI) += mxs_spi.o
 obj-$(CONFIG_NPCM_FIU_SPI) += npcm_fiu_spi.o
+obj-$(CONFIG_NPCM_PSPI) += npcm_pspi.o
 obj-$(CONFIG_NXP_FSPI) += nxp_fspi.o
 obj-$(CONFIG_ATCSPI200_SPI) += atcspi200_spi.o
 obj-$(CONFIG_OCTEON_SPI) += octeon_spi.o
diff --git a/drivers/spi/npcm_pspi.c b/drivers/spi/npcm_pspi.c
new file mode 100644 (file)
index 0000000..bd9ac65
--- /dev/null
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2021 Nuvoton Technology.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <spi.h>
+#include <clk.h>
+#include <asm/gpio.h>
+#include <linux/iopoll.h>
+
+#define MAX_DIV        127
+
+/* Register offsets */
+#define PSPI_DATA              0
+#define PSPI_CTL1              2
+#define PSPI_STAT              4
+
+/* PSPI_CTL1 fields */
+#define PSPI_CTL1_SPIEN                BIT(0)
+#define PSPI_CTL1_SCM          BIT(7)
+#define PSPI_CTL1_SCIDL                BIT(8)
+#define PSPI_CTL1_SCDV_MASK    GENMASK(15, 9)
+#define PSPI_CTL1_SCDV_SHIFT   9
+
+/* PSPI_STAT fields */
+#define PSPI_STAT_BSY          BIT(0)
+#define PSPI_STAT_RBF          BIT(1)
+
+struct npcm_pspi_priv {
+       void __iomem *base;
+       struct clk clk;
+       struct gpio_desc cs_gpio;
+       u32 max_hz;
+};
+
+static inline void spi_cs_activate(struct udevice *dev)
+{
+       struct udevice *bus = dev->parent;
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+
+       dm_gpio_set_value(&priv->cs_gpio, 0);
+}
+
+static inline void spi_cs_deactivate(struct udevice *dev)
+{
+       struct udevice *bus = dev->parent;
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+
+       dm_gpio_set_value(&priv->cs_gpio, 1);
+}
+
+static inline void npcm_pspi_enable(struct npcm_pspi_priv *priv)
+{
+       u16 val;
+
+       val = readw(priv->base + PSPI_CTL1);
+       val |= PSPI_CTL1_SPIEN;
+       writew(val, priv->base + PSPI_CTL1);
+}
+
+static inline void npcm_pspi_disable(struct npcm_pspi_priv *priv)
+{
+       u16 val;
+
+       val = readw(priv->base + PSPI_CTL1);
+       val &= ~PSPI_CTL1_SPIEN;
+       writew(val, priv->base + PSPI_CTL1);
+}
+
+static int npcm_pspi_xfer(struct udevice *dev, unsigned int bitlen,
+                         const void *dout, void *din, unsigned long flags)
+{
+       struct udevice *bus = dev->parent;
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+       void __iomem *base = priv->base;
+       const u8 *tx = dout;
+       u8 *rx = din;
+       u32 bytes = bitlen / 8;
+       u8 tmp;
+       u32 val;
+       int i, ret = 0;
+
+       npcm_pspi_enable(priv);
+
+       if (flags & SPI_XFER_BEGIN)
+               spi_cs_activate(dev);
+
+       for (i = 0; i < bytes; i++) {
+               /* Making sure we can write */
+               ret = readb_poll_timeout(base + PSPI_STAT, val,
+                                        !(val & PSPI_STAT_BSY),
+                                        1000000);
+               if (ret < 0)
+                       break;
+
+               if (tx)
+                       writeb(*tx++, base + PSPI_DATA);
+               else
+                       writeb(0, base + PSPI_DATA);
+
+               /* Wait till write completed */
+               ret = readb_poll_timeout(base + PSPI_STAT, val,
+                                        !(val & PSPI_STAT_BSY),
+                                        1000000);
+               if (ret < 0)
+                       break;
+
+               /* Wait till read buffer full */
+               ret = readb_poll_timeout(base + PSPI_STAT, val,
+                                        (val & PSPI_STAT_RBF),
+                                        1000000);
+               if (ret < 0)
+                       break;
+
+               tmp = readb(base + PSPI_DATA);
+               if (rx)
+                       *rx++ = tmp;
+       }
+
+       if (flags & SPI_XFER_END)
+               spi_cs_deactivate(dev);
+
+       npcm_pspi_disable(priv);
+
+       return ret;
+}
+
+static int npcm_pspi_set_speed(struct udevice *bus, uint speed)
+{
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+       ulong apb_clock;
+       u32 divisor;
+       u16 val;
+
+       apb_clock = clk_get_rate(&priv->clk);
+       if (!apb_clock)
+               return -EINVAL;
+
+       if (speed > priv->max_hz)
+               speed = priv->max_hz;
+
+       divisor = DIV_ROUND_CLOSEST(apb_clock, (2 * speed) - 1);
+       if (divisor > MAX_DIV)
+               divisor = MAX_DIV;
+
+       val = readw(priv->base + PSPI_CTL1);
+       val &= ~PSPI_CTL1_SCDV_MASK;
+       val |= divisor << PSPI_CTL1_SCDV_SHIFT;
+       writew(val, priv->base + PSPI_CTL1);
+
+       debug("%s: apb_clock=%lu speed=%d divisor=%u\n",
+             __func__, apb_clock, speed, divisor);
+
+       return 0;
+}
+
+static int npcm_pspi_set_mode(struct udevice *bus, uint mode)
+{
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+       u16 pspi_mode, val;
+
+       switch (mode & (SPI_CPOL | SPI_CPHA)) {
+       case SPI_MODE_0:
+               pspi_mode = 0;
+               break;
+       case SPI_MODE_1:
+               pspi_mode = PSPI_CTL1_SCM;
+               break;
+       case SPI_MODE_2:
+               pspi_mode = PSPI_CTL1_SCIDL;
+               break;
+       case SPI_MODE_3:
+               pspi_mode = PSPI_CTL1_SCIDL | PSPI_CTL1_SCM;
+               break;
+       default:
+               break;
+       }
+
+       val = readw(priv->base + PSPI_CTL1);
+       val &= ~(PSPI_CTL1_SCIDL | PSPI_CTL1_SCM);
+       val |= pspi_mode;
+       writew(val, priv->base + PSPI_CTL1);
+
+       return 0;
+}
+
+static int npcm_pspi_probe(struct udevice *bus)
+{
+       struct npcm_pspi_priv *priv = dev_get_priv(bus);
+       int node = dev_of_offset(bus);
+       int ret;
+
+       ret = clk_get_by_index(bus, 0, &priv->clk);
+       if (ret < 0)
+               return ret;
+
+       priv->base = dev_read_addr_ptr(bus);
+       priv->max_hz = dev_read_u32_default(bus, "spi-max-frequency", 0);
+       gpio_request_by_name_nodev(offset_to_ofnode(node), "cs-gpios", 0,
+                                  &priv->cs_gpio, GPIOD_IS_OUT);
+
+       return 0;
+}
+
+static const struct dm_spi_ops npcm_pspi_ops = {
+       .xfer           = npcm_pspi_xfer,
+       .set_speed      = npcm_pspi_set_speed,
+       .set_mode       = npcm_pspi_set_mode,
+};
+
+static const struct udevice_id npcm_pspi_ids[] = {
+       { .compatible = "nuvoton,npcm845-pspi"},
+       { .compatible = "nuvoton,npcm750-pspi"},
+       { }
+};
+
+U_BOOT_DRIVER(npcm_pspi) = {
+       .name   = "npcm_pspi",
+       .id     = UCLASS_SPI,
+       .of_match = npcm_pspi_ids,
+       .ops    = &npcm_pspi_ops,
+       .priv_auto = sizeof(struct npcm_pspi_priv),
+       .probe  = npcm_pspi_probe,
+};