From 14af60b6fb3b76634278364b697dae2f9f360abf Mon Sep 17 00:00:00 2001 From: Chris Blair Date: Thu, 2 Feb 2012 13:59:34 +0100 Subject: spi/pl022: Add high priority message pump support This switches the PL022 worker to a kthread in order to get hold of a mechanism to control the message pump priority. On low-latency systems elevating the message kthread to realtime priority give a real sleek response curve. This has been confirmed by measurements. Realtime priority elevation for a certain PL022 port can be requested from platform data. Cc: Mark Brown Acked-by: Viresh Kumar Signed-off-by: Chris Blair Signed-off-by: Linus Walleij Signed-off-by: Grant Likely --- include/linux/amba/pl022.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/linux/amba/pl022.h b/include/linux/amba/pl022.h index 572f637299c9..3672f40f3455 100644 --- a/include/linux/amba/pl022.h +++ b/include/linux/amba/pl022.h @@ -241,6 +241,8 @@ struct dma_chan; * @autosuspend_delay: delay in ms following transfer completion before the * runtime power management system suspends the device. A setting of 0 * indicates no delay and the device will be suspended immediately. + * @rt: indicates the controller should run the message pump with realtime + * priority to minimise the transfer latency on the bus. */ struct pl022_ssp_controller { u16 bus_id; @@ -250,6 +252,7 @@ struct pl022_ssp_controller { void *dma_rx_param; void *dma_tx_param; int autosuspend_delay; + bool rt; }; /** -- cgit v1.2.3 From ffbbdd21329f3e15eeca6df2d4bc11c04d9d91c0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 22 Feb 2012 10:05:38 +0100 Subject: spi: create a message queueing infrastructure This rips the message queue in the PL022 driver out and pushes it into (optional) common infrastructure. Drivers that want to use the message pumping thread will need to define the new per-messags transfer methods and leave the deprecated transfer() method as NULL. Most of the design is described in the documentation changes that are included in this patch. Since there is a queue that need to be stopped when the system is suspending/resuming, two new calls are implemented for the device drivers to call in their suspend()/resume() functions: spi_master_suspend() and spi_master_resume(). ChangeLog v1->v2: - Remove Kconfig entry and do not make the queue support optional at all, instead be more agressive and have it as part of the compulsory infrastructure. - If the .transfer() method is implemented, delete print a small deprecation notice and do not start the transfer pump. - Fix a bitrotted comment. ChangeLog v2->v3: - Fix up a problematic sequence courtesy of Chris Blair. - Stop rather than destroy the queue on suspend() courtesy of Chris Blair. Signed-off-by: Chris Blair Signed-off-by: Linus Walleij Tested-by: Mark Brown Reviewed-by: Mark Brown Signed-off-by: Grant Likely --- Documentation/spi/spi-summary | 58 ++++++-- drivers/spi/spi-pl022.c | 303 +++++++------------------------------ drivers/spi/spi.c | 339 +++++++++++++++++++++++++++++++++++++++++- include/linux/spi/spi.h | 51 +++++++ 4 files changed, 487 insertions(+), 264 deletions(-) (limited to 'include') diff --git a/Documentation/spi/spi-summary b/Documentation/spi/spi-summary index 4884cb33845d..7312ec14dd89 100644 --- a/Documentation/spi/spi-summary +++ b/Documentation/spi/spi-summary @@ -1,7 +1,7 @@ Overview of Linux kernel SPI support ==================================== -21-May-2007 +02-Feb-2012 What is SPI? ------------ @@ -483,9 +483,9 @@ also initialize its own internal state. (See below about bus numbering and those methods.) After you initialize the spi_master, then use spi_register_master() to -publish it to the rest of the system. At that time, device nodes for -the controller and any predeclared spi devices will be made available, -and the driver model core will take care of binding them to drivers. +publish it to the rest of the system. At that time, device nodes for the +controller and any predeclared spi devices will be made available, and +the driver model core will take care of binding them to drivers. If you need to remove your SPI controller driver, spi_unregister_master() will reverse the effect of spi_register_master(). @@ -521,21 +521,53 @@ SPI MASTER METHODS ** When you code setup(), ASSUME that the controller ** is actively processing transfers for another device. - master->transfer(struct spi_device *spi, struct spi_message *message) - This must not sleep. Its responsibility is arrange that the - transfer happens and its complete() callback is issued. The two - will normally happen later, after other transfers complete, and - if the controller is idle it will need to be kickstarted. - master->cleanup(struct spi_device *spi) Your controller driver may use spi_device.controller_state to hold state it dynamically associates with that device. If you do that, be sure to provide the cleanup() method to free that state. + master->prepare_transfer_hardware(struct spi_master *master) + This will be called by the queue mechanism to signal to the driver + that a message is coming in soon, so the subsystem requests the + driver to prepare the transfer hardware by issuing this call. + This may sleep. + + master->unprepare_transfer_hardware(struct spi_master *master) + This will be called by the queue mechanism to signal to the driver + that there are no more messages pending in the queue and it may + relax the hardware (e.g. by power management calls). This may sleep. + + master->transfer_one_message(struct spi_master *master, + struct spi_message *mesg) + The subsystem calls the driver to transfer a single message while + queuing transfers that arrive in the meantime. When the driver is + finished with this message, it must call + spi_finalize_current_message() so the subsystem can issue the next + transfer. This may sleep. + + DEPRECATED METHODS + + master->transfer(struct spi_device *spi, struct spi_message *message) + This must not sleep. Its responsibility is arrange that the + transfer happens and its complete() callback is issued. The two + will normally happen later, after other transfers complete, and + if the controller is idle it will need to be kickstarted. This + method is not used on queued controllers and must be NULL if + transfer_one_message() and (un)prepare_transfer_hardware() are + implemented. + SPI MESSAGE QUEUE -The bulk of the driver will be managing the I/O queue fed by transfer(). +If you are happy with the standard queueing mechanism provided by the +SPI subsystem, just implement the queued methods specified above. Using +the message queue has the upside of centralizing a lot of code and +providing pure process-context execution of methods. The message queue +can also be elevated to realtime priority on high-priority SPI traffic. + +Unless the queueing mechanism in the SPI subsystem is selected, the bulk +of the driver will be managing the I/O queue fed by the now deprecated +function transfer(). That queue could be purely conceptual. For example, a driver used only for low-frequency sensor access might be fine using synchronous PIO. @@ -561,4 +593,6 @@ Stephen Street Mark Underwood Andrew Victor Vitaly Wool - +Grant Likely +Mark Brown +Linus Walleij diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 81847c9a7586..ec17a7af7e28 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +40,6 @@ #include #include #include -#include /* * 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 */ diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index b2ccdea30cb9..5ae1e84d9037 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include static void spidev_release(struct device *dev) { @@ -507,6 +510,293 @@ spi_register_board_info(struct spi_board_info const *info, unsigned n) /*-------------------------------------------------------------------------*/ +/** + * spi_pump_messages - kthread work function which processes spi message queue + * @work: pointer to kthread work struct contained in the master struct + * + * This function checks if there is any spi message in the queue that + * needs processing and if so call out to the driver to initialize hardware + * and transfer each message. + * + */ +static void spi_pump_messages(struct kthread_work *work) +{ + struct spi_master *master = + container_of(work, struct spi_master, pump_messages); + unsigned long flags; + bool was_busy = false; + int ret; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&master->queue_lock, flags); + if (list_empty(&master->queue) || !master->running) { + if (master->busy) { + ret = master->unprepare_transfer_hardware(master); + if (ret) { + dev_err(&master->dev, + "failed to unprepare transfer hardware\n"); + return; + } + } + master->busy = false; + spin_unlock_irqrestore(&master->queue_lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (master->cur_msg) { + spin_unlock_irqrestore(&master->queue_lock, flags); + return; + } + /* Extract head of queue */ + master->cur_msg = + list_entry(master->queue.next, struct spi_message, queue); + + list_del_init(&master->cur_msg->queue); + if (master->busy) + was_busy = true; + else + master->busy = true; + spin_unlock_irqrestore(&master->queue_lock, flags); + + if (!was_busy) { + ret = master->prepare_transfer_hardware(master); + if (ret) { + dev_err(&master->dev, + "failed to prepare transfer hardware\n"); + return; + } + } + + ret = master->transfer_one_message(master, master->cur_msg); + if (ret) { + dev_err(&master->dev, + "failed to transfer one message from queue\n"); + return; + } +} + +static int spi_init_queue(struct spi_master *master) +{ + struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; + + INIT_LIST_HEAD(&master->queue); + spin_lock_init(&master->queue_lock); + + master->running = false; + master->busy = false; + + init_kthread_worker(&master->kworker); + master->kworker_task = kthread_run(kthread_worker_fn, + &master->kworker, + dev_name(&master->dev)); + if (IS_ERR(master->kworker_task)) { + dev_err(&master->dev, "failed to create message pump task\n"); + return -ENOMEM; + } + init_kthread_work(&master->pump_messages, spi_pump_messages); + + /* + * Master 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 (master->rt) { + dev_info(&master->dev, + "will run message pump with realtime priority\n"); + sched_setscheduler(master->kworker_task, SCHED_FIFO, ¶m); + } + + return 0; +} + +/** + * spi_get_next_queued_message() - called by driver to check for queued + * messages + * @master: the master to check for queued messages + * + * If there are more messages in the queue, the next message is returned from + * this call. + */ +struct spi_message *spi_get_next_queued_message(struct spi_master *master) +{ + struct spi_message *next; + unsigned long flags; + + /* get a pointer to the next message, if any */ + spin_lock_irqsave(&master->queue_lock, flags); + if (list_empty(&master->queue)) + next = NULL; + else + next = list_entry(master->queue.next, + struct spi_message, queue); + spin_unlock_irqrestore(&master->queue_lock, flags); + + return next; +} +EXPORT_SYMBOL_GPL(spi_get_next_queued_message); + +/** + * spi_finalize_current_message() - the current message is complete + * @master: the master to return the message to + * + * Called by the driver to notify the core that the message in the front of the + * queue is complete and can be removed from the queue. + */ +void spi_finalize_current_message(struct spi_master *master) +{ + struct spi_message *mesg; + unsigned long flags; + + spin_lock_irqsave(&master->queue_lock, flags); + mesg = master->cur_msg; + master->cur_msg = NULL; + + queue_kthread_work(&master->kworker, &master->pump_messages); + spin_unlock_irqrestore(&master->queue_lock, flags); + + mesg->state = NULL; + if (mesg->complete) + mesg->complete(mesg->context); +} +EXPORT_SYMBOL_GPL(spi_finalize_current_message); + +static int spi_start_queue(struct spi_master *master) +{ + unsigned long flags; + + spin_lock_irqsave(&master->queue_lock, flags); + + if (master->running || master->busy) { + spin_unlock_irqrestore(&master->queue_lock, flags); + return -EBUSY; + } + + master->running = true; + master->cur_msg = NULL; + spin_unlock_irqrestore(&master->queue_lock, flags); + + queue_kthread_work(&master->kworker, &master->pump_messages); + + return 0; +} + +static int spi_stop_queue(struct spi_master *master) +{ + unsigned long flags; + unsigned limit = 500; + int ret = 0; + + spin_lock_irqsave(&master->queue_lock, flags); + + /* + * This is a bit lame, but is optimized for the common execution path. + * A wait_queue on the master->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(&master->queue) || master->busy) && limit--) { + spin_unlock_irqrestore(&master->queue_lock, flags); + msleep(10); + spin_lock_irqsave(&master->queue_lock, flags); + } + + if (!list_empty(&master->queue) || master->busy) + ret = -EBUSY; + else + master->running = false; + + spin_unlock_irqrestore(&master->queue_lock, flags); + + if (ret) { + dev_warn(&master->dev, + "could not stop message queue\n"); + return ret; + } + return ret; +} + +static int spi_destroy_queue(struct spi_master *master) +{ + int ret; + + ret = spi_stop_queue(master); + + /* + * 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 (ret) { + dev_err(&master->dev, "problem destroying queue\n"); + return ret; + } + + flush_kthread_worker(&master->kworker); + kthread_stop(master->kworker_task); + + return 0; +} + +/** + * spi_queued_transfer - transfer function for queued transfers + * @spi: spi device which is requesting transfer + * @msg: spi message which is to handled is queued to driver queue + */ +static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct spi_master *master = spi->master; + unsigned long flags; + + spin_lock_irqsave(&master->queue_lock, flags); + + if (!master->running) { + spin_unlock_irqrestore(&master->queue_lock, flags); + return -ESHUTDOWN; + } + msg->actual_length = 0; + msg->status = -EINPROGRESS; + + list_add_tail(&msg->queue, &master->queue); + if (master->running && !master->busy) + queue_kthread_work(&master->kworker, &master->pump_messages); + + spin_unlock_irqrestore(&master->queue_lock, flags); + return 0; +} + +static int spi_master_initialize_queue(struct spi_master *master) +{ + int ret; + + master->queued = true; + master->transfer = spi_queued_transfer; + + /* Initialize and start queue */ + ret = spi_init_queue(master); + if (ret) { + dev_err(&master->dev, "problem initializing queue\n"); + goto err_init_queue; + } + ret = spi_start_queue(master); + if (ret) { + dev_err(&master->dev, "problem starting queue\n"); + goto err_start_queue; + } + + return 0; + +err_start_queue: +err_init_queue: + spi_destroy_queue(master); + return ret; +} + +/*-------------------------------------------------------------------------*/ + static void spi_master_release(struct device *dev) { struct spi_master *master; @@ -522,6 +812,7 @@ static struct class spi_master_class = { }; + /** * spi_alloc_master - allocate SPI master controller * @dev: the controller, possibly using the platform_bus @@ -621,6 +912,17 @@ int spi_register_master(struct spi_master *master) dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev), dynamic ? " (dynamic)" : ""); + /* If we're using a queued driver, start the queue */ + if (master->transfer) + dev_info(dev, "master is unqueued, this is deprecated\n"); + else { + status = spi_master_initialize_queue(master); + if (status) { + device_unregister(&master->dev); + goto done; + } + } + mutex_lock(&board_lock); list_add_tail(&master->list, &spi_master_list); list_for_each_entry(bi, &board_list, list) @@ -636,7 +938,6 @@ done: } EXPORT_SYMBOL_GPL(spi_register_master); - static int __unregister(struct device *dev, void *null) { spi_unregister_device(to_spi_device(dev)); @@ -657,6 +958,11 @@ void spi_unregister_master(struct spi_master *master) { int dummy; + if (master->queued) { + if (spi_destroy_queue(master)) + dev_err(&master->dev, "queue remove failed\n"); + } + mutex_lock(&board_lock); list_del(&master->list); mutex_unlock(&board_lock); @@ -666,6 +972,37 @@ void spi_unregister_master(struct spi_master *master) } EXPORT_SYMBOL_GPL(spi_unregister_master); +int spi_master_suspend(struct spi_master *master) +{ + int ret; + + /* Basically no-ops for non-queued masters */ + if (!master->queued) + return 0; + + ret = spi_stop_queue(master); + if (ret) + dev_err(&master->dev, "queue stop failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_master_suspend); + +int spi_master_resume(struct spi_master *master) +{ + int ret; + + if (!master->queued) + return 0; + + ret = spi_start_queue(master); + if (ret) + dev_err(&master->dev, "queue restart failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(spi_master_resume); + static int __spi_master_match(struct device *dev, void *data) { struct spi_master *m; diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 176fce9cc6b1..f9e30a5b3543 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -22,6 +22,7 @@ #include #include #include +#include /* * INTERFACES between SPI master-side drivers and SPI infrastructure. @@ -235,6 +236,27 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * the device whose settings are being modified. * @transfer: adds a message to the controller's transfer queue. * @cleanup: frees controller-specific state + * @queued: whether this master is providing an internal message queue + * @kworker: thread struct for message pump + * @kworker_task: pointer to task for message pump kworker thread + * @pump_messages: work struct for scheduling work to the message pump + * @queue_lock: spinlock to syncronise access to message queue + * @queue: message queue + * @cur_msg: the currently in-flight message + * @busy: message pump is busy + * @running: message pump is running + * @rt: whether this queue is set to run as a realtime task + * @prepare_transfer_hardware: a message will soon arrive from the queue + * so the subsystem requests the driver to prepare the transfer hardware + * by issuing this call + * @transfer_one_message: the subsystem calls the driver to transfer a single + * message while queuing transfers that arrive in the meantime. When the + * driver is finished with this message, it must call + * spi_finalize_current_message() so the subsystem can issue the next + * transfer + * @prepare_transfer_hardware: there are currently no more messages on the + * queue so the subsystem notifies the driver that it may relax the + * hardware by issuing this call * * Each SPI master controller can communicate with one or more @spi_device * children. These make a small bus, sharing MOSI, MISO and SCK signals @@ -318,6 +340,28 @@ struct spi_master { /* called on release() to free memory provided by spi_master */ void (*cleanup)(struct spi_device *spi); + + /* + * These hooks are for drivers that want to use the generic + * master transfer queueing mechanism. If these are used, the + * transfer() function above must NOT be specified by the driver. + * Over time we expect SPI drivers to be phased over to this API. + */ + bool queued; + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_work pump_messages; + spinlock_t queue_lock; + struct list_head queue; + struct spi_message *cur_msg; + bool busy; + bool running; + bool rt; + + int (*prepare_transfer_hardware)(struct spi_master *master); + int (*transfer_one_message)(struct spi_master *master, + struct spi_message *mesg); + int (*unprepare_transfer_hardware)(struct spi_master *master); }; static inline void *spi_master_get_devdata(struct spi_master *master) @@ -343,6 +387,13 @@ static inline void spi_master_put(struct spi_master *master) put_device(&master->dev); } +/* PM calls that need to be issued by the driver */ +extern int spi_master_suspend(struct spi_master *master); +extern int spi_master_resume(struct spi_master *master); + +/* Calls the driver make to interact with the message queue */ +extern struct spi_message *spi_get_next_queued_message(struct spi_master *master); +extern void spi_finalize_current_message(struct spi_master *master); /* the spi driver core manages memory for the spi_master classdev */ extern struct spi_master * -- cgit v1.2.3 From d1c8bbd793e4c2f346f8788ad312f5b5b530aff5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 1 Mar 2012 17:10:17 -0800 Subject: spi: Add SuperH HSPI prototype driver This patch adds SuperH HSPI driver. It is still prototype driver, but has enough function at this point. Signed-off-by: Kuninori Morimoto Signed-off-by: Grant Likely --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-sh-hspi.c | 364 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/sh_hspi.h | 34 +++++ 4 files changed, 405 insertions(+) create mode 100644 drivers/spi/spi-sh-hspi.c create mode 100644 include/linux/spi/sh_hspi.h (limited to 'include') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7609c9cecd35..e2083f6d2026 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -330,6 +330,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_SHMOBILE + help + SPI driver for SuperH HSPI blocks. + config SPI_STMP3XXX tristate "Freescale STMP37xx/378x SPI/SSP controller" depends on ARCH_STMP3XXX && SPI_MASTER diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 326e6a1ac0ca..d9a7aed4ae9a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -50,6 +50,7 @@ spi-s3c24xx-hw-y := spi-s3c24xx.o spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o obj-$(CONFIG_SPI_SH) += spi-sh.o +obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c new file mode 100644 index 000000000000..8356ec8cfda2 --- /dev/null +++ b/drivers/spi/spi-sh-hspi.c @@ -0,0 +1,364 @@ +/* + * SuperH HSPI bus driver + * + * Copyright (C) 2011 Kuninori Morimoto + * + * Based on spi-sh.c: + * Based on pxa2xx_spi.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPCR 0x00 +#define SPSR 0x04 +#define SPSCR 0x08 +#define SPTBR 0x0C +#define SPRBR 0x10 +#define SPCR2 0x14 + +/* SPSR */ +#define RXFL (1 << 2) + +#define hspi2info(h) (h->dev->platform_data) + +struct hspi_priv { + void __iomem *addr; + struct spi_master *master; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct ws; + struct device *dev; + spinlock_t lock; +}; + +/* + * basic function + */ +static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) +{ + iowrite32(val, hspi->addr + reg); +} + +static u32 hspi_read(struct hspi_priv *hspi, int reg) +{ + return ioread32(hspi->addr + reg); +} + +/* + * transfer function + */ +static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) +{ + int t = 256; + + while (t--) { + if ((mask & hspi_read(hspi, SPSR)) == val) + return 0; + + msleep(20); + } + + dev_err(hspi->dev, "timeout\n"); + return -ETIMEDOUT; +} + +static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->tx_buf; + + /* + * FIXME + * very simple, but polling transfer + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0x0); + if (ret < 0) + return ret; + + hspi_write(hspi, SPTBR, (u32)data[i]); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + /* dummy read */ + hspi_read(hspi, SPRBR); + } + + return 0; +} + +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->rx_buf; + + /* + * FIXME + * very simple, but polling receive + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0); + if (ret < 0) + return ret; + + /* dummy write */ + hspi_write(hspi, SPTBR, 0x0); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + data[i] = (u8)hspi_read(hspi, SPRBR); + } + + return 0; +} + +static void hspi_work(struct work_struct *work) +{ + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); + struct sh_hspi_info *info = hspi2info(hspi); + struct spi_message *msg; + struct spi_transfer *t; + unsigned long flags; + u32 data; + int ret; + + dev_dbg(hspi->dev, "%s\n", __func__); + + /************************ pm enable ************************/ + pm_runtime_get_sync(hspi->dev); + + /* setup first of all in under pm_runtime */ + data = SH_HSPI_CLK_DIVC(info->flags); + + if (info->flags & SH_HSPI_FBS) + data |= 1 << 7; + if (info->flags & SH_HSPI_CLKP_HIGH) + data |= 1 << 6; + if (info->flags & SH_HSPI_IDIV_DIV128) + data |= 1 << 5; + + hspi_write(hspi, SPCR, data); + hspi_write(hspi, SPSR, 0x0); + hspi_write(hspi, SPSCR, 0x1); /* master mode */ + + while (1) { + msg = NULL; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + if (!list_empty(&hspi->queue)) { + msg = list_entry(hspi->queue.next, + struct spi_message, queue); + list_del_init(&msg->queue); + } + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + if (!msg) + break; + + ret = 0; + list_for_each_entry(t, &msg->transfers, transfer_list) { + if (t->tx_buf) { + ret = hspi_push(hspi, msg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = hspi_pop(hspi, msg, t); + if (ret < 0) + goto error; + } + msg->actual_length += t->len; + } +error: + msg->status = ret; + msg->complete(msg->context); + } + + pm_runtime_put_sync(hspi->dev); + /************************ pm disable ************************/ + + return; +} + +/* + * spi master function + */ +static int hspi_setup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + if (8 != spi->bits_per_word) { + dev_err(dev, "bits_per_word should be 8\n"); + return -EIO; + } + + dev_dbg(dev, "%s setup\n", spi->modalias); + + return 0; +} + +static void hspi_cleanup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + dev_dbg(dev, "%s cleanup\n", spi->modalias); +} + +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + list_add_tail(&msg->queue, &hspi->queue); + + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + + queue_work(hspi->workqueue, &hspi->ws); + + return 0; +} + +static int __devinit hspi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct hspi_priv *hspi; + int ret; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*hspi)); + if (!master) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + hspi = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, hspi); + + /* init hspi */ + hspi->master = master; + hspi->dev = &pdev->dev; + hspi->addr = devm_ioremap(hspi->dev, + res->start, resource_size(res)); + if (!hspi->addr) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + hspi->workqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!hspi->workqueue) { + dev_err(&pdev->dev, "create workqueue error\n"); + ret = -EBUSY; + goto error2; + } + + spin_lock_init(&hspi->lock); + INIT_LIST_HEAD(&hspi->queue); + INIT_WORK(&hspi->ws, hspi_work); + + master->num_chipselect = 1; + master->bus_num = pdev->id; + master->setup = hspi_setup; + master->transfer = hspi_transfer; + master->cleanup = hspi_cleanup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + ret = spi_register_master(master); + if (ret < 0) { + dev_err(&pdev->dev, "spi_register_master error.\n"); + goto error3; + } + + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "probed\n"); + + return 0; + + error3: + destroy_workqueue(hspi->workqueue); + error2: + devm_iounmap(hspi->dev, hspi->addr); + error1: + spi_master_put(master); + + return ret; +} + +static int __devexit hspi_remove(struct platform_device *pdev) +{ + struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + spi_unregister_master(hspi->master); + destroy_workqueue(hspi->workqueue); + devm_iounmap(hspi->dev, hspi->addr); + + return 0; +} + +static struct platform_driver hspi_driver = { + .probe = hspi_probe, + .remove = __devexit_p(hspi_remove), + .driver = { + .name = "sh-hspi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(hspi_driver); + +MODULE_DESCRIPTION("SuperH HSPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_ALIAS("platform:sh_spi"); diff --git a/include/linux/spi/sh_hspi.h b/include/linux/spi/sh_hspi.h new file mode 100644 index 000000000000..956d11288294 --- /dev/null +++ b/include/linux/spi/sh_hspi.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 Kuninori Morimoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SH_HSPI_H +#define SH_HSPI_H + +/* + * flags + * + * + */ +#define SH_HSPI_CLK_DIVC(d) (d & 0xFF) + +#define SH_HSPI_FBS (1 << 8) +#define SH_HSPI_CLKP_HIGH (1 << 9) /* default LOW */ +#define SH_HSPI_IDIV_DIV128 (1 << 10) /* default div16 */ +struct sh_hspi_info { + u32 flags; +}; + +#endif -- cgit v1.2.3 From 8f53602be555e500cfcd957955bb40fac19f2a6b Mon Sep 17 00:00:00 2001 From: Shubhrajyoti D Date: Mon, 27 Feb 2012 19:29:05 +0530 Subject: spi: Trivial warning fix The loop count i traverses for ntrans which is unsigned so make the loop count i also unsigned. Fix the below warning In file included from drivers/spi/spi-omap2-mcspi.c:38: include/linux/spi/spi.h: In function 'spi_message_alloc': include/linux/spi/spi.h:556: warning: comparison between signed and unsigned integer expressions Cc: Vitaly Wool Signed-off-by: Shubhrajyoti D Signed-off-by: Grant Likely --- include/linux/spi/spi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index f9e30a5b3543..98679b061b63 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -600,7 +600,7 @@ static inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags + ntrans * sizeof(struct spi_transfer), flags); if (m) { - int i; + unsigned i; struct spi_transfer *t = (struct spi_transfer *)(m + 1); INIT_LIST_HEAD(&m->transfers); -- cgit v1.2.3 From 49e599b8595f9d33276860c6a02e05f240c4ceca Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Mar 2012 02:48:05 -0700 Subject: spi: sh-hspi: control spi clock more correctly Current sh-hspi had used platform-specific speed. This patch remove it, and use spi_transfer specific speed. It removes unnecessary flags from struct sh_hspi_info, but struct sh_hspi_info is still exist, since sh-hspi needs platform info in the future. Signed-off-by: Kuninori Morimoto Signed-off-by: Grant Likely --- drivers/spi/spi-sh-hspi.c | 86 ++++++++++++++++++++++++++++++++++++--------- include/linux/spi/sh_hspi.h | 11 ------ 2 files changed, 70 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c index 42906731c40d..5784734d257d 100644 --- a/drivers/spi/spi-sh-hspi.c +++ b/drivers/spi/spi-sh-hspi.c @@ -22,6 +22,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ + +#include #include #include #include @@ -50,6 +52,7 @@ struct hspi_priv { void __iomem *addr; struct spi_master *master; struct device *dev; + struct clk *clk; }; /* @@ -162,6 +165,59 @@ static int hspi_unprepare_transfer(struct spi_master *master) return 0; } +static void hspi_hw_setup(struct hspi_priv *hspi, + struct spi_message *msg, + struct spi_transfer *t) +{ + struct spi_device *spi = msg->spi; + struct device *dev = hspi->dev; + u32 target_rate; + u32 spcr, idiv_clk; + u32 rate, best_rate, min, tmp; + + target_rate = t ? t->speed_hz : 0; + if (!target_rate) + target_rate = spi->max_speed_hz; + + /* + * find best IDIV/CLKCx settings + */ + min = ~0; + best_rate = 0; + spcr = 0; + for (idiv_clk = 0x00; idiv_clk <= 0x3F; idiv_clk++) { + rate = clk_get_rate(hspi->clk); + + /* IDIV calculation */ + if (idiv_clk & (1 << 5)) + rate /= 128; + else + rate /= 16; + + /* CLKCx calculation */ + rate /= (((idiv_clk & 0x1F) + 1) * 2) ; + + /* save best settings */ + tmp = abs(target_rate - rate); + if (tmp < min) { + min = tmp; + spcr = idiv_clk; + best_rate = rate; + } + } + + if (spi->mode & SPI_CPHA) + spcr |= 1 << 7; + if (spi->mode & SPI_CPOL) + spcr |= 1 << 6; + + dev_dbg(dev, "speed %d/%d\n", target_rate, best_rate); + + hspi_write(hspi, SPCR, spcr); + hspi_write(hspi, SPSR, 0x0); + hspi_write(hspi, SPSCR, 0x1); /* master mode */ +} + static int hspi_transfer_one_message(struct spi_master *master, struct spi_message *msg) { @@ -173,6 +229,8 @@ static int hspi_transfer_one_message(struct spi_master *master, ret = 0; list_for_each_entry(t, &msg->transfers, transfer_list) { + hspi_hw_setup(hspi, msg, t); + if (t->tx_buf) { ret = hspi_push(hspi, msg, t); if (ret < 0) @@ -197,28 +255,12 @@ static int hspi_setup(struct spi_device *spi) { struct hspi_priv *hspi = spi_master_get_devdata(spi->master); struct device *dev = hspi->dev; - struct sh_hspi_info *info = hspi2info(hspi); - u32 data; if (8 != spi->bits_per_word) { dev_err(dev, "bits_per_word should be 8\n"); return -EIO; } - /* setup first of all in under pm_runtime */ - data = SH_HSPI_CLK_DIVC(info->flags); - - if (info->flags & SH_HSPI_FBS) - data |= 1 << 7; - if (info->flags & SH_HSPI_CLKP_HIGH) - data |= 1 << 6; - if (info->flags & SH_HSPI_IDIV_DIV128) - data |= 1 << 5; - - hspi_write(hspi, SPCR, data); - hspi_write(hspi, SPSR, 0x0); - hspi_write(hspi, SPSCR, 0x1); /* master mode */ - dev_dbg(dev, "%s setup\n", spi->modalias); return 0; @@ -237,6 +279,7 @@ static int __devinit hspi_probe(struct platform_device *pdev) struct resource *res; struct spi_master *master; struct hspi_priv *hspi; + struct clk *clk; int ret; /* get base addr */ @@ -252,12 +295,20 @@ static int __devinit hspi_probe(struct platform_device *pdev) return -ENOMEM; } + clk = clk_get(NULL, "shyway_clk"); + if (!clk) { + dev_err(&pdev->dev, "shyway_clk is required\n"); + ret = -EINVAL; + goto error0; + } + hspi = spi_master_get_devdata(master); dev_set_drvdata(&pdev->dev, hspi); /* init hspi */ hspi->master = master; hspi->dev = &pdev->dev; + hspi->clk = clk; hspi->addr = devm_ioremap(hspi->dev, res->start, resource_size(res)); if (!hspi->addr) { @@ -289,6 +340,8 @@ static int __devinit hspi_probe(struct platform_device *pdev) error2: devm_iounmap(hspi->dev, hspi->addr); error1: + clk_put(clk); + error0: spi_master_put(master); return ret; @@ -300,6 +353,7 @@ static int __devexit hspi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + clk_put(hspi->clk); spi_unregister_master(hspi->master); devm_iounmap(hspi->dev, hspi->addr); diff --git a/include/linux/spi/sh_hspi.h b/include/linux/spi/sh_hspi.h index 956d11288294..a1121f872ac1 100644 --- a/include/linux/spi/sh_hspi.h +++ b/include/linux/spi/sh_hspi.h @@ -17,18 +17,7 @@ #ifndef SH_HSPI_H #define SH_HSPI_H -/* - * flags - * - * - */ -#define SH_HSPI_CLK_DIVC(d) (d & 0xFF) - -#define SH_HSPI_FBS (1 << 8) -#define SH_HSPI_CLKP_HIGH (1 << 9) /* default LOW */ -#define SH_HSPI_IDIV_DIV128 (1 << 10) /* default div16 */ struct sh_hspi_info { - u32 flags; }; #endif -- cgit v1.2.3