From caf110798ccca5d1fe7e75ae73397212262b9ee9 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:43 -0400 Subject: spi: dw: Fix driving MOSI low while recieving The resting state of MOSI is high when nothing is driving it. If we drive it low while recieving, it looks like we are transmitting 0x00 instead of transmitting nothing. This can confuse slaves (like SD cards) which allow new commands to be sent over MOSI while they are returning data over MISO. The return of MOSI from 0 to 1 at the end of recieving a byte can look like a start bit and a transmission bit to an SD card. This will cause the card to become out-of-sync with the SPI device, as it thinks the device has already started transmitting two bytes of a new command. The mmc-spi driver will not detect the R1 response from the SD card, since it is sent too early, and offset by two bits. This patch fixes transfer errors when using SD cards with dw spi. Signed-off-by: Sean Anderson Reviewed-by: Jagan Teki Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 2559aac2e9..74372171aa 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -322,7 +322,7 @@ static inline u32 rx_max(struct dw_spi_priv *priv) static void dw_writer(struct dw_spi_priv *priv) { u32 max = tx_max(priv); - u16 txw = 0; + u16 txw = 0xFFFF; while (max--) { /* Set the tx word if the transfer's original "tx" is not null */ -- cgit From 1b3dd491e6fc787f47a3ae91f6a58aeb97466140 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:44 -0400 Subject: spi: dw: Convert calls to debug to dev_* This allows different log levels to be enabled or disabled depending on the desired level of verbosity. In particular, it allows for general debug information to be printed while excluding more verbose logging which may interfere with timing. Signed-off-by: Sean Anderson Reviewed-by: Jagan Teki Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 74372171aa..b23655d4d9 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -9,6 +9,7 @@ * Copyright (c) 2009, Intel Corporation. */ +#define LOG_CATEGORY UCLASS_SPI #include #include #include @@ -139,7 +140,7 @@ static int request_gpio_cs(struct udevice *bus) return 0; if (ret < 0) { - printf("Error: %d: Can't get %s gpio!\n", ret, bus->name); + dev_err(bus, "Couldn't request gpio! (error %d)\n", ret); return ret; } @@ -148,7 +149,7 @@ static int request_gpio_cs(struct udevice *bus) GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); } - debug("%s: used external gpio for CS management\n", __func__); + dev_dbg(bus, "Using external gpio for CS management\n"); #endif return 0; } @@ -162,8 +163,7 @@ static int dw_spi_ofdata_to_platdata(struct udevice *bus) /* Use 500KHz as a suitable default */ plat->frequency = dev_read_u32_default(bus, "spi-max-frequency", 500000); - debug("%s: regs=%p max-frequency=%d\n", __func__, plat->regs, - plat->frequency); + dev_info(bus, "max-frequency=%d\n", plat->frequency); return request_gpio_cs(bus); } @@ -174,7 +174,7 @@ static inline void spi_enable_chip(struct dw_spi_priv *priv, int enable) } /* Restart the controller, disable all interrupts, clean rx fifo */ -static void spi_hw_init(struct dw_spi_priv *priv) +static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) { spi_enable_chip(priv, 0); dw_write(priv, DW_SPI_IMR, 0xff); @@ -196,7 +196,7 @@ static void spi_hw_init(struct dw_spi_priv *priv) priv->fifo_len = (fifo == 1) ? 0 : fifo; dw_write(priv, DW_SPI_TXFLTR, 0); } - debug("%s: fifo_len=%d\n", __func__, priv->fifo_len); + dev_dbg(bus, "fifo_len=%d\n", priv->fifo_len); } /* @@ -221,8 +221,7 @@ __weak int dw_spi_get_clk(struct udevice *bus, ulong *rate) if (!*rate) goto err_rate; - debug("%s: get spi controller clk via device tree: %lu Hz\n", - __func__, *rate); + dev_dbg(bus, "Got clock via device tree: %lu Hz\n", *rate); return 0; @@ -247,14 +246,16 @@ static int dw_spi_reset(struct udevice *bus) if (ret == -ENOENT || ret == -ENOTSUPP) return 0; - dev_warn(bus, "Can't get reset: %d\n", ret); + dev_warn(bus, "Couldn't find/assert reset device (error %d)\n", + ret); return ret; } ret = reset_deassert_bulk(&priv->resets); if (ret) { reset_release_bulk(&priv->resets); - dev_err(bus, "Failed to reset: %d\n", ret); + dev_err(bus, "Failed to de-assert reset for SPI (error %d)\n", + ret); return ret; } @@ -284,7 +285,7 @@ static int dw_spi_probe(struct udevice *bus) priv->tmode = 0; /* Tx & Rx */ /* Basic HW init */ - spi_hw_init(priv); + spi_hw_init(bus, priv); return 0; } @@ -333,7 +334,7 @@ static void dw_writer(struct dw_spi_priv *priv) txw = *(u16 *)(priv->tx); } dw_write(priv, DW_SPI_DR, txw); - debug("%s: tx=0x%02x\n", __func__, txw); + log_content("tx=0x%02x\n", txw); priv->tx += priv->bits_per_word >> 3; } } @@ -345,7 +346,7 @@ static void dw_reader(struct dw_spi_priv *priv) while (max--) { rxw = dw_read(priv, DW_SPI_DR); - debug("%s: rx=0x%02x\n", __func__, rxw); + log_content("rx=0x%02x\n", rxw); /* Care about rx if the transfer's original "rx" is not null */ if (priv->rx_end - priv->len) { @@ -400,7 +401,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, /* spi core configured to do 8 bit transfers */ if (bitlen % 8) { - debug("Non byte aligned SPI transfer.\n"); + dev_err(dev, "Non byte aligned SPI transfer.\n"); return -1; } @@ -427,7 +428,6 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, cr0 |= (priv->tmode << SPI_TMOD_OFFSET); priv->len = bitlen >> 3; - debug("%s: rx=%p tx=%p len=%d [bytes]\n", __func__, rx, tx, priv->len); priv->tx = (void *)tx; priv->tx_end = priv->tx + priv->len; @@ -437,7 +437,8 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, /* Disable controller before writing control registers */ spi_enable_chip(priv, 0); - debug("%s: cr0=%08x\n", __func__, cr0); + dev_dbg(dev, "cr0=%08x rx=%p tx=%p len=%d [bytes]\n", cr0, rx, tx, + priv->len); /* Reprogram cr0 only if changed */ if (dw_read(priv, DW_SPI_CTRL0) != cr0) dw_write(priv, DW_SPI_CTRL0, cr0); @@ -497,8 +498,7 @@ static int dw_spi_set_speed(struct udevice *bus, uint speed) spi_enable_chip(priv, 1); priv->freq = speed; - debug("%s: regs=%p speed=%d clk_div=%d\n", __func__, priv->regs, - priv->freq, clk_div); + dev_dbg(bus, "speed=%d clk_div=%d\n", priv->freq, clk_div); return 0; } @@ -513,7 +513,7 @@ static int dw_spi_set_mode(struct udevice *bus, uint mode) * real transfer function. */ priv->mode = mode; - debug("%s: regs=%p, mode=%d\n", __func__, priv->regs, priv->mode); + dev_dbg(bus, "mode=%d\n", priv->mode); return 0; } -- cgit From 13fc44e2223b90290a2bfdfe97d3a9d37b90c7a0 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:45 -0400 Subject: spi: dw: Rename "cs-gpio" to "cs-gpios" This property is named differently than other SPI drivers with the same property, as well as the property as used in Linux. Signed-off-by: Sean Anderson Tested-by Eugeniy Paltsev Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index b23655d4d9..32de33f695 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -135,7 +135,8 @@ static int request_gpio_cs(struct udevice *bus) int ret; /* External chip select gpio line is optional */ - ret = gpio_request_by_name(bus, "cs-gpio", 0, &priv->cs_gpio, 0); + ret = gpio_request_by_name(bus, "cs-gpios", 0, &priv->cs_gpio, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); if (ret == -ENOENT) return 0; -- cgit From c785f43ffdf402ecd109bbce972c25e32bc4c85b Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:46 -0400 Subject: spi: dw: Use generic function to read reg address Using an fdt-specific function causes problems when compiled with a live tree. Signed-off-by: Sean Anderson Tested-by Eugeniy Paltsev Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 32de33f695..e8ba80ef41 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -160,6 +160,8 @@ static int dw_spi_ofdata_to_platdata(struct udevice *bus) struct dw_spi_platdata *plat = bus->platdata; plat->regs = dev_read_addr_ptr(bus); + if (!plat->regs) + return -EINVAL; /* Use 500KHz as a suitable default */ plat->frequency = dev_read_u32_default(bus, "spi-max-frequency", -- cgit From 3004034989ac6d4752c384a5c74a69f37b8ce9d1 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:47 -0400 Subject: spi: dw: Rename registers to match datasheet A few registers had slightly different names from what is in the datasheet. Signed-off-by: Sean Anderson Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index e8ba80ef41..8abcdde8a3 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -27,14 +27,14 @@ #include /* Register offsets */ -#define DW_SPI_CTRL0 0x00 -#define DW_SPI_CTRL1 0x04 +#define DW_SPI_CTRLR0 0x00 +#define DW_SPI_CTRLR1 0x04 #define DW_SPI_SSIENR 0x08 #define DW_SPI_MWCR 0x0c #define DW_SPI_SER 0x10 #define DW_SPI_BAUDR 0x14 -#define DW_SPI_TXFLTR 0x18 -#define DW_SPI_RXFLTR 0x1c +#define DW_SPI_TXFTLR 0x18 +#define DW_SPI_RXFTLR 0x1c #define DW_SPI_TXFLR 0x20 #define DW_SPI_RXFLR 0x24 #define DW_SPI_SR 0x28 @@ -191,13 +191,13 @@ static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) u32 fifo; for (fifo = 1; fifo < 256; fifo++) { - dw_write(priv, DW_SPI_TXFLTR, fifo); - if (fifo != dw_read(priv, DW_SPI_TXFLTR)) + dw_write(priv, DW_SPI_TXFTLR, fifo); + if (fifo != dw_read(priv, DW_SPI_TXFTLR)) break; } priv->fifo_len = (fifo == 1) ? 0 : fifo; - dw_write(priv, DW_SPI_TXFLTR, 0); + dw_write(priv, DW_SPI_TXFTLR, 0); } dev_dbg(bus, "fifo_len=%d\n", priv->fifo_len); } @@ -443,8 +443,8 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, dev_dbg(dev, "cr0=%08x rx=%p tx=%p len=%d [bytes]\n", cr0, rx, tx, priv->len); /* Reprogram cr0 only if changed */ - if (dw_read(priv, DW_SPI_CTRL0) != cr0) - dw_write(priv, DW_SPI_CTRL0, cr0); + if (dw_read(priv, DW_SPI_CTRLR0) != cr0) + dw_write(priv, DW_SPI_CTRLR0, cr0); /* * Configure the desired SS (slave select 0...3) in the controller -- cgit From 934beab88248fa8e9a07f2f046427006156dca97 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:48 -0400 Subject: spi: dw: Remove spi_enable_chip This function does nothing but wrap dw_write. Signed-off-by: Sean Anderson Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 8abcdde8a3..89a8266052 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -171,17 +171,12 @@ static int dw_spi_ofdata_to_platdata(struct udevice *bus) return request_gpio_cs(bus); } -static inline void spi_enable_chip(struct dw_spi_priv *priv, int enable) -{ - dw_write(priv, DW_SPI_SSIENR, (enable ? 1 : 0)); -} - /* Restart the controller, disable all interrupts, clean rx fifo */ static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) { - spi_enable_chip(priv, 0); + dw_write(priv, DW_SPI_SSIENR, 0); dw_write(priv, DW_SPI_IMR, 0xff); - spi_enable_chip(priv, 1); + dw_write(priv, DW_SPI_SSIENR, 1); /* * Try to detect the FIFO depth if not set by interface driver, @@ -438,7 +433,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, priv->rx_end = priv->rx + priv->len; /* Disable controller before writing control registers */ - spi_enable_chip(priv, 0); + dw_write(priv, DW_SPI_SSIENR, 0); dev_dbg(dev, "cr0=%08x rx=%p tx=%p len=%d [bytes]\n", cr0, rx, tx, priv->len); @@ -455,7 +450,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, dw_write(priv, DW_SPI_SER, 1 << cs); /* Enable controller after writing control registers */ - spi_enable_chip(priv, 1); + dw_write(priv, DW_SPI_SSIENR, 1); /* Start transfer in a polling loop */ ret = poll_transfer(priv); @@ -490,7 +485,7 @@ static int dw_spi_set_speed(struct udevice *bus, uint speed) speed = plat->frequency; /* Disable controller before writing control registers */ - spi_enable_chip(priv, 0); + dw_write(priv, DW_SPI_SSIENR, 0); /* clk_div doesn't support odd number */ clk_div = priv->bus_clk_rate / speed; @@ -498,7 +493,7 @@ static int dw_spi_set_speed(struct udevice *bus, uint speed) dw_write(priv, DW_SPI_BAUDR, clk_div); /* Enable controller after writing control registers */ - spi_enable_chip(priv, 1); + dw_write(priv, DW_SPI_SSIENR, 1); priv->freq = speed; dev_dbg(bus, "speed=%d clk_div=%d\n", priv->freq, clk_div); -- cgit From ddd3450f399051478263e823f5f778745824bdb3 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:49 -0400 Subject: spi: dw: Rearrange struct dw_spi_priv This should reduce the size of the struct, and also groups more similar fields together. Signed-off-by: Sean Anderson Tested-by Eugeniy Paltsev Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 89a8266052..0ebd2cf3cb 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -95,27 +95,26 @@ struct dw_spi_platdata { }; struct dw_spi_priv { - void __iomem *regs; - unsigned int freq; /* Default frequency */ - unsigned int mode; struct clk clk; - unsigned long bus_clk_rate; - + struct reset_ctl_bulk resets; struct gpio_desc cs_gpio; /* External chip-select gpio */ - int bits_per_word; - u8 cs; /* chip select pin */ - u8 tmode; /* TR/TO/RO/EEPROM */ - u8 type; /* SPI/SSP/MicroWire */ - int len; + void __iomem *regs; + unsigned long bus_clk_rate; + unsigned int freq; /* Default frequency */ + unsigned int mode; - u32 fifo_len; /* depth of the FIFO buffer */ - void *tx; - void *tx_end; + const void *tx; + const void *tx_end; void *rx; void *rx_end; + u32 fifo_len; /* depth of the FIFO buffer */ - struct reset_ctl_bulk resets; + int bits_per_word; + int len; + u8 cs; /* chip select pin */ + u8 tmode; /* TR/TO/RO/EEPROM */ + u8 type; /* SPI/SSP/MicroWire */ }; static inline u32 dw_read(struct dw_spi_priv *priv, u32 offset) -- cgit From 58875790fd50a78bed8df3c75b50285da1729d3c Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:51 -0400 Subject: spi: dw: Add support for multiple CTRLR0 layouts CTRLR0 can have several different layouts depending on the specific device (dw-apb-ssi vs dwc-ssi), and specific parameters set during synthesis. Update the driver to support three specific configurations: dw-apb-ssi with SSI_MAX_XFER_SIZE=16, dw-apb-ssi with SSI_MAX_XFER_SIZE=32, and dwc-ssi. dw-apb-ssi is the version of the device on Altera/Intel SoCFPGAs, MSCC SoCs, and Canaan Kendryte K210 SoCs. This is the only version this driver supported before this change. The register layout before version 3.23a is: | 31 .. 16 | | other stuff | | 15 .. 10 | 9 .. 8 | 7 .. 6 | 5 .. 4 | 3 .. 0 | | other stuff | TMOD | MODE | FRF | DFS | Note that DFS (Data Frame Size) is only 4 bits, limiting transfers to data frames of 16 bits or less. In version 3.23a, the SSI_MAX_XFER_SIZE parameter was introduced. This parameter defaults to 16 (resulting in the same layout as prior versions), but may also be set to 32. To allow setting longer data frame sizes, a new DFS_32 register was introduced: | 31 .. 21 | 20 .. 16 | | other stuff | DFS_32 | | 15 .. 10 | 9 .. 8 | 7 .. 6 | 5 .. 4 | 3 .. 0 | | other stuff | TMOD | MODE | FRF | all zeros | The old DFS field no longer controls the data frame size. To detect this layout, we try writing 0xF to DFS. If we read back 0x0, then this device has SSI_MAX_XFER_SIZE=32. dwc-ssi is the version of the device on Intel Keem Bay SoCs and Canaan Kendryte K210 SoCs. The layout of ctrlr0 is: | 31 .. 16 | | other stuff | | 15 .. 12 | 11 .. 10 | 9 .. 8 | 7 .. 6 | 4 .. 0 | | other stuff | TMOD | MODE | FRF | DFS_32 | The semantics of the fields have not changed since the previous version. However, SSI_MAX_XFER_SIZE is effectively always 32. To support these different layouts, we model our approach on the one which the Linux kernel has taken. During probe, the driver calls an init function stored in driver_data. This init function is responsible for determining the layout of CTRLR0, and supplying the update_cr0 function. The style of and information behind this commit is based on the Linux MMIO driver for these devices. Specific reference was made to the series adding support for Intel Keem Bay SoCs [1]. [1] https://lore.kernel.org/linux-spi/20200505130618.554-1-wan.ahmad.zainie.wan.mohamad@intel.com/ Signed-off-by: Sean Anderson Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 184 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 149 insertions(+), 35 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 0ebd2cf3cb..9e02fce6c6 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -3,6 +3,7 @@ * Designware master SPI core controller driver * * Copyright (C) 2014 Stefan Roese + * Copyright (C) 2020 Sean Anderson * * Very loosely based on the Linux driver: * drivers/spi/spi-dw.c, which is: @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -54,28 +56,48 @@ #define DW_SPI_DR 0x60 /* Bit fields in CTRLR0 */ -#define SPI_DFS_OFFSET 0 - -#define SPI_FRF_OFFSET 4 -#define SPI_FRF_SPI 0x0 -#define SPI_FRF_SSP 0x1 -#define SPI_FRF_MICROWIRE 0x2 -#define SPI_FRF_RESV 0x3 - -#define SPI_MODE_OFFSET 6 -#define SPI_SCPH_OFFSET 6 -#define SPI_SCOL_OFFSET 7 - -#define SPI_TMOD_OFFSET 8 -#define SPI_TMOD_MASK (0x3 << SPI_TMOD_OFFSET) -#define SPI_TMOD_TR 0x0 /* xmit & recv */ -#define SPI_TMOD_TO 0x1 /* xmit only */ -#define SPI_TMOD_RO 0x2 /* recv only */ -#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ - -#define SPI_SLVOE_OFFSET 10 -#define SPI_SRL_OFFSET 11 -#define SPI_CFS_OFFSET 12 +/* + * Only present when SSI_MAX_XFER_SIZE=16. This is the default, and the only + * option before version 3.23a. + */ +#define CTRLR0_DFS_MASK GENMASK(3, 0) + +#define CTRLR0_FRF_MASK GENMASK(5, 4) +#define CTRLR0_FRF_SPI 0x0 +#define CTRLR0_FRF_SSP 0x1 +#define CTRLR0_FRF_MICROWIRE 0x2 +#define CTRLR0_FRF_RESV 0x3 + +#define CTRLR0_MODE_MASK GENMASK(7, 6) +#define CTRLR0_MODE_SCPH 0x1 +#define CTRLR0_MODE_SCPOL 0x2 + +#define CTRLR0_TMOD_MASK GENMASK(9, 8) +#define CTRLR0_TMOD_TR 0x0 /* xmit & recv */ +#define CTRLR0_TMOD_TO 0x1 /* xmit only */ +#define CTRLR0_TMOD_RO 0x2 /* recv only */ +#define CTRLR0_TMOD_EPROMREAD 0x3 /* eeprom read mode */ + +#define CTRLR0_SLVOE_OFFSET 10 +#define CTRLR0_SRL_OFFSET 11 +#define CTRLR0_CFS_MASK GENMASK(15, 12) + +/* Only present when SSI_MAX_XFER_SIZE=32 */ +#define CTRLR0_DFS_32_MASK GENMASK(20, 16) + +/* The next field is only present on versions after 4.00a */ +#define CTRLR0_SPI_FRF_MASK GENMASK(22, 21) +#define CTRLR0_SPI_FRF_BYTE 0x0 +#define CTRLR0_SPI_FRF_DUAL 0x1 +#define CTRLR0_SPI_FRF_QUAD 0x2 + +/* Bit fields in CTRLR0 based on DWC_ssi_databook.pdf v1.01a */ +#define DWC_SSI_CTRLR0_DFS_MASK GENMASK(4, 0) +#define DWC_SSI_CTRLR0_FRF_MASK GENMASK(7, 6) +#define DWC_SSI_CTRLR0_MODE_MASK GENMASK(9, 8) +#define DWC_SSI_CTRLR0_TMOD_MASK GENMASK(11, 10) +#define DWC_SSI_CTRLR0_SRL_OFFSET 13 +#define DWC_SSI_CTRLR0_SPI_FRF_MASK GENMASK(23, 22) /* Bit fields in SR, 7 bits */ #define SR_MASK GENMASK(6, 0) /* cover 7 bits */ @@ -99,6 +121,8 @@ struct dw_spi_priv { struct reset_ctl_bulk resets; struct gpio_desc cs_gpio; /* External chip-select gpio */ + u32 (*update_cr0)(struct dw_spi_priv *priv); + void __iomem *regs; unsigned long bus_clk_rate; unsigned int freq; /* Default frequency */ @@ -109,6 +133,7 @@ struct dw_spi_priv { void *rx; void *rx_end; u32 fifo_len; /* depth of the FIFO buffer */ + u32 max_xfer; /* Maximum transfer size (in bits) */ int bits_per_word; int len; @@ -127,6 +152,53 @@ static inline void dw_write(struct dw_spi_priv *priv, u32 offset, u32 val) __raw_writel(val, priv->regs + offset); } +static u32 dw_spi_dw16_update_cr0(struct dw_spi_priv *priv) +{ + return FIELD_PREP(CTRLR0_DFS_MASK, priv->bits_per_word - 1) + | FIELD_PREP(CTRLR0_FRF_MASK, priv->type) + | FIELD_PREP(CTRLR0_MODE_MASK, priv->mode) + | FIELD_PREP(CTRLR0_TMOD_MASK, priv->tmode); +} + +static u32 dw_spi_dw32_update_cr0(struct dw_spi_priv *priv) +{ + return FIELD_PREP(CTRLR0_DFS_32_MASK, priv->bits_per_word - 1) + | FIELD_PREP(CTRLR0_FRF_MASK, priv->type) + | FIELD_PREP(CTRLR0_MODE_MASK, priv->mode) + | FIELD_PREP(CTRLR0_TMOD_MASK, priv->tmode); +} + +static u32 dw_spi_dwc_update_cr0(struct dw_spi_priv *priv) +{ + return FIELD_PREP(DWC_SSI_CTRLR0_DFS_MASK, priv->bits_per_word - 1) + | FIELD_PREP(DWC_SSI_CTRLR0_FRF_MASK, priv->type) + | FIELD_PREP(DWC_SSI_CTRLR0_MODE_MASK, priv->mode) + | FIELD_PREP(DWC_SSI_CTRLR0_TMOD_MASK, priv->tmode); +} + +static int dw_spi_apb_init(struct udevice *bus, struct dw_spi_priv *priv) +{ + /* If we read zeros from DFS, then we need to use DFS_32 instead */ + dw_write(priv, DW_SPI_SSIENR, 0); + dw_write(priv, DW_SPI_CTRLR0, 0xffffffff); + if (FIELD_GET(CTRLR0_DFS_MASK, dw_read(priv, DW_SPI_CTRLR0))) { + priv->max_xfer = 16; + priv->update_cr0 = dw_spi_dw16_update_cr0; + } else { + priv->max_xfer = 32; + priv->update_cr0 = dw_spi_dw32_update_cr0; + } + + return 0; +} + +static int dw_spi_dwc_init(struct udevice *bus, struct dw_spi_priv *priv) +{ + priv->max_xfer = 32; + priv->update_cr0 = dw_spi_dwc_update_cr0; + return 0; +} + static int request_gpio_cs(struct udevice *bus) { #if CONFIG_IS_ENABLED(DM_GPIO) && !defined(CONFIG_SPL_BUILD) @@ -165,6 +237,10 @@ static int dw_spi_ofdata_to_platdata(struct udevice *bus) /* Use 500KHz as a suitable default */ plat->frequency = dev_read_u32_default(bus, "spi-max-frequency", 500000); + + if (dev_read_bool(bus, "spi-slave")) + return -EINVAL; + dev_info(bus, "max-frequency=%d\n", plat->frequency); return request_gpio_cs(bus); @@ -259,11 +335,15 @@ static int dw_spi_reset(struct udevice *bus) return 0; } +typedef int (*dw_spi_init_t)(struct udevice *bus, struct dw_spi_priv *priv); + static int dw_spi_probe(struct udevice *bus) { + dw_spi_init_t init = (dw_spi_init_t)dev_get_driver_data(bus); struct dw_spi_platdata *plat = dev_get_platdata(bus); struct dw_spi_priv *priv = dev_get_priv(bus); int ret; + u32 version; priv->regs = plat->regs; priv->freq = plat->frequency; @@ -276,6 +356,17 @@ static int dw_spi_probe(struct udevice *bus) if (ret) return ret; + if (!init) + return -EINVAL; + ret = init(bus, priv); + if (ret) + return ret; + + version = dw_read(priv, DW_SPI_VERSION); + dev_dbg(bus, "ssi_version_id=%c.%c%c%c ssi_max_xfer_size=%u\n", + version >> 24, version >> 16, version >> 8, version, + priv->max_xfer); + /* Currently only bits_per_word == 8 supported */ priv->bits_per_word = 8; @@ -320,7 +411,7 @@ static inline u32 rx_max(struct dw_spi_priv *priv) static void dw_writer(struct dw_spi_priv *priv) { u32 max = tx_max(priv); - u16 txw = 0xFFFF; + u32 txw = 0xFFFFFFFF; while (max--) { /* Set the tx word if the transfer's original "tx" is not null */ @@ -406,23 +497,18 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, if (flags & SPI_XFER_BEGIN) external_cs_manage(dev, false); - cr0 = (priv->bits_per_word - 1) | (priv->type << SPI_FRF_OFFSET) | - (priv->mode << SPI_MODE_OFFSET) | - (priv->tmode << SPI_TMOD_OFFSET); - if (rx && tx) - priv->tmode = SPI_TMOD_TR; + priv->tmode = CTRLR0_TMOD_TR; else if (rx) - priv->tmode = SPI_TMOD_RO; + priv->tmode = CTRLR0_TMOD_RO; else /* - * In transmit only mode (SPI_TMOD_TO) input FIFO never gets + * In transmit only mode (CTRL0_TMOD_TO) input FIFO never gets * any data which breaks our logic in poll_transfer() above. */ - priv->tmode = SPI_TMOD_TR; + priv->tmode = CTRLR0_TMOD_TR; - cr0 &= ~SPI_TMOD_MASK; - cr0 |= (priv->tmode << SPI_TMOD_OFFSET); + cr0 = priv->update_cr0(priv); priv->len = bitlen >> 3; @@ -476,7 +562,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, static int dw_spi_set_speed(struct udevice *bus, uint speed) { - struct dw_spi_platdata *plat = bus->platdata; + struct dw_spi_platdata *plat = dev_get_platdata(bus); struct dw_spi_priv *priv = dev_get_priv(bus); u16 clk_div; @@ -547,7 +633,35 @@ static const struct dm_spi_ops dw_spi_ops = { }; static const struct udevice_id dw_spi_ids[] = { - { .compatible = "snps,dw-apb-ssi" }, + /* Generic compatible strings */ + + { .compatible = "snps,dw-apb-ssi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,dw-apb-ssi-3.20a", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,dw-apb-ssi-3.22a", .data = (ulong)dw_spi_apb_init }, + /* First version with SSI_MAX_XFER_SIZE */ + { .compatible = "snps,dw-apb-ssi-3.23a", .data = (ulong)dw_spi_apb_init }, + /* First version with Dual/Quad SPI; unused by this driver */ + { .compatible = "snps,dw-apb-ssi-4.00a", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,dw-apb-ssi-4.01", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,dwc-ssi-1.01a", .data = (ulong)dw_spi_dwc_init }, + + /* Compatible strings for specific SoCs */ + + /* + * Both the Cyclone V and Arria V share a device tree and have the same + * version of this device. This compatible string is used for those + * devices, and is not used for sofpgas in general. + */ + { .compatible = "altr,socfpga-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "altr,socfpga-arria10-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "canaan,kendryte-k210-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "canaan,kendryte-k210-ssi", .data = (ulong)dw_spi_dwc_init }, + { .compatible = "intel,stratix10-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "intel,agilex-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "mscc,ocelot-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "mscc,jaguar2-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,axs10x-spi", .data = (ulong)dw_spi_apb_init }, + { .compatible = "snps,hsdk-spi", .data = (ulong)dw_spi_apb_init }, { } }; -- cgit From fec7bf0460dbf5a6744e1f98298a9b664f856a34 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 16 Oct 2020 18:57:53 -0400 Subject: spi: dw: Add mem_ops The designware ssi device has "broken" chip select behaviour [1], and needs specific manipulation to use the built-in chip select. The existing fix is to use an external GPIO for chip select, but typically the K210 has SPI3 directly connected to a flash chip with dedicated pins. This makes it impossible to use the spi_xfer function to use spi, since the CS is de-asserted in between calls. This patch adds an implementation of exec_op, which gives correct behaviour when reading/writing spi flash. This patch also rearranges the headers to conform to U-Boot style. [1] https://lkml.org/lkml/2015/12/23/132 Signed-off-by: Sean Anderson Tested-by Eugeniy Paltsev Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 118 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 7 deletions(-) (limited to 'drivers/spi/designware_spi.c') diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 9e02fce6c6..ce74ac0abc 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -12,21 +12,23 @@ #define LOG_CATEGORY UCLASS_SPI #include -#include -#include #include #include +#include #include -#include -#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include #include +#include #include #include -#include +#include /* Register offsets */ #define DW_SPI_CTRLR0 0x00 @@ -560,6 +562,107 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, return ret; } +/* + * This function is necessary for reading SPI flash with the native CS + * c.f. https://lkml.org/lkml/2015/12/23/132 + */ +static int dw_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) +{ + bool read = op->data.dir == SPI_MEM_DATA_IN; + int pos, i, ret = 0; + struct udevice *bus = slave->dev->parent; + struct dw_spi_priv *priv = dev_get_priv(bus); + u8 op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; + u8 op_buf[op_len]; + u32 cr0; + + if (read) + priv->tmode = CTRLR0_TMOD_EPROMREAD; + else + priv->tmode = CTRLR0_TMOD_TO; + + cr0 = priv->update_cr0(priv); + dev_dbg(bus, "cr0=%08x buf=%p len=%u [bytes]\n", cr0, op->data.buf.in, + op->data.nbytes); + + dw_write(priv, DW_SPI_SSIENR, 0); + dw_write(priv, DW_SPI_CTRLR0, cr0); + if (read) + dw_write(priv, DW_SPI_CTRLR1, op->data.nbytes - 1); + dw_write(priv, DW_SPI_SSIENR, 1); + + /* From spi_mem_exec_op */ + pos = 0; + op_buf[pos++] = op->cmd.opcode; + if (op->addr.nbytes) { + for (i = 0; i < op->addr.nbytes; i++) + op_buf[pos + i] = op->addr.val >> + (8 * (op->addr.nbytes - i - 1)); + + pos += op->addr.nbytes; + } + if (op->dummy.nbytes) + memset(op_buf + pos, 0xff, op->dummy.nbytes); + + external_cs_manage(slave->dev, false); + + priv->tx = &op_buf; + priv->tx_end = priv->tx + op_len; + priv->rx = NULL; + priv->rx_end = NULL; + while (priv->tx != priv->tx_end) + dw_writer(priv); + + /* + * XXX: The following are tight loops! Enabling debug messages may cause + * them to fail because we are not reading/writing the fifo fast enough. + */ + if (read) { + priv->rx = op->data.buf.in; + priv->rx_end = priv->rx + op->data.nbytes; + + dw_write(priv, DW_SPI_SER, 1 << spi_chip_select(slave->dev)); + while (priv->rx != priv->rx_end) + dw_reader(priv); + } else { + u32 val; + + priv->tx = op->data.buf.out; + priv->tx_end = priv->tx + op->data.nbytes; + + /* Fill up the write fifo before starting the transfer */ + dw_writer(priv); + dw_write(priv, DW_SPI_SER, 1 << spi_chip_select(slave->dev)); + while (priv->tx != priv->tx_end) + dw_writer(priv); + + if (readl_poll_timeout(priv->regs + DW_SPI_SR, val, + (val & SR_TF_EMPT) && !(val & SR_BUSY), + RX_TIMEOUT * 1000)) { + ret = -ETIMEDOUT; + } + } + + dw_write(priv, DW_SPI_SER, 0); + external_cs_manage(slave->dev, true); + + dev_dbg(bus, "%u bytes xfered\n", op->data.nbytes); + return ret; +} + +/* The size of ctrl1 limits data transfers to 64K */ +static int dw_spi_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) +{ + op->data.nbytes = min(op->data.nbytes, (unsigned int)SZ_64K); + + return 0; +} + +static const struct spi_controller_mem_ops dw_spi_mem_ops = { + .exec_op = dw_spi_exec_op, + .adjust_op_size = dw_spi_adjust_op_size, +}; + static int dw_spi_set_speed(struct udevice *bus, uint speed) { struct dw_spi_platdata *plat = dev_get_platdata(bus); @@ -624,6 +727,7 @@ static int dw_spi_remove(struct udevice *bus) static const struct dm_spi_ops dw_spi_ops = { .xfer = dw_spi_xfer, + .mem_ops = &dw_spi_mem_ops, .set_speed = dw_spi_set_speed, .set_mode = dw_spi_set_mode, /* -- cgit