Skip to content

Commit

Permalink
spi: axi-spi-engine: Add watchdog timer
Browse files Browse the repository at this point in the history
If there is an issue with the axi-spi-engine hardware a scheduled transfer
might never be completed and spi_sync() will block forever.

Add a watchdog timer that will abort a transfer after 5 seconds after it
has been started. This will potentially leave the hardware in a broken
state, but it allows software to recover and allow to better diagnose the
underlying issue.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
  • Loading branch information
larsclausen committed Apr 12, 2017
1 parent 51ebbb9 commit fde5597
Showing 1 changed file with 40 additions and 6 deletions.
46 changes: 40 additions & 6 deletions drivers/spi/spi-axi-spi-engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/timer.h>

#define SPI_ENGINE_VERSION_MAJOR(x) ((x >> 16) & 0xff)
#define SPI_ENGINE_VERSION_MINOR(x) ((x >> 8) & 0xff)
Expand Down Expand Up @@ -113,6 +114,8 @@ struct spi_engine {
unsigned int completed_id;

unsigned int int_enable;

struct timer_list watchdog_timer;
};

static void spi_engine_program_add_cmd(struct spi_engine_program *p,
Expand Down Expand Up @@ -439,6 +442,18 @@ static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine)
return spi_engine->rx_length != 0;
}

static void spi_engine_complete_message(struct spi_master *master, int status)
{
struct spi_engine *spi_engine = spi_master_get_devdata(master);
struct spi_message *msg = spi_engine->msg;

kfree(spi_engine->p);
msg->status = status;
msg->actual_length = msg->frame_length;
spi_engine->msg = NULL;
spi_finalize_current_message(master);
}

static irqreturn_t spi_engine_irq(int irq, void *devid)
{
struct spi_master *master = devid;
Expand Down Expand Up @@ -475,13 +490,11 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
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);
msg->status = 0;
msg->actual_length = msg->frame_length;
spi_engine->msg = NULL;
spi_finalize_current_message(master);
del_timer(&spi_engine->watchdog_timer);

spi_engine_complete_message(master, 0);

disable_int |= SPI_ENGINE_INT_SYNC;
}
}
Expand All @@ -497,6 +510,20 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
return IRQ_HANDLED;
}

static void spi_engine_timeout(unsigned long data)
{
struct spi_master *master = (struct spi_master *)data;
struct spi_engine *spi_engine = spi_master_get_devdata(master);


spin_lock(&spi_engine->lock);
if (spi_engine->msg) {
dev_err(&master->dev, "Timeout occured while waiting for transfer to complete. Hardware is probably broken.\n");
spi_engine_complete_message(master, -ETIMEDOUT);
}
spin_unlock(&spi_engine->lock);
}

static int spi_engine_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
Expand All @@ -506,6 +533,8 @@ static int spi_engine_transfer_one_message(struct spi_master *master,
unsigned long flags;
size_t size;

del_timer_sync(&spi_engine->watchdog_timer);

p_dry.length = 0;
spi_engine_compile_message(spi_engine, msg, true, &p_dry);

Expand Down Expand Up @@ -543,6 +572,8 @@ static int spi_engine_transfer_one_message(struct spi_master *master,
spi_engine->int_enable = int_enable;
spin_unlock_irqrestore(&spi_engine->lock, flags);

mod_timer(&spi_engine->watchdog_timer, jiffies + 5*HZ);

return 0;
}

Expand Down Expand Up @@ -623,6 +654,9 @@ static int spi_engine_probe(struct platform_device *pdev)
master->transfer_one_message = spi_engine_transfer_one_message;
master->num_chipselect = 8;

setup_timer(&spi_engine->watchdog_timer, spi_engine_timeout,
(unsigned long)master);

ret = spi_register_master(master);
if (ret)
goto err_free_irq;
Expand Down

0 comments on commit fde5597

Please sign in to comment.