|
|
|
@ -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); |
|
|
|
|