spi: gxp_spi: Add GXP SPI controller driver
authorNick Hawkins <nick.hawkins@hpe.com>
Wed, 8 Jun 2022 21:21:36 +0000 (16:21 -0500)
committerTom Rini <trini@konsulko.com>
Thu, 23 Jun 2022 01:30:05 +0000 (21:30 -0400)
The GXP supports 3 separate SPI interfaces to accommodate the system
flash, core flash, and other functions. The SPI engine supports variable
clock frequency, selectable 3-byte or 4-byte addressing and a
configurable x1, x2, and x4 command/address/data modes. The memory
buffer for reading and writing ranges between 256 bytes and 8KB. This
driver supports access to the core flash.

Signed-off-by: Nick Hawkins <nick.hawkins@hpe.com>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/gxp_spi.c [new file with mode: 0644]

index a1e515c..e48d72d 100644 (file)
@@ -186,6 +186,12 @@ config FSL_QSPI_AHB_FULL_MAP
          Enable the Freescale QSPI driver to use full AHB memory map space for
          flash access.
 
+config GXP_SPI
+       bool "SPI driver for GXP"
+       imply SPI_FLASH_BAR
+       help
+         Enable support for SPI on GXP.
+
 config ICH_SPI
        bool "Intel ICH SPI driver"
        help
index 06e81b4..8755408 100644 (file)
@@ -33,6 +33,7 @@ obj-$(CONFIG_EXYNOS_SPI) += exynos_spi.o
 obj-$(CONFIG_FSL_DSPI) += fsl_dspi.o
 obj-$(CONFIG_FSL_ESPI) += fsl_espi.o
 obj-$(CONFIG_SYNQUACER_SPI) += spi-synquacer.o
+obj-$(CONFIG_GXP_SPI) += gxp_spi.o
 obj-$(CONFIG_ICH_SPI) +=  ich.o
 obj-$(CONFIG_IPROC_QSPI) += iproc_qspi.o
 obj-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o
