556 lines
14 KiB
C
556 lines
14 KiB
C
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
|
|
/*
|
|
* drivers/spi/spifc.c
|
|
*
|
|
* Copyright (C) 2020 Amlogic, Inc. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#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 unfortunately 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,
|
|
};
|