|
@@ -29,7 +29,6 @@
|
|
|
#include <linux/errno.h>
|
|
|
#include <linux/interrupt.h>
|
|
|
#include <linux/spi/spi.h>
|
|
|
-#include <linux/kthread.h>
|
|
|
#include <linux/delay.h>
|
|
|
#include <linux/clk.h>
|
|
|
#include <linux/err.h>
|
|
@@ -41,7 +40,6 @@
|
|
|
#include <linux/dma-mapping.h>
|
|
|
#include <linux/scatterlist.h>
|
|
|
#include <linux/pm_runtime.h>
|
|
|
-#include <linux/sched.h>
|
|
|
|
|
|
/*
|
|
|
* This macro is used to define some register default values.
|
|
@@ -367,15 +365,7 @@ struct pl022 {
|
|
|
struct clk *clk;
|
|
|
struct spi_master *master;
|
|
|
struct pl022_ssp_controller *master_info;
|
|
|
- /* Driver message pump */
|
|
|
- struct kthread_worker kworker;
|
|
|
- struct task_struct *kworker_task;
|
|
|
- struct kthread_work pump_messages;
|
|
|
- spinlock_t queue_lock;
|
|
|
- struct list_head queue;
|
|
|
- bool busy;
|
|
|
- bool running;
|
|
|
- /* Message transfer pump */
|
|
|
+ /* Message per-transfer pump */
|
|
|
struct tasklet_struct pump_transfers;
|
|
|
struct spi_message *cur_msg;
|
|
|
struct spi_transfer *cur_transfer;
|
|
@@ -397,6 +387,7 @@ struct pl022 {
|
|
|
struct sg_table sgt_rx;
|
|
|
struct sg_table sgt_tx;
|
|
|
char *dummypage;
|
|
|
+ bool dma_running;
|
|
|
#endif
|
|
|
};
|
|
|
|
|
@@ -451,8 +442,6 @@ static void null_cs_control(u32 command)
|
|
|
static void giveback(struct pl022 *pl022)
|
|
|
{
|
|
|
struct spi_transfer *last_transfer;
|
|
|
- unsigned long flags;
|
|
|
- struct spi_message *msg;
|
|
|
pl022->next_msg_cs_active = false;
|
|
|
|
|
|
last_transfer = list_entry(pl022->cur_msg->transfers.prev,
|
|
@@ -480,15 +469,8 @@ static void giveback(struct pl022 *pl022)
|
|
|
* sent the current message could be unloaded, which
|
|
|
* could invalidate the cs_control() callback...
|
|
|
*/
|
|
|
-
|
|
|
/* get a pointer to the next message, if any */
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
- if (list_empty(&pl022->queue))
|
|
|
- next_msg = NULL;
|
|
|
- else
|
|
|
- next_msg = list_entry(pl022->queue.next,
|
|
|
- struct spi_message, queue);
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
+ next_msg = spi_get_next_queued_message(pl022->master);
|
|
|
|
|
|
/*
|
|
|
* see if the next and current messages point
|
|
@@ -500,19 +482,13 @@ static void giveback(struct pl022 *pl022)
|
|
|
pl022->cur_chip->cs_control(SSP_CHIP_DESELECT);
|
|
|
else
|
|
|
pl022->next_msg_cs_active = true;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
- msg = pl022->cur_msg;
|
|
|
pl022->cur_msg = NULL;
|
|
|
pl022->cur_transfer = NULL;
|
|
|
pl022->cur_chip = NULL;
|
|
|
- queue_kthread_work(&pl022->kworker, &pl022->pump_messages);
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
-
|
|
|
- msg->state = NULL;
|
|
|
- if (msg->complete)
|
|
|
- msg->complete(msg->context);
|
|
|
+ spi_finalize_current_message(pl022->master);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1066,6 +1042,7 @@ static int configure_dma(struct pl022 *pl022)
|
|
|
dmaengine_submit(txdesc);
|
|
|
dma_async_issue_pending(rxchan);
|
|
|
dma_async_issue_pending(txchan);
|
|
|
+ pl022->dma_running = true;
|
|
|
|
|
|
return 0;
|
|
|
|
|
@@ -1144,11 +1121,12 @@ static void terminate_dma(struct pl022 *pl022)
|
|
|
dmaengine_terminate_all(rxchan);
|
|
|
dmaengine_terminate_all(txchan);
|
|
|
unmap_free_dma_scatter(pl022);
|
|
|
+ pl022->dma_running = false;
|
|
|
}
|
|
|
|
|
|
static void pl022_dma_remove(struct pl022 *pl022)
|
|
|
{
|
|
|
- if (pl022->busy)
|
|
|
+ if (pl022->dma_running)
|
|
|
terminate_dma(pl022);
|
|
|
if (pl022->dma_tx_channel)
|
|
|
dma_release_channel(pl022->dma_tx_channel);
|
|
@@ -1496,73 +1474,20 @@ out:
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * pump_messages - kthread work function which processes spi message queue
|
|
|
- * @work: pointer to kthread work struct contained in the pl022 private struct
|
|
|
- *
|
|
|
- * This function checks if there is any spi message in the queue that
|
|
|
- * needs processing and delegate control to appropriate function
|
|
|
- * do_polling_transfer()/do_interrupt_dma_transfer()
|
|
|
- * based on the kind of the transfer
|
|
|
- *
|
|
|
- */
|
|
|
-static void pump_messages(struct kthread_work *work)
|
|
|
+static int pl022_transfer_one_message(struct spi_master *master,
|
|
|
+ struct spi_message *msg)
|
|
|
{
|
|
|
- struct pl022 *pl022 =
|
|
|
- container_of(work, struct pl022, pump_messages);
|
|
|
- unsigned long flags;
|
|
|
- bool was_busy = false;
|
|
|
-
|
|
|
- /* Lock queue and check for queue work */
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
- if (list_empty(&pl022->queue) || !pl022->running) {
|
|
|
- if (pl022->busy) {
|
|
|
- /* nothing more to do - disable spi/ssp and power off */
|
|
|
- writew((readw(SSP_CR1(pl022->virtbase)) &
|
|
|
- (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase));
|
|
|
-
|
|
|
- if (pl022->master_info->autosuspend_delay > 0) {
|
|
|
- pm_runtime_mark_last_busy(&pl022->adev->dev);
|
|
|
- pm_runtime_put_autosuspend(&pl022->adev->dev);
|
|
|
- } else {
|
|
|
- pm_runtime_put(&pl022->adev->dev);
|
|
|
- }
|
|
|
- }
|
|
|
- pl022->busy = false;
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- /* Make sure we are not already running a message */
|
|
|
- if (pl022->cur_msg) {
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- return;
|
|
|
- }
|
|
|
- /* Extract head of queue */
|
|
|
- pl022->cur_msg =
|
|
|
- list_entry(pl022->queue.next, struct spi_message, queue);
|
|
|
-
|
|
|
- list_del_init(&pl022->cur_msg->queue);
|
|
|
- if (pl022->busy)
|
|
|
- was_busy = true;
|
|
|
- else
|
|
|
- pl022->busy = true;
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
+ struct pl022 *pl022 = spi_master_get_devdata(master);
|
|
|
|
|
|
/* Initial message state */
|
|
|
- pl022->cur_msg->state = STATE_START;
|
|
|
- pl022->cur_transfer = list_entry(pl022->cur_msg->transfers.next,
|
|
|
- struct spi_transfer, transfer_list);
|
|
|
+ pl022->cur_msg = msg;
|
|
|
+ msg->state = STATE_START;
|
|
|
+
|
|
|
+ pl022->cur_transfer = list_entry(msg->transfers.next,
|
|
|
+ struct spi_transfer, transfer_list);
|
|
|
|
|
|
/* Setup the SPI using the per chip configuration */
|
|
|
- pl022->cur_chip = spi_get_ctldata(pl022->cur_msg->spi);
|
|
|
- if (!was_busy)
|
|
|
- /*
|
|
|
- * We enable the core voltage and clocks here, then the clocks
|
|
|
- * and core will be disabled when this thread is run again
|
|
|
- * and there is no more work to be done.
|
|
|
- */
|
|
|
- pm_runtime_get_sync(&pl022->adev->dev);
|
|
|
+ pl022->cur_chip = spi_get_ctldata(msg->spi);
|
|
|
|
|
|
restore_state(pl022);
|
|
|
flush(pl022);
|
|
@@ -1571,119 +1496,37 @@ static void pump_messages(struct kthread_work *work)
|
|
|
do_polling_transfer(pl022);
|
|
|
else
|
|
|
do_interrupt_dma_transfer(pl022);
|
|
|
-}
|
|
|
-
|
|
|
-static int __init init_queue(struct pl022 *pl022)
|
|
|
-{
|
|
|
- struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
|
|
|
-
|
|
|
- INIT_LIST_HEAD(&pl022->queue);
|
|
|
- spin_lock_init(&pl022->queue_lock);
|
|
|
-
|
|
|
- pl022->running = false;
|
|
|
- pl022->busy = false;
|
|
|
-
|
|
|
- tasklet_init(&pl022->pump_transfers, pump_transfers,
|
|
|
- (unsigned long)pl022);
|
|
|
-
|
|
|
- init_kthread_worker(&pl022->kworker);
|
|
|
- pl022->kworker_task = kthread_run(kthread_worker_fn,
|
|
|
- &pl022->kworker,
|
|
|
- dev_name(pl022->master->dev.parent));
|
|
|
- if (IS_ERR(pl022->kworker_task)) {
|
|
|
- dev_err(&pl022->adev->dev,
|
|
|
- "failed to create message pump task\n");
|
|
|
- return -ENOMEM;
|
|
|
- }
|
|
|
- init_kthread_work(&pl022->pump_messages, pump_messages);
|
|
|
-
|
|
|
- /*
|
|
|
- * Board config will indicate if this controller should run the
|
|
|
- * message pump with high (realtime) priority to reduce the transfer
|
|
|
- * latency on the bus by minimising the delay between a transfer
|
|
|
- * request and the scheduling of the message pump thread. Without this
|
|
|
- * setting the message pump thread will remain at default priority.
|
|
|
- */
|
|
|
- if (pl022->master_info->rt) {
|
|
|
- dev_info(&pl022->adev->dev,
|
|
|
- "will run message pump with realtime priority\n");
|
|
|
- sched_setscheduler(pl022->kworker_task, SCHED_FIFO, ¶m);
|
|
|
- }
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int start_queue(struct pl022 *pl022)
|
|
|
+static int pl022_prepare_transfer_hardware(struct spi_master *master)
|
|
|
{
|
|
|
- unsigned long flags;
|
|
|
-
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
-
|
|
|
- if (pl022->running || pl022->busy) {
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- return -EBUSY;
|
|
|
- }
|
|
|
-
|
|
|
- pl022->running = true;
|
|
|
- pl022->cur_msg = NULL;
|
|
|
- pl022->cur_transfer = NULL;
|
|
|
- pl022->cur_chip = NULL;
|
|
|
- pl022->next_msg_cs_active = false;
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
-
|
|
|
- queue_kthread_work(&pl022->kworker, &pl022->pump_messages);
|
|
|
+ struct pl022 *pl022 = spi_master_get_devdata(master);
|
|
|
|
|
|
+ /*
|
|
|
+ * Just make sure we have all we need to run the transfer by syncing
|
|
|
+ * with the runtime PM framework.
|
|
|
+ */
|
|
|
+ pm_runtime_get_sync(&pl022->adev->dev);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int stop_queue(struct pl022 *pl022)
|
|
|
+static int pl022_unprepare_transfer_hardware(struct spi_master *master)
|
|
|
{
|
|
|
- unsigned long flags;
|
|
|
- unsigned limit = 500;
|
|
|
- int status = 0;
|
|
|
+ struct pl022 *pl022 = spi_master_get_devdata(master);
|
|
|
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
+ /* nothing more to do - disable spi/ssp and power off */
|
|
|
+ writew((readw(SSP_CR1(pl022->virtbase)) &
|
|
|
+ (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase));
|
|
|
|
|
|
- /* This is a bit lame, but is optimized for the common execution path.
|
|
|
- * A wait_queue on the pl022->busy could be used, but then the common
|
|
|
- * execution path (pump_messages) would be required to call wake_up or
|
|
|
- * friends on every SPI message. Do this instead */
|
|
|
- while ((!list_empty(&pl022->queue) || pl022->busy) && limit--) {
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- msleep(10);
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
+ if (pl022->master_info->autosuspend_delay > 0) {
|
|
|
+ pm_runtime_mark_last_busy(&pl022->adev->dev);
|
|
|
+ pm_runtime_put_autosuspend(&pl022->adev->dev);
|
|
|
+ } else {
|
|
|
+ pm_runtime_put(&pl022->adev->dev);
|
|
|
}
|
|
|
|
|
|
- if (!list_empty(&pl022->queue) || pl022->busy)
|
|
|
- status = -EBUSY;
|
|
|
- else
|
|
|
- pl022->running = false;
|
|
|
-
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
-
|
|
|
- return status;
|
|
|
-}
|
|
|
-
|
|
|
-static int destroy_queue(struct pl022 *pl022)
|
|
|
-{
|
|
|
- int status;
|
|
|
-
|
|
|
- status = stop_queue(pl022);
|
|
|
-
|
|
|
- /*
|
|
|
- * We are unloading the module or failing to load (only two calls
|
|
|
- * to this routine), and neither call can handle a return value.
|
|
|
- * However, flush_kthread_worker will block until all work is done.
|
|
|
- * If the reason that stop_queue timed out is that the work will never
|
|
|
- * finish, then it does no good to call flush/stop thread, so
|
|
|
- * return anyway.
|
|
|
- */
|
|
|
- if (status != 0)
|
|
|
- return status;
|
|
|
-
|
|
|
- flush_kthread_worker(&pl022->kworker);
|
|
|
- kthread_stop(pl022->kworker_task);
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1803,38 +1646,6 @@ static int verify_controller_parameters(struct pl022 *pl022,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * pl022_transfer - transfer function registered to SPI master framework
|
|
|
- * @spi: spi device which is requesting transfer
|
|
|
- * @msg: spi message which is to handled is queued to driver queue
|
|
|
- *
|
|
|
- * This function is registered to the SPI framework for this SPI master
|
|
|
- * controller. It will queue the spi_message in the queue of driver if
|
|
|
- * the queue is not stopped and return.
|
|
|
- */
|
|
|
-static int pl022_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
|
-{
|
|
|
- struct pl022 *pl022 = spi_master_get_devdata(spi->master);
|
|
|
- unsigned long flags;
|
|
|
-
|
|
|
- spin_lock_irqsave(&pl022->queue_lock, flags);
|
|
|
-
|
|
|
- if (!pl022->running) {
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- return -ESHUTDOWN;
|
|
|
- }
|
|
|
- msg->actual_length = 0;
|
|
|
- msg->status = -EINPROGRESS;
|
|
|
- msg->state = STATE_START;
|
|
|
-
|
|
|
- list_add_tail(&msg->queue, &pl022->queue);
|
|
|
- if (pl022->running && !pl022->busy)
|
|
|
- queue_kthread_work(&pl022->kworker, &pl022->pump_messages);
|
|
|
-
|
|
|
- spin_unlock_irqrestore(&pl022->queue_lock, flags);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
static inline u32 spi_rate(u32 rate, u16 cpsdvsr, u16 scr)
|
|
|
{
|
|
|
return rate / (cpsdvsr * (1 + scr));
|
|
@@ -2197,7 +2008,10 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id)
|
|
|
master->num_chipselect = platform_info->num_chipselect;
|
|
|
master->cleanup = pl022_cleanup;
|
|
|
master->setup = pl022_setup;
|
|
|
- master->transfer = pl022_transfer;
|
|
|
+ master->prepare_transfer_hardware = pl022_prepare_transfer_hardware;
|
|
|
+ master->transfer_one_message = pl022_transfer_one_message;
|
|
|
+ master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
|
|
|
+ master->rt = platform_info->rt;
|
|
|
|
|
|
/*
|
|
|
* Supports mode 0-3, loopback, and active low CS. Transfers are
|
|
@@ -2241,6 +2055,10 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id)
|
|
|
goto err_no_clk_en;
|
|
|
}
|
|
|
|
|
|
+ /* Initialize transfer pump */
|
|
|
+ tasklet_init(&pl022->pump_transfers, pump_transfers,
|
|
|
+ (unsigned long)pl022);
|
|
|
+
|
|
|
/* Disable SSP */
|
|
|
writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)),
|
|
|
SSP_CR1(pl022->virtbase));
|
|
@@ -2260,17 +2078,6 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id)
|
|
|
platform_info->enable_dma = 0;
|
|
|
}
|
|
|
|
|
|
- /* Initialize and start queue */
|
|
|
- status = init_queue(pl022);
|
|
|
- if (status != 0) {
|
|
|
- dev_err(&adev->dev, "probe - problem initializing queue\n");
|
|
|
- goto err_init_queue;
|
|
|
- }
|
|
|
- status = start_queue(pl022);
|
|
|
- if (status != 0) {
|
|
|
- dev_err(&adev->dev, "probe - problem starting queue\n");
|
|
|
- goto err_start_queue;
|
|
|
- }
|
|
|
/* Register with the SPI framework */
|
|
|
amba_set_drvdata(adev, pl022);
|
|
|
status = spi_register_master(master);
|
|
@@ -2296,9 +2103,6 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id)
|
|
|
return 0;
|
|
|
|
|
|
err_spi_register:
|
|
|
- err_start_queue:
|
|
|
- err_init_queue:
|
|
|
- destroy_queue(pl022);
|
|
|
if (platform_info->enable_dma)
|
|
|
pl022_dma_remove(pl022);
|
|
|
|
|
@@ -2334,9 +2138,6 @@ pl022_remove(struct amba_device *adev)
|
|
|
*/
|
|
|
pm_runtime_get_noresume(&adev->dev);
|
|
|
|
|
|
- /* Remove the queue */
|
|
|
- if (destroy_queue(pl022) != 0)
|
|
|
- dev_err(&adev->dev, "queue remove failed\n");
|
|
|
load_ssp_default_config(pl022);
|
|
|
if (pl022->master_info->enable_dma)
|
|
|
pl022_dma_remove(pl022);
|
|
@@ -2358,12 +2159,12 @@ pl022_remove(struct amba_device *adev)
|
|
|
static int pl022_suspend(struct device *dev)
|
|
|
{
|
|
|
struct pl022 *pl022 = dev_get_drvdata(dev);
|
|
|
- int status = 0;
|
|
|
+ int ret;
|
|
|
|
|
|
- status = stop_queue(pl022);
|
|
|
- if (status) {
|
|
|
- dev_warn(dev, "suspend cannot stop queue\n");
|
|
|
- return status;
|
|
|
+ ret = spi_master_suspend(pl022->master);
|
|
|
+ if (ret) {
|
|
|
+ dev_warn(dev, "cannot suspend master\n");
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
dev_dbg(dev, "suspended\n");
|
|
@@ -2373,16 +2174,16 @@ static int pl022_suspend(struct device *dev)
|
|
|
static int pl022_resume(struct device *dev)
|
|
|
{
|
|
|
struct pl022 *pl022 = dev_get_drvdata(dev);
|
|
|
- int status = 0;
|
|
|
+ int ret;
|
|
|
|
|
|
/* Start the queue running */
|
|
|
- status = start_queue(pl022);
|
|
|
- if (status)
|
|
|
- dev_err(dev, "problem starting queue (%d)\n", status);
|
|
|
+ ret = spi_master_resume(pl022->master);
|
|
|
+ if (ret)
|
|
|
+ dev_err(dev, "problem starting queue (%d)\n", ret);
|
|
|
else
|
|
|
dev_dbg(dev, "resumed\n");
|
|
|
|
|
|
- return status;
|
|
|
+ return ret;
|
|
|
}
|
|
|
#endif /* CONFIG_PM */
|
|
|
|