| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Amlogic Meson SPI flash controller(SPIFC) |
| * |
| * Copyright (C) 2018 Amlogic Corporation |
| * |
| * Licensed under the GPL-2 or later. |
| * |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <errno.h> |
| #include <clk.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> |
| #include <dm/pinctrl.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| 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 __iomem *mem_map; |
| #if defined(CONFIG_CLK) && (CONFIG_CLK) |
| struct clk core; |
| #endif/* CONFIG_CLK */ |
| unsigned int wordlen; |
| unsigned char cmd; |
| struct gpio_desc cs_gpios; |
| }; |
| |
| /* 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 |
| |
| #define SPIFC_MAX_CLK_RATE 166666666 |
| #define SPIFC_DEFAULT_SPEED 40000000 |
| |
| 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]; |
| 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; |
| unsigned int *cache; |
| u32 *p; |
| int len32, i; |
| |
| p = (u32 *)buf; |
| cache = (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) |
| ; |
| 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; |
| 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 = (unsigned int *)®s->cache; |
| len32 = (len / 4) + !!(len % 4); |
| for (i = 0; i < len32; i++) |
| *p++ = readl(cache++); |
| memcpy(buf, temp_buf, len); |
| return 0; |
| } |
| |
| static int spifc_claim_bus(struct udevice *dev) |
| { |
| struct udevice *bus = dev->parent; |
| struct spifc_priv *priv = dev_get_priv(bus); |
| int ret; |
| |
| ret = pinctrl_select_state(bus, "default"); |
| if (ret) { |
| pr_err("%s %d ret %d\n", __func__, __LINE__, ret); |
| return ret; |
| } |
| |
| dm_gpio_free(bus, &priv->cs_gpios); |
| ret = gpio_request_by_name(bus, "cs-gpios", |
| 0, &priv->cs_gpios, 0); |
| if (ret) { |
| pr_err("%s %d request gpio error!\n", __func__, __LINE__); |
| return ret; |
| } |
| if (!dm_gpio_is_valid(&priv->cs_gpios)) { |
| pr_err("%s %d cs pin gpio invalid!\n", __func__, __LINE__); |
| return 1; |
| } |
| ret = dm_gpio_set_dir_flags(&priv->cs_gpios, GPIOD_IS_OUT); |
| if (ret) |
| pr_err("%s %d set dir error!\n", __func__, __LINE__); |
| |
| return ret; |
| } |
| |
| static int spifc_release_bus(struct udevice *bus) |
| { |
| 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 spifc_priv *priv = dev_get_priv(bus); |
| struct spi_slave *slave = dev_get_parent_priv(dev); |
| bool level = slave->mode & SPI_CS_HIGH; |
| int cs = spi_chip_select(dev); |
| |
| if (cs > plat->max_cs) { |
| printf("cs %d exceed the bus max_cs\n", cs); |
| return; |
| } |
| if (!select) |
| level = !level; |
| dm_gpio_set_value(&priv->cs_gpios, level); |
| } |
| |
| static int spifc_set_speed(struct udevice *bus, uint hz) |
| { |
| struct spifc_priv *priv = dev_get_priv(bus); |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| struct spifc_regs *regs = priv->regs; |
| static u32 div; |
| u32 value = 0; |
| |
| if (!hz) |
| return 0; |
| |
| value = SPIFC_MAX_CLK_RATE; |
| if (div == value / hz) |
| return 0; |
| div = value / hz; |
| if (div < 2) { |
| pr_err("%s %d can not support %d speed!\n", |
| __func__, __LINE__, hz); |
| if (!plat->speed) |
| div = 2; |
| hz = SPIFC_MAX_CLK_RATE / div; |
| } |
| #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 |
| |
| plat->speed = hz; |
| return 0; |
| } |
| |
| static int spifc_set_mode(struct udevice *bus, uint mode) |
| { |
| struct spifc_platdata *plat= dev_get_platdata(bus); |
| |
| if (mode == plat->mode) |
| return 0; |
| plat->mode = mode; |
| return 0; |
| } |
| |
| static int spifc_set_wordlen(struct udevice *bus, unsigned int wordlen) |
| { |
| if (wordlen != 8) |
| return -1; |
| 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_parent_priv(dev); |
| struct spifc_priv *priv = dev_get_priv(bus); |
| u8 *buf; |
| int len = bitlen >> 3; |
| int lening; |
| int ret = 0; |
| |
| if (bitlen % 8) { |
| printf("%s: error bitlen\n", __func__); |
| return -EINVAL; |
| } |
| spifc_claim_bus(dev); |
| 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); |
| int ret = 0; |
| |
| priv->regs = (struct spifc_regs *)plat->reg; |
| #if defined(CONFIG_CLK) && (CONFIG_CLK) |
| ret = clk_get_by_name(bus, "core", &priv->core); |
| if (ret) { |
| printf("can't get clk source!\n"); |
| return ret; |
| } |
| ret = clk_enable(&priv->core); |
| if (ret) { |
| printf("enable clk source fail\n"); |
| return ret; |
| } |
| #endif/* CONFIG_CLK */ |
| |
| ret = gpio_request_by_name(bus, "cs-gpios", |
| 0, &priv->cs_gpios, 0); |
| if (ret) { |
| pr_err("%s %d can't get cs pin!\n", __func__, __LINE__); |
| return ret; |
| } |
| if (!dm_gpio_is_valid(&priv->cs_gpios)) { |
| pr_err("%s %d cs pin gpio invalid!\n", __func__, __LINE__); |
| return 1; |
| } |
| /* gpio act like cs pin, default dir-out and pull up */ |
| ret = dm_gpio_set_dir_flags(&priv->cs_gpios, GPIOD_IS_OUT); |
| if (ret) |
| pr_err("%s %d set dir error!\n", __func__, __LINE__); |
| |
| return ret; |
| } |
| |
| static int spifc_ofdata_to_platdata(struct udevice *bus) |
| { |
| struct spifc_platdata *plat = dev_get_platdata(bus); |
| const void *blob = gd->fdt_blob; |
| int node = dev_of_offset(bus); |
| |
| plat->reg = (ulong)dev_read_addr_ptr(bus); |
| /* plat->mem_map = fdtdec_get_addr(blob, node, "ahb"); */ |
| |
| plat->speed = fdtdec_get_uint(blob, node, |
| "max-frequency", |
| 40000000); |
| plat->io_num = fdtdec_get_uint(blob, node, |
| "max-io", |
| 2);/* default 2 because some board only have 2 spifc io */ |
| plat->max_cs = fdtdec_get_uint(blob, node, |
| "max-cs", |
| 2); |
| plat->mode = 0; |
| pr_debug("spifc freq %d, max io %d, reg %p\n", |
| plat->speed, plat->io_num, (void *)plat->reg); |
| return 0; |
| } |
| |
| static const struct udevice_id spifc_ids[] = { |
| { .compatible = "amlogic,spifc" }, |
| { } |
| }; |
| |
| 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, |
| .of_match = spifc_ids, |
| .ofdata_to_platdata = spifc_ofdata_to_platdata, |
| .platdata_auto_alloc_size = sizeof(struct spifc_platdata), |
| .priv_auto_alloc_size = sizeof(struct spifc_priv), |
| .per_child_auto_alloc_size = sizeof(struct spi_slave), |
| .ops = &spifc_ops, |
| .probe = spifc_probe, |
| }; |