// SPDX-License-Identifier: GPL-2.0+ /* * Andestech ATCSPI200 SPI controller driver. * * Copyright 2017 Andes Technology, Inc. * Author: Rick Chen (rick@andestech.com) */ #include #include #include #include #include #include #include #include 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<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;icmd_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, timeout = 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; timeout = SPI_TIMEOUT; if(num_bytes == 0) num_bytes = CHUNK_SIZE; __atcspi200_spi_start(ns); while (num_blks && (timeout--)) { 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; } } if (!timeout) { debug("spi_xfer: %s() timeout\n", __func__); break; } } 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_plat *slave_plat = dev_get_parent_plat(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, .of_to_plat = atcspi200_ofdata_to_platadata, .priv_auto = sizeof(struct nds_spi_slave), .probe = atcspi200_spi_probe, };