1209 lines
28 KiB
C
1209 lines
28 KiB
C
// 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 <linux/bitfield.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch/secure_apb.h>
|
|
#include <spi.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include <dm/root.h>
|
|
#include <dm/lists.h>
|
|
#include <dm/util.h>
|
|
#ifdef CONFIG_SECURE_POWER_CONTROL
|
|
#include <asm/arch/pwr_ctrl.h>
|
|
#endif
|
|
#include <mapmem.h>
|
|
|
|
/* Register Map */
|
|
#define SPICC_REG_CFG_READY 0x00
|
|
#define SPICC_REG_CFG_SPI 0x04
|
|
#define SPICC_REG_CFG_START 0x08
|
|
#define SPICC_REG_CFG_BUS 0x0C
|
|
#define SPICC_REG_PIO_TX_DATA_L 0x10
|
|
#define SPICC_REG_PIO_TX_DATA_H 0x14
|
|
#define SPICC_REG_PIO_RX_DATA_L 0x18
|
|
#define SPICC_REG_PIO_RX_DATA_H 0x1C
|
|
#define SPICC_REG_MEM_TX_ADDR_L 0x10
|
|
#define SPICC_REG_MEM_TX_ADDR_H 0x14
|
|
#define SPICC_REG_MEM_RX_ADDR_L 0x18
|
|
#define SPICC_REG_MEM_RX_ADDR_H 0x1C
|
|
#define SPICC_REG_DESC_LIST_L 0x20
|
|
#define SPICC_REG_DESC_LIST_H 0x24
|
|
#define SPICC_DESC_PENDING BIT(31)
|
|
#define SPICC_REG_DESC_CURRENT_L 0x28
|
|
#define SPICC_REG_DESC_CURRENT_H 0x2c
|
|
#define SPICC_REG_IRQ_STS 0x30
|
|
#define SPICC_REG_IRQ_ENABLE 0x34
|
|
#define SPICC_RCH_DESC_EOC BIT(0)
|
|
#define SPICC_RCH_DESC_INVALID BIT(1)
|
|
#define SPICC_RCH_DESC_RESP BIT(2)
|
|
#define SPICC_RCH_DATA_RESP BIT(3)
|
|
#define SPICC_WCH_DESC_EOC BIT(4)
|
|
#define SPICC_WCH_DESC_INVALID BIT(5)
|
|
#define SPICC_WCH_DESC_RESP BIT(6)
|
|
#define SPICC_WCH_DATA_RESP BIT(7)
|
|
#define SPICC_DESC_ERR BIT(8)
|
|
#define SPICC_SPI_READY BIT(9)
|
|
#define SPICC_DESC_DONE BIT(10)
|
|
#define SPICC_DESC_CHAIN_DONE BIT(11)
|
|
|
|
union spicc_cfg_spi {
|
|
uint32_t d32;
|
|
struct {
|
|
uint32_t bus64_en:1;
|
|
uint32_t slave_en:1;
|
|
uint32_t ss:2;
|
|
uint32_t flash_wp_pin_en:1;
|
|
uint32_t flash_hold_pin_en:1;
|
|
uint32_t hw_pos:1; /* start on vsync rising */
|
|
uint32_t hw_neg:1; /* start on vsync falling */
|
|
uint32_t rsv:24;
|
|
} b;
|
|
};
|
|
|
|
union spicc_cfg_start {
|
|
uint32_t d32;
|
|
struct {
|
|
uint32_t block_num:20;
|
|
#define SPICC_BLOCK_MAX 0x100000
|
|
|
|
uint32_t block_size:3;
|
|
uint32_t dc_level:1;
|
|
#define SPICC_DC_DATA0_CMD1 0
|
|
#define SPICC_DC_DATA1_CMD0 1
|
|
|
|
uint32_t op_mode:2;
|
|
#define SPICC_OP_MODE_WRITE_CMD 0
|
|
#define SPICC_OP_MODE_READ_STS 1
|
|
#define SPICC_OP_MODE_WRITE 2
|
|
#define SPICC_OP_MODE_READ 3
|
|
|
|
uint32_t rx_data_mode:2;
|
|
uint32_t tx_data_mode:2;
|
|
#define SPICC_DATA_MODE_NONE 0
|
|
#define SPICC_DATA_MODE_PIO 1
|
|
#define SPICC_DATA_MODE_MEM 2
|
|
#define SPICC_DATA_MODE_SG 3
|
|
|
|
uint32_t eoc:1;
|
|
uint32_t pending:1;
|
|
} b;
|
|
};
|
|
|
|
union spicc_cfg_bus {
|
|
uint32_t d32;
|
|
struct {
|
|
uint32_t clk_div:8;
|
|
#define SPICC_CLK_DIV_MAX 255
|
|
|
|
/* signed, -8~-1 early, 1~7 later */
|
|
uint32_t rx_tuning:4;
|
|
uint32_t tx_tuning:4;
|
|
|
|
uint32_t ss_leading_gap:4;
|
|
uint32_t lane:2;
|
|
#define SPICC_SINGLE_SPI 0
|
|
#define SPICC_DUAL_SPI 1
|
|
#define SPICC_QUAD_SPI 2
|
|
|
|
uint32_t half_duplex_en:1;
|
|
uint32_t little_endian_en:1;
|
|
uint32_t dc_mode:1;
|
|
#define SPICC_DC_MODE_PIN 0
|
|
#define SPICC_DC_MODE_9BIT 1
|
|
|
|
uint32_t null_ctl:1;
|
|
uint32_t dummy_ctl:1;
|
|
uint32_t read_turn_around:2;
|
|
#define SPICC_READ_TURN_AROUND_DEFAULT 1
|
|
|
|
uint32_t keep_ss:1;
|
|
uint32_t cpha:1;
|
|
uint32_t cpol:1;
|
|
} b;
|
|
};
|
|
|
|
struct spicc_sg_link {
|
|
uint32_t valid:1;
|
|
uint32_t eoc:1;
|
|
uint32_t irq:1;
|
|
uint32_t act:3;
|
|
uint32_t ring:1;
|
|
uint32_t rsv:1;
|
|
uint32_t len:24;
|
|
#define SPICC_SG_LEN_MAX 0x1000000
|
|
#define SPICC_SG_TX_LEN_MAX SPICC_SG_LEN_MAX
|
|
#define SPICC_SG_RX_LEN_MAX SPICC_SG_LEN_MAX
|
|
|
|
#ifdef CONFIG_ARM64_SPICC
|
|
uint64_t addr;
|
|
uint32_t addr_rsv;
|
|
#else
|
|
uint32_t addr;
|
|
#endif
|
|
};
|
|
|
|
struct spicc_descriptor {
|
|
union spicc_cfg_start cfg_start;
|
|
union spicc_cfg_bus cfg_bus;
|
|
struct spicc_sg_link *tx_sg;
|
|
struct spicc_sg_link *rx_sg;
|
|
};
|
|
|
|
struct spicc_xfer {
|
|
const void *tx_buf;
|
|
void *rx_buf;
|
|
int len;
|
|
unsigned long flags;
|
|
};
|
|
|
|
struct spicc_device {
|
|
void __iomem *base;
|
|
struct clk sys_clk;
|
|
struct clk spi_clk;
|
|
uint32_t real_speed_hz;
|
|
uint32_t bytes_per_word;
|
|
uint32_t pm_id;
|
|
union spicc_cfg_spi cfg_spi;
|
|
union spicc_cfg_start cfg_start;
|
|
union spicc_cfg_bus cfg_bus;
|
|
uint8_t config_data_mode;
|
|
struct spicc_xfer xfer[8];
|
|
int xfer_num;
|
|
};
|
|
|
|
#define spicc_info(fmt, args...) \
|
|
printf("[info]%s: " fmt, __func__, ## args)
|
|
|
|
#define spicc_err(fmt, args...) \
|
|
printf("[error]%s: " fmt, __func__, ## args)
|
|
|
|
//#define SPICC_DEBUG_EN
|
|
#ifdef SPICC_DEBUG_EN
|
|
#define spicc_dbg(fmt, args...) \
|
|
printf("[debug]%s: " fmt, __func__, ## args)
|
|
#else
|
|
#define spicc_dbg(fmt, args...)
|
|
#endif
|
|
|
|
#define spicc_writel(_spicc, _val, _offset) \
|
|
writel(_val, (_spicc)->base + (_offset))
|
|
#define spicc_readl(_spicc, _offset) \
|
|
readl((_spicc)->base + (_offset))
|
|
|
|
/* test flags for spicc, must keep with xfer flags in spi.h */
|
|
#define SPI_XFER_LANE GENMASK(9, 8) /* 0-single, 1-dual, 2-quad */
|
|
#define SPI_XFER_HALF_DUPLEX_EN BIT(10) /* 0-full, 1-half */
|
|
#define SPI_XFER_DC_MODE BIT(11) /* 0-pin, 1-9bit */
|
|
#define SPI_XFER_DC_LEVEL BIT(12) /* dc bit level */
|
|
#define SPI_XFER_NULL_CTL BIT(13)
|
|
#define SPI_XFER_DUMMY_CTL BIT(14)
|
|
#define SPI_XFER_NO_TX BIT(15)
|
|
#define SPI_XFER_NO_RX BIT(16)
|
|
|
|
static inline void spicc_flush_dcache(uint64_t addr, int sz)
|
|
{
|
|
flush_dcache_range(addr, addr + sz);
|
|
}
|
|
|
|
static inline void spicc_invalidate_dcache(uint64_t addr, int sz)
|
|
{
|
|
invalidate_dcache_range(addr, addr + sz);
|
|
}
|
|
|
|
#ifdef SPICC_DEBUG_EN
|
|
static void spicc_dump_sg(struct spicc_sg_link *sg_table, bool is_tx)
|
|
{
|
|
struct spicc_sg_link *sg = sg_table;
|
|
uint32_t buf[16];
|
|
|
|
do {
|
|
printf("%s_sg@%p\n", is_tx ? "tx" : "rx", sg);
|
|
memcpy(buf, (void *)sg, sizeof(*sg));
|
|
print_buffer((ulong)sg, buf, 4, sizeof(*sg) / sizeof(uint32_t), 0);
|
|
if (sg->eoc)
|
|
break;
|
|
sg++;
|
|
} while (1);
|
|
}
|
|
|
|
static void spicc_dump(struct spicc_device *spicc, struct spicc_descriptor *desc)
|
|
{
|
|
uint32_t buf[16];
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++)
|
|
buf[i] = readl(spicc->base + (i * 4));
|
|
printf("register@%p\n", spicc->base);
|
|
print_buffer((ulong)spicc->base, buf, 4, 16, 0);
|
|
|
|
if (!desc) {
|
|
printf("no desc\n");
|
|
return;
|
|
}
|
|
do {
|
|
printf("desc@%p\n", desc);
|
|
memcpy(buf, (void *)desc, sizeof(*desc));
|
|
print_buffer((ulong)desc, buf, 4, sizeof(*desc) / sizeof(uint32_t), 0);
|
|
|
|
if (!desc->tx_sg)
|
|
printf("no tx\n");
|
|
else if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_dump_sg(desc->tx_sg, 1);
|
|
else
|
|
printf("tx_mem@%p\n", desc->tx_sg);
|
|
|
|
if (!desc->rx_sg)
|
|
printf("no rx\n");
|
|
else if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_dump_sg(desc->rx_sg, 0);
|
|
else
|
|
printf("rx_mem@%p\n", desc->rx_sg);
|
|
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
} while (1);
|
|
}
|
|
#endif
|
|
|
|
static inline int spicc_sem_down_read(struct spicc_device *spicc)
|
|
{
|
|
int ret;
|
|
|
|
ret = spicc_readl(spicc, SPICC_REG_CFG_READY);
|
|
if (ret)
|
|
spicc_writel(spicc, 0, SPICC_REG_CFG_READY);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void spicc_sem_up_write(struct spicc_device *spicc)
|
|
{
|
|
spicc_writel(spicc, 1, SPICC_REG_CFG_READY);
|
|
}
|
|
|
|
static int spicc_wait_complete(struct spicc_device *spicc, uint32_t flags, int len)
|
|
{
|
|
unsigned long long time;
|
|
uint32_t sts;
|
|
|
|
time = 8LL * 1000LL * len / spicc->real_speed_hz; /* unit ms */
|
|
time += time + 20; /* some tolerance */
|
|
time *= 1000; /* unit us */
|
|
|
|
do {
|
|
sts = spicc_readl(spicc, SPICC_REG_IRQ_STS);
|
|
if (sts & (SPICC_RCH_DESC_INVALID |
|
|
SPICC_RCH_DESC_RESP |
|
|
SPICC_RCH_DATA_RESP |
|
|
SPICC_WCH_DESC_INVALID |
|
|
SPICC_WCH_DESC_RESP |
|
|
SPICC_WCH_DATA_RESP |
|
|
SPICC_DESC_ERR)) {
|
|
spicc_err("controller error sts=0x%x\n", sts);
|
|
return -EIO;
|
|
}
|
|
|
|
if (sts & flags) {
|
|
spicc_writel(spicc, sts, SPICC_REG_IRQ_STS);
|
|
return 0;
|
|
}
|
|
udelay(1);
|
|
} while (time--);
|
|
|
|
spicc_err("timedout, sts=0x%x\n", sts);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int spicc_claim_bus(struct udevice *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_priv(dev->parent);
|
|
struct spi_slave *slave = dev_get_parent_priv(dev);
|
|
int ss = spi_chip_select(dev);
|
|
|
|
if (!slave->wordlen || slave->wordlen > 64 || slave->wordlen % 8) {
|
|
spicc_err("invalid wordlen %d\n", slave->wordlen);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spicc->bytes_per_word = slave->wordlen >> 3;
|
|
spicc->cfg_start.b.block_size = spicc->bytes_per_word & 0x7;
|
|
spicc->cfg_spi.b.ss = ss;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spicc_release_bus(struct udevice *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int spicc_set_speed(struct udevice *bus, uint speed_hz)
|
|
{
|
|
struct spicc_device *spicc = dev_get_priv(bus);
|
|
uint32_t pclk_rate;
|
|
uint32_t div;
|
|
|
|
if (!speed_hz)
|
|
return 0;
|
|
|
|
/* speed = spi_clk rate / (div + 1) */
|
|
pclk_rate = clk_get_rate(&spicc->spi_clk);
|
|
div = DIV_ROUND_UP(pclk_rate, speed_hz);
|
|
if (div)
|
|
div--;
|
|
if (div > SPICC_CLK_DIV_MAX)
|
|
div = SPICC_CLK_DIV_MAX;
|
|
|
|
spicc->cfg_bus.b.clk_div = div;
|
|
spicc->real_speed_hz = pclk_rate / (div + 1);
|
|
spicc_dbg("set speed %dHz (real %dHz)\n", speed_hz, spicc->real_speed_hz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spicc_set_mode(struct udevice *bus, uint mode)
|
|
{
|
|
struct spicc_device *spicc = dev_get_priv(bus);
|
|
union spicc_cfg_bus *cfg_bus = &spicc->cfg_bus;
|
|
|
|
cfg_bus->b.cpol = !!(mode & SPI_CPOL);
|
|
cfg_bus->b.cpha = !!(mode & SPI_CPHA);
|
|
cfg_bus->b.little_endian_en = !!(mode & SPI_LSB_FIRST);
|
|
cfg_bus->b.half_duplex_en = !!(mode & SPI_3WIRE);
|
|
|
|
if (mode & SPI_TX_QUAD && mode & SPI_RX_QUAD)
|
|
cfg_bus->b.lane = SPICC_QUAD_SPI;
|
|
else if (mode & SPI_TX_DUAL && mode & SPI_RX_DUAL)
|
|
cfg_bus->b.lane = SPICC_DUAL_SPI;
|
|
else
|
|
cfg_bus->b.lane = SPICC_SINGLE_SPI;
|
|
|
|
spicc_dbg("set mode 0x%x\n", mode);
|
|
return 0;
|
|
}
|
|
|
|
static void spicc_import_config(struct spicc_device *spicc, unsigned long flags)
|
|
{
|
|
spicc->cfg_start.b.dc_level = !!(flags & SPI_XFER_DC_LEVEL);
|
|
spicc->cfg_bus.b.dc_mode = !!(flags & SPI_XFER_DC_MODE);
|
|
spicc->cfg_bus.b.lane = FIELD_GET(SPI_XFER_LANE, flags);
|
|
spicc->cfg_bus.b.half_duplex_en = !!(flags & SPI_XFER_HALF_DUPLEX_EN);
|
|
spicc->cfg_bus.b.null_ctl = !!(flags & SPI_XFER_NULL_CTL);
|
|
spicc->cfg_bus.b.dummy_ctl = !!(flags & SPI_XFER_DUMMY_CTL);
|
|
spicc->cfg_bus.b.ss_leading_gap = 1;
|
|
spicc->cfg_bus.b.read_turn_around = 0;
|
|
}
|
|
|
|
static int spicc_xfer_pio(struct spicc_device *spicc, const void *tx_buf,
|
|
void *rx_buf, int len, bool end)
|
|
{
|
|
const uint8_t *t8 = (const uint8_t *)tx_buf;
|
|
uint8_t *r8 = (uint8_t *)rx_buf;
|
|
const uint16_t *t16 = (const uint16_t *)tx_buf;
|
|
uint16_t *r16 = (uint16_t *)rx_buf;
|
|
const uint32_t *t32 = (const uint32_t *)tx_buf;
|
|
uint32_t *r32 = (uint32_t *)rx_buf;
|
|
int ret;
|
|
|
|
spicc->cfg_start.b.block_num = 1;
|
|
spicc->cfg_start.b.pending = 1;
|
|
|
|
spicc_writel(spicc, 0, SPICC_REG_DESC_LIST_L);
|
|
spicc_writel(spicc, 0, SPICC_REG_DESC_LIST_H);
|
|
len /= spicc->bytes_per_word;
|
|
while (len) {
|
|
len--;
|
|
spicc->cfg_bus.b.keep_ss = (end && !len) ? 0 : 1;
|
|
|
|
if (tx_buf) {
|
|
if (spicc->bytes_per_word == 1) {
|
|
spicc_writel(spicc, *t8++, SPICC_REG_PIO_TX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 2) {
|
|
spicc_writel(spicc, *t16++, SPICC_REG_PIO_TX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 4) {
|
|
spicc_writel(spicc, *t32++, SPICC_REG_PIO_TX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 8) {
|
|
spicc_writel(spicc, *t32++, SPICC_REG_PIO_TX_DATA_L);
|
|
spicc_writel(spicc, *t32++, SPICC_REG_PIO_TX_DATA_H);
|
|
}
|
|
}
|
|
|
|
spicc_writel(spicc, spicc->cfg_spi.d32, SPICC_REG_CFG_SPI);
|
|
spicc_writel(spicc, spicc->cfg_bus.d32, SPICC_REG_CFG_BUS);
|
|
spicc_writel(spicc, spicc->cfg_start.d32, SPICC_REG_CFG_START);
|
|
ret = spicc_wait_complete(spicc, SPICC_DESC_DONE, 8);
|
|
if (ret)
|
|
break;
|
|
|
|
if (rx_buf) {
|
|
if (spicc->bytes_per_word == 1) {
|
|
*r8++ = spicc_readl(spicc, SPICC_REG_PIO_RX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 2) {
|
|
*r16++ = spicc_readl(spicc, SPICC_REG_PIO_RX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 4) {
|
|
*r32++ = spicc_readl(spicc, SPICC_REG_PIO_RX_DATA_L);
|
|
} else if (spicc->bytes_per_word == 8) {
|
|
*r32++ = spicc_readl(spicc, SPICC_REG_PIO_RX_DATA_L);
|
|
*r32++ = spicc_readl(spicc, SPICC_REG_PIO_RX_DATA_H);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef SPICC_DEBUG_EN
|
|
spicc_dump(spicc, 0);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct spicc_sg_link *spicc_map_buf(uint64_t *addr_table, int *len_table, int nents)
|
|
{
|
|
struct spicc_sg_link *sg_table, *sg;
|
|
int i;
|
|
|
|
sg_table = kcalloc(nents, sizeof(*sg), GFP_KERNEL);
|
|
if (!sg_table)
|
|
return NULL;
|
|
|
|
for (i = 0; i < nents; i++) {
|
|
sg = &sg_table[i];
|
|
sg->valid = 1;
|
|
/* EOC specially for the last sg */
|
|
sg->eoc = (i == nents - 1) ? 1 : 0;
|
|
sg->ring = 0;
|
|
sg->len = len_table[i];
|
|
#ifdef CONFIG_ARM64_SPICC
|
|
sg->addr = addr_table[i];
|
|
#else
|
|
sg->addr = (uint32_t)addr_table[i];
|
|
#endif
|
|
}
|
|
|
|
return sg_table;
|
|
}
|
|
|
|
static int spicc_get_sg_num(struct spicc_sg_link *sg_table)
|
|
{
|
|
struct spicc_sg_link *sg = sg_table;
|
|
int sg_num = 0;
|
|
|
|
if (sg) {
|
|
while (1) {
|
|
sg_num++;
|
|
if (sg->eoc)
|
|
break;
|
|
sg++;
|
|
}
|
|
}
|
|
|
|
return sg_num;
|
|
}
|
|
|
|
static int spicc_get_desc_num(struct spicc_descriptor *desc_table)
|
|
{
|
|
struct spicc_descriptor *desc = desc_table;
|
|
int desc_num = 0;
|
|
|
|
if (desc) {
|
|
while (1) {
|
|
desc_num++;
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
}
|
|
}
|
|
|
|
return desc_num;
|
|
}
|
|
|
|
static int spicc_get_desc_blocks(struct spicc_descriptor *desc_table)
|
|
{
|
|
struct spicc_descriptor *desc = desc_table;
|
|
int blocks = 0;
|
|
|
|
if (desc) {
|
|
while (1) {
|
|
blocks += desc->cfg_start.b.block_num;
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
}
|
|
}
|
|
|
|
return blocks;
|
|
}
|
|
|
|
static void spicc_sg_flush_dache(struct spicc_sg_link *sg_table)
|
|
{
|
|
struct spicc_sg_link *sg;
|
|
|
|
if (!sg_table)
|
|
return;
|
|
|
|
spicc_flush_dcache((uint64_t)sg_table,
|
|
sizeof(*sg) * spicc_get_sg_num(sg_table));
|
|
|
|
sg = sg_table;
|
|
while (1) {
|
|
if (sg->addr && sg->len)
|
|
spicc_flush_dcache(sg->addr, sg->len);
|
|
if (sg->eoc)
|
|
break;
|
|
sg++;
|
|
}
|
|
}
|
|
|
|
static void spicc_sg_invalidate_dcache(struct spicc_sg_link *sg_table)
|
|
{
|
|
struct spicc_sg_link *sg;
|
|
|
|
if (!sg_table)
|
|
return;
|
|
|
|
sg = sg_table;
|
|
while (1) {
|
|
if (sg->addr && sg->len)
|
|
spicc_invalidate_dcache(sg->addr, sg->len);
|
|
if (sg->eoc)
|
|
break;
|
|
sg++;
|
|
}
|
|
spicc_invalidate_dcache((uint64_t)sg_table,
|
|
sizeof(*sg) * spicc_get_sg_num(sg_table));
|
|
}
|
|
|
|
static void spicc_desc_flush_dcache(struct spicc_descriptor *desc_table)
|
|
{
|
|
struct spicc_descriptor *desc;
|
|
int len;
|
|
|
|
if (!desc_table)
|
|
return;
|
|
|
|
spicc_flush_dcache((uint64_t)desc_table,
|
|
sizeof(*desc) * spicc_get_desc_num(desc_table));
|
|
|
|
desc = desc_table;
|
|
while (1) {
|
|
len = (desc->cfg_start.b.block_size | 0x8) & 0x7;
|
|
len *= desc->cfg_start.b.block_num;
|
|
if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_sg_flush_dache(desc->tx_sg);
|
|
else if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_MEM)
|
|
spicc_flush_dcache((uint64_t)desc->tx_sg, len);
|
|
if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_sg_flush_dache(desc->rx_sg);
|
|
else if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_MEM)
|
|
spicc_flush_dcache((uint64_t)desc->rx_sg, len);
|
|
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
}
|
|
}
|
|
|
|
static void spicc_desc_invalidate_dcache(struct spicc_descriptor *desc_table)
|
|
{
|
|
struct spicc_descriptor *desc;
|
|
int len;
|
|
|
|
if (!desc_table)
|
|
return;
|
|
|
|
desc = desc_table;
|
|
while (1) {
|
|
len = (desc->cfg_start.b.block_size | 0x8) & 0x7;
|
|
len *= desc->cfg_start.b.block_num;
|
|
if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_sg_invalidate_dcache(desc->tx_sg);
|
|
else if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_MEM)
|
|
spicc_invalidate_dcache((uint64_t)desc->tx_sg, len);
|
|
if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_SG)
|
|
spicc_sg_invalidate_dcache(desc->rx_sg);
|
|
else if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_MEM)
|
|
spicc_invalidate_dcache((uint64_t)desc->rx_sg, len);
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
}
|
|
spicc_invalidate_dcache((uint64_t)desc_table,
|
|
sizeof(*desc) * spicc_get_desc_num(desc_table));
|
|
}
|
|
|
|
static int spicc_config_desc_one_transfer(struct spicc_device *spicc,
|
|
struct spicc_descriptor *desc,
|
|
struct spicc_xfer *xfer)
|
|
{
|
|
uint64_t tx_addr = (uint64_t)xfer->tx_buf;
|
|
uint64_t rx_addr = (uint64_t)xfer->rx_buf;
|
|
int xfer_blocks = xfer->len / spicc->bytes_per_word;
|
|
int blocks, len, desc_used_num = 0;
|
|
bool first_desc = 1;
|
|
|
|
spicc_import_config(spicc, xfer->flags);
|
|
while (xfer_blocks) {
|
|
blocks = min_t(int, xfer_blocks, SPICC_BLOCK_MAX);
|
|
len = blocks * spicc->bytes_per_word;
|
|
desc->cfg_start.d32 = spicc->cfg_start.d32;
|
|
desc->cfg_bus.d32 = spicc->cfg_bus.d32;
|
|
desc->cfg_start.b.block_size = spicc->bytes_per_word & 0x7;
|
|
desc->cfg_start.b.block_num = blocks;
|
|
|
|
if (tx_addr && !(xfer->flags & SPI_XFER_NO_TX)) {
|
|
desc->cfg_start.b.op_mode = SPICC_OP_MODE_WRITE;
|
|
desc->cfg_start.b.tx_data_mode = spicc->config_data_mode;
|
|
if (spicc->config_data_mode == SPICC_DATA_MODE_SG)
|
|
desc->tx_sg = spicc_map_buf(&tx_addr, &len, 1);
|
|
else if (first_desc)
|
|
desc->tx_sg = (struct spicc_sg_link *)tx_addr;
|
|
else
|
|
desc->tx_sg = 0;
|
|
tx_addr += len;
|
|
}
|
|
|
|
if (rx_addr && !(xfer->flags & SPI_XFER_NO_RX)) {
|
|
desc->cfg_start.b.op_mode = SPICC_OP_MODE_READ;
|
|
desc->cfg_start.b.rx_data_mode = spicc->config_data_mode;
|
|
desc->cfg_bus.b.read_turn_around = SPICC_READ_TURN_AROUND_DEFAULT;
|
|
if (spicc->config_data_mode == SPICC_DATA_MODE_SG)
|
|
desc->rx_sg = spicc_map_buf(&rx_addr, &len, 1);
|
|
else if (first_desc)
|
|
desc->rx_sg = (struct spicc_sg_link *)rx_addr;
|
|
else
|
|
desc->rx_sg = 0;
|
|
rx_addr += len;
|
|
}
|
|
|
|
desc++;
|
|
desc_used_num++;
|
|
xfer_blocks -= blocks;
|
|
first_desc = 0;
|
|
}
|
|
|
|
if (desc_used_num && (xfer->flags & SPI_XFER_END)) {
|
|
desc--;
|
|
desc->cfg_start.b.eoc = 1;
|
|
desc->cfg_bus.b.keep_ss = 0;
|
|
}
|
|
|
|
return desc_used_num;
|
|
}
|
|
|
|
static struct spicc_descriptor *spicc_create_desc_table
|
|
(struct spicc_device *spicc,
|
|
struct spicc_xfer *xfer,
|
|
int xfer_num)
|
|
{
|
|
struct spicc_descriptor *desc, *desc_bk;
|
|
int blocks, desc_num = 0;
|
|
int i;
|
|
|
|
/*calculate the desc num for all xfer */
|
|
for (i = 0; i < xfer_num; i++) {
|
|
blocks = xfer[i].len / spicc->bytes_per_word;
|
|
desc_num += DIV_ROUND_UP(blocks, SPICC_BLOCK_MAX);
|
|
}
|
|
if (!desc_num)
|
|
return NULL;
|
|
|
|
desc = kcalloc(desc_num, sizeof(*desc), GFP_KERNEL);
|
|
if (!desc)
|
|
return NULL;
|
|
desc_bk = desc;
|
|
|
|
/* default */
|
|
spicc->cfg_start.b.tx_data_mode = SPICC_DATA_MODE_NONE;
|
|
spicc->cfg_start.b.rx_data_mode = SPICC_DATA_MODE_NONE;
|
|
spicc->cfg_start.b.pending = 1;
|
|
spicc->cfg_start.b.eoc = 0;
|
|
spicc->cfg_bus.b.keep_ss = 1;
|
|
|
|
while (xfer_num--) {
|
|
desc_num = spicc_config_desc_one_transfer(spicc, desc, xfer);
|
|
desc += desc_num;
|
|
xfer++;
|
|
}
|
|
|
|
return desc_bk;
|
|
}
|
|
|
|
static void spicc_destroy_desc_table(struct spicc_descriptor *desc_table)
|
|
{
|
|
struct spicc_descriptor *desc;
|
|
|
|
if (!desc_table)
|
|
return;
|
|
|
|
desc = desc_table;
|
|
while (1) {
|
|
if (desc->tx_sg && desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_SG)
|
|
free(desc->tx_sg);
|
|
if (desc->rx_sg && desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_SG)
|
|
free(desc->rx_sg);
|
|
|
|
if (desc->cfg_start.b.eoc)
|
|
break;
|
|
desc++;
|
|
}
|
|
|
|
kfree(desc_table);
|
|
}
|
|
|
|
static int spicc_xfer_desc(struct spicc_device *spicc,
|
|
struct spicc_descriptor *desc_table)
|
|
{
|
|
int ret;
|
|
|
|
spicc_desc_flush_dcache(desc_table);
|
|
spicc_writel(spicc, spicc->cfg_spi.d32, SPICC_REG_CFG_SPI);
|
|
spicc_writel(spicc, 0, SPICC_REG_CFG_BUS);
|
|
spicc_writel(spicc, 0, SPICC_REG_CFG_START);
|
|
spicc_writel(spicc, (uint64_t)desc_table & 0xffffffff,
|
|
SPICC_REG_DESC_LIST_L);
|
|
spicc_writel(spicc, ((uint64_t)desc_table >> 32) | SPICC_DESC_PENDING,
|
|
SPICC_REG_DESC_LIST_H);
|
|
ret = spicc_wait_complete(spicc, SPICC_DESC_CHAIN_DONE,
|
|
spicc_get_desc_blocks(desc_table) * spicc->bytes_per_word);
|
|
#ifdef SPICC_DEBUG_EN
|
|
spicc_dump(spicc, desc_table);
|
|
#endif
|
|
spicc_desc_invalidate_dcache(desc_table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int spicc_xfer(struct udevice *dev, unsigned int bitlen,
|
|
const void *dout, void *din, unsigned long flags)
|
|
{
|
|
struct spicc_device *spicc = dev_get_priv(dev->parent);
|
|
struct spi_slave *slave = dev_get_parent_priv(dev);
|
|
int len = bitlen >> 3;
|
|
struct spicc_descriptor *desc_table;
|
|
int ret = -EIO;
|
|
|
|
spicc_dbg("bitlen %d, dout %p, din %p, flags 0x%x\n",
|
|
bitlen, dout, din, (unsigned int)flags);
|
|
|
|
if (bitlen % slave->wordlen) {
|
|
spicc_err("invalid transfer length %d\n", bitlen);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spicc->config_data_mode != SPICC_DATA_MODE_PIO) {
|
|
struct spicc_xfer *xfer;
|
|
|
|
if (flags & SPI_XFER_BEGIN)
|
|
spicc->xfer_num = 0;
|
|
xfer = &spicc->xfer[spicc->xfer_num];
|
|
xfer->tx_buf = dout;
|
|
xfer->rx_buf = din;
|
|
xfer->len = len;
|
|
xfer->flags = flags;
|
|
spicc->xfer_num++;
|
|
|
|
if (!(flags & SPI_XFER_END))
|
|
return spicc->xfer_num;
|
|
}
|
|
|
|
if (!spicc_sem_down_read(spicc)) {
|
|
spicc_err("controller busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (spicc->config_data_mode == SPICC_DATA_MODE_PIO) {
|
|
ret = spicc_xfer_pio(spicc, dout, din, len, flags & SPI_XFER_END);
|
|
} else {
|
|
desc_table = spicc_create_desc_table(spicc, spicc->xfer,
|
|
spicc->xfer_num);
|
|
if (desc_table) {
|
|
ret = spicc_xfer_desc(spicc, desc_table);
|
|
spicc_destroy_desc_table(desc_table);
|
|
} else {
|
|
ret = -ENOMEM;
|
|
}
|
|
}
|
|
|
|
spicc_sem_up_write(spicc);
|
|
spicc_dbg("end: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int spicc_probe(struct udevice *bus)
|
|
{
|
|
struct spicc_device *spicc = dev_get_priv(bus);
|
|
int ret;
|
|
|
|
spicc->base = (void __iomem *)dev_read_addr(bus);
|
|
spicc_info("base 0x%p\n", spicc->base);
|
|
|
|
#ifdef CONFIG_SECURE_POWER_CONTROL
|
|
if (!dev_read_u32(bus, "pm-id", &spicc->pm_id)) {
|
|
pwr_ctrl_psci_smc(spicc->pm_id, true);
|
|
spicc_info("power on %d\n", spicc->pm_id);
|
|
}
|
|
#endif
|
|
|
|
/* there are 2 clk using in spi controller,
|
|
* spi_clk: from clock tree control, using for spi phy;
|
|
* sys_clk: from clk81(166MHz), using for others such as apb bus;
|
|
*/
|
|
if (!clk_get_by_name(bus, "sys_clk", &spicc->sys_clk))
|
|
clk_enable(&spicc->sys_clk);
|
|
ret = clk_get_by_name(bus, "spi_clk", &spicc->spi_clk);
|
|
if (ret) {
|
|
spicc_err("failed to get spi_clk(%d)\n", ret);
|
|
return ret;
|
|
}
|
|
clk_enable(&spicc->spi_clk);
|
|
spicc_info("spi_clk %luMHz\n", clk_get_rate(&spicc->spi_clk));
|
|
|
|
spicc->config_data_mode = SPICC_DATA_MODE_MEM;
|
|
spicc->cfg_spi.d32 = 0;
|
|
spicc->cfg_start.d32 = 0;
|
|
spicc->cfg_bus.d32 = 0;
|
|
|
|
spicc->cfg_spi.b.flash_wp_pin_en = 1;
|
|
spicc->cfg_spi.b.flash_hold_pin_en = 1;
|
|
/* default no eoc */
|
|
spicc->cfg_start.b.eoc = 0;
|
|
/* default pending */
|
|
spicc->cfg_start.b.pending = 1;
|
|
/* default keep ss */
|
|
spicc->cfg_bus.b.keep_ss = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
//#define SPICC_NOR_TEST
|
|
#ifdef SPICC_NOR_TEST
|
|
#define SPICC_NOR_TEST_WRITING
|
|
#define NOR_CMD_RDID 0x9F
|
|
#define NOR_CMD_WREN 0x06
|
|
#define NOR_CMD_WRDI 0x04
|
|
#define NOR_CMD_SE 0x20
|
|
#define NOR_CMD_PP 0x02
|
|
#define NOR_CMD_QPP 0x32
|
|
#define NOR_CMD_READ 0x03
|
|
#define NOR_CMD_FREAD 0x0B
|
|
#define NOR_CMD_DREAD 0x3B
|
|
#define NOR_CMD_QREAD 0x6B
|
|
#define NOR_CMD_RDSR1 0x05
|
|
#define NOR_CMD_WRSR1 0x01
|
|
#define NOR_SR1_WIP BIT(0)
|
|
#define NOR_SR1_WEL BIT(1)
|
|
#define NOR_CMD_RDSR2 0x35
|
|
#define NOR_CMD_WRSR2 0x31
|
|
#define NOR_SR2_QE BIT(1)
|
|
|
|
static int spicc_nor_read_id(struct spi_slave *slave, uint8_t *id_buf)
|
|
{
|
|
uint8_t cmd = NOR_CMD_RDID;
|
|
|
|
spi_xfer(slave, 8, &cmd, 0, SPI_XFER_BEGIN);
|
|
return spi_xfer(slave, 24, 0, id_buf, SPI_XFER_END);
|
|
}
|
|
|
|
static int spicc_nor_rdsr(struct spi_slave *slave, uint8_t cmd, uint8_t *sta)
|
|
{
|
|
uint8_t dout[2], din[2];
|
|
int ret;
|
|
|
|
dout[0] = cmd;
|
|
dout[1] = 0;
|
|
ret = spi_xfer(slave, 16, dout, din, SPI_XFER_ONCE);
|
|
*sta = din[1];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int spicc_nor_wrsr(struct spi_slave *slave, uint8_t cmd, uint8_t sta)
|
|
{
|
|
uint8_t dout[2];
|
|
|
|
dout[0] = cmd;
|
|
dout[1] = sta;
|
|
return spi_xfer(slave, 16, dout, 0, SPI_XFER_ONCE);
|
|
}
|
|
|
|
#define POLLING_PERIOD_US 100
|
|
static int spicc_nor_wait(struct spi_slave *slave, int ms)
|
|
{
|
|
int retry = ms * 1000 / POLLING_PERIOD_US;
|
|
uint8_t sta;
|
|
int ret;
|
|
|
|
while (retry--) {
|
|
ret = spicc_nor_rdsr(slave, NOR_CMD_RDSR1, &sta);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!(sta & NOR_SR1_WIP))
|
|
return 0;
|
|
udelay(POLLING_PERIOD_US);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static inline int spicc_nor_cmd(struct spi_slave *slave, uint8_t cmd)
|
|
{
|
|
return spi_xfer(slave, 8, &cmd, 0, SPI_XFER_ONCE);
|
|
}
|
|
|
|
static int spicc_nor_qe(struct spi_slave *slave, bool enable)
|
|
{
|
|
uint8_t sta;
|
|
int ret;
|
|
|
|
ret = spicc_nor_rdsr(slave, NOR_CMD_RDSR2, &sta);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = spicc_nor_cmd(slave, NOR_CMD_WREN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (enable)
|
|
sta |= NOR_SR2_QE;
|
|
else
|
|
sta &= ~NOR_SR2_QE;
|
|
ret = spicc_nor_wrsr(slave, NOR_CMD_WRSR2, sta);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return spicc_nor_wait(slave, 1000);
|
|
}
|
|
|
|
#ifdef SPICC_NOR_TEST_WRITING
|
|
static int spicc_nor_write(struct spi_slave *slave,
|
|
uint8_t cmd, uint32_t addr,
|
|
uint8_t *buf, int len)
|
|
{
|
|
uint8_t dout[4];
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
ret = spicc_nor_cmd(slave, NOR_CMD_WREN);
|
|
if (ret < 0) {
|
|
printf("SE-WREN failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dout[0] = NOR_CMD_SE;
|
|
dout[1] = (addr >> 16) & 0xff;
|
|
dout[2] = (addr >> 8) & 0xff;
|
|
dout[3] = (addr >> 0) & 0xff;
|
|
ret = spi_xfer(slave, 32, dout, 0, SPI_XFER_ONCE);
|
|
if (ret < 0) {
|
|
printf("SE failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = spicc_nor_wait(slave, 1000);
|
|
if (ret < 0) {
|
|
printf("SE wait failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* PP */
|
|
ret = spicc_nor_cmd(slave, NOR_CMD_WRDI);
|
|
if (ret < 0) {
|
|
printf("SE-WRDI failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = spicc_nor_cmd(slave, NOR_CMD_WREN);
|
|
if (ret < 0) {
|
|
printf("PP-WREN failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (cmd == NOR_CMD_QPP) {
|
|
flags = FIELD_PREP(SPI_XFER_LANE, SPICC_QUAD_SPI);
|
|
ret = spicc_nor_qe(slave, 1);
|
|
if (ret < 0) {
|
|
printf("PP-QE1 failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
} else {
|
|
flags = FIELD_PREP(SPI_XFER_LANE, SPICC_SINGLE_SPI);
|
|
}
|
|
|
|
dout[0] = cmd;
|
|
ret = spi_xfer(slave, 32, dout, 0, SPI_XFER_BEGIN);
|
|
ret = spi_xfer(slave, len * 8, buf, 0, flags | SPI_XFER_END);
|
|
if (ret < 0) {
|
|
printf("PP failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = spicc_nor_wait(slave, 1000);
|
|
if (ret < 0) {
|
|
printf("PP wait failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (cmd == NOR_CMD_QPP) {
|
|
ret = spicc_nor_qe(slave, 0);
|
|
if (ret < 0) {
|
|
printf("PP-QE0 failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return spicc_nor_cmd(slave, NOR_CMD_WRDI);
|
|
}
|
|
#endif
|
|
|
|
static int spicc_nor_read(struct spi_slave *slave,
|
|
uint8_t cmd, uint32_t addr,
|
|
uint8_t *buf, int len)
|
|
{
|
|
uint8_t dout[5];
|
|
unsigned long flags;
|
|
bool dummy;
|
|
int ret;
|
|
|
|
if (cmd == NOR_CMD_DREAD) {
|
|
flags = FIELD_PREP(SPI_XFER_LANE, SPICC_DUAL_SPI);
|
|
dummy = 1;
|
|
} else if (cmd == NOR_CMD_QREAD) {
|
|
ret = spicc_nor_qe(slave, 1);
|
|
if (ret < 0) {
|
|
printf("QREAD-QE1 failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
flags = FIELD_PREP(SPI_XFER_LANE, SPICC_QUAD_SPI);
|
|
dummy = 1;
|
|
} else {
|
|
flags = FIELD_PREP(SPI_XFER_LANE, SPICC_SINGLE_SPI);
|
|
dummy = 0;
|
|
}
|
|
|
|
dout[0] = cmd;
|
|
dout[1] = (addr >> 16) & 0xff;
|
|
dout[2] = (addr >> 8) & 0xff;
|
|
dout[3] = (addr >> 0) & 0xff;
|
|
dout[4] = 0;
|
|
ret = spi_xfer(slave, dummy ? 40 : 32, dout, 0, SPI_XFER_BEGIN);
|
|
ret = spi_xfer(slave, len * 8, 0, buf, flags | SPI_XFER_END);
|
|
if (ret < 0) {
|
|
printf("READ(0x%x) failed %d\n", cmd, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (cmd == NOR_CMD_QREAD) {
|
|
ret = spicc_nor_qe(slave, 0);
|
|
if (ret < 0)
|
|
printf("QREAD-QE0 failed %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void spicc_print_buffer(uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
printf("%02X", buf[i]);
|
|
printf("\n");
|
|
}
|
|
|
|
void spicc_nor_test(int bus, int cs, int speed, uint32_t addr, int len)
|
|
{
|
|
struct spi_slave *slave;
|
|
struct udevice *dev;
|
|
char name[30], *str;
|
|
uint8_t *buf;
|
|
|
|
snprintf(name, sizeof(name), "generic_%d:%d", bus, cs);
|
|
str = strdup(name);
|
|
if (!str)
|
|
return;
|
|
|
|
if (spi_get_bus_and_cs(bus, cs, speed, 0, "spi_generic_drv",
|
|
str, &dev, &slave))
|
|
return;
|
|
|
|
slave->wordlen = 8;
|
|
if (spi_claim_bus(slave)) {
|
|
spi_release_bus(slave);
|
|
return;
|
|
}
|
|
|
|
buf = malloc(len);
|
|
if (!buf) {
|
|
spi_release_bus(slave);
|
|
return;
|
|
}
|
|
|
|
printf("RDID\n");
|
|
if (!spicc_nor_read_id(slave, buf))
|
|
spicc_print_buffer(buf, 3);
|
|
|
|
#ifdef SPICC_NOR_TEST_WRITING
|
|
printf("SE-PP(0x02)\n");
|
|
//{0x01, 0xfe, 0x23, 0xdc, 0x45, 0xba, 0x67, 0x98}
|
|
for (int i = 0; i < len; i++)
|
|
buf[i] = i;
|
|
if (!spicc_nor_write(slave, NOR_CMD_PP, addr, buf, len))
|
|
spicc_print_buffer(buf, len);
|
|
#endif
|
|
|
|
printf("READ(03h)\n");
|
|
memset(buf, 0, len);
|
|
if (!spicc_nor_read(slave, NOR_CMD_READ, addr, buf, len))
|
|
spicc_print_buffer(buf, len);
|
|
|
|
printf("DREAD(3bh)\n");
|
|
memset(buf, 0, len);
|
|
if (!spicc_nor_read(slave, NOR_CMD_DREAD, addr, buf, len))
|
|
spicc_print_buffer(buf, len);
|
|
|
|
printf("QREAD(6bh)\n");
|
|
memset(buf, 0, len);
|
|
if (!spicc_nor_read(slave, NOR_CMD_QREAD, addr, buf, len))
|
|
spicc_print_buffer(buf, len);
|
|
|
|
spi_release_bus(slave);
|
|
free(buf);
|
|
}
|
|
#endif /* end of SPICC_NOR_TEST */
|
|
|
|
static const struct udevice_id spicc_v2_of_match[] = {
|
|
{
|
|
.compatible = "amlogic,meson-a4-spicc",
|
|
.data = 0,
|
|
},
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static const struct dm_spi_ops spicc_v2_ops = {
|
|
.claim_bus = spicc_claim_bus,
|
|
.release_bus = spicc_release_bus,
|
|
.xfer = spicc_xfer,
|
|
.set_speed = spicc_set_speed,
|
|
.set_mode = spicc_set_mode,
|
|
.set_wordlen = 0,
|
|
};
|
|
|
|
U_BOOT_DRIVER(spicc_v2) = {
|
|
.name = "spicc_v2",
|
|
.id = UCLASS_SPI,
|
|
.of_match = spicc_v2_of_match,
|
|
.priv_auto_alloc_size = sizeof(struct spicc_device),
|
|
.per_child_auto_alloc_size = sizeof(struct spi_slave),
|
|
.ops = &spicc_v2_ops,
|
|
.probe = spicc_probe,
|
|
};
|