diff options
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Kconfig | 7 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/cadence_qspi.c | 69 | ||||
-rw-r--r-- | drivers/spi/cadence_qspi.h | 16 | ||||
-rw-r--r-- | drivers/spi/cadence_qspi_apb.c | 292 | ||||
-rw-r--r-- | drivers/spi/mtk_snfi_spi.c | 3 | ||||
-rw-r--r-- | drivers/spi/spi-mem-nodm.c | 66 | ||||
-rw-r--r-- | drivers/spi/spi-mem.c | 46 | ||||
-rw-r--r-- | drivers/spi/spi-mxic.c | 547 |
9 files changed, 1004 insertions, 43 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 1494c917635..e317d8a2c64 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -255,6 +255,13 @@ config MXS_SPI Enable the MXS SPI controller driver. This driver can be used on the i.MX23 and i.MX28 SoCs. +config SPI_MXIC + bool "Macronix MX25F0A SPI controller" + help + Enable the Macronix MX25F0A SPI controller driver. This driver + can be used to access the SPI flash on platforms embedding + this Macronix IP core. + config NXP_FSPI bool "NXP FlexSPI driver" depends on SPI_MEM diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index cfe4fae1d4b..3dc83089b8f 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o obj-$(CONFIG_PIC32_SPI) += pic32_spi.o obj-$(CONFIG_PL022_SPI) += pl022_spi.o obj-$(CONFIG_SPI_QUP) += spi-qup.o +obj-$(CONFIG_SPI_MXIC) += spi-mxic.o obj-$(CONFIG_RENESAS_RPC_SPI) += renesas_rpc_spi.o obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o diff --git a/drivers/spi/cadence_qspi.c b/drivers/spi/cadence_qspi.c index 67980431ba0..d1b3808c4db 100644 --- a/drivers/spi/cadence_qspi.c +++ b/drivers/spi/cadence_qspi.c @@ -20,6 +20,8 @@ #include <linux/sizes.h> #include "cadence_qspi.h" +#define NSEC_PER_SEC 1000000000L + #define CQSPI_STIG_READ 0 #define CQSPI_STIG_WRITE 1 #define CQSPI_READ 2 @@ -41,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; @@ -69,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; @@ -88,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; @@ -141,12 +145,20 @@ static int cadence_spi_set_speed(struct udevice *bus, uint hz) cadence_qspi_apb_controller_disable(priv->regbase); /* - * Calibration required for different current SCLK speed, requested - * SCLK speed or chip select + * If the device tree already provides a read delay value, use that + * instead of calibrating. */ - if (priv->previous_hz != hz || - priv->qspi_calibrated_hz != hz || - priv->qspi_calibrated_cs != spi_chip_select(bus)) { + if (plat->read_delay >= 0) { + cadence_spi_write_speed(bus, hz); + cadence_qspi_apb_readdata_capture(priv->regbase, 1, + plat->read_delay); + } else if (priv->previous_hz != hz || + priv->qspi_calibrated_hz != hz || + priv->qspi_calibrated_cs != spi_chip_select(bus)) { + /* + * Calibration required for different current SCLK speed, + * requested SCLK speed or chip select + */ err = spi_calibration(bus, hz); if (err) return err; @@ -200,6 +212,8 @@ static int cadence_spi_probe(struct udevice *bus) priv->qspi_is_init = 1; } + plat->wr_delay = 50 * DIV_ROUND_UP(NSEC_PER_SEC, plat->ref_clk_hz); + return 0; } @@ -259,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); @@ -282,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); @@ -320,6 +358,14 @@ static int cadence_spi_of_to_plat(struct udevice *bus) 255); plat->tchsh_ns = ofnode_read_u32_default(subnode, "cdns,tchsh-ns", 20); plat->tslch_ns = ofnode_read_u32_default(subnode, "cdns,tslch-ns", 20); + /* + * Read delay should be an unsigned value but we use a signed integer + * so that negative values can indicate that the device tree did not + * specify any signed values and we need to perform the calibration + * sequence to find it out. + */ + plat->read_delay = ofnode_read_s32_default(subnode, "cdns,read-delay", + -1); debug("%s: regbase=%p ahbbase=%p max-frequency=%d page-size=%d\n", __func__, plat->regbase, plat->ahbbase, plat->max_hz, @@ -330,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 64c5867609c..49b401168fc 100644 --- a/drivers/spi/cadence_qspi.h +++ b/drivers/spi/cadence_qspi.h @@ -26,6 +26,8 @@ struct cadence_spi_plat { u32 trigger_address; fdt_addr_t ahbsize; bool use_dac_mode; + int read_delay; + u32 wr_delay; /* Flash parameters */ u32 page_size; @@ -34,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 { @@ -57,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 b051f462ed2..c36a652211a 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); @@ -730,6 +972,12 @@ cadence_qspi_apb_indirect_write_execute(struct cadence_spi_plat *plat, writel(CQSPI_REG_INDIRECTWR_START, plat->regbase + CQSPI_REG_INDIRECTWR); + /* + * Some delay is required for the above bit to be internally + * synchronized by the QSPI module. + */ + ndelay(plat->wr_delay); + while (remaining > 0) { write_bytes = remaining > page_size ? page_size : remaining; writesl(plat->ahbbase, bb_txbuf, write_bytes >> 2); @@ -781,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; diff --git a/drivers/spi/mtk_snfi_spi.c b/drivers/spi/mtk_snfi_spi.c index b6ab5fa3ada..65d0ce0981b 100644 --- a/drivers/spi/mtk_snfi_spi.c +++ b/drivers/spi/mtk_snfi_spi.c @@ -64,8 +64,7 @@ static int mtk_snfi_adjust_op_size(struct spi_slave *slave, * or the output+input data must not exceed the GPRAM size. */ - nbytes = sizeof(op->cmd.opcode) + op->addr.nbytes + - op->dummy.nbytes; + nbytes = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; if (nbytes + op->data.nbytes <= SNFI_GPRAM_SIZE) return 0; diff --git a/drivers/spi/spi-mem-nodm.c b/drivers/spi/spi-mem-nodm.c index 765f05fe542..a228c808c74 100644 --- a/drivers/spi/spi-mem-nodm.c +++ b/drivers/spi/spi-mem-nodm.c @@ -27,7 +27,7 @@ int spi_mem_exec_op(struct spi_slave *slave, tx_buf = op->data.buf.out; } - op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; + op_len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; op_buf = calloc(1, op_len); ret = spi_claim_bus(slave); @@ -89,7 +89,7 @@ int spi_mem_adjust_op_size(struct spi_slave *slave, { unsigned int len; - len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; + len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; if (slave->max_write_size && len > slave->max_write_size) return -EINVAL; @@ -105,3 +105,65 @@ int spi_mem_adjust_op_size(struct spi_slave *slave, return 0; } + +static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx) +{ + u32 mode = slave->mode; + + switch (buswidth) { + case 1: + return 0; + + case 2: + if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) || + (!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD)))) + return 0; + + break; + + case 4: + if ((tx && (mode & SPI_TX_QUAD)) || + (!tx && (mode & SPI_RX_QUAD))) + return 0; + + break; + case 8: + if ((tx && (mode & SPI_TX_OCTAL)) || + (!tx && (mode & SPI_RX_OCTAL))) + return 0; + + break; + + default: + break; + } + + return -ENOTSUPP; +} + +bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op) +{ + if (spi_check_buswidth_req(slave, op->cmd.buswidth, true)) + return false; + + if (op->addr.nbytes && + spi_check_buswidth_req(slave, op->addr.buswidth, true)) + return false; + + if (op->dummy.nbytes && + spi_check_buswidth_req(slave, op->dummy.buswidth, true)) + return false; + + if (op->data.nbytes && + spi_check_buswidth_req(slave, op->data.buswidth, + op->data.dir == SPI_MEM_DATA_OUT)) + return false; + + if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) + return false; + + if (op->cmd.nbytes != 1) + return false; + + return true; +} diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index c095ae9505a..9c1ede1b61c 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -145,8 +145,8 @@ static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx) return -ENOTSUPP; } -bool spi_mem_default_supports_op(struct spi_slave *slave, - const struct spi_mem_op *op) +static bool spi_mem_check_buswidth(struct spi_slave *slave, + const struct spi_mem_op *op) { if (spi_check_buswidth_req(slave, op->cmd.buswidth, true)) return false; @@ -166,6 +166,38 @@ bool spi_mem_default_supports_op(struct spi_slave *slave, return true; } + +bool spi_mem_dtr_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + if (op->cmd.buswidth == 8 && op->cmd.nbytes % 2) + return false; + + if (op->addr.nbytes && op->addr.buswidth == 8 && op->addr.nbytes % 2) + return false; + + if (op->dummy.nbytes && op->dummy.buswidth == 8 && op->dummy.nbytes % 2) + return false; + + if (op->data.dir != SPI_MEM_NO_DATA && + op->dummy.buswidth == 8 && op->data.nbytes % 2) + return false; + + return spi_mem_check_buswidth(slave, op); +} +EXPORT_SYMBOL_GPL(spi_mem_dtr_supports_op); + +bool spi_mem_default_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) + return false; + + if (op->cmd.nbytes != 1) + return false; + + return spi_mem_check_buswidth(slave, op); +} EXPORT_SYMBOL_GPL(spi_mem_default_supports_op); /** @@ -270,8 +302,7 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) } #ifndef __UBOOT__ - tmpbufsize = sizeof(op->cmd.opcode) + op->addr.nbytes + - op->dummy.nbytes; + tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; /* * Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so @@ -286,7 +317,7 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) tmpbuf[0] = op->cmd.opcode; xfers[xferpos].tx_buf = tmpbuf; - xfers[xferpos].len = sizeof(op->cmd.opcode); + xfers[xferpos].len = op->cmd.nbytes; xfers[xferpos].tx_nbits = op->cmd.buswidth; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; @@ -350,7 +381,7 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) tx_buf = op->data.buf.out; } - op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; + op_len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; /* * Avoid using malloc() here so that we can use this code in SPL where @@ -439,8 +470,7 @@ int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) if (!ops->mem_ops || !ops->mem_ops->exec_op) { unsigned int len; - len = sizeof(op->cmd.opcode) + op->addr.nbytes + - op->dummy.nbytes; + len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; if (slave->max_write_size && len > slave->max_write_size) return -EINVAL; diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c new file mode 100644 index 00000000000..6aae9f79556 --- /dev/null +++ b/drivers/spi/spi-mxic.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Macronix International Co., Ltd. + * + * Authors: + * zhengxunli <zhengxunli@mxic.com.tw> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <asm/io.h> +#include <malloc.h> +#include <spi.h> +#include <spi-mem.h> +#include <linux/bug.h> +#include <linux/iopoll.h> + +#define HC_CFG 0x0 +#define HC_CFG_IF_CFG(x) ((x) << 27) +#define HC_CFG_DUAL_SLAVE BIT(31) +#define HC_CFG_INDIVIDUAL BIT(30) +#define HC_CFG_NIO(x) (((x) / 4) << 27) +#define HC_CFG_TYPE(s, t) ((t) << (23 + ((s) * 2))) +#define HC_CFG_TYPE_SPI_NOR 0 +#define HC_CFG_TYPE_SPI_NAND 1 +#define HC_CFG_TYPE_SPI_RAM 2 +#define HC_CFG_TYPE_RAW_NAND 3 +#define HC_CFG_SLV_ACT(x) ((x) << 21) +#define HC_CFG_CLK_PH_EN BIT(20) +#define HC_CFG_CLK_POL_INV BIT(19) +#define HC_CFG_BIG_ENDIAN BIT(18) +#define HC_CFG_DATA_PASS BIT(17) +#define HC_CFG_IDLE_SIO_LVL(x) ((x) << 16) +#define HC_CFG_MAN_START_EN BIT(3) +#define HC_CFG_MAN_START BIT(2) +#define HC_CFG_MAN_CS_EN BIT(1) +#define HC_CFG_MAN_CS_ASSERT BIT(0) + +#define INT_STS 0x4 +#define INT_STS_EN 0x8 +#define INT_SIG_EN 0xc +#define INT_STS_ALL GENMASK(31, 0) +#define INT_RDY_PIN BIT(26) +#define INT_RDY_SR BIT(25) +#define INT_LNR_SUSP BIT(24) +#define INT_ECC_ERR BIT(17) +#define INT_CRC_ERR BIT(16) +#define INT_LWR_DIS BIT(12) +#define INT_LRD_DIS BIT(11) +#define INT_SDMA_INT BIT(10) +#define INT_DMA_FINISH BIT(9) +#define INT_RX_NOT_FULL BIT(3) +#define INT_RX_NOT_EMPTY BIT(2) +#define INT_TX_NOT_FULL BIT(1) +#define INT_TX_EMPTY BIT(0) + +#define HC_EN 0x10 +#define HC_EN_BIT BIT(0) + +#define TXD(x) (0x14 + ((x) * 4)) +#define RXD 0x24 + +#define SS_CTRL(s) (0x30 + ((s) * 4)) +#define LRD_CFG 0x44 +#define LWR_CFG 0x80 +#define RWW_CFG 0x70 +#define OP_READ BIT(23) +#define OP_DUMMY_CYC(x) ((x) << 17) +#define OP_ADDR_BYTES(x) ((x) << 14) +#define OP_CMD_BYTES(x) (((x) - 1) << 13) +#define OP_OCTA_CRC_EN BIT(12) +#define OP_DQS_EN BIT(11) +#define OP_ENHC_EN BIT(10) +#define OP_PREAMBLE_EN BIT(9) +#define OP_DATA_DDR BIT(8) +#define OP_DATA_BUSW(x) ((x) << 6) +#define OP_ADDR_DDR BIT(5) +#define OP_ADDR_BUSW(x) ((x) << 3) +#define OP_CMD_DDR BIT(2) +#define OP_CMD_BUSW(x) (x) +#define OP_BUSW_1 0 +#define OP_BUSW_2 1 +#define OP_BUSW_4 2 +#define OP_BUSW_8 3 + +#define OCTA_CRC 0x38 +#define OCTA_CRC_IN_EN(s) BIT(3 + ((s) * 16)) +#define OCTA_CRC_CHUNK(s, x) ((fls((x) / 32)) << (1 + ((s) * 16))) +#define OCTA_CRC_OUT_EN(s) BIT(0 + ((s) * 16)) + +#define ONFI_DIN_CNT(s) (0x3c + (s)) + +#define LRD_CTRL 0x48 +#define RWW_CTRL 0x74 +#define LWR_CTRL 0x84 +#define LMODE_EN BIT(31) +#define LMODE_SLV_ACT(x) ((x) << 21) +#define LMODE_CMD1(x) ((x) << 8) +#define LMODE_CMD0(x) (x) + +#define LRD_ADDR 0x4c +#define LWR_ADDR 0x88 +#define LRD_RANGE 0x50 +#define LWR_RANGE 0x8c + +#define AXI_SLV_ADDR 0x54 + +#define DMAC_RD_CFG 0x58 +#define DMAC_WR_CFG 0x94 +#define DMAC_CFG_PERIPH_EN BIT(31) +#define DMAC_CFG_ALLFLUSH_EN BIT(30) +#define DMAC_CFG_LASTFLUSH_EN BIT(29) +#define DMAC_CFG_QE(x) (((x) + 1) << 16) +#define DMAC_CFG_BURST_LEN(x) (((x) + 1) << 12) +#define DMAC_CFG_BURST_SZ(x) ((x) << 8) +#define DMAC_CFG_DIR_READ BIT(1) +#define DMAC_CFG_START BIT(0) + +#define DMAC_RD_CNT 0x5c +#define DMAC_WR_CNT 0x98 + +#define SDMA_ADDR 0x60 + +#define DMAM_CFG 0x64 +#define DMAM_CFG_START BIT(31) +#define DMAM_CFG_CONT BIT(30) +#define DMAM_CFG_SDMA_GAP(x) (fls((x) / 8192) << 2) +#define DMAM_CFG_DIR_READ BIT(1) +#define DMAM_CFG_EN BIT(0) + +#define DMAM_CNT 0x68 + +#define LNR_TIMER_TH 0x6c + +#define RDM_CFG0 0x78 +#define RDM_CFG0_POLY(x) (x) + +#define RDM_CFG1 0x7c +#define RDM_CFG1_RDM_EN BIT(31) +#define RDM_CFG1_SEED(x) (x) + +#define LWR_SUSP_CTRL 0x90 +#define LWR_SUSP_CTRL_EN BIT(31) + +#define DMAS_CTRL 0x9c +#define DMAS_CTRL_EN BIT(31) +#define DMAS_CTRL_DIR_READ BIT(30) + +#define DATA_STROB 0xa0 +#define DATA_STROB_EDO_EN BIT(2) +#define DATA_STROB_INV_POL BIT(1) +#define DATA_STROB_DELAY_2CYC BIT(0) + +#define IDLY_CODE(x) (0xa4 + ((x) * 4)) +#define IDLY_CODE_VAL(x, v) ((v) << (((x) % 4) * 8)) + +#define GPIO 0xc4 +#define GPIO_PT(x) BIT(3 + ((x) * 16)) +#define GPIO_RESET(x) BIT(2 + ((x) * 16)) +#define GPIO_HOLDB(x) BIT(1 + ((x) * 16)) +#define GPIO_WPB(x) BIT((x) * 16) + +#define HC_VER 0xd0 + +#define HW_TEST(x) (0xe0 + ((x) * 4)) + +struct mxic_spi_priv { + struct clk *send_clk; + struct clk *send_dly_clk; + void __iomem *regs; + u32 cur_speed_hz; +}; + +static int mxic_spi_clk_enable(struct mxic_spi_priv *priv) +{ + int ret; + + ret = clk_prepare_enable(priv->send_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->send_dly_clk); + if (ret) + goto err_send_dly_clk; + + return ret; + +err_send_dly_clk: + clk_disable_unprepare(priv->send_clk); + + return ret; +} + +static void mxic_spi_clk_disable(struct mxic_spi_priv *priv) +{ + clk_disable_unprepare(priv->send_clk); + clk_disable_unprepare(priv->send_dly_clk); +} + +static void mxic_spi_set_input_delay_dqs(struct mxic_spi_priv *priv, + u8 idly_code) +{ + writel(IDLY_CODE_VAL(0, idly_code) | + IDLY_CODE_VAL(1, idly_code) | + IDLY_CODE_VAL(2, idly_code) | + IDLY_CODE_VAL(3, idly_code), + priv->regs + IDLY_CODE(0)); + writel(IDLY_CODE_VAL(4, idly_code) | + IDLY_CODE_VAL(5, idly_code) | + IDLY_CODE_VAL(6, idly_code) | + IDLY_CODE_VAL(7, idly_code), + priv->regs + IDLY_CODE(1)); +} + +static int mxic_spi_clk_setup(struct mxic_spi_priv *priv, uint freq) +{ + int ret; + + ret = clk_set_rate(priv->send_clk, freq); + if (ret) + return ret; + + ret = clk_set_rate(priv->send_dly_clk, freq); + if (ret) + return ret; + + /* + * A constant delay range from 0x0 ~ 0x1F for input delay, + * the unit is 78 ps, the max input delay is 2.418 ns. + */ + mxic_spi_set_input_delay_dqs(priv, 0xf); + + return 0; +} + +static int mxic_spi_set_speed(struct udevice *bus, uint freq) +{ + struct mxic_spi_priv *priv = dev_get_priv(bus); + int ret; + + if (priv->cur_speed_hz == freq) + return 0; + + mxic_spi_clk_disable(priv); + ret = mxic_spi_clk_setup(priv, freq); + if (ret) + return ret; + + ret = mxic_spi_clk_enable(priv); + if (ret) + return ret; + + priv->cur_speed_hz = freq; + + return 0; +} + +static int mxic_spi_set_mode(struct udevice *bus, uint mode) +{ + struct mxic_spi_priv *priv = dev_get_priv(bus); + u32 hc_config = 0; + + if (mode & SPI_CPHA) + hc_config |= HC_CFG_CLK_PH_EN; + if (mode & SPI_CPOL) + hc_config |= HC_CFG_CLK_POL_INV; + + writel(hc_config, priv->regs + HC_CFG); + + return 0; +} + +static void mxic_spi_hw_init(struct mxic_spi_priv *priv) +{ + writel(0, priv->regs + DATA_STROB); + writel(INT_STS_ALL, priv->regs + INT_STS_EN); + writel(0, priv->regs + HC_EN); + writel(0, priv->regs + LRD_CFG); + writel(0, priv->regs + LRD_CTRL); + writel(HC_CFG_NIO(1) | HC_CFG_TYPE(0, HC_CFG_TYPE_SPI_NOR) | + HC_CFG_SLV_ACT(0) | HC_CFG_MAN_CS_EN | HC_CFG_IDLE_SIO_LVL(1), + priv->regs + HC_CFG); +} + +static int mxic_spi_data_xfer(struct mxic_spi_priv *priv, const void *txbuf, + void *rxbuf, unsigned int len) +{ + unsigned int pos = 0; + + while (pos < len) { + unsigned int nbytes = len - pos; + u32 data = 0xffffffff; + u32 sts; + int ret; + + if (nbytes > 4) + nbytes = 4; + + if (txbuf) + memcpy(&data, txbuf + pos, nbytes); + + ret = readl_poll_timeout(priv->regs + INT_STS, sts, + sts & INT_TX_EMPTY, 1000000); + if (ret) + return ret; + + writel(data, priv->regs + TXD(nbytes % 4)); + + if (rxbuf) { + ret = readl_poll_timeout(priv->regs + INT_STS, sts, + sts & INT_TX_EMPTY, + 1000000); + if (ret) + return ret; + + ret = readl_poll_timeout(priv->regs + INT_STS, sts, + sts & INT_RX_NOT_EMPTY, + 1000000); + if (ret) + return ret; + + data = readl(priv->regs + RXD); + data >>= (8 * (4 - nbytes)); + memcpy(rxbuf + pos, &data, nbytes); + WARN_ON(readl(priv->regs + INT_STS) & INT_RX_NOT_EMPTY); + } else { + readl(priv->regs + RXD); + } + WARN_ON(readl(priv->regs + INT_STS) & INT_RX_NOT_EMPTY); + + pos += nbytes; + } + + return 0; +} + +static bool mxic_spi_mem_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + if (op->data.buswidth > 8 || op->addr.buswidth > 8 || + op->dummy.buswidth > 8 || op->cmd.buswidth > 8) + return false; + + if (op->addr.nbytes > 7) + return false; + + return spi_mem_default_supports_op(slave, op); +} + +static int mxic_spi_mem_exec_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(slave->dev); + struct udevice *bus = slave->dev->parent; + struct mxic_spi_priv *priv = dev_get_priv(bus); + int nio = 1, i, ret; + u32 ss_ctrl; + u8 addr[8], dummy_bytes = 0; + + if (slave->mode & (SPI_TX_OCTAL | SPI_RX_OCTAL)) + nio = 8; + else if (slave->mode & (SPI_TX_QUAD | SPI_RX_QUAD)) + nio = 4; + else if (slave->mode & (SPI_TX_DUAL | SPI_RX_DUAL)) + nio = 2; + + writel(HC_CFG_NIO(nio) | + HC_CFG_TYPE(slave_plat->cs, HC_CFG_TYPE_SPI_NOR) | + HC_CFG_SLV_ACT(slave_plat->cs) | HC_CFG_IDLE_SIO_LVL(1) | + HC_CFG_MAN_CS_EN, + priv->regs + HC_CFG); + writel(HC_EN_BIT, priv->regs + HC_EN); + + ss_ctrl = OP_CMD_BYTES(1) | OP_CMD_BUSW(fls(op->cmd.buswidth) - 1); + + if (op->addr.nbytes) + ss_ctrl |= OP_ADDR_BYTES(op->addr.nbytes) | + OP_ADDR_BUSW(fls(op->addr.buswidth) - 1); + + /* + * Since the SPI MXIC dummy buswidth is aligned with the data buswidth, + * the dummy byte needs to be recalculated to send out the correct + * dummy cycle. + */ + if (op->dummy.nbytes) { + dummy_bytes = op->dummy.nbytes / + op->addr.buswidth * + op->data.buswidth; + ss_ctrl |= OP_DUMMY_CYC(dummy_bytes); + } + + if (op->data.nbytes) { + ss_ctrl |= OP_DATA_BUSW(fls(op->data.buswidth) - 1); + if (op->data.dir == SPI_MEM_DATA_IN) + ss_ctrl |= OP_READ; + } + + writel(ss_ctrl, priv->regs + SS_CTRL(slave_plat->cs)); + + writel(readl(priv->regs + HC_CFG) | HC_CFG_MAN_CS_ASSERT, + priv->regs + HC_CFG); + + ret = mxic_spi_data_xfer(priv, &op->cmd.opcode, NULL, 1); + if (ret) + goto out; + + for (i = 0; i < op->addr.nbytes; i++) + addr[i] = op->addr.val >> (8 * (op->addr.nbytes - i - 1)); + + ret = mxic_spi_data_xfer(priv, addr, NULL, op->addr.nbytes); + if (ret) + goto out; + + ret = mxic_spi_data_xfer(priv, NULL, NULL, dummy_bytes); + if (ret) + goto out; + + ret = mxic_spi_data_xfer(priv, + op->data.dir == SPI_MEM_DATA_OUT ? + op->data.buf.out : NULL, + op->data.dir == SPI_MEM_DATA_IN ? + op->data.buf.in : NULL, + op->data.nbytes); + +out: + writel(readl(priv->regs + HC_CFG) & ~HC_CFG_MAN_CS_ASSERT, + priv->regs + HC_CFG); + writel(0, priv->regs + HC_EN); + + return ret; +} + +static const struct spi_controller_mem_ops mxic_spi_mem_ops = { + .supports_op = mxic_spi_mem_supports_op, + .exec_op = mxic_spi_mem_exec_op, +}; + +static int mxic_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct mxic_spi_priv *priv = dev_get_priv(bus); + + writel(readl(priv->regs + HC_CFG) | HC_CFG_MAN_CS_EN, + priv->regs + HC_CFG); + writel(HC_EN_BIT, priv->regs + HC_EN); + writel(readl(priv->regs + HC_CFG) | HC_CFG_MAN_CS_ASSERT, + priv->regs + HC_CFG); + + return 0; +} + +static int mxic_spi_release_bus(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct mxic_spi_priv *priv = dev_get_priv(bus); + + writel(readl(priv->regs + HC_CFG) & ~HC_CFG_MAN_CS_ASSERT, + priv->regs + HC_CFG); + writel(0, priv->regs + HC_EN); + + return 0; +} + +static int mxic_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev_get_parent(dev); + struct mxic_spi_priv *priv = dev_get_priv(bus); + struct spi_slave *slave = dev_get_parent_priv(dev); + unsigned int busw = OP_BUSW_1; + unsigned int len = bitlen / 8; + int ret; + + if (dout && din) { + if (((slave->mode & SPI_TX_QUAD) && + !(slave->mode & SPI_RX_QUAD)) || + ((slave->mode & SPI_TX_DUAL) && + !(slave->mode & SPI_RX_DUAL))) + return -ENOTSUPP; + } + + if (din) { + if (slave->mode & SPI_TX_QUAD) + busw = OP_BUSW_4; + else if (slave->mode & SPI_TX_DUAL) + busw = OP_BUSW_2; + } else if (dout) { + if (slave->mode & SPI_RX_QUAD) + busw = OP_BUSW_4; + else if (slave->mode & SPI_RX_DUAL) + busw = OP_BUSW_2; + } + + writel(OP_CMD_BYTES(1) | OP_CMD_BUSW(busw) | + OP_DATA_BUSW(busw) | (din ? OP_READ : 0), + priv->regs + SS_CTRL(0)); + + ret = mxic_spi_data_xfer(priv, dout, din, len); + if (ret) + return ret; + + return 0; +} + +static int mxic_spi_probe(struct udevice *bus) +{ + struct mxic_spi_priv *priv = dev_get_priv(bus); + + priv->regs = (void *)dev_read_addr(bus); + + priv->send_clk = devm_clk_get(bus, "send_clk"); + if (IS_ERR(priv->send_clk)) + return PTR_ERR(priv->send_clk); + + priv->send_dly_clk = devm_clk_get(bus, "send_dly_clk"); + if (IS_ERR(priv->send_dly_clk)) + return PTR_ERR(priv->send_dly_clk); + + mxic_spi_hw_init(priv); + + return 0; +} + +static const struct dm_spi_ops mxic_spi_ops = { + .claim_bus = mxic_spi_claim_bus, + .release_bus = mxic_spi_release_bus, + .xfer = mxic_spi_xfer, + .set_speed = mxic_spi_set_speed, + .set_mode = mxic_spi_set_mode, + .mem_ops = &mxic_spi_mem_ops, +}; + +static const struct udevice_id mxic_spi_ids[] = { + { .compatible = "mxicy,mx25f0a-spi", }, + { } +}; + +U_BOOT_DRIVER(mxic_spi) = { + .name = "mxic_spi", + .id = UCLASS_SPI, + .of_match = mxic_spi_ids, + .ops = &mxic_spi_ops, + .priv_auto = sizeof(struct mxic_spi_priv), + .probe = mxic_spi_probe, +}; |