u-boot-brain/drivers/spi/atcspi200_spi.c
Simon Glass 41575d8e4c dm: treewide: Rename auto_alloc_size members to be shorter
This construct is quite long-winded. In earlier days it made some sense
since auto-allocation was a strange concept. But with driver model now
used pretty universally, we can shorten this to 'auto'. This reduces
verbosity and makes it easier to read.

Coincidentally it also ensures that every declaration is on one line,
thus making dtoc's job easier.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-12-13 08:00:25 -07:00

415 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 <log.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(dev_read_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 = sizeof(struct nds_spi_slave),
.probe = atcspi200_spi_probe,
};