>From 76b99206d208614ef5a7202aee6ee7725d961226 Mon Sep 17 00:00:00 2001 From: Russ Gorby <[email protected]> Date: Fri, 30 Jul 2010 10:48:26 -0700 Subject: [PATCH 1/1] PM support for SSP-SPI master controller driver v.01
Signed-off-by: Russ Gorby <[email protected]> This is v.01 of the pm-supp patch for SSP-SPI This patch enables some of the power management support like D3 suspend/resume and the PM runtime as well This is a delta patch based on Alan's 2.6-mid-ref repo --- drivers/spi/pw_spi3.c | 341 ++++++++++++++++++++++++++++++++++++++++--------- 1 files changed, 282 insertions(+), 59 deletions(-) diff --git a/drivers/spi/pw_spi3.c b/drivers/spi/pw_spi3.c index fbb20a8..fa1ac8d 100644 --- a/drivers/spi/pw_spi3.c +++ b/drivers/spi/pw_spi3.c @@ -29,6 +29,7 @@ #include <linux/intel_mid_dma.h> #include <linux/interrupt.h> #include <linux/spi/spi.h> +#include <linux/pm_runtime.h> #include <linux/spi/pw_spi3.h> #define DRIVER_NAME "pw_spi3" @@ -92,6 +93,7 @@ struct callback_param { void *drv_data; int *donep; }; + struct driver_data { /* Driver model hookup */ struct pci_dev *pdev; @@ -110,12 +112,9 @@ struct driver_data { u32 clear_sr; u32 mask_sr; - struct tasklet_struct poll_transfer; - - int busy; - int run; /* Current message transfer state info */ + struct tasklet_struct poll_transfer; struct spi_message *cur_msg; size_t len; void *tx; @@ -132,8 +131,17 @@ struct driver_data { int (*read)(struct driver_data *drv_data); irqreturn_t (*transfer_handler)(struct driver_data *drv_data); void (*cs_control)(u32 command); + + /* controller state */ int dma_inited; + /* pwrstate mgmt */ + atomic_t active; + int pwrstate; +#define PWRSTATE_ON 1 +#define PWRSTATE_IDLE 2 +#define PWRSTATE_OFF 3 + /* used by DMA code */ struct pci_dev *dmac1; struct intel_mid_dma_slave dmas_tx; @@ -162,22 +170,47 @@ struct chip_data { int (*read)(struct driver_data *drv_data); }; +static struct pm_qos_request_list *pw_spi_pm_qos_req; + +static inline void active_inc(struct driver_data *drv_data) +{ + int i; + i = atomic_add_return(1, &drv_data->active); + pm_runtime_get(&drv_data->pdev->dev); +} + +static inline void active_dec(struct driver_data *drv_data) +{ + int i; + i = atomic_sub_return(1, &drv_data->active); + pm_runtime_put(&drv_data->pdev->dev); +} + +static inline int have_fifo_data(struct driver_data *drv_data, u32 *sssrp) +{ + u32 sssr; + void *reg = drv_data->ioaddr; + sssr = ioread32(reg + SSSR); + + if (sssrp) + *sssrp = sssr; + return ((sssr & SSSR_TFL) || !(sssr & SSSR_TNF)) || + ((sssr & SSSR_RFL) != SSSR_RFL || (sssr & SSSR_RNE)); +} + static void flush(struct driver_data *drv_data) { void *reg = drv_data->ioaddr; u32 sssr; /* If the transmit fifo is not empty, reset the interface. */ - sssr = ioread32(reg + SSSR); - if ((sssr & SSSR_TFL) || (sssr & SSSR_TNF) == 0) { + if (have_fifo_data(drv_data, &sssr)) { + dev_warn(&drv_data->pdev->dev, + "ERROR: flush: fifos not empty! sssr:%x", sssr); iowrite32(ioread32(reg + SSCR0) & ~SSCR0_SSE, reg + SSCR0); return; } - /* FIXME?: Timeout reset */ - while (ioread32(reg + SSSR) & SSSR_RNE) - ioread32(reg + SSDR); - iowrite32(SSSR_ROR, reg + SSSR); iowrite32(SSSR_TUR, reg + SSSR); } @@ -323,8 +356,6 @@ static void giveback(struct driver_data *drv_data) msg->complete(msg->context); } -static void int_transfer_complete(struct driver_data *drv_data); - static bool chan_filter(struct dma_chan *chan, void *param) { struct driver_data *drv_data = (struct driver_data *)param; @@ -339,7 +370,7 @@ static bool chan_filter(struct dma_chan *chan, void *param) return ret; } -static void pw_spi_dma_done(void *arg) +static void dma_transfer_complete(void *arg) { struct callback_param *param = arg; struct driver_data *drv_data; @@ -355,7 +386,6 @@ static void pw_spi_dma_done(void *arg) if (!drv_data->txdma_done || !drv_data->rxdma_done) return; - /* Clear Status Register */ iowrite32(drv_data->clear_sr, reg + SSSR); @@ -376,6 +406,7 @@ static void pw_spi_dma_done(void *arg) drv_data->cur_msg->status = 0; giveback(drv_data); + active_dec(drv_data); } static void pw_spi_dma_init(struct driver_data *drv_data) @@ -431,7 +462,6 @@ static void pw_spi_dma_init(struct driver_data *drv_data) goto free_rxchan; drv_data->txchan->private = txs; - /* set the dma done bit to 1 */ drv_data->dma_inited = 1; drv_data->txdma_done = 1; drv_data->rxdma_done = 1; @@ -464,7 +494,6 @@ static void dma_transfer(struct driver_data *drv_data) struct dma_chan *txchan, *rxchan; enum dma_ctrl_flags flag; - /* get Data Read/Write address */ ssdr_addr = (dma_addr_t)(drv_data->paddr + 0x10); @@ -480,11 +509,6 @@ static void dma_transfer(struct driver_data *drv_data) flag = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; - /* - * RRG - * (re)set rxchan->private->src_with based on drv_data->nbytes - * (re)set txchan->private->dst_with based on drv_data->nbytes - */ if (drv_data->rx_dma) { rxdesc = rxchan->device->device_prep_dma_memcpy (rxchan, /* DMA Channel */ @@ -493,7 +517,7 @@ static void dma_transfer(struct driver_data *drv_data) drv_data->len, /* Data Length */ flag); /* Flag */ - rxdesc->callback = pw_spi_dma_done; + rxdesc->callback = dma_transfer_complete; rxdesc->callback_param = &drv_data->rx_param; } @@ -506,15 +530,15 @@ static void dma_transfer(struct driver_data *drv_data) drv_data->len, /* Data Length */ flag); /* Flag */ - txdesc->callback = pw_spi_dma_done; + txdesc->callback = dma_transfer_complete; txdesc->callback_param = &drv_data->tx_param; } if (rxdesc) rxdesc->tx_submit(rxdesc); - if (txdesc) txdesc->tx_submit(txdesc); + } @@ -543,8 +567,7 @@ static int map_dma_buffers(struct driver_data *drv_data, static void set_dma_width(struct spi_device *spi, int bits) { - struct driver_data *drv_data = \ - spi_master_get_devdata(spi->master); + struct driver_data *drv_data = spi_master_get_devdata(spi->master); struct intel_mid_dma_slave *rxs, *txs; rxs = &drv_data->dmas_rx; txs = &drv_data->dmas_tx; @@ -602,6 +625,7 @@ static void int_transfer_complete(struct driver_data *drv_data) drv_data->cur_msg->status = 0; giveback(drv_data); + active_dec(drv_data); } static void transfer_complete(struct driver_data *drv_data) @@ -612,6 +636,7 @@ static void transfer_complete(struct driver_data *drv_data) drv_data->cur_msg->status = 0; giveback(drv_data); + active_dec(drv_data); } static irqreturn_t interrupt_transfer(struct driver_data *drv_data) @@ -713,8 +738,7 @@ static unsigned int ssp_get_clk_div(int speed) static int transfer(struct spi_device *spi, struct spi_message *msg) { - struct driver_data *drv_data = \ - spi_master_get_devdata(spi->master); + struct driver_data *drv_data = spi_master_get_devdata(spi->master); struct chip_data *chip = NULL; struct spi_transfer *transfer = NULL; void *reg = drv_data->ioaddr; @@ -724,6 +748,11 @@ static int transfer(struct spi_device *spi, struct spi_message *msg) u32 cr0; u32 cr1; + if (drv_data->pwrstate != PWRSTATE_ON) { + dev_dbg(&drv_data->pdev->dev, "transfer: busy, pwrstate:%d", + drv_data->pwrstate); + return -EBUSY; + } msg->actual_length = 0; msg->status = -EINPROGRESS; drv_data->cur_msg = msg; @@ -731,12 +760,8 @@ static int transfer(struct spi_device *spi, struct spi_message *msg) /* We handle only one transfer message since the protocol module has to control the out of band signaling. */ - transfer = list_entry(msg->transfers.next, - struct spi_transfer, transfer_list); - - chip = spi_get_ctldata(msg->spi); - - drv_data->busy = 1; + transfer = list_entry(msg->transfers.next, struct spi_transfer, + transfer_list); /* Check transfer length */ if (transfer->len > 8192) { @@ -748,6 +773,8 @@ static int transfer(struct spi_device *spi, struct spi_message *msg) } /* Setup the transfer state based on the type of transfer */ + active_inc(drv_data); + chip = spi_get_ctldata(msg->spi); flush(drv_data); drv_data->n_bytes = chip->n_bytes; drv_data->tx = (void *)transfer->tx_buf; @@ -821,14 +848,14 @@ static int transfer(struct spi_device *spi, struct spi_message *msg) cr1 |= drv_data->int_cr1; dev_dbg(&drv_data->pdev->dev, - "%s drv_data:%p len:%d n_bytes:%d cr0:%x cr1:%x", + "%s drv_data:%p len:%d n_bytes:%d cr0:%x cr1:%x", (drv_data->dma_mapped ? "DMA io:" : (chip->poll_mode ? "Poll io:" : "Intr io:")), drv_data, drv_data->len, drv_data->n_bytes, cr0, cr1); /* see if we need to reload the config registers */ if ((ioread32(reg + SSCR0) != cr0) - || (ioread32(reg + SSCR0) & SSCR1_CHANGE_MASK) != + || (ioread32(reg + SSCR1) & SSCR1_CHANGE_MASK) != (cr1 & SSCR1_CHANGE_MASK)) { /* stop the SSP, and update the other bits */ @@ -864,7 +891,13 @@ static int setup(struct spi_device *spi) uint tx_thres = TX_THRESH_DFLT; uint rx_thres = RX_THRESH_DFLT; u32 clk_div; + struct driver_data *drv_data = spi_master_get_devdata(spi->master); + if (drv_data->pwrstate != PWRSTATE_ON) { + dev_dbg(&drv_data->pdev->dev, "setup: busy, pwrstate:%d", + drv_data->pwrstate); + return -EBUSY; + } if (!spi->bits_per_word) spi->bits_per_word = 8; @@ -956,18 +989,17 @@ static void cleanup(struct spi_device *spi) spi_set_ctldata(spi, NULL); } -static int pw_spi_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) +static int pw_spi_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct device *dev = &pdev->dev; struct spi_master *master; - struct driver_data *drv_data = 0; + struct driver_data *drv_data; int status = 0; int pci_bar = 0; void __iomem *syscfg_ioaddr; unsigned long syscfg; - dev_info(&pdev->dev, "found PCI SSP controller(ID: %04x:%04x)", + dev_info(dev, "found PCI SSP controller(ID: %04x:%04x)", pdev->vendor, pdev->device); status = pci_enable_device(pdev); @@ -978,15 +1010,16 @@ static int pw_spi_probe(struct pci_dev *pdev, master = spi_alloc_master(dev, sizeof(struct driver_data)); if (!master) { - dev_err(&pdev->dev, "cannot alloc spi_master"); + dev_err(dev, "cannot alloc spi_master"); status = -ENOMEM; goto err_free_0; } drv_data = spi_master_get_devdata(master); + atomic_set(&drv_data->active, 0); drv_data->master = master; - drv_data->pdev = pdev; + drv_data->pwrstate = PWRSTATE_ON; master->mode_bits = SPI_CPOL | SPI_CPHA; master->bus_num = 3; @@ -999,7 +1032,7 @@ static int pw_spi_probe(struct pci_dev *pdev, drv_data->paddr = pci_resource_start(pdev, pci_bar); drv_data->iolen = pci_resource_len(pdev, pci_bar); - status = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev)); + status = pci_request_region(pdev, pci_bar, dev_name(dev)); if (status) goto err_free_1; @@ -1008,9 +1041,9 @@ static int pw_spi_probe(struct pci_dev *pdev, status = -ENOMEM; goto err_free_2; } - dev_dbg(&pdev->dev, "paddr = : %08lx", drv_data->paddr); - dev_dbg(&pdev->dev, "ioaddr = : %p", drv_data->ioaddr); - dev_dbg(&pdev->dev, "attaching to IRQ: %04x", pdev->irq); + dev_dbg(dev, "paddr = : %08lx", drv_data->paddr); + dev_dbg(dev, "ioaddr = : %p", drv_data->ioaddr); + dev_dbg(dev, "attaching to IRQ: %04x", pdev->irq); /* Attach to IRQ */ drv_data->irq = pdev->irq; @@ -1018,7 +1051,7 @@ static int pw_spi_probe(struct pci_dev *pdev, status = request_irq(drv_data->irq, ssp_int, IRQF_SHARED, "pw_spi3", drv_data); if (status < 0) { - dev_err(&pdev->dev, "can not get IRQ %d", drv_data->irq); + dev_err(dev, "can not get IRQ %d", drv_data->irq); goto err_free_3; } @@ -1041,7 +1074,7 @@ static int pw_spi_probe(struct pci_dev *pdev, poll_transfer, (unsigned long)drv_data); /* Load default SSP configuration */ - dev_info(&pdev->dev, "setup default SSP configuration"); + dev_info(dev, "setup default SSP configuration"); iowrite32(0, drv_data->ioaddr + SSCR0); iowrite32(SSCR1_RxTresh(RX_THRESH_DFLT) | SSCR1_TxTresh(TX_THRESH_DFLT), @@ -1051,18 +1084,18 @@ static int pw_spi_probe(struct pci_dev *pdev, iowrite32(PNWL_SSPSP, drv_data->ioaddr + SSPSP); /* Register with the SPI framework */ - dev_info(&pdev->dev, "register with SPI framework"); - + dev_info(dev, "register with SPI framework"); status = spi_register_master(master); - if (status != 0) { - dev_err(&pdev->dev, "problem registering driver"); + dev_err(dev, "problem registering driver"); goto err_free_4; } - drv_data->dma_inited = 0; - pw_spi_dma_init(drv_data); + /* set pm runtime power state and register with power system */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pw_spi_dma_init(drv_data); pci_set_drvdata(pdev, drv_data); return status; @@ -1110,22 +1143,143 @@ static void __devexit pw_spi_remove(struct pci_dev *pdev) #ifdef CONFIG_PM +/* + * for now IDLE and OFF states are treated the same + */ +static int _pm_suspend(struct pci_dev *pdev, int to) +{ + struct driver_data *drv_data = pci_get_drvdata(pdev); + void *reg = drv_data->ioaddr; + int from = drv_data->pwrstate; + u32 sssr; + + + if (to != PWRSTATE_IDLE && to != PWRSTATE_OFF) { + dev_err(&pdev->dev, "ERROR: suspend: invalid dst pwrstate %x", + to); + return -EINVAL; + } + + switch (from) { + case PWRSTATE_ON: + /* to != PWRSTATE_ON: disables transfer() */ + drv_data->pwrstate = to; + if (atomic_read(&drv_data->active) != 0) { + drv_data->pwrstate = from; + return -EBUSY; + } + dev_dbg(&pdev->dev, "suspend: turn off SSP"); + tasklet_disable(&drv_data->poll_transfer); + if (have_fifo_data(drv_data, &sssr)) { + dev_err(&pdev->dev, + "ERROR: suspend: i/o present! sssr:%x", sssr); + } + iowrite32(0, reg + SSCR0); + dev_dbg(&pdev->dev, "suspend: cr0:%x cr1:%x sssr:%x", + ioread32(reg + SSCR0), ioread32(reg + SSCR1), + ioread32(reg + SSSR)); + break; + case PWRSTATE_IDLE: + case PWRSTATE_OFF: + /* to != PWRSTATE_ON: disables transfer() */ + drv_data->pwrstate = to; + break; + default: + dev_err(&pdev->dev, "ERROR: suspend: invalid src pwrstate %x", + from); + return -EINVAL; + } + + return 0; +} + static int pw_spi_suspend(struct pci_dev *pdev, pm_message_t state) { + int retval; struct driver_data *drv_data = pci_get_drvdata(pdev); + dev_dbg(&pdev->dev, "suspend"); - tasklet_disable(&drv_data->poll_transfer); + if (drv_data->pwrstate != PWRSTATE_ON) + dev_warn(&pdev->dev, "suspend: !on, pwrstate:%d", + drv_data->pwrstate); + retval = _pm_suspend(pdev, PWRSTATE_OFF); + if (retval) + return retval; + retval = pci_prepare_to_sleep(pdev); + if (retval) { + dev_err(&pdev->dev, "suspend: prepare to sleep failed"); + return retval; + } + pci_disable_device(pdev); return 0; } +/* + * for now IDLE and OFF states are treated the same + */ +static void _pw_resume(struct pci_dev *pdev, int to) +{ + struct driver_data *drv_data = pci_get_drvdata(pdev); + void *reg = drv_data->ioaddr; + int from = drv_data->pwrstate; + + if (to != PWRSTATE_IDLE && to != PWRSTATE_ON) { + dev_err(&pdev->dev, "ERROR: resume: invalid dst pwrstate %x", + to); + return; + } + switch (from) { + case PWRSTATE_IDLE: + case PWRSTATE_OFF: + if (to == PWRSTATE_ON) { + dev_dbg(&pdev->dev, "resume: turn on SSP"); + + /* + * we don't bother reconfiguring the registers + * on resume - that will get done when transfer() + * is called + */ + tasklet_enable(&drv_data->poll_transfer); + dev_dbg(&pdev->dev, "resume: cr0:%x cr1:%x sssr:%x", + ioread32(reg + SSCR0), ioread32(reg + SSCR1), + ioread32(reg + SSSR)); + + /* to == PWRSTATE_ON: enables transfer() */ + drv_data->pwrstate = PWRSTATE_ON; + } + break; + case PWRSTATE_ON: + break; + default: + dev_err(&pdev->dev, "ERROR: resume: invalid src pwrstate %x", + from); + return; + } +} + static int pw_spi_resume(struct pci_dev *pdev) { + int retval; struct driver_data *drv_data = pci_get_drvdata(pdev); + dev_dbg(&pdev->dev, "resume"); - tasklet_enable(&drv_data->poll_transfer); + if (drv_data->pwrstate != PWRSTATE_OFF) + dev_warn(&pdev->dev, "resume: !off, pwrstate:%d", + drv_data->pwrstate); + retval = pci_enable_device(pdev); + if (retval) { + dev_err(&pdev->dev, "resume: back from sleep failed"); + return retval; + } + retval = pci_back_from_sleep(pdev); + if (retval) { + dev_err(&pdev->dev, "resume: back from sleep failed"); + return retval; + } + _pw_resume(pdev, PWRSTATE_ON); return 0; } @@ -1135,12 +1289,77 @@ static int pw_spi_resume(struct pci_dev *pdev) #endif /* CONFIG_PM */ +static int pw_spi_pm_runtime_resume(struct device *dev) +{ + int retval; + struct pci_dev *pdev = to_pci_dev(dev); + struct driver_data *drv_data = pci_get_drvdata(pdev); + + dev_dbg(dev, "pm runtime resume"); + + if (drv_data->pwrstate != PWRSTATE_IDLE) + dev_warn(&pdev->dev, "rt suspend: !idle, pwrstate:%d", + drv_data->pwrstate); + retval = pci_back_from_sleep(pdev); + if (retval) + dev_err(&pdev->dev, "rt resume: back from sleep failed"); + _pw_resume(pdev, PWRSTATE_ON); + + return 0; +} + +static int pw_spi_pm_runtime_suspend(struct device *dev) +{ + int retval; + struct pci_dev *pdev = to_pci_dev(dev); + struct driver_data *drv_data = pci_get_drvdata(pdev); + + dev_dbg(dev, "pm runtime suspend"); + + if (drv_data->pwrstate != PWRSTATE_ON) + dev_warn(&pdev->dev, "rt suspend: !on, pwrstate:%d", + drv_data->pwrstate); + retval = _pm_suspend(pdev, PWRSTATE_IDLE); + if (retval) + return retval; + retval = pci_prepare_to_sleep(pdev); + if (retval) + dev_err(&pdev->dev, "rt suspend: prepare to sleep failed"); + + return 0; +} + +/* check conditions and queue runtime suspend if idle */ +static int pw_spi_pm_runtime_idle(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct driver_data *drv_data = pci_get_drvdata(pdev); + + dev_dbg(dev, "pm runtime idle"); + + if (atomic_read(&drv_data->active) == 0) { + dev_dbg(dev, "rt idle: queue suspend request"); + pm_runtime_suspend(dev); + } + + return 0; +} + +static const struct dev_pm_ops pw_spi_pm = { + .runtime_resume = pw_spi_pm_runtime_resume, + .runtime_suspend = pw_spi_pm_runtime_suspend, + .runtime_idle = pw_spi_pm_runtime_idle +}; + static const struct pci_device_id pci_ids[] __devinitdata = { { PCI_VDEVICE(INTEL, 0x0816) }, { } }; -static struct pci_driver pnwl_spi3_driver = { +static struct pci_driver pnwl_spi_driver = { + .driver = { + .pm = &pw_spi_pm, + }, .name = DRIVER_NAME, .id_table = pci_ids, .probe = pw_spi_probe, @@ -1151,12 +1370,16 @@ static struct pci_driver pnwl_spi3_driver = { static int __init pw_spi_init(void) { - return pci_register_driver(&pnwl_spi3_driver); + pw_spi_pm_qos_req = + pm_qos_add_request(PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + return pci_register_driver(&pnwl_spi_driver); } late_initcall(pw_spi_init); static void __exit pw_spi_exit(void) { - pci_unregister_driver(&pnwl_spi3_driver); + pci_unregister_driver(&pnwl_spi_driver); + pm_qos_remove_request(pw_spi_pm_qos_req); } module_exit(pw_spi_exit); -- 1.6.0.6
0001-PM-support-for-SSP-SPI-master-controller-driver.patch
Description: 0001-PM-support-for-SSP-SPI-master-controller-driver.patch
_______________________________________________ MeeGo-dev mailing list [email protected] http://lists.meego.com/listinfo/meego-dev
