mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-28 15:41:40 +00:00
spi: cadence-qspi: Add support for octal DTR flashes
Set up opcode extension and enable/disable DTR mode based on whether the command is DTR or not. xSPI flashes can have a 4-byte dummy address associated with some commands like the Read Status Register command in octal DTR mode. Since the flash does not support sending the dummy address, we can not use automatic write completion polling in DTR mode. Further, no write completion polling makes it impossible to use DAC mode for DTR writes. In that mode, the controller does not know beforehand how long a write will be and so it can de-assert Chip Select (CS#) at any time. Once CS# is de-assert, the flash will go into burning phase. But since the controller does not do write completion polling, it does not know when the flash is busy and might send in writes while the flash is not ready. So, disable write completion polling and make writes go through indirect mode for DTR writes and let spi-mem take care of polling the SR. Signed-off-by: Pratyush Yadav <p.yadav@ti.com> Acked-by: Jagan Teki <jagan@amarulasolutions.com>
This commit is contained in:
parent
a6903aa7ea
commit
38b0852b0e
3 changed files with 314 additions and 27 deletions
|
@ -43,20 +43,22 @@ static int cadence_spi_write_speed(struct udevice *bus, uint hz)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cadence_spi_read_id(void *reg_base, u8 len, u8 *idcode)
|
static int cadence_spi_read_id(struct cadence_spi_plat *plat, u8 len,
|
||||||
|
u8 *idcode)
|
||||||
{
|
{
|
||||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x9F, 1),
|
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x9F, 1),
|
||||||
SPI_MEM_OP_NO_ADDR,
|
SPI_MEM_OP_NO_ADDR,
|
||||||
SPI_MEM_OP_NO_DUMMY,
|
SPI_MEM_OP_NO_DUMMY,
|
||||||
SPI_MEM_OP_DATA_IN(len, idcode, 1));
|
SPI_MEM_OP_DATA_IN(len, idcode, 1));
|
||||||
|
|
||||||
return cadence_qspi_apb_command_read(reg_base, &op);
|
return cadence_qspi_apb_command_read(plat, &op);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calibration sequence to determine the read data capture delay register */
|
/* Calibration sequence to determine the read data capture delay register */
|
||||||
static int spi_calibration(struct udevice *bus, uint hz)
|
static int spi_calibration(struct udevice *bus, uint hz)
|
||||||
{
|
{
|
||||||
struct cadence_spi_priv *priv = dev_get_priv(bus);
|
struct cadence_spi_priv *priv = dev_get_priv(bus);
|
||||||
|
struct cadence_spi_plat *plat = dev_get_plat(bus);
|
||||||
void *base = priv->regbase;
|
void *base = priv->regbase;
|
||||||
unsigned int idcode = 0, temp = 0;
|
unsigned int idcode = 0, temp = 0;
|
||||||
int err = 0, i, range_lo = -1, range_hi = -1;
|
int err = 0, i, range_lo = -1, range_hi = -1;
|
||||||
|
@ -71,7 +73,7 @@ static int spi_calibration(struct udevice *bus, uint hz)
|
||||||
cadence_qspi_apb_controller_enable(base);
|
cadence_qspi_apb_controller_enable(base);
|
||||||
|
|
||||||
/* read the ID which will be our golden value */
|
/* read the ID which will be our golden value */
|
||||||
err = cadence_spi_read_id(base, 3, (u8 *)&idcode);
|
err = cadence_spi_read_id(plat, 3, (u8 *)&idcode);
|
||||||
if (err) {
|
if (err) {
|
||||||
puts("SF: Calibration failed (read)\n");
|
puts("SF: Calibration failed (read)\n");
|
||||||
return err;
|
return err;
|
||||||
|
@ -90,7 +92,7 @@ static int spi_calibration(struct udevice *bus, uint hz)
|
||||||
cadence_qspi_apb_controller_enable(base);
|
cadence_qspi_apb_controller_enable(base);
|
||||||
|
|
||||||
/* issue a RDID to get the ID value */
|
/* issue a RDID to get the ID value */
|
||||||
err = cadence_spi_read_id(base, 3, (u8 *)&temp);
|
err = cadence_spi_read_id(plat, 3, (u8 *)&temp);
|
||||||
if (err) {
|
if (err) {
|
||||||
puts("SF: Calibration failed (read)\n");
|
puts("SF: Calibration failed (read)\n");
|
||||||
return err;
|
return err;
|
||||||
|
@ -271,10 +273,14 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi,
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CQSPI_STIG_READ:
|
case CQSPI_STIG_READ:
|
||||||
err = cadence_qspi_apb_command_read(base, op);
|
err = cadence_qspi_apb_command_read_setup(plat, op);
|
||||||
|
if (!err)
|
||||||
|
err = cadence_qspi_apb_command_read(plat, op);
|
||||||
break;
|
break;
|
||||||
case CQSPI_STIG_WRITE:
|
case CQSPI_STIG_WRITE:
|
||||||
err = cadence_qspi_apb_command_write(base, op);
|
err = cadence_qspi_apb_command_write_setup(plat, op);
|
||||||
|
if (!err)
|
||||||
|
err = cadence_qspi_apb_command_write(plat, op);
|
||||||
break;
|
break;
|
||||||
case CQSPI_READ:
|
case CQSPI_READ:
|
||||||
err = cadence_qspi_apb_read_setup(plat, op);
|
err = cadence_qspi_apb_read_setup(plat, op);
|
||||||
|
@ -294,6 +300,26 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool cadence_spi_mem_supports_op(struct spi_slave *slave,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
|
{
|
||||||
|
bool all_true, all_false;
|
||||||
|
|
||||||
|
all_true = op->cmd.dtr && op->addr.dtr && op->dummy.dtr &&
|
||||||
|
op->data.dtr;
|
||||||
|
all_false = !op->cmd.dtr && !op->addr.dtr && !op->dummy.dtr &&
|
||||||
|
!op->data.dtr;
|
||||||
|
|
||||||
|
/* Mixed DTR modes not supported. */
|
||||||
|
if (!(all_true || all_false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (all_true)
|
||||||
|
return spi_mem_dtr_supports_op(slave, op);
|
||||||
|
else
|
||||||
|
return spi_mem_default_supports_op(slave, op);
|
||||||
|
}
|
||||||
|
|
||||||
static int cadence_spi_of_to_plat(struct udevice *bus)
|
static int cadence_spi_of_to_plat(struct udevice *bus)
|
||||||
{
|
{
|
||||||
struct cadence_spi_plat *plat = dev_get_plat(bus);
|
struct cadence_spi_plat *plat = dev_get_plat(bus);
|
||||||
|
@ -350,6 +376,7 @@ static int cadence_spi_of_to_plat(struct udevice *bus)
|
||||||
|
|
||||||
static const struct spi_controller_mem_ops cadence_spi_mem_ops = {
|
static const struct spi_controller_mem_ops cadence_spi_mem_ops = {
|
||||||
.exec_op = cadence_spi_mem_exec_op,
|
.exec_op = cadence_spi_mem_exec_op,
|
||||||
|
.supports_op = cadence_spi_mem_supports_op,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct dm_spi_ops cadence_spi_ops = {
|
static const struct dm_spi_ops cadence_spi_ops = {
|
||||||
|
|
|
@ -36,6 +36,12 @@ struct cadence_spi_plat {
|
||||||
u32 tsd2d_ns;
|
u32 tsd2d_ns;
|
||||||
u32 tchsh_ns;
|
u32 tchsh_ns;
|
||||||
u32 tslch_ns;
|
u32 tslch_ns;
|
||||||
|
|
||||||
|
/* Transaction protocol parameters. */
|
||||||
|
u8 inst_width;
|
||||||
|
u8 addr_width;
|
||||||
|
u8 data_width;
|
||||||
|
bool dtr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cadence_spi_priv {
|
struct cadence_spi_priv {
|
||||||
|
@ -59,9 +65,13 @@ void cadence_qspi_apb_controller_enable(void *reg_base_addr);
|
||||||
void cadence_qspi_apb_controller_disable(void *reg_base_addr);
|
void cadence_qspi_apb_controller_disable(void *reg_base_addr);
|
||||||
void cadence_qspi_apb_dac_mode_enable(void *reg_base);
|
void cadence_qspi_apb_dac_mode_enable(void *reg_base);
|
||||||
|
|
||||||
int cadence_qspi_apb_command_read(void *reg_base_addr,
|
int cadence_qspi_apb_command_read_setup(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op);
|
||||||
|
int cadence_qspi_apb_command_read(struct cadence_spi_plat *plat,
|
||||||
const struct spi_mem_op *op);
|
const struct spi_mem_op *op);
|
||||||
int cadence_qspi_apb_command_write(void *reg_base_addr,
|
int cadence_qspi_apb_command_write_setup(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op);
|
||||||
|
int cadence_qspi_apb_command_write(struct cadence_spi_plat *plat,
|
||||||
const struct spi_mem_op *op);
|
const struct spi_mem_op *op);
|
||||||
|
|
||||||
int cadence_qspi_apb_read_setup(struct cadence_spi_plat *plat,
|
int cadence_qspi_apb_read_setup(struct cadence_spi_plat *plat,
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
#define CQSPI_STIG_DATA_LEN_MAX 8
|
#define CQSPI_STIG_DATA_LEN_MAX 8
|
||||||
|
|
||||||
#define CQSPI_DUMMY_CLKS_PER_BYTE 8
|
#define CQSPI_DUMMY_CLKS_PER_BYTE 8
|
||||||
#define CQSPI_DUMMY_BYTES_MAX 4
|
#define CQSPI_DUMMY_CLKS_MAX 31
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Controller's configuration and status register (offset from QSPI_BASE)
|
* Controller's configuration and status register (offset from QSPI_BASE)
|
||||||
|
@ -65,6 +65,8 @@
|
||||||
#define CQSPI_REG_CONFIG_XIP_IMM BIT(18)
|
#define CQSPI_REG_CONFIG_XIP_IMM BIT(18)
|
||||||
#define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10
|
#define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10
|
||||||
#define CQSPI_REG_CONFIG_BAUD_LSB 19
|
#define CQSPI_REG_CONFIG_BAUD_LSB 19
|
||||||
|
#define CQSPI_REG_CONFIG_DTR_PROTO BIT(24)
|
||||||
|
#define CQSPI_REG_CONFIG_DUAL_OPCODE BIT(30)
|
||||||
#define CQSPI_REG_CONFIG_IDLE_LSB 31
|
#define CQSPI_REG_CONFIG_IDLE_LSB 31
|
||||||
#define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF
|
#define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF
|
||||||
#define CQSPI_REG_CONFIG_BAUD_MASK 0xF
|
#define CQSPI_REG_CONFIG_BAUD_MASK 0xF
|
||||||
|
@ -83,6 +85,7 @@
|
||||||
|
|
||||||
#define CQSPI_REG_WR_INSTR 0x08
|
#define CQSPI_REG_WR_INSTR 0x08
|
||||||
#define CQSPI_REG_WR_INSTR_OPCODE_LSB 0
|
#define CQSPI_REG_WR_INSTR_OPCODE_LSB 0
|
||||||
|
#define CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB 12
|
||||||
#define CQSPI_REG_WR_INSTR_TYPE_DATA_LSB 16
|
#define CQSPI_REG_WR_INSTR_TYPE_DATA_LSB 16
|
||||||
|
|
||||||
#define CQSPI_REG_DELAY 0x0C
|
#define CQSPI_REG_DELAY 0x0C
|
||||||
|
@ -120,6 +123,9 @@
|
||||||
#define CQSPI_REG_SDRAMLEVEL_RD_MASK 0xFFFF
|
#define CQSPI_REG_SDRAMLEVEL_RD_MASK 0xFFFF
|
||||||
#define CQSPI_REG_SDRAMLEVEL_WR_MASK 0xFFFF
|
#define CQSPI_REG_SDRAMLEVEL_WR_MASK 0xFFFF
|
||||||
|
|
||||||
|
#define CQSPI_REG_WR_COMPLETION_CTRL 0x38
|
||||||
|
#define CQSPI_REG_WR_DISABLE_AUTO_POLL BIT(14)
|
||||||
|
|
||||||
#define CQSPI_REG_IRQSTATUS 0x40
|
#define CQSPI_REG_IRQSTATUS 0x40
|
||||||
#define CQSPI_REG_IRQMASK 0x44
|
#define CQSPI_REG_IRQMASK 0x44
|
||||||
|
|
||||||
|
@ -166,6 +172,11 @@
|
||||||
#define CQSPI_REG_CMDWRITEDATALOWER 0xA8
|
#define CQSPI_REG_CMDWRITEDATALOWER 0xA8
|
||||||
#define CQSPI_REG_CMDWRITEDATAUPPER 0xAC
|
#define CQSPI_REG_CMDWRITEDATAUPPER 0xAC
|
||||||
|
|
||||||
|
#define CQSPI_REG_OP_EXT_LOWER 0xE0
|
||||||
|
#define CQSPI_REG_OP_EXT_READ_LSB 24
|
||||||
|
#define CQSPI_REG_OP_EXT_WRITE_LSB 16
|
||||||
|
#define CQSPI_REG_OP_EXT_STIG_LSB 0
|
||||||
|
|
||||||
#define CQSPI_REG_IS_IDLE(base) \
|
#define CQSPI_REG_IS_IDLE(base) \
|
||||||
((readl(base + CQSPI_REG_CONFIG) >> \
|
((readl(base + CQSPI_REG_CONFIG) >> \
|
||||||
CQSPI_REG_CONFIG_IDLE_LSB) & 0x1)
|
CQSPI_REG_CONFIG_IDLE_LSB) & 0x1)
|
||||||
|
@ -203,6 +214,75 @@ void cadence_qspi_apb_dac_mode_enable(void *reg_base)
|
||||||
writel(reg, reg_base + CQSPI_REG_CONFIG);
|
writel(reg, reg_base + CQSPI_REG_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned int cadence_qspi_calc_dummy(const struct spi_mem_op *op,
|
||||||
|
bool dtr)
|
||||||
|
{
|
||||||
|
unsigned int dummy_clk;
|
||||||
|
|
||||||
|
dummy_clk = op->dummy.nbytes * (8 / op->dummy.buswidth);
|
||||||
|
if (dtr)
|
||||||
|
dummy_clk /= 2;
|
||||||
|
|
||||||
|
return dummy_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 cadence_qspi_calc_rdreg(struct cadence_spi_plat *plat)
|
||||||
|
{
|
||||||
|
u32 rdreg = 0;
|
||||||
|
|
||||||
|
rdreg |= plat->inst_width << CQSPI_REG_RD_INSTR_TYPE_INSTR_LSB;
|
||||||
|
rdreg |= plat->addr_width << CQSPI_REG_RD_INSTR_TYPE_ADDR_LSB;
|
||||||
|
rdreg |= plat->data_width << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB;
|
||||||
|
|
||||||
|
return rdreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cadence_qspi_buswidth_to_inst_type(u8 buswidth)
|
||||||
|
{
|
||||||
|
switch (buswidth) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
return CQSPI_INST_TYPE_SINGLE;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return CQSPI_INST_TYPE_DUAL;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
return CQSPI_INST_TYPE_QUAD;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
return CQSPI_INST_TYPE_OCTAL;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -ENOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cadence_qspi_set_protocol(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
plat->dtr = op->data.dtr && op->cmd.dtr && op->addr.dtr;
|
||||||
|
|
||||||
|
ret = cadence_qspi_buswidth_to_inst_type(op->cmd.buswidth);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
plat->inst_width = ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_buswidth_to_inst_type(op->addr.buswidth);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
plat->addr_width = ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_buswidth_to_inst_type(op->data.buswidth);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
plat->data_width = ret;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return 1 if idle, otherwise return 0 (busy). */
|
/* Return 1 if idle, otherwise return 0 (busy). */
|
||||||
static unsigned int cadence_qspi_wait_idle(void *reg_base)
|
static unsigned int cadence_qspi_wait_idle(void *reg_base)
|
||||||
{
|
{
|
||||||
|
@ -434,21 +514,109 @@ static int cadence_qspi_apb_exec_flash_cmd(void *reg_base,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For command RDID, RDSR. */
|
static int cadence_qspi_setup_opcode_ext(struct cadence_spi_plat *plat,
|
||||||
int cadence_qspi_apb_command_read(void *reg_base, const struct spi_mem_op *op)
|
const struct spi_mem_op *op,
|
||||||
|
unsigned int shift)
|
||||||
{
|
{
|
||||||
|
unsigned int reg;
|
||||||
|
u8 ext;
|
||||||
|
|
||||||
|
if (op->cmd.nbytes != 2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Opcode extension is the LSB. */
|
||||||
|
ext = op->cmd.opcode & 0xff;
|
||||||
|
|
||||||
|
reg = readl(plat->regbase + CQSPI_REG_OP_EXT_LOWER);
|
||||||
|
reg &= ~(0xff << shift);
|
||||||
|
reg |= ext << shift;
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_OP_EXT_LOWER);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cadence_qspi_enable_dtr(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op,
|
||||||
|
unsigned int shift,
|
||||||
|
bool enable)
|
||||||
|
{
|
||||||
|
unsigned int reg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
reg = readl(plat->regbase + CQSPI_REG_CONFIG);
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
reg |= CQSPI_REG_CONFIG_DTR_PROTO;
|
||||||
|
reg |= CQSPI_REG_CONFIG_DUAL_OPCODE;
|
||||||
|
|
||||||
|
/* Set up command opcode extension. */
|
||||||
|
ret = cadence_qspi_setup_opcode_ext(plat, op, shift);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
reg &= ~CQSPI_REG_CONFIG_DTR_PROTO;
|
||||||
|
reg &= ~CQSPI_REG_CONFIG_DUAL_OPCODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_CONFIG);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cadence_qspi_apb_command_read_setup(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned int reg;
|
||||||
|
|
||||||
|
ret = cadence_qspi_set_protocol(plat, op);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_enable_dtr(plat, op, CQSPI_REG_OP_EXT_STIG_LSB,
|
||||||
|
plat->dtr);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
reg = cadence_qspi_calc_rdreg(plat);
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_RD_INSTR);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For command RDID, RDSR. */
|
||||||
|
int cadence_qspi_apb_command_read(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
|
{
|
||||||
|
void *reg_base = plat->regbase;
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
unsigned int read_len;
|
unsigned int read_len;
|
||||||
int status;
|
int status;
|
||||||
unsigned int rxlen = op->data.nbytes;
|
unsigned int rxlen = op->data.nbytes;
|
||||||
void *rxbuf = op->data.buf.in;
|
void *rxbuf = op->data.buf.in;
|
||||||
|
unsigned int dummy_clk;
|
||||||
|
u8 opcode;
|
||||||
|
|
||||||
if (rxlen > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) {
|
if (rxlen > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) {
|
||||||
printf("QSPI: Invalid input arguments rxlen %u\n", rxlen);
|
printf("QSPI: Invalid input arguments rxlen %u\n", rxlen);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
reg = op->cmd.opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;
|
if (plat->dtr)
|
||||||
|
opcode = op->cmd.opcode >> 8;
|
||||||
|
else
|
||||||
|
opcode = op->cmd.opcode;
|
||||||
|
|
||||||
|
reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;
|
||||||
|
|
||||||
|
/* Set up dummy cycles. */
|
||||||
|
dummy_clk = cadence_qspi_calc_dummy(op, plat->dtr);
|
||||||
|
if (dummy_clk > CQSPI_DUMMY_CLKS_MAX)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
|
if (dummy_clk)
|
||||||
|
reg |= (dummy_clk & CQSPI_REG_CMDCTRL_DUMMY_MASK)
|
||||||
|
<< CQSPI_REG_CMDCTRL_DUMMY_LSB;
|
||||||
|
|
||||||
reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB);
|
reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB);
|
||||||
|
|
||||||
|
@ -475,15 +643,39 @@ int cadence_qspi_apb_command_read(void *reg_base, const struct spi_mem_op *op)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cadence_qspi_apb_command_write_setup(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned int reg;
|
||||||
|
|
||||||
|
ret = cadence_qspi_set_protocol(plat, op);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_enable_dtr(plat, op, CQSPI_REG_OP_EXT_STIG_LSB,
|
||||||
|
plat->dtr);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
reg = cadence_qspi_calc_rdreg(plat);
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_RD_INSTR);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* For commands: WRSR, WREN, WRDI, CHIP_ERASE, BE, etc. */
|
/* For commands: WRSR, WREN, WRDI, CHIP_ERASE, BE, etc. */
|
||||||
int cadence_qspi_apb_command_write(void *reg_base, const struct spi_mem_op *op)
|
int cadence_qspi_apb_command_write(struct cadence_spi_plat *plat,
|
||||||
|
const struct spi_mem_op *op)
|
||||||
{
|
{
|
||||||
unsigned int reg = 0;
|
unsigned int reg = 0;
|
||||||
unsigned int wr_data;
|
unsigned int wr_data;
|
||||||
unsigned int wr_len;
|
unsigned int wr_len;
|
||||||
unsigned int txlen = op->data.nbytes;
|
unsigned int txlen = op->data.nbytes;
|
||||||
const void *txbuf = op->data.buf.out;
|
const void *txbuf = op->data.buf.out;
|
||||||
|
void *reg_base = plat->regbase;
|
||||||
u32 addr;
|
u32 addr;
|
||||||
|
u8 opcode;
|
||||||
|
|
||||||
/* Reorder address to SPI bus order if only transferring address */
|
/* Reorder address to SPI bus order if only transferring address */
|
||||||
if (!txlen) {
|
if (!txlen) {
|
||||||
|
@ -499,7 +691,12 @@ int cadence_qspi_apb_command_write(void *reg_base, const struct spi_mem_op *op)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
reg |= op->cmd.opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;
|
if (plat->dtr)
|
||||||
|
opcode = op->cmd.opcode >> 8;
|
||||||
|
else
|
||||||
|
opcode = op->cmd.opcode;
|
||||||
|
|
||||||
|
reg |= opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;
|
||||||
|
|
||||||
if (txlen) {
|
if (txlen) {
|
||||||
/* writing data = yes */
|
/* writing data = yes */
|
||||||
|
@ -533,29 +730,39 @@ int cadence_qspi_apb_read_setup(struct cadence_spi_plat *plat,
|
||||||
unsigned int rd_reg;
|
unsigned int rd_reg;
|
||||||
unsigned int dummy_clk;
|
unsigned int dummy_clk;
|
||||||
unsigned int dummy_bytes = op->dummy.nbytes;
|
unsigned int dummy_bytes = op->dummy.nbytes;
|
||||||
|
int ret;
|
||||||
|
u8 opcode;
|
||||||
|
|
||||||
|
ret = cadence_qspi_set_protocol(plat, op);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_enable_dtr(plat, op, CQSPI_REG_OP_EXT_READ_LSB,
|
||||||
|
plat->dtr);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* Setup the indirect trigger address */
|
/* Setup the indirect trigger address */
|
||||||
writel(plat->trigger_address,
|
writel(plat->trigger_address,
|
||||||
plat->regbase + CQSPI_REG_INDIRECTTRIGGER);
|
plat->regbase + CQSPI_REG_INDIRECTTRIGGER);
|
||||||
|
|
||||||
/* Configure the opcode */
|
/* Configure the opcode */
|
||||||
rd_reg = op->cmd.opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB;
|
if (plat->dtr)
|
||||||
|
opcode = op->cmd.opcode >> 8;
|
||||||
|
else
|
||||||
|
opcode = op->cmd.opcode;
|
||||||
|
|
||||||
if (op->data.buswidth == 8)
|
rd_reg = opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB;
|
||||||
/* Instruction and address at DQ0, data at DQ0-7. */
|
rd_reg |= cadence_qspi_calc_rdreg(plat);
|
||||||
rd_reg |= CQSPI_INST_TYPE_OCTAL << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB;
|
|
||||||
else if (op->data.buswidth == 4)
|
|
||||||
/* Instruction and address at DQ0, data at DQ0-3. */
|
|
||||||
rd_reg |= CQSPI_INST_TYPE_QUAD << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB;
|
|
||||||
|
|
||||||
writel(op->addr.val, plat->regbase + CQSPI_REG_INDIRECTRDSTARTADDR);
|
writel(op->addr.val, plat->regbase + CQSPI_REG_INDIRECTRDSTARTADDR);
|
||||||
|
|
||||||
if (dummy_bytes) {
|
if (dummy_bytes) {
|
||||||
if (dummy_bytes > CQSPI_DUMMY_BYTES_MAX)
|
|
||||||
dummy_bytes = CQSPI_DUMMY_BYTES_MAX;
|
|
||||||
|
|
||||||
/* Convert to clock cycles. */
|
/* Convert to clock cycles. */
|
||||||
dummy_clk = dummy_bytes * CQSPI_DUMMY_CLKS_PER_BYTE;
|
dummy_clk = cadence_qspi_calc_dummy(op, plat->dtr);
|
||||||
|
|
||||||
|
if (dummy_clk > CQSPI_DUMMY_CLKS_MAX)
|
||||||
|
return -ENOTSUPP;
|
||||||
|
|
||||||
if (dummy_clk)
|
if (dummy_clk)
|
||||||
rd_reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK)
|
rd_reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK)
|
||||||
|
@ -682,17 +889,52 @@ int cadence_qspi_apb_write_setup(struct cadence_spi_plat *plat,
|
||||||
const struct spi_mem_op *op)
|
const struct spi_mem_op *op)
|
||||||
{
|
{
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
|
int ret;
|
||||||
|
u8 opcode;
|
||||||
|
|
||||||
|
ret = cadence_qspi_set_protocol(plat, op);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cadence_qspi_enable_dtr(plat, op, CQSPI_REG_OP_EXT_WRITE_LSB,
|
||||||
|
plat->dtr);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* Setup the indirect trigger address */
|
/* Setup the indirect trigger address */
|
||||||
writel(plat->trigger_address,
|
writel(plat->trigger_address,
|
||||||
plat->regbase + CQSPI_REG_INDIRECTTRIGGER);
|
plat->regbase + CQSPI_REG_INDIRECTTRIGGER);
|
||||||
|
|
||||||
/* Configure the opcode */
|
/* Configure the opcode */
|
||||||
reg = op->cmd.opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB;
|
if (plat->dtr)
|
||||||
|
opcode = op->cmd.opcode >> 8;
|
||||||
|
else
|
||||||
|
opcode = op->cmd.opcode;
|
||||||
|
|
||||||
|
reg = opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB;
|
||||||
|
reg |= plat->data_width << CQSPI_REG_WR_INSTR_TYPE_DATA_LSB;
|
||||||
|
reg |= plat->addr_width << CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB;
|
||||||
writel(reg, plat->regbase + CQSPI_REG_WR_INSTR);
|
writel(reg, plat->regbase + CQSPI_REG_WR_INSTR);
|
||||||
|
|
||||||
|
reg = cadence_qspi_calc_rdreg(plat);
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_RD_INSTR);
|
||||||
|
|
||||||
writel(op->addr.val, plat->regbase + CQSPI_REG_INDIRECTWRSTARTADDR);
|
writel(op->addr.val, plat->regbase + CQSPI_REG_INDIRECTWRSTARTADDR);
|
||||||
|
|
||||||
|
if (plat->dtr) {
|
||||||
|
/*
|
||||||
|
* Some flashes like the cypress Semper flash expect a 4-byte
|
||||||
|
* dummy address with the Read SR command in DTR mode, but this
|
||||||
|
* controller does not support sending address with the Read SR
|
||||||
|
* command. So, disable write completion polling on the
|
||||||
|
* controller's side. spi-nor will take care of polling the
|
||||||
|
* status register.
|
||||||
|
*/
|
||||||
|
reg = readl(plat->regbase + CQSPI_REG_WR_COMPLETION_CTRL);
|
||||||
|
reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL;
|
||||||
|
writel(reg, plat->regbase + CQSPI_REG_WR_COMPLETION_CTRL);
|
||||||
|
}
|
||||||
|
|
||||||
reg = readl(plat->regbase + CQSPI_REG_SIZE);
|
reg = readl(plat->regbase + CQSPI_REG_SIZE);
|
||||||
reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK;
|
reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK;
|
||||||
reg |= (op->addr.nbytes - 1);
|
reg |= (op->addr.nbytes - 1);
|
||||||
|
@ -787,7 +1029,15 @@ int cadence_qspi_apb_write_execute(struct cadence_spi_plat *plat,
|
||||||
const void *buf = op->data.buf.out;
|
const void *buf = op->data.buf.out;
|
||||||
size_t len = op->data.nbytes;
|
size_t len = op->data.nbytes;
|
||||||
|
|
||||||
if (plat->use_dac_mode && (to + len < plat->ahbsize)) {
|
/*
|
||||||
|
* Some flashes like the Cypress Semper flash expect a dummy 4-byte
|
||||||
|
* address (all 0s) with the read status register command in DTR mode.
|
||||||
|
* But this controller does not support sending dummy address bytes to
|
||||||
|
* the flash when it is polling the write completion register in DTR
|
||||||
|
* mode. So, we can not use direct mode when in DTR mode for writing
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
|
if (!plat->dtr && plat->use_dac_mode && (to + len < plat->ahbsize)) {
|
||||||
memcpy_toio(plat->ahbbase + to, buf, len);
|
memcpy_toio(plat->ahbbase + to, buf, len);
|
||||||
if (!cadence_qspi_wait_idle(plat->regbase))
|
if (!cadence_qspi_wait_idle(plat->regbase))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
Loading…
Reference in a new issue