mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-25 13:14:19 +08:00
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 <seanga2@gmail.com> Tested-by Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com> Reviewed-by: Jagan Teki <jagan@amarulasolutions.com>
This commit is contained in:
parent
0d98f6de62
commit
fec7bf0460
@ -12,21 +12,23 @@
|
||||
|
||||
#define LOG_CATEGORY UCLASS_SPI
|
||||
#include <common.h>
|
||||
#include <log.h>
|
||||
#include <asm-generic/gpio.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <spi.h>
|
||||
#include <fdtdec.h>
|
||||
#include <reset.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <errno.h>
|
||||
#include <fdtdec.h>
|
||||
#include <log.h>
|
||||
#include <malloc.h>
|
||||
#include <reset.h>
|
||||
#include <spi.h>
|
||||
#include <spi-mem.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm-generic/gpio.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/* 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,
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user