Nick Hawkins | 4f689b3 | 2022-06-08 16:21:36 -0500 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * GXP SPI driver |
| 4 | * |
| 5 | * (C) Copyright 2022 Hewlett Packard Enterprise Development LP. |
| 6 | * Author: Nick Hawkins <nick.hawkins@hpe.com> |
| 7 | * Author: Jean-Marie Verdun <verdun@hpe.com> |
| 8 | */ |
| 9 | |
| 10 | #include <spi.h> |
| 11 | #include <asm/io.h> |
| 12 | #include <dm.h> |
| 13 | |
| 14 | #define GXP_SPI0_MAX_CHIPSELECT 2 |
| 15 | |
| 16 | #define MANUAL_MODE 0 |
| 17 | #define AUTO_MODE 1 |
| 18 | #define OFFSET_SPIMCFG 0x00 |
| 19 | #define OFFSET_SPIMCTRL 0x04 |
| 20 | #define OFFSET_SPICMD 0x05 |
| 21 | #define OFFSET_SPIDCNT 0x06 |
| 22 | #define OFFSET_SPIADDR 0x08 |
| 23 | #define OFFSET_SPILDAT 0x40 |
| 24 | #define GXP_SPILDAT_SIZE 64 |
| 25 | |
| 26 | #define SPIMCTRL_START 0x01 |
| 27 | #define SPIMCTRL_BUSY 0x02 |
| 28 | |
| 29 | #define CMD_READ_ARRAY_FAST 0x0b |
| 30 | |
| 31 | struct gxp_spi_priv { |
| 32 | struct spi_slave slave; |
| 33 | void __iomem *base; |
| 34 | unsigned int mode; |
| 35 | |
| 36 | }; |
| 37 | |
| 38 | static void spi_set_mode(struct gxp_spi_priv *priv, int mode) |
| 39 | { |
| 40 | unsigned char value; |
| 41 | |
| 42 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 43 | if (mode == MANUAL_MODE) { |
| 44 | writeb(0x55, priv->base + OFFSET_SPICMD); |
| 45 | writeb(0xaa, priv->base + OFFSET_SPICMD); |
| 46 | /* clear bit5 and bit4, auto_start and start_mask */ |
| 47 | value &= ~(0x03 << 4); |
| 48 | } else { |
| 49 | value |= (0x03 << 4); |
| 50 | } |
| 51 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 52 | } |
| 53 | |
| 54 | static int gxp_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, |
| 55 | unsigned long flags) |
| 56 | { |
| 57 | struct gxp_spi_priv *priv = dev_get_priv(dev->parent); |
| 58 | struct spi_slave *slave = dev_get_parent_priv(dev); |
| 59 | struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); |
| 60 | |
| 61 | unsigned int len = bitlen / 8; |
| 62 | unsigned int value; |
| 63 | unsigned int addr = 0; |
| 64 | unsigned char uchar_out[len]; |
| 65 | unsigned char *uchar_in = (unsigned char *)din; |
| 66 | int read_len; |
| 67 | int read_ptr; |
| 68 | |
| 69 | if (dout && din) { |
| 70 | /* |
| 71 | * error: gxp spi engin cannot send data to dout and read data from din at the same |
| 72 | * time |
| 73 | */ |
| 74 | return -1; |
| 75 | } |
| 76 | |
| 77 | memset(uchar_out, 0, sizeof(uchar_out)); |
| 78 | if (dout) |
| 79 | memcpy(uchar_out, dout, len); |
| 80 | |
| 81 | if (flags & SPI_XFER_BEGIN) { |
| 82 | /* the dout is cmd + addr, cmd=dout[0], add1~3=dout[1~3]. */ |
| 83 | /* cmd reg */ |
| 84 | writeb(uchar_out[0], priv->base + OFFSET_SPICMD); |
| 85 | |
| 86 | /* config reg */ |
| 87 | value = readl(priv->base + OFFSET_SPIMCFG); |
| 88 | value &= ~(1 << 24); |
| 89 | /* set chipselect */ |
| 90 | value |= (slave_plat->cs << 24); |
| 91 | |
| 92 | /* addr reg and addr size */ |
| 93 | if (len >= 4) { |
| 94 | addr = uchar_out[1] << 16 | uchar_out[2] << 8 | uchar_out[3]; |
| 95 | writel(addr, priv->base + OFFSET_SPIADDR); |
| 96 | value &= ~(0x07 << 16); |
| 97 | /* set the address size to 3 byte */ |
| 98 | value |= (3 << 16); |
| 99 | } else { |
| 100 | writel(0, priv->base + OFFSET_SPIADDR); |
| 101 | /* set the address size to 0 byte */ |
| 102 | value &= ~(0x07 << 16); |
| 103 | } |
| 104 | |
| 105 | /* dummy */ |
| 106 | /* clear dummy_cnt to */ |
| 107 | value &= ~(0x1f << 19); |
| 108 | if (uchar_out[0] == CMD_READ_ARRAY_FAST) { |
| 109 | /* fast read needs 8 dummy clocks */ |
| 110 | value |= (8 << 19); |
| 111 | } |
| 112 | |
| 113 | writel(value, priv->base + OFFSET_SPIMCFG); |
| 114 | |
| 115 | if (flags & SPI_XFER_END) { |
| 116 | /* no data cmd just start it */ |
| 117 | /* set the data direction bit to 1 */ |
| 118 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 119 | value |= (1 << 3); |
| 120 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 121 | |
| 122 | /* set the data byte count */ |
| 123 | writeb(0, priv->base + OFFSET_SPIDCNT); |
| 124 | |
| 125 | /* set the start bit */ |
| 126 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 127 | value |= SPIMCTRL_START; |
| 128 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 129 | |
| 130 | /* wait busy bit is cleared */ |
| 131 | do { |
| 132 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 133 | } while (value & SPIMCTRL_BUSY); |
| 134 | return 0; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | if (!(flags & SPI_XFER_END) && (flags & SPI_XFER_BEGIN)) { |
| 139 | /* first of spi_xfer calls */ |
| 140 | return 0; |
| 141 | } |
| 142 | |
| 143 | /* if dout != null, write data to buf and start transaction */ |
| 144 | if (dout) { |
| 145 | if (len > slave->max_write_size) { |
| 146 | printf("SF: write length is too big(>%d)\n", slave->max_write_size); |
| 147 | return -1; |
| 148 | } |
| 149 | |
| 150 | /* load the data bytes */ |
| 151 | memcpy((u8 *)priv->base + OFFSET_SPILDAT, dout, len); |
| 152 | |
| 153 | /* write: set the data direction bit to 1 */ |
| 154 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 155 | value |= (1 << 3); |
| 156 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 157 | |
| 158 | /* set the data byte count */ |
| 159 | writeb(len, priv->base + OFFSET_SPIDCNT); |
| 160 | |
| 161 | /* set the start bit */ |
| 162 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 163 | value |= SPIMCTRL_START; |
| 164 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 165 | |
| 166 | /* wait busy bit is cleared */ |
| 167 | do { |
| 168 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 169 | } while (value & SPIMCTRL_BUSY); |
| 170 | |
| 171 | return 0; |
| 172 | } |
| 173 | |
| 174 | /* if din !=null, start and read data */ |
| 175 | if (uchar_in) { |
| 176 | read_ptr = 0; |
| 177 | |
| 178 | while (read_ptr < len) { |
| 179 | read_len = len - read_ptr; |
| 180 | if (read_len > GXP_SPILDAT_SIZE) |
| 181 | read_len = GXP_SPILDAT_SIZE; |
| 182 | |
| 183 | /* read: set the data direction bit to 0 */ |
| 184 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 185 | value &= ~(1 << 3); |
| 186 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 187 | |
| 188 | /* set the data byte count */ |
| 189 | writeb(read_len, priv->base + OFFSET_SPIDCNT); |
| 190 | |
| 191 | /* set the start bit */ |
| 192 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 193 | value |= SPIMCTRL_START; |
| 194 | writeb(value, priv->base + OFFSET_SPIMCTRL); |
| 195 | |
| 196 | /* wait busy bit is cleared */ |
| 197 | do { |
| 198 | value = readb(priv->base + OFFSET_SPIMCTRL); |
| 199 | } while (value & SPIMCTRL_BUSY); |
| 200 | |
| 201 | /* store the data bytes */ |
| 202 | memcpy(uchar_in + read_ptr, (u8 *)priv->base + OFFSET_SPILDAT, read_len); |
| 203 | /* update read_ptr and addr reg */ |
| 204 | read_ptr += read_len; |
| 205 | |
| 206 | addr = readl(priv->base + OFFSET_SPIADDR); |
| 207 | addr += read_len; |
| 208 | writel(addr, priv->base + OFFSET_SPIADDR); |
| 209 | } |
| 210 | |
| 211 | return 0; |
| 212 | } |
| 213 | return -2; |
| 214 | } |
| 215 | |
| 216 | static int gxp_spi_set_speed(struct udevice *dev, unsigned int speed) |
| 217 | { |
| 218 | /* Accept any speed */ |
| 219 | return 0; |
| 220 | } |
| 221 | |
| 222 | static int gxp_spi_set_mode(struct udevice *dev, unsigned int mode) |
| 223 | { |
| 224 | struct gxp_spi_priv *priv = dev_get_priv(dev->parent); |
| 225 | |
| 226 | priv->mode = mode; |
| 227 | |
| 228 | return 0; |
| 229 | } |
| 230 | |
| 231 | static int gxp_spi_claim_bus(struct udevice *dev) |
| 232 | { |
| 233 | struct gxp_spi_priv *priv = dev_get_priv(dev->parent); |
| 234 | unsigned char cmd; |
| 235 | |
| 236 | spi_set_mode(priv, MANUAL_MODE); |
| 237 | |
| 238 | /* exit 4 bytes addr mode, uboot spi_flash only supports 3 byets address mode */ |
| 239 | cmd = 0xe9; |
| 240 | gxp_spi_xfer(dev, 1 * 8, &cmd, NULL, SPI_XFER_BEGIN | SPI_XFER_END); |
| 241 | return 0; |
| 242 | } |
| 243 | |
| 244 | static int gxp_spi_release_bus(struct udevice *dev) |
| 245 | { |
| 246 | struct gxp_spi_priv *priv = dev_get_priv(dev->parent); |
| 247 | |
| 248 | spi_set_mode(priv, AUTO_MODE); |
| 249 | |
| 250 | return 0; |
| 251 | } |
| 252 | |
| 253 | int gxp_spi_cs_info(struct udevice *bus, unsigned int cs, struct spi_cs_info *info) |
| 254 | { |
| 255 | if (cs < GXP_SPI0_MAX_CHIPSELECT) |
| 256 | return 0; |
| 257 | else |
| 258 | return -ENODEV; |
| 259 | } |
| 260 | |
| 261 | static int gxp_spi_probe(struct udevice *bus) |
| 262 | { |
| 263 | struct gxp_spi_priv *priv = dev_get_priv(bus); |
| 264 | |
| 265 | priv->base = dev_read_addr_ptr(bus); |
| 266 | if (!priv->base) |
| 267 | return -ENOENT; |
| 268 | |
| 269 | return 0; |
| 270 | } |
| 271 | |
| 272 | static int gxp_spi_child_pre_probe(struct udevice *dev) |
| 273 | { |
| 274 | struct spi_slave *slave = dev_get_parent_priv(dev); |
| 275 | |
| 276 | slave->max_write_size = GXP_SPILDAT_SIZE; |
| 277 | |
| 278 | return 0; |
| 279 | } |
| 280 | |
| 281 | static const struct dm_spi_ops gxp_spi_ops = { |
| 282 | .claim_bus = gxp_spi_claim_bus, |
| 283 | .release_bus = gxp_spi_release_bus, |
| 284 | .xfer = gxp_spi_xfer, |
| 285 | .set_speed = gxp_spi_set_speed, |
| 286 | .set_mode = gxp_spi_set_mode, |
| 287 | .cs_info = gxp_spi_cs_info, |
| 288 | }; |
| 289 | |
| 290 | static const struct udevice_id gxp_spi_ids[] = { |
| 291 | { .compatible = "hpe,gxp-spi" }, |
| 292 | { } |
| 293 | }; |
| 294 | |
| 295 | U_BOOT_DRIVER(gxp_spi) = { |
| 296 | .name = "gxp_spi", |
| 297 | .id = UCLASS_SPI, |
| 298 | .of_match = gxp_spi_ids, |
| 299 | .ops = &gxp_spi_ops, |
| 300 | .priv_auto = sizeof(struct gxp_spi_priv), |
| 301 | .probe = gxp_spi_probe, |
| 302 | .child_pre_probe = gxp_spi_child_pre_probe, |
| 303 | }; |
| 304 | |