X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=drivers%2Fspi%2Fdesignware_spi.c;h=d8b73ea326f1ab8ee0b9537ae82be587548de5b9;hb=83d290c56fab2d38cd1ab4c4cc7099559c1d5046;hp=c501aeea166e2cae4fdb13f7a1f9d569ebcda20e;hpb=ab21ecef7a38dd211fe6db35c6e60800445eb6a2;p=oweals%2Fu-boot.git diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index c501aeea16..d8b73ea326 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Designware master SPI core controller driver * @@ -6,10 +7,9 @@ * Very loosely based on the Linux driver: * drivers/spi/spi-dw.c, which is: * Copyright (c) 2009, Intel Corporation. - * - * SPDX-License-Identifier: GPL-2.0 */ +#include #include #include #include @@ -18,6 +18,7 @@ #include #include #include +#include #include DECLARE_GLOBAL_DATA_PTR; @@ -97,6 +98,8 @@ struct dw_spi_priv { struct clk clk; unsigned long bus_clk_rate; + struct gpio_desc cs_gpio; /* External chip-select gpio */ + int bits_per_word; u8 cs; /* chip select pin */ u8 tmode; /* TR/TO/RO/EEPROM */ @@ -110,24 +113,40 @@ struct dw_spi_priv { void *rx_end; }; -static inline u32 dw_readl(struct dw_spi_priv *priv, u32 offset) +static inline u32 dw_read(struct dw_spi_priv *priv, u32 offset) { return __raw_readl(priv->regs + offset); } -static inline void dw_writel(struct dw_spi_priv *priv, u32 offset, u32 val) +static inline void dw_write(struct dw_spi_priv *priv, u32 offset, u32 val) { __raw_writel(val, priv->regs + offset); } -static inline u16 dw_readw(struct dw_spi_priv *priv, u32 offset) +static int request_gpio_cs(struct udevice *bus) { - return __raw_readw(priv->regs + offset); -} +#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_SPL_BUILD) + struct dw_spi_priv *priv = dev_get_priv(bus); + int ret; -static inline void dw_writew(struct dw_spi_priv *priv, u32 offset, u16 val) -{ - __raw_writew(val, priv->regs + offset); + /* External chip select gpio line is optional */ + ret = gpio_request_by_name(bus, "cs-gpio", 0, &priv->cs_gpio, 0); + if (ret == -ENOENT) + return 0; + + if (ret < 0) { + printf("Error: %d: Can't get %s gpio!\n", ret, bus->name); + return ret; + } + + if (dm_gpio_is_valid(&priv->cs_gpio)) { + dm_gpio_set_dir_flags(&priv->cs_gpio, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); + } + + debug("%s: used external gpio for CS management\n", __func__); +#endif + return 0; } static int dw_spi_ofdata_to_platdata(struct udevice *bus) @@ -144,19 +163,19 @@ static int dw_spi_ofdata_to_platdata(struct udevice *bus) debug("%s: regs=%p max-frequency=%d\n", __func__, plat->regs, plat->frequency); - return 0; + return request_gpio_cs(bus); } static inline void spi_enable_chip(struct dw_spi_priv *priv, int enable) { - dw_writel(priv, DW_SPI_SSIENR, (enable ? 1 : 0)); + dw_write(priv, DW_SPI_SSIENR, (enable ? 1 : 0)); } /* Restart the controller, disable all interrupts, clean rx fifo */ static void spi_hw_init(struct dw_spi_priv *priv) { spi_enable_chip(priv, 0); - dw_writel(priv, DW_SPI_IMR, 0xff); + dw_write(priv, DW_SPI_IMR, 0xff); spi_enable_chip(priv, 1); /* @@ -167,13 +186,13 @@ static void spi_hw_init(struct dw_spi_priv *priv) u32 fifo; for (fifo = 1; fifo < 256; fifo++) { - dw_writew(priv, DW_SPI_TXFLTR, fifo); - if (fifo != dw_readw(priv, DW_SPI_TXFLTR)) + dw_write(priv, DW_SPI_TXFLTR, fifo); + if (fifo != dw_read(priv, DW_SPI_TXFLTR)) break; } priv->fifo_len = (fifo == 1) ? 0 : fifo; - dw_writew(priv, DW_SPI_TXFLTR, 0); + dw_write(priv, DW_SPI_TXFLTR, 0); } debug("%s: fifo_len=%d\n", __func__, priv->fifo_len); } @@ -242,7 +261,7 @@ static inline u32 tx_max(struct dw_spi_priv *priv) u32 tx_left, tx_room, rxtx_gap; tx_left = (priv->tx_end - priv->tx) / (priv->bits_per_word >> 3); - tx_room = priv->fifo_len - dw_readw(priv, DW_SPI_TXFLR); + tx_room = priv->fifo_len - dw_read(priv, DW_SPI_TXFLR); /* * Another concern is about the tx/rx mismatch, we @@ -263,7 +282,7 @@ static inline u32 rx_max(struct dw_spi_priv *priv) { u32 rx_left = (priv->rx_end - priv->rx) / (priv->bits_per_word >> 3); - return min_t(u32, rx_left, dw_readw(priv, DW_SPI_RXFLR)); + return min_t(u32, rx_left, dw_read(priv, DW_SPI_RXFLR)); } static void dw_writer(struct dw_spi_priv *priv) @@ -279,34 +298,22 @@ static void dw_writer(struct dw_spi_priv *priv) else txw = *(u16 *)(priv->tx); } - dw_writew(priv, DW_SPI_DR, txw); + dw_write(priv, DW_SPI_DR, txw); debug("%s: tx=0x%02x\n", __func__, txw); priv->tx += priv->bits_per_word >> 3; } } -static int dw_reader(struct dw_spi_priv *priv) +static void dw_reader(struct dw_spi_priv *priv) { - unsigned start = get_timer(0); - u32 max; + u32 max = rx_max(priv); u16 rxw; - /* Wait for rx data to be ready */ - while (rx_max(priv) == 0) { - if (get_timer(start) > RX_TIMEOUT) - return -ETIMEDOUT; - } - - max = rx_max(priv); - while (max--) { - rxw = dw_readw(priv, DW_SPI_DR); + rxw = dw_read(priv, DW_SPI_DR); debug("%s: rx=0x%02x\n", __func__, rxw); - /* - * Care about rx only if the transfer's original "rx" is - * not null - */ + /* Care about rx if the transfer's original "rx" is not null */ if (priv->rx_end - priv->len) { if (priv->bits_per_word == 8) *(u8 *)(priv->rx) = rxw; @@ -315,24 +322,30 @@ static int dw_reader(struct dw_spi_priv *priv) } priv->rx += priv->bits_per_word >> 3; } - - return 0; } static int poll_transfer(struct dw_spi_priv *priv) { - int ret; - do { dw_writer(priv); - ret = dw_reader(priv); - if (ret < 0) - return ret; + dw_reader(priv); } while (priv->rx_end > priv->rx); return 0; } +static void external_cs_manage(struct udevice *dev, bool on) +{ +#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_SPL_BUILD) + struct dw_spi_priv *priv = dev_get_priv(dev->parent); + + if (!dm_gpio_is_valid(&priv->cs_gpio)) + return; + + dm_gpio_set_value(&priv->cs_gpio, on ? 1 : 0); +#endif +} + static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { @@ -342,6 +355,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, u8 *rx = din; int ret = 0; u32 cr0 = 0; + u32 val; u32 cs; /* spi core configured to do 8 bit transfers */ @@ -350,6 +364,10 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, return -1; } + /* Start the transaction if necessary. */ + 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); @@ -359,7 +377,11 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, else if (rx) priv->tmode = SPI_TMOD_RO; else - priv->tmode = SPI_TMOD_TO; + /* + * In transmit only mode (SPI_TMOD_TO) input FIFO never gets + * any data which breaks our logic in poll_transfer() above. + */ + priv->tmode = SPI_TMOD_TR; cr0 &= ~SPI_TMOD_MASK; cr0 |= (priv->tmode << SPI_TMOD_OFFSET); @@ -377,8 +399,8 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, debug("%s: cr0=%08x\n", __func__, cr0); /* Reprogram cr0 only if changed */ - if (dw_readw(priv, DW_SPI_CTRL0) != cr0) - dw_writew(priv, DW_SPI_CTRL0, cr0); + if (dw_read(priv, DW_SPI_CTRL0) != cr0) + dw_write(priv, DW_SPI_CTRL0, cr0); /* * Configure the desired SS (slave select 0...3) in the controller @@ -386,7 +408,7 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, * automatically. So no cs_activate() etc is needed in this driver. */ cs = spi_chip_select(dev); - dw_writel(priv, DW_SPI_SER, 1 << cs); + dw_write(priv, DW_SPI_SER, 1 << cs); /* Enable controller after writing control registers */ spi_enable_chip(priv, 1); @@ -394,6 +416,23 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, /* Start transfer in a polling loop */ ret = poll_transfer(priv); + /* + * Wait for current transmit operation to complete. + * Otherwise if some data still exists in Tx FIFO it can be + * silently flushed, i.e. dropped on disabling of the controller, + * which happens when writing 0 to DW_SPI_SSIENR which happens + * in the beginning of new transfer. + */ + if (readl_poll_timeout(priv->regs + DW_SPI_SR, val, + (val & SR_TF_EMPT) && !(val & SR_BUSY), + RX_TIMEOUT * 1000)) { + ret = -ETIMEDOUT; + } + + /* Stop the transaction if necessary */ + if (flags & SPI_XFER_END) + external_cs_manage(dev, true); + return ret; } @@ -412,7 +451,7 @@ static int dw_spi_set_speed(struct udevice *bus, uint speed) /* clk_div doesn't support odd number */ clk_div = priv->bus_clk_rate / speed; clk_div = (clk_div + 1) & 0xfffe; - dw_writel(priv, DW_SPI_BAUDR, clk_div); + dw_write(priv, DW_SPI_BAUDR, clk_div); /* Enable controller after writing control registers */ spi_enable_chip(priv, 1);