diff options
Diffstat (limited to 'drivers/spi/sh_qspi.c')
-rw-r--r-- | drivers/spi/sh_qspi.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/drivers/spi/sh_qspi.c b/drivers/spi/sh_qspi.c new file mode 100644 index 00000000000..b7364a61929 --- /dev/null +++ b/drivers/spi/sh_qspi.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SH QSPI (Quad SPI) driver + * + * Copyright (C) 2013 Renesas Electronics Corporation + * Copyright (C) 2013 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> + */ + +#define LOG_CATEGORY UCLASS_SPI + +#include <console.h> +#include <malloc.h> +#include <spi.h> +#include <wait_bit.h> +#include <asm/arch/renesas.h> +#include <asm/io.h> +#include <linux/bitops.h> + +/* SH QSPI register bit masks <REG>_<BIT> */ +#define SPCR_MSTR 0x08 +#define SPCR_SPE 0x40 +#define SPSR_SPRFF 0x80 +#define SPSR_SPTEF 0x20 +#define SPPCR_IO3FV 0x04 +#define SPPCR_IO2FV 0x02 +#define SPPCR_IO1FV 0x01 +#define SPBDCR_RXBC0 BIT(0) +#define SPCMD_SCKDEN BIT(15) +#define SPCMD_SLNDEN BIT(14) +#define SPCMD_SPNDEN BIT(13) +#define SPCMD_SSLKP BIT(7) +#define SPCMD_BRDV0 BIT(2) +#define SPCMD_INIT1 SPCMD_SCKDEN | SPCMD_SLNDEN | \ + SPCMD_SPNDEN | SPCMD_SSLKP | \ + SPCMD_BRDV0 +#define SPCMD_INIT2 SPCMD_SPNDEN | SPCMD_SSLKP | \ + SPCMD_BRDV0 +#define SPBFCR_TXRST BIT(7) +#define SPBFCR_RXRST BIT(6) +#define SPBFCR_TXTRG 0x30 +#define SPBFCR_RXTRG 0x07 + +/* SH QSPI register set */ +struct sh_qspi_regs { + u8 spcr; + u8 sslp; + u8 sppcr; + u8 spsr; + u32 spdr; + u8 spscr; + u8 spssr; + u8 spbr; + u8 spdcr; + u8 spckd; + u8 sslnd; + u8 spnd; + u8 dummy0; + u16 spcmd0; + u16 spcmd1; + u16 spcmd2; + u16 spcmd3; + u8 spbfcr; + u8 dummy1; + u16 spbdcr; + u32 spbmul0; + u32 spbmul1; + u32 spbmul2; + u32 spbmul3; +}; + +struct sh_qspi_slave { +#if !CONFIG_IS_ENABLED(DM_SPI) + struct spi_slave slave; +#endif + struct sh_qspi_regs *regs; +}; + +static void sh_qspi_init(struct sh_qspi_slave *ss) +{ + /* QSPI initialize */ + /* Set master mode only */ + writeb(SPCR_MSTR, &ss->regs->spcr); + + /* Set SSL signal level */ + writeb(0x00, &ss->regs->sslp); + + /* Set MOSI signal value when transfer is in idle state */ + writeb(SPPCR_IO3FV|SPPCR_IO2FV, &ss->regs->sppcr); + + /* Set bit rate. See 58.3.8 Quad Serial Peripheral Interface */ + writeb(0x01, &ss->regs->spbr); + + /* Disable Dummy Data Transmission */ + writeb(0x00, &ss->regs->spdcr); + + /* Set clock delay value */ + writeb(0x00, &ss->regs->spckd); + + /* Set SSL negation delay value */ + writeb(0x00, &ss->regs->sslnd); + + /* Set next-access delay value */ + writeb(0x00, &ss->regs->spnd); + + /* Set equence command */ + writew(SPCMD_INIT2, &ss->regs->spcmd0); + + /* Reset transfer and receive Buffer */ + setbits_8(&ss->regs->spbfcr, SPBFCR_TXRST|SPBFCR_RXRST); + + /* Clear transfer and receive Buffer control bit */ + clrbits_8(&ss->regs->spbfcr, SPBFCR_TXRST|SPBFCR_RXRST); + + /* Set equence control method. Use equence0 only */ + writeb(0x00, &ss->regs->spscr); + + /* Enable SPI function */ + setbits_8(&ss->regs->spcr, SPCR_SPE); +} + +static void sh_qspi_cs_activate(struct sh_qspi_slave *ss) +{ + /* Set master mode only */ + writeb(SPCR_MSTR, &ss->regs->spcr); + + /* Set command */ + writew(SPCMD_INIT1, &ss->regs->spcmd0); + + /* Reset transfer and receive Buffer */ + setbits_8(&ss->regs->spbfcr, SPBFCR_TXRST|SPBFCR_RXRST); + + /* Clear transfer and receive Buffer control bit */ + clrbits_8(&ss->regs->spbfcr, SPBFCR_TXRST|SPBFCR_RXRST); + + /* Set equence control method. Use equence0 only */ + writeb(0x00, &ss->regs->spscr); + + /* Enable SPI function */ + setbits_8(&ss->regs->spcr, SPCR_SPE); +} + +static void sh_qspi_cs_deactivate(struct sh_qspi_slave *ss) +{ + /* Disable SPI Function */ + clrbits_8(&ss->regs->spcr, SPCR_SPE); +} + +static int sh_qspi_xfer_common(struct sh_qspi_slave *ss, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + u32 nbyte, chunk; + int i, ret = 0; + u8 dtdata = 0, drdata; + u8 *tdata = &dtdata, *rdata = &drdata; + u32 *spbmul0 = &ss->regs->spbmul0; + + if (dout == NULL && din == NULL) { + if (flags & SPI_XFER_END) + sh_qspi_cs_deactivate(ss); + return 0; + } + + if (bitlen % 8) { + log_warning("bitlen is not 8bit aligned %d", bitlen); + return 1; + } + + nbyte = bitlen / 8; + + if (flags & SPI_XFER_BEGIN) { + sh_qspi_cs_activate(ss); + + /* Set 1048576 byte */ + writel(0x100000, spbmul0); + } + + if (flags & SPI_XFER_END) + writel(nbyte, spbmul0); + + if (dout != NULL) + tdata = (u8 *)dout; + + if (din != NULL) + rdata = din; + + while (nbyte > 0) { + /* + * Check if there is 32 Byte chunk and if there is, transfer + * it in one burst, otherwise transfer on byte-by-byte basis. + */ + chunk = (nbyte >= 32) ? 32 : 1; + + clrsetbits_8(&ss->regs->spbfcr, SPBFCR_TXTRG | SPBFCR_RXTRG, + chunk == 32 ? SPBFCR_TXTRG | SPBFCR_RXTRG : 0); + + ret = wait_for_bit_8(&ss->regs->spsr, SPSR_SPTEF, + true, 1000, true); + if (ret) + return ret; + + for (i = 0; i < chunk; i++) { + writeb(*tdata, &ss->regs->spdr); + if (dout != NULL) + tdata++; + } + + ret = wait_for_bit_8(&ss->regs->spsr, SPSR_SPRFF, + true, 1000, true); + if (ret) + return ret; + + for (i = 0; i < chunk; i++) { + *rdata = readb(&ss->regs->spdr); + if (din != NULL) + rdata++; + } + + nbyte -= chunk; + } + + if (flags & SPI_XFER_END) + sh_qspi_cs_deactivate(ss); + + return ret; +} + +#if !CONFIG_IS_ENABLED(DM_SPI) +static inline struct sh_qspi_slave *to_sh_qspi(struct spi_slave *slave) +{ + return container_of(slave, struct sh_qspi_slave, slave); +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + return 1; +} + +void spi_cs_activate(struct spi_slave *slave) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + sh_qspi_cs_activate(ss); +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + sh_qspi_cs_deactivate(ss); +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct sh_qspi_slave *ss; + + if (!spi_cs_is_valid(bus, cs)) + return NULL; + + ss = spi_alloc_slave(struct sh_qspi_slave, bus, cs); + if (!ss) { + printf("SPI_error: Fail to allocate sh_qspi_slave\n"); + return NULL; + } + + ss->regs = (struct sh_qspi_regs *)SH_QSPI_BASE; + + /* Init SH QSPI */ + sh_qspi_init(ss); + + return &ss->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ + struct sh_qspi_slave *spi = to_sh_qspi(slave); + + free(spi); +} + +int spi_claim_bus(struct spi_slave *slave) +{ + return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + return sh_qspi_xfer_common(ss, bitlen, dout, din, flags); +} + +#else + +#include <dm.h> + +static int sh_qspi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct sh_qspi_slave *ss = dev_get_plat(bus); + + return sh_qspi_xfer_common(ss, bitlen, dout, din, flags); +} + +static int sh_qspi_set_speed(struct udevice *dev, uint speed) +{ + /* This is a SPI NOR controller, do nothing. */ + return 0; +} + +static int sh_qspi_set_mode(struct udevice *dev, uint mode) +{ + /* This is a SPI NOR controller, do nothing. */ + return 0; +} + +static int sh_qspi_probe(struct udevice *dev) +{ + struct sh_qspi_slave *ss = dev_get_plat(dev); + + sh_qspi_init(ss); + + return 0; +} + +static int sh_qspi_of_to_plat(struct udevice *dev) +{ + struct sh_qspi_slave *plat = dev_get_plat(dev); + + plat->regs = dev_read_addr_ptr(dev); + + return 0; +} + +static const struct dm_spi_ops sh_qspi_ops = { + .xfer = sh_qspi_xfer, + .set_speed = sh_qspi_set_speed, + .set_mode = sh_qspi_set_mode, +}; + +static const struct udevice_id sh_qspi_ids[] = { + { .compatible = "renesas,qspi" }, + { } +}; + +U_BOOT_DRIVER(sh_qspi) = { + .name = "sh_qspi", + .id = UCLASS_SPI, + .of_match = sh_qspi_ids, + .ops = &sh_qspi_ops, + .of_to_plat = sh_qspi_of_to_plat, + .plat_auto = sizeof(struct sh_qspi_slave), + .probe = sh_qspi_probe, +}; +#endif |