0cbee8544b
Like clk_get_by_index, there is requirement for clk_get_by_index_nodev. In this case to make common code functionalities for dev and nodev, clk_get_by_index is trying to get the index of clock by passing ofnode instead of actual dev like current gpio uclass does. In these scenarios with current order of include files the atcspi200_spi driver is unable to find CONFIG_ENV_SIZE. In file included from arch/nds32/include/asm/u-boot.h:24, from include/dm/of.h:10, from include/dm/ofnode.h:12, from include/clk.h:11, from drivers/spi/atcspi200_spi.c:9: include/environment.h:145:19: error: 'CONFIG_ENV_SIZE' undeclared here (not in a function); did you mean 'CONFIG_CMD_XIMG'? #define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE) So, fix consists of changing the order of include files in atcspi200_spi.c to include first common.h file. Reviewed-by: Bin Meng <bmeng.cn@gmail.com> Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
414 lines
9.0 KiB
C
414 lines
9.0 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Andestech ATCSPI200 SPI controller driver.
|
|
*
|
|
* Copyright 2017 Andes Technology, Inc.
|
|
* Author: Rick Chen (rick@andestech.com)
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <malloc.h>
|
|
#include <spi.h>
|
|
#include <asm/io.h>
|
|
#include <dm.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#define MAX_TRANSFER_LEN 512
|
|
#define CHUNK_SIZE 1
|
|
#define SPI_TIMEOUT 0x100000
|
|
#define SPI0_BUS 0
|
|
#define SPI1_BUS 1
|
|
#define SPI0_BASE 0xf0b00000
|
|
#define SPI1_BASE 0xf0f00000
|
|
#define NSPI_MAX_CS_NUM 1
|
|
|
|
struct atcspi200_spi_regs {
|
|
u32 rev;
|
|
u32 reserve1[3];
|
|
u32 format; /* 0x10 */
|
|
#define DATA_LENGTH(x) ((x-1)<<8)
|
|
u32 pio;
|
|
u32 reserve2[2];
|
|
u32 tctrl; /* 0x20 */
|
|
#define TRAMODE_OFFSET 24
|
|
#define TRAMODE_MASK (0x0F<<TRAMODE_OFFSET)
|
|
#define TRAMODE_WR_SYNC (0<<TRAMODE_OFFSET)
|
|
#define TRAMODE_WO (1<<TRAMODE_OFFSET)
|
|
#define TRAMODE_RO (2<<TRAMODE_OFFSET)
|
|
#define TRAMODE_WR (3<<TRAMODE_OFFSET)
|
|
#define TRAMODE_RW (4<<TRAMODE_OFFSET)
|
|
#define TRAMODE_WDR (5<<TRAMODE_OFFSET)
|
|
#define TRAMODE_RDW (6<<TRAMODE_OFFSET)
|
|
#define TRAMODE_NONE (7<<TRAMODE_OFFSET)
|
|
#define TRAMODE_DW (8<<TRAMODE_OFFSET)
|
|
#define TRAMODE_DR (9<<TRAMODE_OFFSET)
|
|
#define WCNT_OFFSET 12
|
|
#define WCNT_MASK (0x1FF<<WCNT_OFFSET)
|
|
#define RCNT_OFFSET 0
|
|
#define RCNT_MASK (0x1FF<<RCNT_OFFSET)
|
|
u32 cmd;
|
|
u32 addr;
|
|
u32 data;
|
|
u32 ctrl; /* 0x30 */
|
|
#define TXFTH_OFFSET 16
|
|
#define RXFTH_OFFSET 8
|
|
#define TXDMAEN (1<<4)
|
|
#define RXDMAEN (1<<3)
|
|
#define TXFRST (1<<2)
|
|
#define RXFRST (1<<1)
|
|
#define SPIRST (1<<0)
|
|
u32 status;
|
|
#define TXFFL (1<<23)
|
|
#define TXEPTY (1<<22)
|
|
#define TXFVE_MASK (0x1F<<16)
|
|
#define RXFEM (1<<14)
|
|
#define RXFVE_OFFSET (8)
|
|
#define RXFVE_MASK (0x1F<<RXFVE_OFFSET)
|
|
#define SPIBSY (1<<0)
|
|
u32 inten;
|
|
u32 intsta;
|
|
u32 timing; /* 0x40 */
|
|
#define SCLK_DIV_MASK 0xFF
|
|
};
|
|
|
|
struct nds_spi_slave {
|
|
volatile struct atcspi200_spi_regs *regs;
|
|
int to;
|
|
unsigned int freq;
|
|
ulong clock;
|
|
unsigned int mode;
|
|
u8 num_cs;
|
|
unsigned int mtiming;
|
|
size_t cmd_len;
|
|
u8 cmd_buf[16];
|
|
size_t data_len;
|
|
size_t tran_len;
|
|
u8 *din;
|
|
u8 *dout;
|
|
unsigned int max_transfer_length;
|
|
};
|
|
|
|
static int __atcspi200_spi_set_speed(struct nds_spi_slave *ns)
|
|
{
|
|
u32 tm;
|
|
u8 div;
|
|
tm = ns->regs->timing;
|
|
tm &= ~SCLK_DIV_MASK;
|
|
|
|
if(ns->freq >= ns->clock)
|
|
div =0xff;
|
|
else{
|
|
for (div = 0; div < 0xff; div++) {
|
|
if (ns->freq >= ns->clock / (2 * (div + 1)))
|
|
break;
|
|
}
|
|
}
|
|
|
|
tm |= div;
|
|
ns->regs->timing = tm;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int __atcspi200_spi_claim_bus(struct nds_spi_slave *ns)
|
|
{
|
|
unsigned int format=0;
|
|
ns->regs->ctrl |= (TXFRST|RXFRST|SPIRST);
|
|
while((ns->regs->ctrl &(TXFRST|RXFRST|SPIRST))&&(ns->to--))
|
|
if(!ns->to)
|
|
return -EINVAL;
|
|
|
|
ns->cmd_len = 0;
|
|
format = ns->mode|DATA_LENGTH(8);
|
|
ns->regs->format = format;
|
|
__atcspi200_spi_set_speed(ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __atcspi200_spi_release_bus(struct nds_spi_slave *ns)
|
|
{
|
|
/* do nothing */
|
|
return 0;
|
|
}
|
|
|
|
static int __atcspi200_spi_start(struct nds_spi_slave *ns)
|
|
{
|
|
int i,olen=0;
|
|
int tc = ns->regs->tctrl;
|
|
|
|
tc &= ~(WCNT_MASK|RCNT_MASK|TRAMODE_MASK);
|
|
if ((ns->din)&&(ns->cmd_len))
|
|
tc |= TRAMODE_WR;
|
|
else if (ns->din)
|
|
tc |= TRAMODE_RO;
|
|
else
|
|
tc |= TRAMODE_WO;
|
|
|
|
if(ns->dout)
|
|
olen = ns->tran_len;
|
|
tc |= (ns->cmd_len+olen-1) << WCNT_OFFSET;
|
|
|
|
if(ns->din)
|
|
tc |= (ns->tran_len-1) << RCNT_OFFSET;
|
|
|
|
ns->regs->tctrl = tc;
|
|
ns->regs->cmd = 1;
|
|
|
|
for (i=0;i<ns->cmd_len;i++)
|
|
ns->regs->data = ns->cmd_buf[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __atcspi200_spi_stop(struct nds_spi_slave *ns)
|
|
{
|
|
ns->regs->timing = ns->mtiming;
|
|
while ((ns->regs->status & SPIBSY)&&(ns->to--))
|
|
if (!ns->to)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __nspi_espi_tx(struct nds_spi_slave *ns, const void *dout)
|
|
{
|
|
ns->regs->data = *(u8 *)dout;
|
|
}
|
|
|
|
static int __nspi_espi_rx(struct nds_spi_slave *ns, void *din, unsigned int bytes)
|
|
{
|
|
*(u8 *)din = ns->regs->data;
|
|
return bytes;
|
|
}
|
|
|
|
|
|
static int __atcspi200_spi_xfer(struct nds_spi_slave *ns,
|
|
unsigned int bitlen, const void *data_out, void *data_in,
|
|
unsigned long flags)
|
|
{
|
|
unsigned int event, rx_bytes;
|
|
const void *dout = NULL;
|
|
void *din = NULL;
|
|
int num_blks, num_chunks, max_tran_len, tran_len;
|
|
int num_bytes;
|
|
u8 *cmd_buf = ns->cmd_buf;
|
|
size_t cmd_len = ns->cmd_len;
|
|
unsigned long data_len = bitlen / 8;
|
|
int rf_cnt;
|
|
int ret = 0;
|
|
|
|
max_tran_len = ns->max_transfer_length;
|
|
switch (flags) {
|
|
case SPI_XFER_BEGIN:
|
|
cmd_len = ns->cmd_len = data_len;
|
|
memcpy(cmd_buf, data_out, cmd_len);
|
|
return 0;
|
|
|
|
case 0:
|
|
case SPI_XFER_END:
|
|
if (bitlen == 0) {
|
|
return 0;
|
|
}
|
|
ns->data_len = data_len;
|
|
ns->din = (u8 *)data_in;
|
|
ns->dout = (u8 *)data_out;
|
|
break;
|
|
|
|
case SPI_XFER_BEGIN | SPI_XFER_END:
|
|
ns->data_len = 0;
|
|
ns->din = 0;
|
|
ns->dout = 0;
|
|
cmd_len = ns->cmd_len = data_len;
|
|
memcpy(cmd_buf, data_out, cmd_len);
|
|
data_out = 0;
|
|
data_len = 0;
|
|
__atcspi200_spi_start(ns);
|
|
break;
|
|
}
|
|
if (data_out)
|
|
debug("spi_xfer: data_out %08X(%p) data_in %08X(%p) data_len %lu\n",
|
|
*(uint *)data_out, data_out, *(uint *)data_in,
|
|
data_in, data_len);
|
|
num_chunks = DIV_ROUND_UP(data_len, max_tran_len);
|
|
din = data_in;
|
|
dout = data_out;
|
|
while (num_chunks--) {
|
|
tran_len = min((size_t)data_len, (size_t)max_tran_len);
|
|
ns->tran_len = tran_len;
|
|
num_blks = DIV_ROUND_UP(tran_len , CHUNK_SIZE);
|
|
num_bytes = (tran_len) % CHUNK_SIZE;
|
|
if(num_bytes == 0)
|
|
num_bytes = CHUNK_SIZE;
|
|
__atcspi200_spi_start(ns);
|
|
|
|
while (num_blks) {
|
|
event = in_le32(&ns->regs->status);
|
|
if ((event & TXEPTY) && (data_out)) {
|
|
__nspi_espi_tx(ns, dout);
|
|
num_blks -= CHUNK_SIZE;
|
|
dout += CHUNK_SIZE;
|
|
}
|
|
|
|
if ((event & RXFVE_MASK) && (data_in)) {
|
|
rf_cnt = ((event & RXFVE_MASK)>> RXFVE_OFFSET);
|
|
if (rf_cnt >= CHUNK_SIZE)
|
|
rx_bytes = CHUNK_SIZE;
|
|
else if (num_blks == 1 && rf_cnt == num_bytes)
|
|
rx_bytes = num_bytes;
|
|
else
|
|
continue;
|
|
|
|
if (__nspi_espi_rx(ns, din, rx_bytes) == rx_bytes) {
|
|
num_blks -= CHUNK_SIZE;
|
|
din = (unsigned char *)din + rx_bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
data_len -= tran_len;
|
|
if(data_len)
|
|
{
|
|
ns->cmd_buf[1] += ((tran_len>>16)&0xff);
|
|
ns->cmd_buf[2] += ((tran_len>>8)&0xff);
|
|
ns->cmd_buf[3] += ((tran_len)&0xff);
|
|
ns->data_len = data_len;
|
|
}
|
|
ret = __atcspi200_spi_stop(ns);
|
|
}
|
|
ret = __atcspi200_spi_stop(ns);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int atcspi200_spi_set_speed(struct udevice *bus, uint max_hz)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
|
|
debug("%s speed %u\n", __func__, max_hz);
|
|
|
|
ns->freq = max_hz;
|
|
__atcspi200_spi_set_speed(ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atcspi200_spi_set_mode(struct udevice *bus, uint mode)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
|
|
debug("%s mode %u\n", __func__, mode);
|
|
ns->mode = mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atcspi200_spi_claim_bus(struct udevice *dev)
|
|
{
|
|
struct dm_spi_slave_platdata *slave_plat =
|
|
dev_get_parent_platdata(dev);
|
|
struct udevice *bus = dev->parent;
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
|
|
if (slave_plat->cs >= ns->num_cs) {
|
|
printf("Invalid SPI chipselect\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return __atcspi200_spi_claim_bus(ns);
|
|
}
|
|
|
|
static int atcspi200_spi_release_bus(struct udevice *dev)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(dev->parent);
|
|
|
|
return __atcspi200_spi_release_bus(ns);
|
|
}
|
|
|
|
static int atcspi200_spi_xfer(struct udevice *dev, unsigned int bitlen,
|
|
const void *dout, void *din,
|
|
unsigned long flags)
|
|
{
|
|
struct udevice *bus = dev->parent;
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
|
|
return __atcspi200_spi_xfer(ns, bitlen, dout, din, flags);
|
|
}
|
|
|
|
static int atcspi200_spi_get_clk(struct udevice *bus)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
struct clk clk;
|
|
ulong clk_rate;
|
|
int ret;
|
|
|
|
ret = clk_get_by_index(bus, 0, &clk);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
clk_rate = clk_get_rate(&clk);
|
|
if (!clk_rate)
|
|
return -EINVAL;
|
|
|
|
ns->clock = clk_rate;
|
|
clk_free(&clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atcspi200_spi_probe(struct udevice *bus)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
|
|
ns->to = SPI_TIMEOUT;
|
|
ns->max_transfer_length = MAX_TRANSFER_LEN;
|
|
ns->mtiming = ns->regs->timing;
|
|
atcspi200_spi_get_clk(bus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atcspi200_ofdata_to_platadata(struct udevice *bus)
|
|
{
|
|
struct nds_spi_slave *ns = dev_get_priv(bus);
|
|
const void *blob = gd->fdt_blob;
|
|
int node = dev_of_offset(bus);
|
|
|
|
ns->regs = map_physmem(devfdt_get_addr(bus),
|
|
sizeof(struct atcspi200_spi_regs),
|
|
MAP_NOCACHE);
|
|
if (!ns->regs) {
|
|
printf("%s: could not map device address\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
ns->num_cs = fdtdec_get_int(blob, node, "num-cs", 4);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dm_spi_ops atcspi200_spi_ops = {
|
|
.claim_bus = atcspi200_spi_claim_bus,
|
|
.release_bus = atcspi200_spi_release_bus,
|
|
.xfer = atcspi200_spi_xfer,
|
|
.set_speed = atcspi200_spi_set_speed,
|
|
.set_mode = atcspi200_spi_set_mode,
|
|
};
|
|
|
|
static const struct udevice_id atcspi200_spi_ids[] = {
|
|
{ .compatible = "andestech,atcspi200" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(atcspi200_spi) = {
|
|
.name = "atcspi200_spi",
|
|
.id = UCLASS_SPI,
|
|
.of_match = atcspi200_spi_ids,
|
|
.ops = &atcspi200_spi_ops,
|
|
.ofdata_to_platdata = atcspi200_ofdata_to_platadata,
|
|
.priv_auto_alloc_size = sizeof(struct nds_spi_slave),
|
|
.probe = atcspi200_spi_probe,
|
|
};
|