Skip to content
Snippets Groups Projects
Commit 2dc5165e authored by Tom Rini's avatar Tom Rini
Browse files
parents f7c9e76f 63018a3e
No related branches found
No related tags found
No related merge requests found
......@@ -10,6 +10,7 @@
* SPDX-License-Identifier: GPL-2.0
*/
#include <asm-generic/gpio.h>
#include <common.h>
#include <clk.h>
#include <dm.h>
......@@ -18,6 +19,7 @@
#include <spi.h>
#include <fdtdec.h>
#include <linux/compat.h>
#include <linux/iopoll.h>
#include <asm/io.h>
DECLARE_GLOBAL_DATA_PTR;
......@@ -97,6 +99,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 +114,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 +164,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 +187,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 +262,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 +283,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 +299,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 +323,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 +356,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 +365,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 +378,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 +400,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 +409,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 +417,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 +452,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);
......
......@@ -630,8 +630,10 @@ static int omap3_spi_probe(struct udevice *dev)
(struct omap2_mcspi_platform_config*)dev_get_driver_data(dev);
priv->regs = (struct mcspi *)(devfdt_get_addr(dev) + data->regs_offset);
priv->pin_dir = fdtdec_get_uint(blob, node, "ti,pindir-d0-out-d1-in",
MCSPI_PINDIR_D0_IN_D1_OUT);
if (fdtdec_get_bool(blob, node, "ti,pindir-d0-out-d1-in"))
priv->pin_dir = MCSPI_PINDIR_D0_OUT_D1_IN;
else
priv->pin_dir = MCSPI_PINDIR_D0_IN_D1_OUT;
priv->wordlen = SPI_DEFAULT_WORDLEN;
return 0;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment