Skip to content

Commit

Permalink
spi/pxa2xx: convert to the pump message infrastructure
Browse files Browse the repository at this point in the history
The SPI core provides infrastructure for standard message queueing so use
that instead of handling everything in the driver. This simplifies the
driver.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
  • Loading branch information
Mika Westerberg authored and Mark Brown committed Jan 26, 2013
1 parent 2b9b84f commit 7f86bde
Showing 1 changed file with 12 additions and 200 deletions.
212 changes: 12 additions & 200 deletions drivers/spi/spi-pxa2xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ DEFINE_SSP_REG(SSPSP, 0x2c)
#define DONE_STATE ((void*)2)
#define ERROR_STATE ((void*)-1)

#define QUEUE_RUNNING 0
#define QUEUE_STOPPED 1

struct driver_data {
/* Driver model hookup */
struct platform_device *pdev;
Expand Down Expand Up @@ -117,14 +114,6 @@ struct driver_data {
u32 clear_sr;
u32 mask_sr;

/* Driver message queue */
struct workqueue_struct *workqueue;
struct work_struct pump_messages;
spinlock_t lock;
struct list_head queue;
int busy;
int run;

/* Message Transfer pump */
struct tasklet_struct pump_transfers;

Expand Down Expand Up @@ -173,8 +162,6 @@ struct chip_data {
void (*cs_control)(u32 command);
};

static void pump_messages(struct work_struct *work);

static void cs_assert(struct driver_data *drv_data)
{
struct chip_data *chip = drv_data->cur_chip;
Expand Down Expand Up @@ -444,15 +431,11 @@ static void unmap_dma_buffers(struct driver_data *drv_data)
static void giveback(struct driver_data *drv_data)
{
struct spi_transfer* last_transfer;
unsigned long flags;
struct spi_message *msg;

spin_lock_irqsave(&drv_data->lock, flags);
msg = drv_data->cur_msg;
drv_data->cur_msg = NULL;
drv_data->cur_transfer = NULL;
queue_work(drv_data->workqueue, &drv_data->pump_messages);
spin_unlock_irqrestore(&drv_data->lock, flags);

last_transfer = list_entry(msg->transfers.prev,
struct spi_transfer,
Expand Down Expand Up @@ -481,13 +464,7 @@ static void giveback(struct driver_data *drv_data)
*/

/* get a pointer to the next message, if any */
spin_lock_irqsave(&drv_data->lock, flags);
if (list_empty(&drv_data->queue))
next_msg = NULL;
else
next_msg = list_entry(drv_data->queue.next,
struct spi_message, queue);
spin_unlock_irqrestore(&drv_data->lock, flags);
next_msg = spi_get_next_queued_message(drv_data->master);

/* see if the next and current messages point
* to the same chip
Expand All @@ -498,10 +475,7 @@ static void giveback(struct driver_data *drv_data)
cs_deassert(drv_data);
}

msg->state = NULL;
if (msg->complete)
msg->complete(msg->context);

spi_finalize_current_message(drv_data->master);
drv_data->cur_chip = NULL;
}

Expand Down Expand Up @@ -1176,31 +1150,12 @@ static void pump_transfers(unsigned long data)
write_SSCR1(cr1, reg);
}

static void pump_messages(struct work_struct *work)
static int pxa2xx_spi_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
struct driver_data *drv_data =
container_of(work, struct driver_data, pump_messages);
unsigned long flags;

/* Lock queue and check for queue work */
spin_lock_irqsave(&drv_data->lock, flags);
if (list_empty(&drv_data->queue) || drv_data->run == QUEUE_STOPPED) {
drv_data->busy = 0;
spin_unlock_irqrestore(&drv_data->lock, flags);
return;
}

/* Make sure we are not already running a message */
if (drv_data->cur_msg) {
spin_unlock_irqrestore(&drv_data->lock, flags);
return;
}

/* Extract head of queue */
drv_data->cur_msg = list_entry(drv_data->queue.next,
struct spi_message, queue);
list_del_init(&drv_data->cur_msg->queue);
struct driver_data *drv_data = spi_master_get_devdata(master);

drv_data->cur_msg = msg;
/* Initial message state*/
drv_data->cur_msg->state = START_STATE;
drv_data->cur_transfer = list_entry(drv_data->cur_msg->transfers.next,
Expand All @@ -1213,34 +1168,6 @@ static void pump_messages(struct work_struct *work)

/* Mark as busy and launch transfers */
tasklet_schedule(&drv_data->pump_transfers);

drv_data->busy = 1;
spin_unlock_irqrestore(&drv_data->lock, flags);
}

static int transfer(struct spi_device *spi, struct spi_message *msg)
{
struct driver_data *drv_data = spi_master_get_devdata(spi->master);
unsigned long flags;

spin_lock_irqsave(&drv_data->lock, flags);

if (drv_data->run == QUEUE_STOPPED) {
spin_unlock_irqrestore(&drv_data->lock, flags);
return -ESHUTDOWN;
}

msg->actual_length = 0;
msg->status = -EINPROGRESS;
msg->state = START_STATE;

list_add_tail(&msg->queue, &drv_data->queue);

if (drv_data->run == QUEUE_RUNNING && !drv_data->busy)
queue_work(drv_data->workqueue, &drv_data->pump_messages);

spin_unlock_irqrestore(&drv_data->lock, flags);

return 0;
}

Expand Down Expand Up @@ -1438,94 +1365,6 @@ static void cleanup(struct spi_device *spi)
kfree(chip);
}

static int init_queue(struct driver_data *drv_data)
{
INIT_LIST_HEAD(&drv_data->queue);
spin_lock_init(&drv_data->lock);

drv_data->run = QUEUE_STOPPED;
drv_data->busy = 0;

tasklet_init(&drv_data->pump_transfers,
pump_transfers, (unsigned long)drv_data);

INIT_WORK(&drv_data->pump_messages, pump_messages);
drv_data->workqueue = create_singlethread_workqueue(
dev_name(drv_data->master->dev.parent));
if (drv_data->workqueue == NULL)
return -EBUSY;

return 0;
}

static int start_queue(struct driver_data *drv_data)
{
unsigned long flags;

spin_lock_irqsave(&drv_data->lock, flags);

if (drv_data->run == QUEUE_RUNNING || drv_data->busy) {
spin_unlock_irqrestore(&drv_data->lock, flags);
return -EBUSY;
}

drv_data->run = QUEUE_RUNNING;
drv_data->cur_msg = NULL;
drv_data->cur_transfer = NULL;
drv_data->cur_chip = NULL;
spin_unlock_irqrestore(&drv_data->lock, flags);

queue_work(drv_data->workqueue, &drv_data->pump_messages);

return 0;
}

static int stop_queue(struct driver_data *drv_data)
{
unsigned long flags;
unsigned limit = 500;
int status = 0;

spin_lock_irqsave(&drv_data->lock, flags);

/* This is a bit lame, but is optimized for the common execution path.
* A wait_queue on the drv_data->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 */
drv_data->run = QUEUE_STOPPED;
while ((!list_empty(&drv_data->queue) || drv_data->busy) && limit--) {
spin_unlock_irqrestore(&drv_data->lock, flags);
msleep(10);
spin_lock_irqsave(&drv_data->lock, flags);
}

if (!list_empty(&drv_data->queue) || drv_data->busy)
status = -EBUSY;

spin_unlock_irqrestore(&drv_data->lock, flags);

return status;
}

static int destroy_queue(struct driver_data *drv_data)
{
int status;

status = stop_queue(drv_data);
/* we are unloading the module or failing to load (only two calls
* to this routine), and neither call can handle a return value.
* However, destroy_workqueue calls flush_workqueue, and that 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 destroy_workqueue, so return anyway. */
if (status != 0)
return status;

destroy_workqueue(drv_data->workqueue);

return 0;
}

static int pxa2xx_spi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
Expand Down Expand Up @@ -1573,7 +1412,7 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
master->dma_alignment = DMA_ALIGNMENT;
master->cleanup = cleanup;
master->setup = setup;
master->transfer = transfer;
master->transfer_one_message = pxa2xx_spi_transfer_one_message;

drv_data->ssp_type = ssp->type;
drv_data->null_dma_buf = (u32 *)PTR_ALIGN(&drv_data[1], DMA_ALIGNMENT);
Expand Down Expand Up @@ -1646,31 +1485,19 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
write_SSTO(0, drv_data->ioaddr);
write_SSPSP(0, drv_data->ioaddr);

/* Initial and start queue */
status = init_queue(drv_data);
if (status != 0) {
dev_err(&pdev->dev, "problem initializing queue\n");
goto out_error_clock_enabled;
}
status = start_queue(drv_data);
if (status != 0) {
dev_err(&pdev->dev, "problem starting queue\n");
goto out_error_clock_enabled;
}
tasklet_init(&drv_data->pump_transfers, pump_transfers,
(unsigned long)drv_data);

/* Register with the SPI framework */
platform_set_drvdata(pdev, drv_data);
status = spi_register_master(master);
if (status != 0) {
dev_err(&pdev->dev, "problem registering spi master\n");
goto out_error_queue_alloc;
goto out_error_clock_enabled;
}

return status;

out_error_queue_alloc:
destroy_queue(drv_data);

out_error_clock_enabled:
clk_disable(ssp->clk);

Expand All @@ -1693,26 +1520,11 @@ static int pxa2xx_spi_remove(struct platform_device *pdev)
{
struct driver_data *drv_data = platform_get_drvdata(pdev);
struct ssp_device *ssp;
int status = 0;

if (!drv_data)
return 0;
ssp = drv_data->ssp;

/* Remove the queue */
status = destroy_queue(drv_data);
if (status != 0)
/* the kernel does not check the return status of this
* this routine (mod->exit, within the kernel). Therefore
* nothing is gained by returning from here, the module is
* going away regardless, and we should not leave any more
* resources allocated than necessary. We cannot free the
* message memory in drv_data->queue, but we can release the
* resources below. I think the kernel should honor -EBUSY
* returns but... */
dev_err(&pdev->dev, "pxa2xx_spi_remove: workqueue will not "
"complete, message memory not freed\n");

/* Disable the SSP at the peripheral and SOC level */
write_SSCR0(0, drv_data->ioaddr);
clk_disable(ssp->clk);
Expand Down Expand Up @@ -1755,7 +1567,7 @@ static int pxa2xx_spi_suspend(struct device *dev)
struct ssp_device *ssp = drv_data->ssp;
int status = 0;

status = stop_queue(drv_data);
status = spi_master_suspend(drv_data->master);
if (status != 0)
return status;
write_SSCR0(0, drv_data->ioaddr);
Expand All @@ -1781,7 +1593,7 @@ static int pxa2xx_spi_resume(struct device *dev)
clk_enable(ssp->clk);

/* Start the queue running */
status = start_queue(drv_data);
status = spi_master_resume(drv_data->master);
if (status != 0) {
dev_err(dev, "problem starting queue (%d)\n", status);
return status;
Expand Down

0 comments on commit 7f86bde

Please sign in to comment.