| /* |
| * Amlogic Meson SPI flash controller(SPIFC) |
| * |
| * Copyright (C) 2017 Amlogic Corporation |
| * |
| * Licensed under the GPL-2 or later. |
| * |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <asm/errno.h> |
| #include <asm/io.h> |
| #include <asm/gpio.h> |
| #include <asm/arch/secure_apb.h> |
| #include <spi.h> |
| #include <amlogic/spifc.h> |
| #include <dm/device-internal.h> |
| #include <dm/uclass-internal.h> |
| #include <dm/root.h> |
| #include <dm/lists.h> |
| #include <dm/util.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| unsigned int spifc_flags = 0; |
| #define spifc_dbg(fmt, args...) { \ |
| if (spifc_flags & 1) \ |
| printf("%s: " fmt, __func__, ## args); \ |
| } |
| #define spifc_dbg_buff(_buf, _len, _i) { \ |
| if (spifc_flags & 2) { \ |
| for (_i=0; _i<_len; _i++) \ |
| printf("0x%x,", _buf[_i]); \ |
| printf("\n%s: total %d\n", __func__, _len); \ |
| } \ |
| } |
| |
| struct spifc_regs { |
| u32 cmd; |
| #define SPI_FLASH_READ 31 |
| #define SPI_FLASH_WREN 30 |
| #define SPI_FLASH_WRDI 29 |
| #define SPI_FLASH_RDID 28 |
| #define SPI_FLASH_RDSR 27 |
| #define SPI_FLASH_WRSR 26 |
| #define SPI_FLASH_PP 25 |
| #define SPI_FLASH_SE 24 |
| #define SPI_FLASH_BE 23 |
| #define SPI_FLASH_CE 22 |
| #define SPI_FLASH_DP 21 |
| #define SPI_FLASH_RES 20 |
| #define SPI_HPM 19 |
| #define SPI_FLASH_USR 18 |
| #define SPI_FLASH_USR_ADDR 15 |
| #define SPI_FLASH_USR_DUMMY 14 |
| #define SPI_FLASH_USR_DIN 13 |
| #define SPI_FLASH_USR_DOUT 12 |
| #define SPI_FLASH_USR_DUMMY_BLEN 10 |
| #define SPI_FLASH_USR_CMD 0 |
| u32 addr; |
| #define SPI_FLASH_BYTES_LEN 24 |
| #define SPI_FLASH_ADDR_START 0 |
| u32 ctrl; |
| #define FAST_READ_QUAD_IO 24 |
| #define FAST_READ_DUAL_IO 23 |
| #define FAST_READ_QUAD_OUT 20 |
| #define SPI_ENABLE_AHB 17 |
| #define SPI_SST_AAI 16 |
| #define SPI_RES_RID 15 |
| #define FAST_READ_DUAL_OUT 14 |
| #define SPI_READ_READ_EN 13 |
| #define SPI_CLK_DIV0 12 |
| #define SPI_CLKCNT_N 8 |
| #define SPI_CLKCNT_H 4 |
| #define SPI_CLKCNT_L 0 |
| u32 ctrl1; |
| u32 status; |
| u32 ctrl2; |
| u32 clock; |
| #define SPI_CLK_DIV0_NEW 31 |
| #define SPI_PRE_SCALE_DIV 18 |
| #define SPI_CLKCNT_N_NEW 12 |
| #define SPI_CLKCNT_H_NEW 6 |
| #define SPI_CLKCNT_L_NEW 0 |
| u32 user; |
| #define USER_CMD_INCLUDE_CMD 31 |
| #define USER_CMD_INCLUDE_ADDR 30 |
| #define USER_CMD_INCLUDE_DUMMY 29 |
| #define USER_CMD_INCLUDE_DIN 28 |
| #define USER_CMD_INCLUDE_DOUT 27 |
| #define USER_CMD_DUMMY_IDLE 26 |
| #define USER_CMD_HIGHPART_DURING_SPI_DOUT_STAGE 25 |
| #define USER_CMD_HIGHPART_DURING_SPI_DIN_STAGE 24 |
| #define USER_CMD_EXT_HOLD_IN_STA_PREP 23 |
| #define USER_CMD_EXT_HOLD_IN_STA_CMD 22 |
| #define USER_CMD_EXT_HOLD_IN_STA_ADDR 21 |
| #define USER_CMD_EXT_HOLD_IN_STA_DUMMY 20 |
| #define USER_CMD_EXT_HOLD_IN_STA_DIN 19 |
| #define USER_CMD_EXT_HOLD_IN_STA_DOUT 18 |
| #define USER_CMD_EXT_HOLD_POLARITY 17 |
| #define SINGLE_DIO_MODE 16 |
| #define FAST_WRITE_QUAD_IO 15 |
| #define FAST_WRITE_DUAL_IO 14 |
| #define FAST_WRITE_QUAD_OUT 13 |
| #define FAST_WRITE_DUAL_OUT 12 |
| #define WRITE_BYTE_ORDER 11 |
| #define READ_BYTE_ORDER 10 |
| #define AHB_ENDIAN_MODE 8 |
| #define MASTER_CLK_EDGE 7 |
| #define SLAVE_CLK_EDGE 6 |
| #define CS_VALID_IN_STA_PREP 5 |
| #define CS_VALID_IN_STA_DONE 4 |
| #define AHB_READ_APPLY_CONFIG 3 |
| #define COMPATIBLE_TO_APPOLO 2 |
| #define AHP_READ_SUPPORT_4BYTE_ADDR 1 |
| #define EN_DIN_DURING_SPI_DOUT_STAGE 0 |
| u32 user1; |
| #define USER_CMD_ADDR_BITS 26 |
| #define USER_CMD_DOUT_BITS 17 |
| #define USER_CMD_DIN_BITS 8 |
| #define USER_CMD_DUMMY_BITS 0 |
| u32 user2; |
| #define USER_CMD_CMD_BITS 28 |
| #define USER_CMD_CMD_VALUE 0 |
| u32 user3; |
| u32 user4; //pin register |
| #define CS_KEEP_ACTIVE_AFTER_TRANS 30 |
| u32 slave; |
| u32 slave1; |
| u32 slave2; |
| u32 slave3; |
| u32 cache[8]; |
| u32 buffer[8]; |
| #define SPIFC_CACHE_SIZE_IN_WORD 16 |
| #define SPIFC_CACHE_SIZE_IN_BYTE (SPIFC_CACHE_SIZE_IN_WORD<<2) |
| }; |
| |
| |
| struct spifc_priv { |
| struct spifc_regs *regs; |
| void *clk; |
| void *pinctrl; |
| unsigned int speed; |
| unsigned int mode; |
| unsigned int wordlen; |
| unsigned char cmd; |
| }; |
| |
| /* flash dual/quad read command */ |
| #define FCMD_READ 0x03 |
| #define FCMD_READ_FAST 0x0b |
| #define FCMD_READ_DUAL_OUT 0x3b |
| #define FCMD_READ_QUAD_OUT 0x6b |
| #define FCMD_READ_DUAL_IO 0xbb |
| #define FCMD_READ_QUAD_IO 0xeb |
| /* flash quad write command */ |
| #define FCMD_WRITE 0x02 |
| #define FCMD_WRITE_QUAD_OUT 0x32 |
| |
| static void spifc_set_rx_op_mode( |
| struct spifc_priv *priv, |
| unsigned int slave_mode, |
| unsigned char cmd) |
| { |
| unsigned int val; |
| |
| val = readl(&priv->regs->ctrl); |
| val &= ~((1 << FAST_READ_DUAL_OUT) | |
| (1 << FAST_READ_QUAD_OUT) | |
| (1 << FAST_READ_DUAL_IO) | |
| (1 << FAST_READ_QUAD_IO)); |
| |
| if (slave_mode & SPI_RX_DUAL) { |
| if (cmd == FCMD_READ_DUAL_OUT) |
| val |= 1<<FAST_READ_DUAL_OUT; |
| } |
| if (slave_mode & SPI_RX_QUAD) { |
| if (cmd == FCMD_READ_QUAD_OUT) |
| val |= 1<<FAST_READ_QUAD_OUT; |
| } |
| writel(val, &priv->regs->ctrl); |
| |
| } |
| |
| static void spifc_set_tx_op_mode( |
| struct spifc_priv *priv, |
| unsigned int slave_mode, |
| unsigned char cmd) |
| { |
| unsigned int val = 0; |
| |
| if (slave_mode & SPI_TX_QUAD) { |
| if (cmd == FCMD_WRITE_QUAD_OUT) |
| val |= 1 << FAST_WRITE_QUAD_OUT; |
| } |
| writel(val, &priv->regs->user); |
| } |
| |
| |
| static int spifc_user_cmd( |
| struct spifc_priv *priv, |
| u8 cmd, u8 *buf, u8 len) |
| { |
| struct spifc_regs *regs = priv->regs; |
| u16 bits = len ? ((len << 3) - 1) : 0; |
| u32 addr = 0; |
| |
| if (buf) |
| addr = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; |
| spifc_dbg("cmd=0x%x, len=%d, addr=0x%08x\n", cmd, len, addr); |
| clrbits_le32(®s->ctrl, |
| (1<<FAST_READ_DUAL_OUT) | |
| (1<<FAST_READ_QUAD_OUT) | |
| (1<<FAST_READ_DUAL_IO) | |
| (1<<FAST_READ_QUAD_IO)); |
| writel((1 << USER_CMD_INCLUDE_CMD) | |
| ((!!len) << USER_CMD_INCLUDE_ADDR), |
| ®s->user); |
| writel((7 << USER_CMD_CMD_BITS) | |
| (cmd << USER_CMD_CMD_VALUE), |
| ®s->user2); |
| writel(bits << USER_CMD_ADDR_BITS, ®s->user1); |
| writel(addr << SPI_FLASH_ADDR_START, ®s->addr); |
| writel((1 << SPI_FLASH_USR) | |
| (cmd << SPI_FLASH_USR_CMD), |
| ®s->cmd); |
| while ((readl(®s->cmd) >> SPI_FLASH_USR) & 1); |
| return 0; |
| } |
| |
| static int spifc_user_cmd_dout( |
| struct spifc_priv *priv, |
| u8 *buf, int len, unsigned long flags) |
| { |
| struct spifc_regs *regs = priv->regs; |
| volatile unsigned int *cache; |
| u32 *p; |
| int len32, i; |
| |
| p = (u32 *)buf; |
| cache = (volatile unsigned int *)®s->cache; |
| len32 = (len/4) + !!(len%4); |
| for (i=0; i<len32; i++) |
| writel(*p++, cache++); |
| |
| setbits_le32(®s->user, 1 << USER_CMD_INCLUDE_DOUT); |
| writel(0, ®s->user2); |
| writel(((len << 3) - 1) << USER_CMD_DOUT_BITS, ®s->user1); |
| writel(0, ®s->addr); |
| writel(1 << SPI_FLASH_USR, ®s->cmd); |
| while ((readl(®s->cmd) >> SPI_FLASH_USR) & 1); |
| spifc_dbg_buff(buf, len, i); |
| return 0; |
| } |
| |
| static int spifc_user_cmd_din( |
| struct spifc_priv *priv, |
| u8 *buf, int len, unsigned long flags) |
| { |
| struct spifc_regs *regs = priv->regs; |
| volatile unsigned int *cache; |
| u32 *p; |
| int len32, i; |
| u8 temp_buf[SPIFC_CACHE_SIZE_IN_BYTE]; |
| |
| writel(1 << USER_CMD_INCLUDE_DIN, ®s->user); |
| writel(0, ®s->user2); |
| writel(((len << 3) - 1) << USER_CMD_DIN_BITS, ®s->user1); |
| writel(0, ®s->addr); |
| writel(1 << SPI_FLASH_USR, ®s->cmd); |
| while ((readl(®s->cmd) >> SPI_FLASH_USR) & 1); |
| p = (u32 *)temp_buf; |
| cache = (volatile unsigned int *)®s->cache; |
| len32 = (len/4) + !!(len%4); |
| for (i=0; i<len32; i++) |
| *p++ = readl(cache++); |
| memcpy(buf, temp_buf, len); |
| spifc_dbg_buff(buf, len, i); |
| return 0; |
| } |
| |
| static int spifc_claim_bus(struct udevice *bus) |
| { |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| |
| spifc_dbg("pinctrl enable\n"); |
| if (plat->pinctrl_enable) |
| plat->pinctrl_enable(priv->pinctrl, 1); |
| return 0; |
| } |
| |
| static int spifc_release_bus(struct udevice *bus) |
| { |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| |
| spifc_dbg("pinctrl disable\n"); |
| if (plat->pinctrl_enable) |
| plat->pinctrl_enable(priv->pinctrl, 0); |
| return 0; |
| } |
| |
| static int spifc_cs_gpios_init( |
| struct spifc_priv *priv, |
| struct spifc_platdata *plat) |
| { |
| int gpio; |
| int i; |
| |
| if (!plat->cs_gpios) |
| return 0; |
| |
| for (i=0; i<plat->num_chipselect; i++) { |
| gpio = plat->cs_gpios[i]; |
| if (gpio_request(gpio, "spifc_cs")) { |
| printf("%s: requesting pin %u failed\n", __func__, gpio); |
| return -1; |
| } |
| /* It's unsuitable to set cs high here, but unfortunitely and |
| specially for NOR flash, without this setting, it will probe |
| failed because of the pullup resistance absence. */ |
| gpio_direction_output(gpio, 1); |
| } |
| return 0; |
| } |
| |
| static void spifc_chipselect(struct udevice *dev, bool select) |
| { |
| struct udevice *bus = dev->parent; |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spi_slave *slave = dev_get_parentdata(dev); |
| int cs = slave->cs; |
| bool level = slave->mode & SPI_CS_HIGH; |
| |
| if (cs >= plat->num_chipselect) { |
| printf("cs %d exceed the bus num_chipselect\n", cs); |
| return; |
| } |
| spifc_dbg("%sselect %s(cs=%d)\n", select ? "" : "de", dev->name, cs); |
| if (!select) |
| level = !level; |
| if (plat->cs_gpios) { |
| int cs_gpio = plat->cs_gpios[cs]; |
| gpio_direction_output(cs_gpio, level); |
| spifc_dbg("set gpio %d %d\n", cs_gpio, level); |
| } |
| else { |
| printf("auto chipselect TODO\n"); |
| } |
| } |
| |
| static int spifc_set_speed( |
| struct udevice *bus, |
| uint hz) |
| { |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| struct spifc_regs *regs = priv->regs; |
| u32 div, value; |
| |
| if (hz == priv->speed) |
| return 0; |
| spifc_dbg("set speed to %d\n", hz); |
| priv->speed = hz; |
| if (plat->clk_enable) |
| plat->clk_enable(priv->clk, !!hz); |
| if (!hz) |
| return 0; |
| |
| value = SPIFC_DEFAULT_CLK_RATE; |
| if (plat->clk_get_rate) |
| value = plat->clk_get_rate(priv->clk); |
| div = value / hz; |
| if (div < 2) |
| div = 2; |
| #ifdef CONFIG_SPIFC_COMPATIBLE_TO_APPOLO |
| if (div > 0x10) |
| div = 0x10; |
| value = readl(®s->ctrl); |
| value &= ~(0x1fff<<SPI_CLKCNT_L); |
| value |= ((div >> 1) - 1) << SPI_CLKCNT_H; |
| value |= (div - 1) << SPI_CLKCNT_N; |
| value |= (div - 1) << SPI_CLKCNT_L; |
| writel(value, ®s->ctrl); |
| #else |
| if (div > 0x40) |
| div = 0x40; |
| value = ((div >> 1) - 1) << SPI_CLKCNT_H_NEW; |
| value |= (div - 1) << SPI_CLKCNT_N_NEW; |
| value |= (div - 1) << SPI_CLKCNT_L_NEW; |
| writel(value, ®s->clock); |
| #endif |
| spifc_dbg("div=%d, value=0x%x\n", div, value); |
| return 0; |
| } |
| |
| static int spifc_set_mode( |
| struct udevice *bus, |
| uint mode) |
| { |
| struct spifc_priv *priv = dev_get_priv(bus); |
| |
| if (mode == priv->mode) |
| return 0; |
| spifc_dbg("fix mode 0 now, TODO 0x%x\n", mode); |
| priv->mode = mode; |
| return 0; |
| } |
| |
| static int spifc_set_wordlen( |
| struct udevice *bus, |
| unsigned int wordlen) |
| { |
| struct spifc_priv *priv = dev_get_priv(bus); |
| |
| if (wordlen == priv->wordlen) |
| return 0; |
| spifc_dbg("fix 8 now, TODO %d\n", wordlen); |
| priv->wordlen = wordlen; |
| return 0; |
| } |
| |
| static int spifc_xfer( |
| struct udevice *dev, |
| unsigned int bitlen, |
| const void *dout, |
| void *din, |
| unsigned long flags) |
| { |
| struct udevice *bus = dev->parent; |
| struct spi_slave *slave = dev_get_parentdata(dev); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| u8 *buf; |
| int len = bitlen >> 3; |
| int lening; |
| int ret = 0; |
| |
| spifc_dbg("slave %u:%u bitlen %u\n", bus->seq, |
| spi_chip_select(dev), bitlen); |
| |
| if (bitlen % 8) { |
| printf("%s: error bitlen\n", __func__); |
| return -EINVAL; |
| } |
| spifc_set_speed(bus, slave->max_hz); |
| spifc_set_mode(bus, slave->mode); |
| if (flags & SPI_XFER_BEGIN) { |
| spifc_chipselect(dev, 1); |
| buf = (u8 *)dout; |
| if (!buf || (len > 5)) { |
| printf("%s: error command\n", __func__); |
| ret = -EINVAL; |
| } |
| else { |
| spifc_user_cmd(priv, buf[0], &buf[1], len - 1); |
| /* save the command for next xfer dual/quad setting */ |
| priv->cmd = buf[0]; |
| } |
| } |
| else if (dout && priv->cmd) { |
| buf = (u8 *)dout; |
| spifc_set_tx_op_mode(priv, slave->mode, priv->cmd); |
| while (len > 0) { |
| lening = min_t(size_t, SPIFC_CACHE_SIZE_IN_BYTE, len); |
| ret = spifc_user_cmd_dout(priv, buf, lening, flags); |
| if (ret) |
| break; |
| buf += lening; |
| len -= lening; |
| } |
| } |
| else if (din && priv->cmd) { |
| buf = (u8 *)din; |
| spifc_set_rx_op_mode(priv, slave->mode, priv->cmd); |
| while (len > 0) { |
| lening = min_t(size_t, SPIFC_CACHE_SIZE_IN_BYTE, len); |
| ret = spifc_user_cmd_din(priv, buf, lening, flags); |
| if (ret) |
| break; |
| buf += lening; |
| len -= lening; |
| } |
| } |
| |
| if (ret || flags & SPI_XFER_END) { |
| spifc_chipselect(dev, 0); |
| priv->cmd = 0; |
| } |
| |
| return ret; |
| } |
| |
| static int spifc_probe(struct udevice *bus) |
| { |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| |
| priv->regs = (struct spifc_regs *)plat->reg; |
| if (plat->clk_get) |
| priv->clk = plat->clk_get(bus, "spifc_clk"); |
| if (plat->pinctrl_get) |
| priv->pinctrl = plat->pinctrl_get(bus, "spifc_pinctrl"); |
| spifc_cs_gpios_init(priv, plat); |
| priv->speed = -1; |
| priv->mode = -1; |
| printf("%s: reg=%p, mem_map=%p\n", __func__, |
| (void *)priv->regs, (void *)plat->mem_map); |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF_CONTROL |
| static int spifc_ofdata_to_platdata(struct udevice *bus) |
| { |
| spifc_dbg("step 1\n"); |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| const void *blob = gd->fdt_blob; |
| int node = bus->of_offset; |
| |
| plat->reg = fdtdec_get_addr(blob, node, "reg"); |
| plat->mem_map = fdtdec_get_addr(blob, node, "ahb"); |
| /* default 5MHz */ |
| |
| return 0; |
| } |
| |
| static const struct udevice_id spifc_ids[] = { |
| { .compatible = "amlogic, spifc" }, |
| { } |
| }; |
| #endif |
| |
| static const struct dm_spi_ops spifc_ops = { |
| .claim_bus = spifc_claim_bus, |
| .release_bus = spifc_release_bus, |
| .xfer = spifc_xfer, |
| .set_speed = spifc_set_speed, |
| .set_mode = spifc_set_mode, |
| .set_wordlen = spifc_set_wordlen, |
| }; |
| |
| U_BOOT_DRIVER(spifc) = { |
| .name = "spifc", |
| .id = UCLASS_SPI, |
| #ifdef CONFIG_OF_CONTROL |
| .of_match = spifc_ids, |
| .ofdata_to_platdata = spifc_ofdata_to_platdata, |
| .platdata_auto_alloc_size = sizeof(struct spifc_platdata), |
| #endif |
| .priv_auto_alloc_size = sizeof(struct spifc_priv), |
| .per_child_auto_alloc_size = sizeof(struct spi_slave), |
| .ops= &spifc_ops, |
| .probe = spifc_probe, |
| }; |