| // 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, |
| }; |
| |