diff options
-rw-r--r-- | drivers/spi/cadence_qspi.c | 39 | ||||
-rw-r--r-- | drivers/spi/cadence_qspi.h | 14 | ||||
-rw-r--r-- | drivers/spi/cadence_qspi_apb.c | 286 |
3 files changed, 313 insertions, 26 deletions
diff --git a/drivers/spi/cadence_qspi.c b/drivers/spi/cadence_qspi.c index a961193..d1b3808 100644 --- a/drivers/spi/cadence_qspi.c +++ b/drivers/spi/cadence_qspi.c @@ -43,20 +43,22 @@ static int cadence_spi_write_speed(struct udevice *bus, uint hz) 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), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, 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 */ static int spi_calibration(struct udevice *bus, uint hz) { struct cadence_spi_priv *priv = dev_get_priv(bus); + struct cadence_spi_plat *plat = dev_get_plat(bus); void *base = priv->regbase; unsigned int idcode = 0, temp = 0; 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); /* 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) { puts("SF: Calibration failed (read)\n"); return err; @@ -90,7 +92,7 @@ static int spi_calibration(struct udevice *bus, uint hz) cadence_qspi_apb_controller_enable(base); /* 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) { puts("SF: Calibration failed (read)\n"); return err; @@ -271,10 +273,14 @@ static int cadence_spi_mem_exec_op(struct spi_slave *spi, switch (mode) { 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; 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; case CQSPI_READ: 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; } +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) { 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 = { .exec_op = cadence_spi_mem_exec_op, + .supports_op = cadence_spi_mem_supports_op, }; static const struct dm_spi_ops cadence_spi_ops = { diff --git a/drivers/spi/cadence_qspi.h b/drivers/spi/cadence_qspi.h index 5c74554..49b4011 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -36,6 +36,12 @@ struct cadence_spi_plat { u32 tsd2d_ns; u32 tchsh_ns; u32 tslch_ns; + + /* Transaction protocol parameters. */ + u8 inst_width; + u8 addr_width; + u8 data_width; + bool dtr; }; 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_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); -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); int cadence_qspi_apb_read_setup(struct cadence_spi_plat *plat, diff --git a/drivers/spi/cadence_qspi_apb.c b/drivers/spi/cadence_qspi_apb.c index 92e5773..c36a652 100644 --- a/drivers/spi/cadence_qspi_apb.c +++ b/drivers/spi/cadence_qspi_apb.c @@ -51,7 +51,7 @@ #define CQSPI_STIG_DATA_LEN_MAX 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) @@ -65,6 +65,8 @@ #define CQSPI_REG_CONFIG_XIP_IMM BIT(18) #define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10 #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_CHIPSELECT_MASK 0xF #define CQSPI_REG_CONFIG_BAUD_MASK 0xF @@ -83,6 +85,7 @@ #define CQSPI_REG_WR_INSTR 0x08 #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_DELAY 0x0C @@ -120,6 +123,9 @@ #define CQSPI_REG_SDRAMLEVEL_RD_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_IRQMASK 0x44 @@ -166,6 +172,11 @@ #define CQSPI_REG_CMDWRITEDATALOWER 0xA8 #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) \ ((readl(base + CQSPI_REG_CONFIG) >> \ 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); } +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). */ 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; } +static int cadence_qspi_setup_opcode_ext(struct cadence_spi_plat *plat, + 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(void *reg_base, const struct spi_mem_op *op) +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 read_len; int status; unsigned int rxlen = op->data.nbytes; void *rxbuf = op->data.buf.in; + unsigned int dummy_clk; + u8 opcode; if (rxlen > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) { printf("QSPI: Invalid input arguments rxlen %u\n", rxlen); 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); @@ -475,15 +643,39 @@ int cadence_qspi_apb_command_read(void *reg_base, const struct spi_mem_op *op) 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. */ -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 wr_data; unsigned int wr_len; unsigned int txlen = op->data.nbytes; const void *txbuf = op->data.buf.out; + void *reg_base = plat->regbase; u32 addr; + u8 opcode; /* Reorder address to SPI bus order if only transferring address */ if (!txlen) { @@ -499,7 +691,12 @@ int cadence_qspi_apb_command_write(void *reg_base, const struct spi_mem_op *op) 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) { /* writing data = yes */ @@ -533,29 +730,39 @@ int cadence_qspi_apb_read_setup(struct cadence_spi_plat *plat, unsigned int rd_reg; unsigned int dummy_clk; 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 */ writel(plat->trigger_address, plat->regbase + CQSPI_REG_INDIRECTTRIGGER); /* 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) - /* Instruction and address at DQ0, data at DQ0-7. */ - 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; + rd_reg = opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB; + rd_reg |= cadence_qspi_calc_rdreg(plat); writel(op->addr.val, plat->regbase + CQSPI_REG_INDIRECTRDSTARTADDR); if (dummy_bytes) { - if (dummy_bytes > CQSPI_DUMMY_BYTES_MAX) - dummy_bytes = CQSPI_DUMMY_BYTES_MAX; - /* 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) 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) { 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 */ writel(plat->trigger_address, plat->regbase + CQSPI_REG_INDIRECTTRIGGER); /* 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); + reg = cadence_qspi_calc_rdreg(plat); + writel(reg, plat->regbase + CQSPI_REG_RD_INSTR); + 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 &= ~CQSPI_REG_SIZE_ADDRESS_MASK; 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; 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); if (!cadence_qspi_wait_idle(plat->regbase)) return -EIO; |