aboutsummaryrefslogtreecommitdiff
path: root/drivers/spi/spi-axi-spi-engine.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi-axi-spi-engine.c')
-rw-r--r--drivers/spi/spi-axi-spi-engine.c515
1 files changed, 310 insertions, 205 deletions
diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
index b96e55f59d1a..e358ac5b4509 100644
--- a/drivers/spi/spi-axi-spi-engine.c
+++ b/drivers/spi/spi-axi-spi-engine.c
@@ -6,19 +6,16 @@
*/
#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/fpga/adi-axi-common.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
-#define SPI_ENGINE_VERSION_MAJOR(x) ((x >> 16) & 0xff)
-#define SPI_ENGINE_VERSION_MINOR(x) ((x >> 8) & 0xff)
-#define SPI_ENGINE_VERSION_PATCH(x) (x & 0xff)
-
-#define SPI_ENGINE_REG_VERSION 0x00
-
#define SPI_ENGINE_REG_RESET 0x40
#define SPI_ENGINE_REG_INT_ENABLE 0x80
@@ -52,6 +49,7 @@
#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0
#define SPI_ENGINE_CMD_REG_CONFIG 0x1
+#define SPI_ENGINE_CMD_REG_XFER_BITS 0x2
#define SPI_ENGINE_MISC_SYNC 0x0
#define SPI_ENGINE_MISC_SLEEP 0x1
@@ -59,6 +57,9 @@
#define SPI_ENGINE_TRANSFER_WRITE 0x1
#define SPI_ENGINE_TRANSFER_READ 0x2
+/* Arbitrary sync ID for use by host->cur_msg */
+#define AXI_SPI_ENGINE_CUR_MSG_SYNC_ID 0x1
+
#define SPI_ENGINE_CMD(inst, arg1, arg2) \
(((inst) << 12) | ((arg1) << 8) | (arg2))
@@ -75,42 +76,50 @@
struct spi_engine_program {
unsigned int length;
- uint16_t instructions[];
+ uint16_t instructions[] __counted_by(length);
};
-struct spi_engine {
- struct clk *clk;
- struct clk *ref_clk;
-
- spinlock_t lock;
-
- void __iomem *base;
-
- struct spi_message *msg;
- struct spi_engine_program *p;
+/**
+ * struct spi_engine_message_state - SPI engine per-message state
+ */
+struct spi_engine_message_state {
+ /** @cmd_length: Number of elements in cmd_buf array. */
unsigned cmd_length;
+ /** @cmd_buf: Array of commands not yet written to CMD FIFO. */
const uint16_t *cmd_buf;
-
+ /** @tx_xfer: Next xfer with tx_buf not yet fully written to TX FIFO. */
struct spi_transfer *tx_xfer;
+ /** @tx_length: Size of tx_buf in bytes. */
unsigned int tx_length;
+ /** @tx_buf: Bytes not yet written to TX FIFO. */
const uint8_t *tx_buf;
-
+ /** @rx_xfer: Next xfer with rx_buf not yet fully written to RX FIFO. */
struct spi_transfer *rx_xfer;
+ /** @rx_length: Size of tx_buf in bytes. */
unsigned int rx_length;
+ /** @rx_buf: Bytes not yet written to the RX FIFO. */
uint8_t *rx_buf;
+};
- unsigned int sync_id;
- unsigned int completed_id;
+struct spi_engine {
+ struct clk *clk;
+ struct clk *ref_clk;
+ spinlock_t lock;
+
+ void __iomem *base;
+ struct spi_engine_message_state msg_state;
+ struct completion msg_complete;
unsigned int int_enable;
};
static void spi_engine_program_add_cmd(struct spi_engine_program *p,
bool dry, uint16_t cmd)
{
- if (!dry)
- p->instructions[p->length] = cmd;
p->length++;
+
+ if (!dry)
+ p->instructions[p->length - 1] = cmd;
}
static unsigned int spi_engine_get_config(struct spi_device *spi)
@@ -127,25 +136,17 @@ static unsigned int spi_engine_get_config(struct spi_device *spi)
return config;
}
-static unsigned int spi_engine_get_clk_div(struct spi_engine *spi_engine,
- struct spi_device *spi, struct spi_transfer *xfer)
-{
- unsigned int clk_div;
-
- clk_div = DIV_ROUND_UP(clk_get_rate(spi_engine->ref_clk),
- xfer->speed_hz * 2);
- if (clk_div > 255)
- clk_div = 255;
- else if (clk_div > 0)
- clk_div -= 1;
-
- return clk_div;
-}
-
static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
struct spi_transfer *xfer)
{
- unsigned int len = xfer->len;
+ unsigned int len;
+
+ if (xfer->bits_per_word <= 8)
+ len = xfer->len;
+ else if (xfer->bits_per_word <= 16)
+ len = xfer->len / 2;
+ else
+ len = xfer->len / 4;
while (len) {
unsigned int n = min(len, 256U);
@@ -163,22 +164,16 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
}
static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry,
- struct spi_engine *spi_engine, unsigned int clk_div,
- struct spi_transfer *xfer)
+ int delay_ns, u32 sclk_hz)
{
- unsigned int spi_clk = clk_get_rate(spi_engine->ref_clk);
unsigned int t;
- int delay;
-
- delay = spi_delay_to_ns(&xfer->delay, xfer);
- if (delay < 0)
- return;
- delay /= 1000;
- if (delay == 0)
+ /* negative delay indicates error, e.g. from spi_delay_to_ns() */
+ if (delay_ns <= 0)
return;
- t = DIV_ROUND_UP(delay * spi_clk, (clk_div + 1) * 2);
+ /* rounding down since executing the instruction adds a couple of ticks delay */
+ t = DIV_ROUND_DOWN_ULL((u64)delay_ns * sclk_hz, NSEC_PER_SEC);
while (t) {
unsigned int n = min(t, 256U);
@@ -195,53 +190,105 @@ static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
if (assert)
mask ^= BIT(spi_get_chipselect(spi, 0));
- spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(1, mask));
+ spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(0, mask));
+}
+
+/*
+ * Performs precompile steps on the message.
+ *
+ * The SPI core does most of the message/transfer validation and filling in
+ * fields for us via __spi_validate(). This fixes up anything remaining not
+ * done there.
+ *
+ * NB: This is separate from spi_engine_compile_message() because the latter
+ * is called twice and would otherwise result in double-evaluation.
+ */
+static void spi_engine_precompile_message(struct spi_message *msg)
+{
+ unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz;
+ struct spi_transfer *xfer;
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ clk_div = DIV_ROUND_UP(max_hz, xfer->speed_hz);
+ xfer->effective_speed_hz = max_hz / min(clk_div, 256U);
+ }
}
-static int spi_engine_compile_message(struct spi_engine *spi_engine,
- struct spi_message *msg, bool dry, struct spi_engine_program *p)
+static void spi_engine_compile_message(struct spi_message *msg, bool dry,
+ struct spi_engine_program *p)
{
struct spi_device *spi = msg->spi;
+ struct spi_controller *host = spi->controller;
struct spi_transfer *xfer;
int clk_div, new_clk_div;
- bool cs_change = true;
+ bool keep_cs = false;
+ u8 bits_per_word = 0;
- clk_div = -1;
+ clk_div = 1;
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG,
spi_engine_get_config(spi)));
+ xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list);
+ spi_engine_gen_cs(p, dry, spi, !xfer->cs_off);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- new_clk_div = spi_engine_get_clk_div(spi_engine, spi, xfer);
+ new_clk_div = host->max_speed_hz / xfer->effective_speed_hz;
if (new_clk_div != clk_div) {
clk_div = new_clk_div;
+ /* actual divider used is register value + 1 */
spi_engine_program_add_cmd(p, dry,
SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV,
- clk_div));
+ clk_div - 1));
}
- if (cs_change)
- spi_engine_gen_cs(p, dry, spi, true);
+ if (bits_per_word != xfer->bits_per_word) {
+ bits_per_word = xfer->bits_per_word;
+ spi_engine_program_add_cmd(p, dry,
+ SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_XFER_BITS,
+ bits_per_word));
+ }
spi_engine_gen_xfer(p, dry, xfer);
- spi_engine_gen_sleep(p, dry, spi_engine, clk_div, xfer);
-
- cs_change = xfer->cs_change;
- if (list_is_last(&xfer->transfer_list, &msg->transfers))
- cs_change = !cs_change;
-
- if (cs_change)
- spi_engine_gen_cs(p, dry, spi, false);
+ spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
+ xfer->effective_speed_hz);
+
+ if (xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
+ keep_cs = true;
+ } else {
+ if (!xfer->cs_off)
+ spi_engine_gen_cs(p, dry, spi, false);
+
+ spi_engine_gen_sleep(p, dry, spi_delay_to_ns(
+ &xfer->cs_change_delay, xfer),
+ xfer->effective_speed_hz);
+
+ if (!list_next_entry(xfer, transfer_list)->cs_off)
+ spi_engine_gen_cs(p, dry, spi, true);
+ }
+ } else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
+ xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) {
+ spi_engine_gen_cs(p, dry, spi, xfer->cs_off);
+ }
}
- return 0;
+ if (!keep_cs)
+ spi_engine_gen_cs(p, dry, spi, false);
+
+ /*
+ * Restore clockdiv to default so that future gen_sleep commands don't
+ * have to be aware of the current register state.
+ */
+ if (clk_div != 1)
+ spi_engine_program_add_cmd(p, dry,
+ SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, 0));
}
-static void spi_engine_xfer_next(struct spi_engine *spi_engine,
+static void spi_engine_xfer_next(struct spi_message *msg,
struct spi_transfer **_xfer)
{
- struct spi_message *msg = spi_engine->msg;
struct spi_transfer *xfer = *_xfer;
if (!xfer) {
@@ -256,147 +303,188 @@ static void spi_engine_xfer_next(struct spi_engine *spi_engine,
*_xfer = xfer;
}
-static void spi_engine_tx_next(struct spi_engine *spi_engine)
+static void spi_engine_tx_next(struct spi_message *msg)
{
- struct spi_transfer *xfer = spi_engine->tx_xfer;
+ struct spi_engine_message_state *st = msg->state;
+ struct spi_transfer *xfer = st->tx_xfer;
do {
- spi_engine_xfer_next(spi_engine, &xfer);
+ spi_engine_xfer_next(msg, &xfer);
} while (xfer && !xfer->tx_buf);
- spi_engine->tx_xfer = xfer;
+ st->tx_xfer = xfer;
if (xfer) {
- spi_engine->tx_length = xfer->len;
- spi_engine->tx_buf = xfer->tx_buf;
+ st->tx_length = xfer->len;
+ st->tx_buf = xfer->tx_buf;
} else {
- spi_engine->tx_buf = NULL;
+ st->tx_buf = NULL;
}
}
-static void spi_engine_rx_next(struct spi_engine *spi_engine)
+static void spi_engine_rx_next(struct spi_message *msg)
{
- struct spi_transfer *xfer = spi_engine->rx_xfer;
+ struct spi_engine_message_state *st = msg->state;
+ struct spi_transfer *xfer = st->rx_xfer;
do {
- spi_engine_xfer_next(spi_engine, &xfer);
+ spi_engine_xfer_next(msg, &xfer);
} while (xfer && !xfer->rx_buf);
- spi_engine->rx_xfer = xfer;
+ st->rx_xfer = xfer;
if (xfer) {
- spi_engine->rx_length = xfer->len;
- spi_engine->rx_buf = xfer->rx_buf;
+ st->rx_length = xfer->len;
+ st->rx_buf = xfer->rx_buf;
} else {
- spi_engine->rx_buf = NULL;
+ st->rx_buf = NULL;
}
}
-static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine)
+static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine,
+ struct spi_message *msg)
{
void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO;
+ struct spi_engine_message_state *st = msg->state;
unsigned int n, m, i;
const uint16_t *buf;
n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM);
- while (n && spi_engine->cmd_length) {
- m = min(n, spi_engine->cmd_length);
- buf = spi_engine->cmd_buf;
+ while (n && st->cmd_length) {
+ m = min(n, st->cmd_length);
+ buf = st->cmd_buf;
for (i = 0; i < m; i++)
writel_relaxed(buf[i], addr);
- spi_engine->cmd_buf += m;
- spi_engine->cmd_length -= m;
+ st->cmd_buf += m;
+ st->cmd_length -= m;
n -= m;
}
- return spi_engine->cmd_length != 0;
+ return st->cmd_length != 0;
}
-static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine)
+static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine,
+ struct spi_message *msg)
{
void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO;
+ struct spi_engine_message_state *st = msg->state;
unsigned int n, m, i;
- const uint8_t *buf;
n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM);
- while (n && spi_engine->tx_length) {
- m = min(n, spi_engine->tx_length);
- buf = spi_engine->tx_buf;
- for (i = 0; i < m; i++)
- writel_relaxed(buf[i], addr);
- spi_engine->tx_buf += m;
- spi_engine->tx_length -= m;
+ while (n && st->tx_length) {
+ if (st->tx_xfer->bits_per_word <= 8) {
+ const u8 *buf = st->tx_buf;
+
+ m = min(n, st->tx_length);
+ for (i = 0; i < m; i++)
+ writel_relaxed(buf[i], addr);
+ st->tx_buf += m;
+ st->tx_length -= m;
+ } else if (st->tx_xfer->bits_per_word <= 16) {
+ const u16 *buf = (const u16 *)st->tx_buf;
+
+ m = min(n, st->tx_length / 2);
+ for (i = 0; i < m; i++)
+ writel_relaxed(buf[i], addr);
+ st->tx_buf += m * 2;
+ st->tx_length -= m * 2;
+ } else {
+ const u32 *buf = (const u32 *)st->tx_buf;
+
+ m = min(n, st->tx_length / 4);
+ for (i = 0; i < m; i++)
+ writel_relaxed(buf[i], addr);
+ st->tx_buf += m * 4;
+ st->tx_length -= m * 4;
+ }
n -= m;
- if (spi_engine->tx_length == 0)
- spi_engine_tx_next(spi_engine);
+ if (st->tx_length == 0)
+ spi_engine_tx_next(msg);
}
- return spi_engine->tx_length != 0;
+ return st->tx_length != 0;
}
-static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine)
+static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine,
+ struct spi_message *msg)
{
void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO;
+ struct spi_engine_message_state *st = msg->state;
unsigned int n, m, i;
- uint8_t *buf;
n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL);
- while (n && spi_engine->rx_length) {
- m = min(n, spi_engine->rx_length);
- buf = spi_engine->rx_buf;
- for (i = 0; i < m; i++)
- buf[i] = readl_relaxed(addr);
- spi_engine->rx_buf += m;
- spi_engine->rx_length -= m;
+ while (n && st->rx_length) {
+ if (st->rx_xfer->bits_per_word <= 8) {
+ u8 *buf = st->rx_buf;
+
+ m = min(n, st->rx_length);
+ for (i = 0; i < m; i++)
+ buf[i] = readl_relaxed(addr);
+ st->rx_buf += m;
+ st->rx_length -= m;
+ } else if (st->rx_xfer->bits_per_word <= 16) {
+ u16 *buf = (u16 *)st->rx_buf;
+
+ m = min(n, st->rx_length / 2);
+ for (i = 0; i < m; i++)
+ buf[i] = readl_relaxed(addr);
+ st->rx_buf += m * 2;
+ st->rx_length -= m * 2;
+ } else {
+ u32 *buf = (u32 *)st->rx_buf;
+
+ m = min(n, st->rx_length / 4);
+ for (i = 0; i < m; i++)
+ buf[i] = readl_relaxed(addr);
+ st->rx_buf += m * 4;
+ st->rx_length -= m * 4;
+ }
n -= m;
- if (spi_engine->rx_length == 0)
- spi_engine_rx_next(spi_engine);
+ if (st->rx_length == 0)
+ spi_engine_rx_next(msg);
}
- return spi_engine->rx_length != 0;
+ return st->rx_length != 0;
}
static irqreturn_t spi_engine_irq(int irq, void *devid)
{
struct spi_controller *host = devid;
+ struct spi_message *msg = host->cur_msg;
struct spi_engine *spi_engine = spi_controller_get_devdata(host);
unsigned int disable_int = 0;
unsigned int pending;
+ int completed_id = -1;
pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
if (pending & SPI_ENGINE_INT_SYNC) {
writel_relaxed(SPI_ENGINE_INT_SYNC,
spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
- spi_engine->completed_id = readl_relaxed(
+ completed_id = readl_relaxed(
spi_engine->base + SPI_ENGINE_REG_SYNC_ID);
}
spin_lock(&spi_engine->lock);
if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) {
- if (!spi_engine_write_cmd_fifo(spi_engine))
+ if (!spi_engine_write_cmd_fifo(spi_engine, msg))
disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY;
}
if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) {
- if (!spi_engine_write_tx_fifo(spi_engine))
+ if (!spi_engine_write_tx_fifo(spi_engine, msg))
disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY;
}
if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) {
- if (!spi_engine_read_rx_fifo(spi_engine))
+ if (!spi_engine_read_rx_fifo(spi_engine, msg))
disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL;
}
- if (pending & SPI_ENGINE_INT_SYNC) {
- if (spi_engine->msg &&
- spi_engine->completed_id == spi_engine->sync_id) {
- struct spi_message *msg = spi_engine->msg;
-
- kfree(spi_engine->p);
+ if (pending & SPI_ENGINE_INT_SYNC && msg) {
+ if (completed_id == AXI_SPI_ENGINE_CUR_MSG_SYNC_ID) {
msg->status = 0;
msg->actual_length = msg->frame_length;
- spi_engine->msg = NULL;
- spi_finalize_current_message(host);
+ complete(&spi_engine->msg_complete);
disable_int |= SPI_ENGINE_INT_SYNC;
}
}
@@ -412,43 +500,64 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
return IRQ_HANDLED;
}
+static int spi_engine_optimize_message(struct spi_message *msg)
+{
+ struct spi_engine_program p_dry, *p;
+
+ spi_engine_precompile_message(msg);
+
+ p_dry.length = 0;
+ spi_engine_compile_message(msg, true, &p_dry);
+
+ p = kzalloc(struct_size(p, instructions, p_dry.length + 1), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ spi_engine_compile_message(msg, false, p);
+
+ spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(
+ AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
+
+ msg->opt_state = p;
+
+ return 0;
+}
+
+static int spi_engine_unoptimize_message(struct spi_message *msg)
+{
+ kfree(msg->opt_state);
+
+ return 0;
+}
+
static int spi_engine_transfer_one_message(struct spi_controller *host,
struct spi_message *msg)
{
- struct spi_engine_program p_dry, *p;
struct spi_engine *spi_engine = spi_controller_get_devdata(host);
+ struct spi_engine_message_state *st = &spi_engine->msg_state;
+ struct spi_engine_program *p = msg->opt_state;
unsigned int int_enable = 0;
unsigned long flags;
- size_t size;
- p_dry.length = 0;
- spi_engine_compile_message(spi_engine, msg, true, &p_dry);
+ /* reinitialize message state for this transfer */
+ memset(st, 0, sizeof(*st));
+ st->cmd_buf = p->instructions;
+ st->cmd_length = p->length;
+ msg->state = st;
- size = sizeof(*p->instructions) * (p_dry.length + 1);
- p = kzalloc(sizeof(*p) + size, GFP_KERNEL);
- if (!p)
- return -ENOMEM;
- spi_engine_compile_message(spi_engine, msg, false, p);
+ reinit_completion(&spi_engine->msg_complete);
spin_lock_irqsave(&spi_engine->lock, flags);
- spi_engine->sync_id = (spi_engine->sync_id + 1) & 0xff;
- spi_engine_program_add_cmd(p, false,
- SPI_ENGINE_CMD_SYNC(spi_engine->sync_id));
-
- spi_engine->msg = msg;
- spi_engine->p = p;
- spi_engine->cmd_buf = p->instructions;
- spi_engine->cmd_length = p->length;
- if (spi_engine_write_cmd_fifo(spi_engine))
+ if (spi_engine_write_cmd_fifo(spi_engine, msg))
int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY;
- spi_engine_tx_next(spi_engine);
- if (spi_engine_write_tx_fifo(spi_engine))
+ spi_engine_tx_next(msg);
+ if (spi_engine_write_tx_fifo(spi_engine, msg))
int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY;
- spi_engine_rx_next(spi_engine);
- if (spi_engine->rx_length != 0)
+ spi_engine_rx_next(msg);
+ if (st->rx_length != 0)
int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL;
int_enable |= SPI_ENGINE_INT_SYNC;
@@ -458,7 +567,25 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
spi_engine->int_enable = int_enable;
spin_unlock_irqrestore(&spi_engine->lock, flags);
- return 0;
+ if (!wait_for_completion_timeout(&spi_engine->msg_complete,
+ msecs_to_jiffies(5000))) {
+ dev_err(&host->dev,
+ "Timeout occurred while waiting for transfer to complete. Hardware is probably broken.\n");
+ msg->status = -ETIMEDOUT;
+ }
+
+ spi_finalize_current_message(host);
+
+ return msg->status;
+}
+
+static void spi_engine_release_hw(void *p)
+{
+ struct spi_engine *spi_engine = p;
+
+ writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
+ writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
+ writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET);
}
static int spi_engine_probe(struct platform_device *pdev)
@@ -473,90 +600,69 @@ static int spi_engine_probe(struct platform_device *pdev)
if (irq < 0)
return irq;
- spi_engine = devm_kzalloc(&pdev->dev, sizeof(*spi_engine), GFP_KERNEL);
- if (!spi_engine)
- return -ENOMEM;
-
- host = spi_alloc_host(&pdev->dev, 0);
+ host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi_engine));
if (!host)
return -ENOMEM;
- spi_controller_set_devdata(host, spi_engine);
+ spi_engine = spi_controller_get_devdata(host);
spin_lock_init(&spi_engine->lock);
+ init_completion(&spi_engine->msg_complete);
spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
- if (IS_ERR(spi_engine->clk)) {
- ret = PTR_ERR(spi_engine->clk);
- goto err_put_host;
- }
+ if (IS_ERR(spi_engine->clk))
+ return PTR_ERR(spi_engine->clk);
spi_engine->ref_clk = devm_clk_get_enabled(&pdev->dev, "spi_clk");
- if (IS_ERR(spi_engine->ref_clk)) {
- ret = PTR_ERR(spi_engine->ref_clk);
- goto err_put_host;
- }
+ if (IS_ERR(spi_engine->ref_clk))
+ return PTR_ERR(spi_engine->ref_clk);
spi_engine->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(spi_engine->base)) {
- ret = PTR_ERR(spi_engine->base);
- goto err_put_host;
- }
-
- version = readl(spi_engine->base + SPI_ENGINE_REG_VERSION);
- if (SPI_ENGINE_VERSION_MAJOR(version) != 1) {
- dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n",
- SPI_ENGINE_VERSION_MAJOR(version),
- SPI_ENGINE_VERSION_MINOR(version),
- SPI_ENGINE_VERSION_PATCH(version));
- ret = -ENODEV;
- goto err_put_host;
+ if (IS_ERR(spi_engine->base))
+ return PTR_ERR(spi_engine->base);
+
+ version = readl(spi_engine->base + ADI_AXI_REG_VERSION);
+ if (ADI_AXI_PCORE_VER_MAJOR(version) != 1) {
+ dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%u\n",
+ ADI_AXI_PCORE_VER_MAJOR(version),
+ ADI_AXI_PCORE_VER_MINOR(version),
+ ADI_AXI_PCORE_VER_PATCH(version));
+ return -ENODEV;
}
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET);
writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
- ret = request_irq(irq, spi_engine_irq, 0, pdev->name, host);
+ ret = devm_add_action_or_reset(&pdev->dev, spi_engine_release_hw,
+ spi_engine);
if (ret)
- goto err_put_host;
+ return ret;
+
+ ret = devm_request_irq(&pdev->dev, irq, spi_engine_irq, 0, pdev->name,
+ host);
+ if (ret)
+ return ret;
host->dev.of_node = pdev->dev.of_node;
host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE;
- host->bits_per_word_mask = SPI_BPW_MASK(8);
+ host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
host->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2;
host->transfer_one_message = spi_engine_transfer_one_message;
+ host->optimize_message = spi_engine_optimize_message;
+ host->unoptimize_message = spi_engine_unoptimize_message;
host->num_chipselect = 8;
- ret = spi_register_controller(host);
+ if (host->max_speed_hz == 0)
+ return dev_err_probe(&pdev->dev, -EINVAL, "spi_clk rate is 0");
+
+ ret = devm_spi_register_controller(&pdev->dev, host);
if (ret)
- goto err_free_irq;
+ return ret;
platform_set_drvdata(pdev, host);
return 0;
-err_free_irq:
- free_irq(irq, host);
-err_put_host:
- spi_controller_put(host);
- return ret;
-}
-
-static void spi_engine_remove(struct platform_device *pdev)
-{
- struct spi_controller *host = spi_controller_get(platform_get_drvdata(pdev));
- struct spi_engine *spi_engine = spi_controller_get_devdata(host);
- int irq = platform_get_irq(pdev, 0);
-
- spi_unregister_controller(host);
-
- free_irq(irq, host);
-
- spi_controller_put(host);
-
- writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
- writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
- writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET);
}
static const struct of_device_id spi_engine_match_table[] = {
@@ -567,7 +673,6 @@ MODULE_DEVICE_TABLE(of, spi_engine_match_table);
static struct platform_driver spi_engine_driver = {
.probe = spi_engine_probe,
- .remove_new = spi_engine_remove,
.driver = {
.name = "spi-engine",
.of_match_table = spi_engine_match_table,