diff --git a/drivers/spi/gxp_spi.c b/drivers/spi/gxp_spi.c
new file mode 100644 (file)
index 0000000..70d76ac
--- /dev/null
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GXP SPI driver
+ *
+ * (C) Copyright 2022 Hewlett Packard Enterprise Development LP.
+ * Author: Nick Hawkins <nick.hawkins@hpe.com>
+ * Author: Jean-Marie Verdun <verdun@hpe.com>
+ */
+
+#include <spi.h>
+#include <asm/io.h>
+#include <dm.h>
+
+#define GXP_SPI0_MAX_CHIPSELECT                2
+
+#define MANUAL_MODE    0
+#define AUTO_MODE              1
+#define OFFSET_SPIMCFG 0x00
+#define OFFSET_SPIMCTRL        0x04
+#define OFFSET_SPICMD          0x05
+#define OFFSET_SPIDCNT 0x06
+#define OFFSET_SPIADDR 0x08
+#define OFFSET_SPILDAT 0x40
+#define GXP_SPILDAT_SIZE 64
+
+#define SPIMCTRL_START 0x01
+#define SPIMCTRL_BUSY          0x02
+
+#define CMD_READ_ARRAY_FAST            0x0b
+
+struct gxp_spi_priv {
+       struct spi_slave        slave;
+       void __iomem *base;
+       unsigned int mode;
+
+};
+
+static void spi_set_mode(struct gxp_spi_priv *priv, int mode)
+{
+       unsigned char value;
+
+       value = readb(priv->base + OFFSET_SPIMCTRL);
+       if (mode == MANUAL_MODE) {
+               writeb(0x55, priv->base + OFFSET_SPICMD);
+               writeb(0xaa, priv->base + OFFSET_SPICMD);
+               /* clear bit5 and bit4, auto_start and start_mask */
+               value &= ~(0x03 << 4);
+       } else {
+               value |= (0x03 << 4);
+       }
+       writeb(value, priv->base + OFFSET_SPIMCTRL);
+}
+
+static int gxp_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din,
+                       unsigned long flags)
+{
+       struct gxp_spi_priv *priv = dev_get_priv(dev->parent);
+       struct spi_slave *slave = dev_get_parent_priv(dev);
+       struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+
+       unsigned int len = bitlen / 8;
+       unsigned int value;
+       unsigned int addr = 0;
+       unsigned char uchar_out[len];
+       unsigned char *uchar_in = (unsigned char *)din;
+       int read_len;
+       int read_ptr;
+
+       if (dout && din) {
+               /*
+                * error: gxp spi engin cannot send data to dout and read data from din at the same
+                * time
+                */
+               return -1;
+       }
+
+       memset(uchar_out, 0, sizeof(uchar_out));
+       if (dout)
+               memcpy(uchar_out, dout, len);
+
+       if (flags & SPI_XFER_BEGIN) {
+               /* the dout is cmd + addr, cmd=dout[0], add1~3=dout[1~3]. */
+               /* cmd reg */
+               writeb(uchar_out[0], priv->base + OFFSET_SPICMD);
+
+               /* config reg */
+               value = readl(priv->base + OFFSET_SPIMCFG);
+               value &= ~(1 << 24);
+               /* set chipselect */
+               value |= (slave_plat->cs << 24);
+
+               /* addr reg and addr size */
+               if (len >= 4) {
+                       addr = uchar_out[1] << 16 | uchar_out[2] << 8 | uchar_out[3];
+                       writel(addr, priv->base + OFFSET_SPIADDR);
+                       value &= ~(0x07 << 16);
+                       /* set the address size to 3 byte */
+                       value |= (3 << 16);
+               } else {
+                       writel(0, priv->base + OFFSET_SPIADDR);
+                       /* set the address size to 0 byte */
+                       value &= ~(0x07 << 16);
+               }
+
+               /* dummy */
+               /* clear dummy_cnt to */
+               value &= ~(0x1f << 19);
+               if (uchar_out[0] == CMD_READ_ARRAY_FAST) {
+                       /* fast read needs 8 dummy clocks */
+                       value |= (8 << 19);
+               }
+
+               writel(value, priv->base + OFFSET_SPIMCFG);
+
+               if (flags & SPI_XFER_END) {
+                       /* no data cmd just start it */
+                       /* set the data direction bit to 1 */
+                       value = readb(priv->base + OFFSET_SPIMCTRL);
+                       value |= (1 << 3);
+                       writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+                       /* set the data byte count */
+                       writeb(0, priv->base + OFFSET_SPIDCNT);
+
+                       /* set the start bit */
+                       value = readb(priv->base + OFFSET_SPIMCTRL);
+                       value |= SPIMCTRL_START;
+                       writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+                       /* wait busy bit is cleared */
+                       do {
+                               value = readb(priv->base + OFFSET_SPIMCTRL);
+                       } while (value & SPIMCTRL_BUSY);
+                       return 0;
+               }
+       }
+
+       if (!(flags & SPI_XFER_END) && (flags & SPI_XFER_BEGIN)) {
+               /* first of spi_xfer calls */
+               return 0;
+       }
+
+       /* if dout != null, write data to buf and start transaction */
+       if (dout) {
+               if (len > slave->max_write_size) {
+                       printf("SF: write length is too big(>%d)\n", slave->max_write_size);
+                       return -1;
+               }
+
+               /* load the data bytes */
+               memcpy((u8 *)priv->base + OFFSET_SPILDAT, dout, len);
+
+               /* write: set the data direction bit to 1 */
+               value = readb(priv->base + OFFSET_SPIMCTRL);
+               value |= (1 << 3);
+               writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+               /* set the data byte count */
+               writeb(len, priv->base + OFFSET_SPIDCNT);
+
+               /* set the start bit */
+               value = readb(priv->base + OFFSET_SPIMCTRL);
+               value |= SPIMCTRL_START;
+               writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+               /* wait busy bit is cleared */
+               do {
+                       value = readb(priv->base + OFFSET_SPIMCTRL);
+               } while (value & SPIMCTRL_BUSY);
+
+               return 0;
+       }
+
+       /* if din !=null, start and read data */
+       if (uchar_in) {
+               read_ptr = 0;
+
+               while (read_ptr < len) {
+                       read_len = len - read_ptr;
+                       if (read_len > GXP_SPILDAT_SIZE)
+                               read_len = GXP_SPILDAT_SIZE;
+
+                       /* read: set the data direction bit to 0 */
+                       value = readb(priv->base + OFFSET_SPIMCTRL);
+                       value &= ~(1 << 3);
+                       writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+                       /* set the data byte count */
+                       writeb(read_len, priv->base + OFFSET_SPIDCNT);
+
+                       /* set the start bit */
+                       value = readb(priv->base + OFFSET_SPIMCTRL);
+                       value |= SPIMCTRL_START;
+                       writeb(value, priv->base + OFFSET_SPIMCTRL);
+
+                       /* wait busy bit is cleared */
+                       do {
+                               value = readb(priv->base + OFFSET_SPIMCTRL);
+                       } while (value & SPIMCTRL_BUSY);
+
+                       /* store the data bytes */
+                       memcpy(uchar_in + read_ptr, (u8 *)priv->base + OFFSET_SPILDAT, read_len);
+                       /* update read_ptr and addr reg */
+                       read_ptr += read_len;
+
+                       addr = readl(priv->base + OFFSET_SPIADDR);
+                       addr += read_len;
+                       writel(addr, priv->base + OFFSET_SPIADDR);
+               }
+
+               return 0;
+       }
+       return -2;
+}
+
+static int gxp_spi_set_speed(struct udevice *dev, unsigned int speed)
+{
+       /* Accept any speed */
+       return 0;
+}
+
+static int gxp_spi_set_mode(struct udevice *dev, unsigned int mode)
+{
+       struct gxp_spi_priv *priv = dev_get_priv(dev->parent);
+
+       priv->mode = mode;
+
+       return 0;
+}
+
+static int gxp_spi_claim_bus(struct udevice *dev)
+{
+       struct gxp_spi_priv *priv = dev_get_priv(dev->parent);
+       unsigned char cmd;
+
+       spi_set_mode(priv, MANUAL_MODE);
+
+       /* exit 4 bytes addr mode, uboot spi_flash only supports 3 byets address mode */
+       cmd = 0xe9;
+       gxp_spi_xfer(dev, 1 * 8, &cmd, NULL, SPI_XFER_BEGIN | SPI_XFER_END);
+       return 0;
+}
+
+static int gxp_spi_release_bus(struct udevice *dev)
+{
+       struct gxp_spi_priv *priv = dev_get_priv(dev->parent);
+
+       spi_set_mode(priv, AUTO_MODE);
+
+       return 0;
+}
+
+int gxp_spi_cs_info(struct udevice *bus, unsigned int cs, struct spi_cs_info *info)
+{
+       if (cs < GXP_SPI0_MAX_CHIPSELECT)
+               return 0;
+       else
+               return -ENODEV;
+}
+
+static int gxp_spi_probe(struct udevice *bus)
+{
+       struct gxp_spi_priv *priv = dev_get_priv(bus);
+
+       priv->base = dev_read_addr_ptr(bus);
+       if (!priv->base)
+               return -ENOENT;
+
+       return 0;
+}
+
+static int gxp_spi_child_pre_probe(struct udevice *dev)
+{
+       struct spi_slave *slave = dev_get_parent_priv(dev);
+
+       slave->max_write_size = GXP_SPILDAT_SIZE;
+
+       return 0;
+}
+
+static const struct dm_spi_ops gxp_spi_ops = {
+       .claim_bus = gxp_spi_claim_bus,
+       .release_bus = gxp_spi_release_bus,
+       .xfer = gxp_spi_xfer,
+       .set_speed = gxp_spi_set_speed,
+       .set_mode = gxp_spi_set_mode,
+       .cs_info = gxp_spi_cs_info,
+};
+
+static const struct udevice_id gxp_spi_ids[] = {
+       { .compatible = "hpe,gxp-spi" },
+       { }
+};
+
+U_BOOT_DRIVER(gxp_spi) = {
+       .name   = "gxp_spi",
+       .id     = UCLASS_SPI,
+       .of_match = gxp_spi_ids,
+       .ops    = &gxp_spi_ops,
+       .priv_auto = sizeof(struct gxp_spi_priv),
+       .probe  = gxp_spi_probe,
+       .child_pre_probe = gxp_spi_child_pre_probe,
+};
+