blob: 0424444eb38ea9214e363221bb35c91116e36d0d [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#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_priv {
struct spifc_regs *regs;
void __iomem *mem_map;
#if defined(CONFIG_CLK) && (CONFIG_CLK)
struct clk spifc_source;
struct clk spifc_mux;
struct clk spifc_div;
struct clk spifc_gate;
struct clk spifc_xtal_gate;
#endif/* CONFIG_CLK */
unsigned int wordlen;
unsigned char cmd;
/* If datain or dataout is included, save the current command address */
unsigned char save_cmd;
u32 save_addr;
u32 save_addr_len;
u32 loop;
};
/* 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 FCMD_RANDOM_DATA_PROGRAM 0x84
#define FCMD_RANDOM_DATA_PROGRAM_QUAD 0x34
#define SPIFC_DEFAULT_SPEED 24000000
#define SPIFC_CACHE_SIZE_IN_WORD 128
#define SPIFC_CACHE_SIZE_IN_BYTE SPIFC_CACHE_SIZE_IN_WORD << 2
static void spifc_set_rx_op_mode(struct spifc_priv *priv,
unsigned int slave_mode, unsigned char cmd)
{
unsigned int val;
val = readl(SPIFC_USER_CTRL3);
val &= ~(3 << 27);
if (slave_mode & SPI_RX_DUAL) {
if (cmd == FCMD_READ_DUAL_OUT)
val |= 1 << 27;
}
if (slave_mode & SPI_RX_QUAD) {
if (cmd == FCMD_READ_QUAD_OUT)
val |= 1 << 28;
}
writel(val, SPIFC_USER_CTRL3);
}
static void spifc_set_tx_op_mode(struct spifc_priv *priv,
unsigned int slave_mode, unsigned char cmd)
{
unsigned int val = 0;
val = readl(SPIFC_USER_CTRL1);
val &= ~(3 << 10);
if (slave_mode & SPI_TX_QUAD) {
if (cmd == FCMD_WRITE_QUAD_OUT)
val |= 1 << 11;
}
writel(val, SPIFC_USER_CTRL1);
}
static void spifc_init(void)
{
/* disable ahb */
writel((readl(SPIFC_AHB_REQ_CTRL) & ~(1 << 31)), SPIFC_AHB_REQ_CTRL);
writel((readl(SPIFC_AHB_CTRL) & ~(1 << 31)), SPIFC_AHB_CTRL);
}
static int spifc_user_cmd(struct spifc_priv *priv,
u8 cmd, u8 *buf, u8 len)
{
u16 bits = len ? (len - 1) : 0;
u32 addr = 0;
int i = 0;
if (len)
for (i = 0; i < len; i++) {
addr = addr << 8;
addr |= buf[i];
}
writel((1 << 30) | ((!!len) << 19) | (cmd << 20) | (bits << 15), SPIFC_USER_CTRL1);
writel(0, SPIFC_USER_CTRL2);
writel(0, SPIFC_USER_CTRL3);
writel(addr, SPIFC_USER_ADDR);
writel(1 << 31, SPIFC_USER_CTRL0);
while (!(readl(SPIFC_USER_CTRL0) & 0x40000000));
return 0;
}
static int spifc_user_cmd_dout(struct spifc_priv *priv,
u8 *buf, int len, unsigned long flags)
{
u32 *p;
unsigned int val;
int len32, i;
u16 bits = priv->save_addr_len ? (priv->save_addr_len - 1) : 0;
/* The commands FCMD_WRITE and FCMD_WRITE_QUAD_OUT
* do not support continuous writes. You need to change
* the commands to FCMD_RANDOM_DATA_PROGRAM and
* FCMD_RANDOM_DATA_PROGRAM_QUAD.
*/
if (priv->loop) {
if (priv->save_cmd == FCMD_WRITE)
priv->save_cmd = FCMD_RANDOM_DATA_PROGRAM;
else if (priv->save_cmd == FCMD_WRITE_QUAD_OUT)
priv->save_cmd = FCMD_RANDOM_DATA_PROGRAM_QUAD;
}
/* set write DBUF. auto update address */
writel((0x3 << 30), SPIFC_DBUF_CTRL);
p = (u32 *)buf;
len32 = (len / 4) + !!(len % 4);
for (i = 0; i < len32; i++)
writel(*p++, SPIFC_DBUF_DATA);
val = readl(SPIFC_USER_CTRL1);
val &= ~((3 << 28) | (0xff << 20) | (1 << 19) | (3 << 17) | (3 << 15) | (1 << 13) | (1 << 12) | (0x3FF << 0));
val |= ((1 << 30) | ((priv->save_cmd << 20)) | ((!!priv->save_addr_len) << 19) | (bits << 15) | (1 << 14) | (len << 0));
writel(val, SPIFC_USER_CTRL1);
/* disable dummy */
writel(0, SPIFC_USER_CTRL2);
/* disable date in */
writel(0, SPIFC_USER_CTRL3);
/* clear buffer start address */
writel(0, SPIFC_USER_DBUF_ADDR);
writel(priv->save_addr, SPIFC_USER_ADDR);
writel(1 << 31, SPIFC_USER_CTRL0);
while (!(readl(SPIFC_USER_CTRL0) & 0x40000000));
return 0;
}
static int spifc_user_cmd_din(struct spifc_priv *priv,
u8 *buf, int len, unsigned long flags)
{
u32 *p;
int len32, i;
unsigned int val;
u8 temp_buf[SPIFC_CACHE_SIZE_IN_BYTE];
u16 bits = priv->save_addr_len ? (priv->save_addr_len - 1) : 0;
/* enable and set cmd addr */
writel((1 << 30) | ((!!priv->save_addr_len) << 19) | (priv->save_cmd << 20) | (bits << 15), SPIFC_USER_CTRL1);
writel(priv->save_addr, SPIFC_USER_ADDR);
/* disable dummy */
writel(0, SPIFC_USER_CTRL2);
val = readl(SPIFC_USER_CTRL3);
val &= ~((1 << 30) | (1 << 29) | (0x3FF << 16));
val |= ((1 << 31) | (len << 16));
writel(val, SPIFC_USER_CTRL3);
/* clear buffer start address */
writel(0, SPIFC_USER_DBUF_ADDR);
writel(1 << 31, SPIFC_USER_CTRL0);
while (!(readl(SPIFC_USER_CTRL0) & 0x40000000));
while (!(readl(SPIFC_USER_CTRL0) & 1));
writel((0x1 << 30) ,SPIFC_DBUF_CTRL);
p = (u32 *)temp_buf;
len32 = (len / 4) + !!(len % 4);
for (i = 0; i < len32; i++) {
*p++ = readl(SPIFC_DBUF_DATA);
}
memcpy(buf, temp_buf, len);
return 0;
}
static int spifc_claim_bus(struct udevice *dev)
{
return 0;
}
static int spifc_release_bus(struct udevice *dev)
{
return 0;
}
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);
int ret;
if (!hz)
return 0;
ret = clk_set_rate(&priv->spifc_div, plat->speed);
if (ret) {
pr_err("can't set clk rate 0x%x\n !",plat->speed);
return ret;
}
writel((1 << 30) | (1 << 28) | (7 << 16) | (4 << 12) | (4 << 8) | (2), SPIFC_ACTIMING0);
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;
}
#define CMD_NREAD 0x03
int spifc_xip_prepare(void)
{
uint32_t ctrl, ctrl1;
/* Temporary default x1 for pxp */
ctrl = (1 << 31) | (1 << 30) | (CMD_NREAD << 20) | (1 << 19) | (2 << 15);
ctrl1 = 0;
writel(0, SPIFC_USER_CTRL1);
writel(ctrl, SPIFC_AHB_REQ_CTRL);
writel(ctrl1, SPIFC_AHB_REQ_CTRL1);
writel(0, SPIFC_AHB_REQ_CTRL2);
writel(0, SPIFC_USER_DBUF_ADDR);
writel((1 << 31), SPIFC_AHB_CTRL);
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, ret = 0, i = 0;
if (bitlen % 8) {
pr_err("%s: error bitlen\n", __func__);
return -EINVAL;
}
/* spi core will call back, No need to call repeatedly */
//spifc_claim_bus(dev);
spifc_set_speed(bus, slave->max_hz);
spifc_set_mode(bus, slave->mode);
if (flags & SPI_XFER_BEGIN) {
if (flags & SPI_XFER_END) {
buf = (u8 *)dout;
if (!buf || len > 5) {
pr_err("%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 {
buf = (u8 *)dout;
priv->save_cmd = buf[0];
priv->save_addr_len = len - 1;
priv->save_addr = 0;
if (priv->save_addr_len) {
for (i = 0; i < priv->save_addr_len; i++) {
priv->save_addr = priv->save_addr << 8;
priv->save_addr |= buf[i + 1];
}
}
else
priv->save_addr = 0;
priv->cmd = buf[0];
}
} else if (dout && priv->cmd) {
buf = (u8 *)dout;
priv->loop = 0;
spifc_set_tx_op_mode(priv, slave->mode, priv->cmd);
while (len > 0) {
lening = min_t(size_t, 512, len);
ret = spifc_user_cmd_dout(priv, buf, lening, flags);
if (ret)
break;
buf += lening;
len -= lening;
priv->save_addr += lening;
priv->loop++;
}
} 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, 512, len);
ret = spifc_user_cmd_din(priv, buf, lening, flags);
if (ret)
break;
buf += lening;
len -= lening;
priv->save_addr = priv->save_addr >> 8;
priv->save_addr += lening;
priv->save_addr = priv->save_addr << 8;
}
}
if (ret || flags & SPI_XFER_END) {
priv->cmd = 0;
}
return ret;
}
static int spifc_probe(struct udevice *bus)
{
struct spifc_priv *priv = dev_get_priv(bus);
int ret = 0;
/* In consideration of compatibility with other storage media,
* reset pinmux to spifc here.
*/
ret = pinctrl_select_state(bus, "default");
if (ret) {
pr_err("select state %s failed\n", "default");
return ret;
}
#if defined(CONFIG_CLK) && (CONFIG_CLK)
ret = clk_get_by_name(bus, "fclk_source", &priv->spifc_source);
if (ret) {
pr_err("can't get clk fclk_source!\n");
return ret;
}
ret = clk_get_by_name(bus, "spifc_mux", &priv->spifc_mux);
if (ret) {
pr_err("can't get clk spifc_mux!\n");
return ret;
}
ret = clk_get_by_name(bus, "spifc_div", &priv->spifc_div);
if (ret) {
pr_err("can't get clk spifc_div!\n");
return ret;
}
ret = clk_get_by_name(bus, "spifc_gate", &priv->spifc_gate);
if (ret) {
pr_err("can't get clk spifc_gate!\n");
return ret;
}
ret = clk_get_by_name(bus, "spifc_xtal_gate", &priv->spifc_xtal_gate);
if (ret) {
pr_err("can't get clk spifc_xtal_gate!\n");
return ret;
}
/* Do not use xtal */
ret = clk_disable(&priv->spifc_xtal_gate);
if (ret) {
pr_err("disable xtal_gate fail\n");
return ret;
}
ret = clk_set_parent(&priv->spifc_mux, &priv->spifc_source);
if (ret) {
pr_err("can't set clk parent!\n");
return ret;
}
ret = clk_set_rate(&priv->spifc_div, SPIFC_DEFAULT_SPEED);
if (ret) {
pr_err("failed to set rate to 24M\n");
return ret;
}
ret = clk_enable(&priv->spifc_gate);
if (ret) {
pr_err("enable clk fail\n");
return ret;
}
#endif
spifc_init();
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->speed = fdtdec_get_uint(blob, node,
"max-frequency",
40000000);
plat->max_cs = fdtdec_get_uint(blob, node,
"max-cs",
2);
plat->mode = 0;
printf("spifc freq %d reg %p\n", plat->speed, (void *)plat->reg);
return 0;
}
static const struct udevice_id spifc_ids[] = {
{ .compatible = "amlogic,spifc" },
{ }
};
static 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,
};