From 056676dabd9f4c69a6adcad208e9aa2ca7241400 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 19 May 2010 18:34:16 +0000 Subject: sh: prepare the SDHI MFD driver to pass DMA configuration to tmio_mmc.c Pass DMA slave IDs from platform down to the tmio_mmc driver, to be used for dmaengine configuration. Signed-off-by: Guennadi Liakhovetski Acked-by: Samuel Ortiz Signed-off-by: Paul Mundt --- drivers/mfd/sh_mobile_sdhi.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index 497f91b6138e..555374db8662 100644 --- a/drivers/mfd/sh_mobile_sdhi.c +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -26,11 +26,15 @@ #include #include #include +#include struct sh_mobile_sdhi { struct clk *clk; struct tmio_mmc_data mmc_data; struct mfd_cell cell_mmc; + struct sh_dmae_slave param_tx; + struct sh_dmae_slave param_rx; + struct tmio_mmc_dma dma_priv; }; static struct resource sh_mobile_sdhi_resources[] = { @@ -64,6 +68,8 @@ static void sh_mobile_sdhi_set_pwr(struct platform_device *tmio, int state) static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) { struct sh_mobile_sdhi *priv; + struct tmio_mmc_data *mmc_data; + struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; struct resource *mem; char clk_name[8]; int ret, irq; @@ -85,6 +91,8 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) return -ENOMEM; } + mmc_data = &priv->mmc_data; + snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id); priv->clk = clk_get(&pdev->dev, clk_name); if (IS_ERR(priv->clk)) { @@ -96,12 +104,20 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) clk_enable(priv->clk); - priv->mmc_data.hclk = clk_get_rate(priv->clk); - priv->mmc_data.set_pwr = sh_mobile_sdhi_set_pwr; - priv->mmc_data.capabilities = MMC_CAP_MMC_HIGHSPEED; + mmc_data->hclk = clk_get_rate(priv->clk); + mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; + mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; + + if (p && p->dma_slave_tx >= 0 && p->dma_slave_rx >= 0) { + priv->param_tx.slave_id = p->dma_slave_tx; + priv->param_rx.slave_id = p->dma_slave_rx; + priv->dma_priv.chan_priv_tx = &priv->param_tx; + priv->dma_priv.chan_priv_rx = &priv->param_rx; + mmc_data->dma = &priv->dma_priv; + } memcpy(&priv->cell_mmc, &sh_mobile_sdhi_cell, sizeof(priv->cell_mmc)); - priv->cell_mmc.driver_data = &priv->mmc_data; + priv->cell_mmc.driver_data = mmc_data; priv->cell_mmc.platform_data = &priv->cell_mmc; priv->cell_mmc.data_size = sizeof(priv->cell_mmc); -- cgit v1.2.3 From 311f3ac76826bfd8ed6213ded91ec947df164def Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 19 May 2010 18:34:22 +0000 Subject: mmc: add DMA support to tmio_mmc driver, when used on SuperH SDHI controllers on SuperH, served by the tmio_mmc driver, can use slave DMA for data transfer. This patch adds support for the dmaengine API to the tmio_mmc driver. Signed-off-by: Guennadi Liakhovetski Acked-by: Ian Molton Signed-off-by: Paul Mundt --- drivers/mfd/Kconfig | 6 + drivers/mmc/host/tmio_mmc.c | 359 ++++++++++++++++++++++++++++++++++++++++---- drivers/mmc/host/tmio_mmc.h | 13 ++ 3 files changed, 348 insertions(+), 30 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index de3e74cde51c..3c6a9860dd9c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -49,6 +49,7 @@ config MFD_SH_MOBILE_SDHI bool "Support for SuperH Mobile SDHI" depends on SUPERH || ARCH_SHMOBILE select MFD_CORE + select TMIO_MMC_DMA ---help--- This driver supports the SDHI hardware block found in many SuperH Mobile SoCs. @@ -162,6 +163,11 @@ config MFD_TMIO bool default n +config TMIO_MMC_DMA + bool + select DMA_ENGINE + select DMADEVICES + config MFD_T7L66XB bool "Support Toshiba T7L66XB" depends on ARM && HAVE_CLK diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index b2b577f6afd4..3ecd41875fac 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -131,8 +132,8 @@ tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd) host->cmd = cmd; -/* FIXME - this seems to be ok comented out but the spec suggest this bit should - * be set when issuing app commands. +/* FIXME - this seems to be ok commented out but the spec suggest this bit + * should be set when issuing app commands. * if(cmd->flags & MMC_FLAG_ACMD) * c |= APP_CMD; */ @@ -155,12 +156,12 @@ tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd) return 0; } -/* This chip always returns (at least?) as much data as you ask for. +/* + * This chip always returns (at least?) as much data as you ask for. * I'm unsure what happens if you ask for less than a block. This should be * looked into to ensure that a funny length read doesnt hose the controller. - * */ -static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host) +static void tmio_mmc_pio_irq(struct tmio_mmc_host *host) { struct mmc_data *data = host->data; unsigned short *buf; @@ -180,7 +181,7 @@ static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host) count = data->blksz; pr_debug("count: %08x offset: %08x flags %08x\n", - count, host->sg_off, data->flags); + count, host->sg_off, data->flags); /* Transfer the data */ if (data->flags & MMC_DATA_READ) @@ -198,7 +199,7 @@ static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host) return; } -static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) +static void tmio_mmc_do_data_irq(struct tmio_mmc_host *host) { struct mmc_data *data = host->data; struct mmc_command *stop; @@ -206,7 +207,7 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) host->data = NULL; if (!data) { - pr_debug("Spurious data end IRQ\n"); + dev_warn(&host->pdev->dev, "Spurious data end IRQ\n"); return; } stop = data->stop; @@ -219,7 +220,8 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) pr_debug("Completed data request\n"); - /*FIXME - other drivers allow an optional stop command of any given type + /* + * FIXME: other drivers allow an optional stop command of any given type * which we dont do, as the chip can auto generate them. * Perhaps we can be smarter about when to use auto CMD12 and * only issue the auto request when we know this is the desired @@ -227,10 +229,17 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) * upper layers expect. For now, we do what works. */ - if (data->flags & MMC_DATA_READ) - disable_mmc_irqs(host, TMIO_MASK_READOP); - else - disable_mmc_irqs(host, TMIO_MASK_WRITEOP); + if (data->flags & MMC_DATA_READ) { + if (!host->chan_rx) + disable_mmc_irqs(host, TMIO_MASK_READOP); + dev_dbg(&host->pdev->dev, "Complete Rx request %p\n", + host->mrq); + } else { + if (!host->chan_tx) + disable_mmc_irqs(host, TMIO_MASK_WRITEOP); + dev_dbg(&host->pdev->dev, "Complete Tx request %p\n", + host->mrq); + } if (stop) { if (stop->opcode == 12 && !stop->arg) @@ -242,7 +251,35 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) tmio_mmc_finish_request(host); } -static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, +static void tmio_mmc_data_irq(struct tmio_mmc_host *host) +{ + struct mmc_data *data = host->data; + + if (!data) + return; + + if (host->chan_tx && (data->flags & MMC_DATA_WRITE)) { + /* + * Has all data been written out yet? Testing on SuperH showed, + * that in most cases the first interrupt comes already with the + * BUSY status bit clear, but on some operations, like mount or + * in the beginning of a write / sync / umount, there is one + * DATAEND interrupt with the BUSY bit set, in this cases + * waiting for one more interrupt fixes the problem. + */ + if (!(sd_ctrl_read32(host, CTL_STATUS) & TMIO_STAT_CMD_BUSY)) { + disable_mmc_irqs(host, TMIO_STAT_DATAEND); + tasklet_schedule(&host->dma_complete); + } + } else if (host->chan_rx && (data->flags & MMC_DATA_READ)) { + disable_mmc_irqs(host, TMIO_STAT_DATAEND); + tasklet_schedule(&host->dma_complete); + } else { + tmio_mmc_do_data_irq(host); + } +} + +static void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, unsigned int stat) { struct mmc_command *cmd = host->cmd; @@ -282,10 +319,16 @@ static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, * If theres no data or we encountered an error, finish now. */ if (host->data && !cmd->error) { - if (host->data->flags & MMC_DATA_READ) - enable_mmc_irqs(host, TMIO_MASK_READOP); - else - enable_mmc_irqs(host, TMIO_MASK_WRITEOP); + if (host->data->flags & MMC_DATA_READ) { + if (!host->chan_rx) + enable_mmc_irqs(host, TMIO_MASK_READOP); + } else { + struct dma_chan *chan = host->chan_tx; + if (!chan) + enable_mmc_irqs(host, TMIO_MASK_WRITEOP); + else + tasklet_schedule(&host->dma_issue); + } } else { tmio_mmc_finish_request(host); } @@ -293,7 +336,6 @@ static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, return; } - static irqreturn_t tmio_mmc_irq(int irq, void *devid) { struct tmio_mmc_host *host = devid; @@ -311,7 +353,7 @@ static irqreturn_t tmio_mmc_irq(int irq, void *devid) if (!ireg) { disable_mmc_irqs(host, status & ~irq_mask); - pr_debug("tmio_mmc: Spurious irq, disabling! " + pr_warning("tmio_mmc: Spurious irq, disabling! " "0x%08x 0x%08x 0x%08x\n", status, irq_mask, ireg); pr_debug_status(status); @@ -363,16 +405,265 @@ out: return IRQ_HANDLED; } +#ifdef CONFIG_TMIO_MMC_DMA +static void tmio_mmc_enable_dma(struct tmio_mmc_host *host, bool enable) +{ +#if defined(CONFIG_SUPERH) || defined(CONFIG_ARCH_SHMOBILE) + /* Switch DMA mode on or off - SuperH specific? */ + sd_ctrl_write16(host, 0xd8, enable ? 2 : 0); +#endif +} + +static void tmio_dma_complete(void *arg) +{ + struct tmio_mmc_host *host = arg; + + dev_dbg(&host->pdev->dev, "Command completed\n"); + + if (!host->data) + dev_warn(&host->pdev->dev, "NULL data in DMA completion!\n"); + else + enable_mmc_irqs(host, TMIO_STAT_DATAEND); +} + +static int tmio_mmc_start_dma_rx(struct tmio_mmc_host *host) +{ + struct scatterlist *sg = host->sg_ptr; + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *chan = host->chan_rx; + int ret; + + ret = dma_map_sg(&host->pdev->dev, sg, host->sg_len, DMA_FROM_DEVICE); + if (ret > 0) { + host->dma_sglen = ret; + desc = chan->device->device_prep_slave_sg(chan, sg, ret, + DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + } + + if (desc) { + host->desc = desc; + desc->callback = tmio_dma_complete; + desc->callback_param = host; + host->cookie = desc->tx_submit(desc); + if (host->cookie < 0) { + host->desc = NULL; + ret = host->cookie; + } else { + chan->device->device_issue_pending(chan); + } + } + dev_dbg(&host->pdev->dev, "%s(): mapped %d -> %d, cookie %d, rq %p\n", + __func__, host->sg_len, ret, host->cookie, host->mrq); + + if (!host->desc) { + /* DMA failed, fall back to PIO */ + if (ret >= 0) + ret = -EIO; + host->chan_rx = NULL; + dma_release_channel(chan); + /* Free the Tx channel too */ + chan = host->chan_tx; + if (chan) { + host->chan_tx = NULL; + dma_release_channel(chan); + } + dev_warn(&host->pdev->dev, + "DMA failed: %d, falling back to PIO\n", ret); + tmio_mmc_enable_dma(host, false); + reset(host); + /* Fail this request, let above layers recover */ + host->mrq->cmd->error = ret; + tmio_mmc_finish_request(host); + } + + dev_dbg(&host->pdev->dev, "%s(): desc %p, cookie %d, sg[%d]\n", __func__, + desc, host->cookie, host->sg_len); + + return ret > 0 ? 0 : ret; +} + +static int tmio_mmc_start_dma_tx(struct tmio_mmc_host *host) +{ + struct scatterlist *sg = host->sg_ptr; + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *chan = host->chan_tx; + int ret; + + ret = dma_map_sg(&host->pdev->dev, sg, host->sg_len, DMA_TO_DEVICE); + if (ret > 0) { + host->dma_sglen = ret; + desc = chan->device->device_prep_slave_sg(chan, sg, ret, + DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + } + + if (desc) { + host->desc = desc; + desc->callback = tmio_dma_complete; + desc->callback_param = host; + host->cookie = desc->tx_submit(desc); + if (host->cookie < 0) { + host->desc = NULL; + ret = host->cookie; + } + } + dev_dbg(&host->pdev->dev, "%s(): mapped %d -> %d, cookie %d, rq %p\n", + __func__, host->sg_len, ret, host->cookie, host->mrq); + + if (!host->desc) { + /* DMA failed, fall back to PIO */ + if (ret >= 0) + ret = -EIO; + host->chan_tx = NULL; + dma_release_channel(chan); + /* Free the Rx channel too */ + chan = host->chan_rx; + if (chan) { + host->chan_rx = NULL; + dma_release_channel(chan); + } + dev_warn(&host->pdev->dev, + "DMA failed: %d, falling back to PIO\n", ret); + tmio_mmc_enable_dma(host, false); + reset(host); + /* Fail this request, let above layers recover */ + host->mrq->cmd->error = ret; + tmio_mmc_finish_request(host); + } + + dev_dbg(&host->pdev->dev, "%s(): desc %p, cookie %d\n", __func__, + desc, host->cookie); + + return ret > 0 ? 0 : ret; +} + +static int tmio_mmc_start_dma(struct tmio_mmc_host *host, + struct mmc_data *data) +{ + if (data->flags & MMC_DATA_READ) { + if (host->chan_rx) + return tmio_mmc_start_dma_rx(host); + } else { + if (host->chan_tx) + return tmio_mmc_start_dma_tx(host); + } + + return 0; +} + +static void tmio_issue_tasklet_fn(unsigned long priv) +{ + struct tmio_mmc_host *host = (struct tmio_mmc_host *)priv; + struct dma_chan *chan = host->chan_tx; + + chan->device->device_issue_pending(chan); +} + +static void tmio_tasklet_fn(unsigned long arg) +{ + struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg; + + if (host->data->flags & MMC_DATA_READ) + dma_unmap_sg(&host->pdev->dev, host->sg_ptr, host->dma_sglen, + DMA_FROM_DEVICE); + else + dma_unmap_sg(&host->pdev->dev, host->sg_ptr, host->dma_sglen, + DMA_TO_DEVICE); + + tmio_mmc_do_data_irq(host); +} + +/* It might be necessary to make filter MFD specific */ +static bool tmio_mmc_filter(struct dma_chan *chan, void *arg) +{ + dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg); + chan->private = arg; + return true; +} + +static void tmio_mmc_request_dma(struct tmio_mmc_host *host, + struct tmio_mmc_data *pdata) +{ + host->cookie = -EINVAL; + host->desc = NULL; + + /* We can only either use DMA for both Tx and Rx or not use it at all */ + if (pdata->dma) { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + host->chan_tx = dma_request_channel(mask, tmio_mmc_filter, + pdata->dma->chan_priv_tx); + dev_dbg(&host->pdev->dev, "%s: TX: got channel %p\n", __func__, + host->chan_tx); + + if (!host->chan_tx) + return; + + host->chan_rx = dma_request_channel(mask, tmio_mmc_filter, + pdata->dma->chan_priv_rx); + dev_dbg(&host->pdev->dev, "%s: RX: got channel %p\n", __func__, + host->chan_rx); + + if (!host->chan_rx) { + dma_release_channel(host->chan_tx); + host->chan_tx = NULL; + return; + } + + tasklet_init(&host->dma_complete, tmio_tasklet_fn, (unsigned long)host); + tasklet_init(&host->dma_issue, tmio_issue_tasklet_fn, (unsigned long)host); + + tmio_mmc_enable_dma(host, true); + } +} + +static void tmio_mmc_release_dma(struct tmio_mmc_host *host) +{ + if (host->chan_tx) { + struct dma_chan *chan = host->chan_tx; + host->chan_tx = NULL; + dma_release_channel(chan); + } + if (host->chan_rx) { + struct dma_chan *chan = host->chan_rx; + host->chan_rx = NULL; + dma_release_channel(chan); + } + + host->cookie = -EINVAL; + host->desc = NULL; +} +#else +static int tmio_mmc_start_dma(struct tmio_mmc_host *host, + struct mmc_data *data) +{ + return 0; +} + +static void tmio_mmc_request_dma(struct tmio_mmc_host *host, + struct tmio_mmc_data *pdata) +{ + host->chan_tx = NULL; + host->chan_rx = NULL; +} + +static void tmio_mmc_release_dma(struct tmio_mmc_host *host) +{ +} +#endif + static int tmio_mmc_start_data(struct tmio_mmc_host *host, struct mmc_data *data) { pr_debug("setup data transfer: blocksize %08x nr_blocks %d\n", - data->blksz, data->blocks); + data->blksz, data->blocks); /* Hardware cannot perform 1 and 2 byte requests in 4 bit mode */ if (data->blksz < 4 && host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) { - printk(KERN_ERR "%s: %d byte block unsupported in 4 bit mode\n", - mmc_hostname(host->mmc), data->blksz); + pr_err("%s: %d byte block unsupported in 4 bit mode\n", + mmc_hostname(host->mmc), data->blksz); return -EINVAL; } @@ -383,7 +674,7 @@ static int tmio_mmc_start_data(struct tmio_mmc_host *host, sd_ctrl_write16(host, CTL_SD_XFER_LEN, data->blksz); sd_ctrl_write16(host, CTL_XFER_BLK_COUNT, data->blocks); - return 0; + return tmio_mmc_start_dma(host, data); } /* Process requests from the MMC layer */ @@ -404,7 +695,6 @@ static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) } ret = tmio_mmc_start_command(host, mrq->cmd); - if (!ret) return; @@ -459,10 +749,10 @@ static int tmio_mmc_get_ro(struct mmc_host *mmc) { struct tmio_mmc_host *host = mmc_priv(mmc); - return (sd_ctrl_read16(host, CTL_STATUS) & TMIO_STAT_WRPROTECT) ? 0 : 1; + return (sd_ctrl_read32(host, CTL_STATUS) & TMIO_STAT_WRPROTECT) ? 0 : 1; } -static struct mmc_host_ops tmio_mmc_ops = { +static const struct mmc_host_ops tmio_mmc_ops = { .request = tmio_mmc_request, .set_ios = tmio_mmc_set_ios, .get_ro = tmio_mmc_get_ro, @@ -515,6 +805,7 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev) struct tmio_mmc_host *host; struct mmc_host *mmc; int ret = -EINVAL; + u32 irq_mask = TMIO_MASK_CMD; if (dev->num_resources != 2) goto out; @@ -578,13 +869,20 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev) if (ret) goto cell_disable; + /* See if we also get DMA */ + tmio_mmc_request_dma(host, pdata); + mmc_add_host(mmc); - printk(KERN_INFO "%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc), - (unsigned long)host->ctl, host->irq); + pr_info("%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc), + (unsigned long)host->ctl, host->irq); /* Unmask the IRQs we want to know about */ - enable_mmc_irqs(host, TMIO_MASK_IRQ); + if (!host->chan_rx) + irq_mask |= TMIO_MASK_READOP; + if (!host->chan_tx) + irq_mask |= TMIO_MASK_WRITEOP; + enable_mmc_irqs(host, irq_mask); return 0; @@ -609,6 +907,7 @@ static int __devexit tmio_mmc_remove(struct platform_device *dev) if (mmc) { struct tmio_mmc_host *host = mmc_priv(mmc); mmc_remove_host(mmc); + tmio_mmc_release_dma(host); free_irq(host->irq, host); if (cell->disable) cell->disable(dev); diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index dafecfbcd91a..64f7d5dfc106 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -10,6 +10,8 @@ */ #include +#include +#include #define CTL_SD_CMD 0x00 #define CTL_ARG_REG 0x04 @@ -106,6 +108,17 @@ struct tmio_mmc_host { unsigned int sg_off; struct platform_device *pdev; + + /* DMA support */ + struct dma_chan *chan_rx; + struct dma_chan *chan_tx; + struct tasklet_struct dma_complete; + struct tasklet_struct dma_issue; +#ifdef CONFIG_TMIO_MMC_DMA + struct dma_async_tx_descriptor *desc; + unsigned int dma_sglen; + dma_cookie_t cookie; +#endif }; #include -- cgit v1.2.3 From f87c20a9db43f6f731270eeef616e1bcc266c2d4 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 19 May 2010 18:36:08 +0000 Subject: mfd: pass platform flags down to the tmio_mmc driver Signed-off-by: Guennadi Liakhovetski Acked-by: Ian Molton Signed-off-by: Paul Mundt --- drivers/mfd/sh_mobile_sdhi.c | 2 ++ include/linux/mfd/sh_mobile_sdhi.h | 1 + 2 files changed, 3 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index 555374db8662..19e0d48a599a 100644 --- a/drivers/mfd/sh_mobile_sdhi.c +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -107,6 +107,8 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data->hclk = clk_get_rate(priv->clk); mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; + if (p) + mmc_data->flags = p->tmio_flags; if (p && p->dma_slave_tx >= 0 && p->dma_slave_rx >= 0) { priv->param_tx.slave_id = p->dma_slave_tx; diff --git a/include/linux/mfd/sh_mobile_sdhi.h b/include/linux/mfd/sh_mobile_sdhi.h index c305461e7f47..c7b47f8ad650 100644 --- a/include/linux/mfd/sh_mobile_sdhi.h +++ b/include/linux/mfd/sh_mobile_sdhi.h @@ -4,6 +4,7 @@ struct sh_mobile_sdhi_info { int dma_slave_tx; int dma_slave_rx; + unsigned long tmio_flags; void (*set_pwr)(struct platform_device *pdev, int state); }; -- cgit v1.2.3 From bb0fe533514c1d480c6c16f8bad070270a73a96c Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 19 May 2010 18:37:36 +0000 Subject: sh: allow platforms to specify SD-card supported voltages Boards can have different supplied voltages on different SD card slots. This information has to be passed down to the SD/MMC driver. Signed-off-by: Guennadi Liakhovetski Acked-by: Ian Molton Signed-off-by: Paul Mundt --- drivers/mfd/sh_mobile_sdhi.c | 4 +++- include/linux/mfd/sh_mobile_sdhi.h | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index 19e0d48a599a..cd164595f08a 100644 --- a/drivers/mfd/sh_mobile_sdhi.c +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -107,8 +107,10 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) mmc_data->hclk = clk_get_rate(priv->clk); mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; - if (p) + if (p) { mmc_data->flags = p->tmio_flags; + mmc_data->ocr_mask = p->tmio_ocr_mask; + } if (p && p->dma_slave_tx >= 0 && p->dma_slave_rx >= 0) { priv->param_tx.slave_id = p->dma_slave_tx; diff --git a/include/linux/mfd/sh_mobile_sdhi.h b/include/linux/mfd/sh_mobile_sdhi.h index c7b47f8ad650..49067802a6d7 100644 --- a/include/linux/mfd/sh_mobile_sdhi.h +++ b/include/linux/mfd/sh_mobile_sdhi.h @@ -1,10 +1,13 @@ #ifndef __SH_MOBILE_SDHI_H__ #define __SH_MOBILE_SDHI_H__ +#include + struct sh_mobile_sdhi_info { int dma_slave_tx; int dma_slave_rx; unsigned long tmio_flags; + u32 tmio_ocr_mask; /* available MMC voltages */ void (*set_pwr)(struct platform_device *pdev, int state); }; -- cgit v1.2.3 From 192bbb95ca16f2b4d4383e76b3262672e6116daa Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 29 Apr 2010 13:33:50 -0400 Subject: regulator: make 88pm860x sharing one driver structure Remove a lot of driver structures in 88pm860x driver. Make regulators share one driver structure. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/mfd/88pm860x-core.c | 34 ++++++++++++++--------------- drivers/regulator/88pm8607.c | 51 +++++++++----------------------------------- 2 files changed, 27 insertions(+), 58 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 6a14d2b1ccf0..21de38daf948 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -176,30 +176,30 @@ static struct resource regulator_resources[] = { PM8607_REG_RESOURCE(LDO14, LDO14), }; -#define PM8607_REG_DEVS(_name, _id) \ +#define PM8607_REG_DEVS(_id) \ { \ - .name = "88pm8607-" #_name, \ + .name = "88pm860x-regulator", \ .num_resources = 1, \ .resources = ®ulator_resources[PM8607_ID_##_id], \ .id = PM8607_ID_##_id, \ } static struct mfd_cell regulator_devs[] = { - PM8607_REG_DEVS(buck1, BUCK1), - PM8607_REG_DEVS(buck2, BUCK2), - PM8607_REG_DEVS(buck3, BUCK3), - PM8607_REG_DEVS(ldo1, LDO1), - PM8607_REG_DEVS(ldo2, LDO2), - PM8607_REG_DEVS(ldo3, LDO3), - PM8607_REG_DEVS(ldo4, LDO4), - PM8607_REG_DEVS(ldo5, LDO5), - PM8607_REG_DEVS(ldo6, LDO6), - PM8607_REG_DEVS(ldo7, LDO7), - PM8607_REG_DEVS(ldo8, LDO8), - PM8607_REG_DEVS(ldo9, LDO9), - PM8607_REG_DEVS(ldo10, LDO10), - PM8607_REG_DEVS(ldo12, LDO12), - PM8607_REG_DEVS(ldo14, LDO14), + PM8607_REG_DEVS(BUCK1), + PM8607_REG_DEVS(BUCK2), + PM8607_REG_DEVS(BUCK3), + PM8607_REG_DEVS(LDO1), + PM8607_REG_DEVS(LDO2), + PM8607_REG_DEVS(LDO3), + PM8607_REG_DEVS(LDO4), + PM8607_REG_DEVS(LDO5), + PM8607_REG_DEVS(LDO6), + PM8607_REG_DEVS(LDO7), + PM8607_REG_DEVS(LDO8), + PM8607_REG_DEVS(LDO9), + PM8607_REG_DEVS(LDO10), + PM8607_REG_DEVS(LDO12), + PM8607_REG_DEVS(LDO14), }; struct pm860x_irq_data { diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index 5fb83e2ced25..121bab4c0dc0 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -484,60 +484,29 @@ static int __devexit pm8607_regulator_remove(struct platform_device *pdev) { struct pm8607_regulator_info *info = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); regulator_unregister(info->regulator); return 0; } -#define PM8607_REGULATOR_DRIVER(_name) \ -{ \ - .driver = { \ - .name = "88pm8607-" #_name, \ - .owner = THIS_MODULE, \ - }, \ - .probe = pm8607_regulator_probe, \ - .remove = __devexit_p(pm8607_regulator_remove), \ -} - -static struct platform_driver pm8607_regulator_driver[] = { - PM8607_REGULATOR_DRIVER(buck1), - PM8607_REGULATOR_DRIVER(buck2), - PM8607_REGULATOR_DRIVER(buck3), - PM8607_REGULATOR_DRIVER(ldo1), - PM8607_REGULATOR_DRIVER(ldo2), - PM8607_REGULATOR_DRIVER(ldo3), - PM8607_REGULATOR_DRIVER(ldo4), - PM8607_REGULATOR_DRIVER(ldo5), - PM8607_REGULATOR_DRIVER(ldo6), - PM8607_REGULATOR_DRIVER(ldo7), - PM8607_REGULATOR_DRIVER(ldo8), - PM8607_REGULATOR_DRIVER(ldo9), - PM8607_REGULATOR_DRIVER(ldo10), - PM8607_REGULATOR_DRIVER(ldo12), - PM8607_REGULATOR_DRIVER(ldo14), +static struct platform_driver pm8607_regulator_driver = { + .driver = { + .name = "88pm860x-regulator", + .owner = THIS_MODULE, + }, + .probe = pm8607_regulator_probe, + .remove = __devexit_p(pm8607_regulator_remove), }; static int __init pm8607_regulator_init(void) { - int i, count, ret; - - count = ARRAY_SIZE(pm8607_regulator_driver); - for (i = 0; i < count; i++) { - ret = platform_driver_register(&pm8607_regulator_driver[i]); - if (ret != 0) - pr_err("Failed to register regulator driver: %d\n", - ret); - } - return 0; + return platform_driver_register(&pm8607_regulator_driver); } subsys_initcall(pm8607_regulator_init); static void __exit pm8607_regulator_exit(void) { - int i, count; - - count = ARRAY_SIZE(pm8607_regulator_driver); - for (i = 0; i < count; i++) - platform_driver_unregister(&pm8607_regulator_driver[i]); + platform_driver_unregister(&pm8607_regulator_driver); } module_exit(pm8607_regulator_exit); -- cgit v1.2.3 From 9f79e9db2e282857912bcfe7f741bcdd5c46e860 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Tue, 4 May 2010 09:54:51 -0400 Subject: regulator: use voltage number array in 88pm860x A lot of condition comparision statements are used in original driver. These statements are used to check the boundary of voltage numbers since voltage number isn't linear. Now use array of voltage numbers instead. Clean code with simpler way. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- drivers/mfd/88pm860x-core.c | 2 + drivers/regulator/88pm8607.c | 482 ++++++++++++++++++++----------------------- include/linux/mfd/88pm860x.h | 3 +- 3 files changed, 224 insertions(+), 263 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 21de38daf948..405d2d5183cf 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -173,6 +173,7 @@ static struct resource regulator_resources[] = { PM8607_REG_RESOURCE(LDO9, LDO9), PM8607_REG_RESOURCE(LDO10, LDO10), PM8607_REG_RESOURCE(LDO12, LDO12), + PM8607_REG_RESOURCE(VIBRATOR_SET, VIBRATOR_SET), PM8607_REG_RESOURCE(LDO14, LDO14), }; @@ -199,6 +200,7 @@ static struct mfd_cell regulator_devs[] = { PM8607_REG_DEVS(LDO9), PM8607_REG_DEVS(LDO10), PM8607_REG_DEVS(LDO12), + PM8607_REG_DEVS(LDO13), PM8607_REG_DEVS(LDO14), }; diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index 121bab4c0dc0..7d149a8d8d9b 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -23,9 +23,9 @@ struct pm8607_regulator_info { struct regulator_dev *regulator; struct i2c_client *i2c; - int min_uV; - int max_uV; - int step_uV; + unsigned int *vol_table; + unsigned int *vol_suspend; + int vol_reg; int vol_shift; int vol_nbits; @@ -36,83 +36,189 @@ struct pm8607_regulator_info { int slope_double; }; -static inline int check_range(struct pm8607_regulator_info *info, - int min_uV, int max_uV) -{ - if (max_uV < info->min_uV || min_uV > info->max_uV || min_uV > max_uV) - return -EINVAL; +static const unsigned int BUCK1_table[] = { + 725000, 750000, 775000, 800000, 825000, 850000, 875000, 900000, + 925000, 950000, 975000, 1000000, 1025000, 1050000, 1075000, 1100000, + 1125000, 1150000, 1175000, 1200000, 1225000, 1250000, 1275000, 1300000, + 1325000, 1350000, 1375000, 1400000, 1425000, 1450000, 1475000, 1500000, + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, +}; - return 0; -} +static const unsigned int BUCK1_suspend_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int BUCK2_table[] = { + 0, 50000, 100000, 150000, 200000, 250000, 300000, 350000, + 400000, 450000, 500000, 550000, 600000, 650000, 700000, 750000, + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, 1950000, + 2000000, 2050000, 2100000, 2150000, 2200000, 2250000, 2300000, 2350000, + 2400000, 2450000, 2500000, 2550000, 2600000, 2650000, 2700000, 2750000, + 2800000, 2850000, 2900000, 2950000, 3000000, 3000000, 3000000, 3000000, +}; + +static const unsigned int BUCK2_suspend_table[] = { + 0, 50000, 100000, 150000, 200000, 250000, 300000, 350000, + 400000, 450000, 500000, 550000, 600000, 650000, 700000, 750000, + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, 1950000, + 2000000, 2050000, 2100000, 2150000, 2200000, 2250000, 2300000, 2350000, + 2400000, 2450000, 2500000, 2550000, 2600000, 2650000, 2700000, 2750000, + 2800000, 2850000, 2900000, 2950000, 3000000, 3000000, 3000000, 3000000, +}; + +static const unsigned int BUCK3_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int BUCK3_suspend_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int LDO1_table[] = { + 1800000, 1200000, 2800000, 0, +}; + +static const unsigned int LDO1_suspend_table[] = { + 1800000, 1200000, 0, 0, +}; + +static const unsigned int LDO2_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO2_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO3_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO3_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO4_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2900000, 3300000, +}; + +static const unsigned int LDO4_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2900000, 2900000, +}; + +static const unsigned int LDO5_table[] = { + 2900000, 3000000, 3100000, 3300000, +}; + +static const unsigned int LDO5_suspend_table[] = { + 2900000, 0, 0, 0, +}; + +static const unsigned int LDO6_table[] = { + 1800000, 1850000, 2600000, 2650000, 2700000, 2750000, 2800000, 3300000, +}; + +static const unsigned int LDO6_suspend_table[] = { + 1800000, 1850000, 2600000, 2650000, 2700000, 2750000, 2800000, 2900000, +}; + +static const unsigned int LDO7_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO7_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO8_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO8_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO9_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO9_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO10_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO10_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO12_table[] = { + 1800000, 1900000, 2700000, 2800000, 2900000, 3000000, 3100000, 3300000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO12_suspend_table[] = { + 1800000, 1900000, 2700000, 2800000, 2900000, 2900000, 2900000, 2900000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO13_table[] = { + 1300000, 1800000, 2000000, 2500000, 2800000, 3000000, 0, 0, +}; + +static const unsigned int LDO13_suspend_table[] = { + 0, +}; + +static const unsigned int LDO14_table[] = { + 1800000, 1850000, 2700000, 2750000, 2800000, 2850000, 2900000, 3300000, +}; + +static const unsigned int LDO14_suspend_table[] = { + 1800000, 1850000, 2700000, 2750000, 2800000, 2850000, 2900000, 2900000, +}; static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); int ret = -EINVAL; - switch (info->desc.id) { - case PM8607_ID_BUCK1: - ret = (index < 0x1d) ? (index * 25000 + 800000) : - ((index < 0x20) ? 1500000 : - ((index < 0x40) ? ((index - 0x20) * 25000) : - -EINVAL)); - break; - case PM8607_ID_BUCK3: - ret = (index < 0x3d) ? (index * 25000) : - ((index < 0x40) ? 1500000 : -EINVAL); - if (ret < 0) - break; + if (info->vol_table && (index < (2 << info->vol_nbits))) { + ret = info->vol_table[index]; if (info->slope_double) ret <<= 1; - break; - case PM8607_ID_LDO1: - ret = (index == 0) ? 1800000 : - ((index == 1) ? 1200000 : - ((index == 2) ? 2800000 : -EINVAL)); - break; - case PM8607_ID_LDO5: - ret = (index == 0) ? 2900000 : - ((index == 1) ? 3000000 : - ((index == 2) ? 3100000 : 3300000)); - break; - case PM8607_ID_LDO7: - case PM8607_ID_LDO8: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2550000) : - -EINVAL); - break; - case PM8607_ID_LDO12: - ret = (index < 2) ? (index * 100000 + 1800000) : - ((index < 7) ? (index * 100000 + 2500000) : - ((index == 7) ? 3300000 : 1200000)); - break; - case PM8607_ID_LDO2: - case PM8607_ID_LDO3: - case PM8607_ID_LDO9: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2550000) : - 3300000); - break; - case PM8607_ID_LDO4: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 6) ? (index * 50000 + 2550000) : - ((index == 6) ? 2900000 : 3300000)); - break; - case PM8607_ID_LDO6: - ret = (index < 2) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2500000) : - 3300000); - break; - case PM8607_ID_LDO10: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2550000) : - ((index == 7) ? 3300000 : 1200000)); - break; - case PM8607_ID_LDO14: - ret = (index < 2) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2600000) : - 3300000); - break; } return ret; } @@ -120,174 +226,26 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - int val = -ENOENT; - int ret; + int i, ret = -ENOENT; - switch (info->desc.id) { - case PM8607_ID_BUCK1: - if (min_uV >= 800000) /* 800mV ~ 1500mV / 25mV */ - val = (min_uV - 775001) / 25000; - else { /* 25mV ~ 775mV / 25mV */ - val = (min_uV + 249999) / 25000; - val += 32; - } - break; - case PM8607_ID_BUCK3: - if (info->slope_double) - min_uV = min_uV >> 1; - val = (min_uV + 249999) / 25000; /* 0mV ~ 1500mV / 25mV */ - - break; - case PM8607_ID_LDO1: - if (min_uV > 1800000) - val = 2; - else if (min_uV > 1200000) - val = 0; - else - val = 1; - break; - case PM8607_ID_LDO5: - if (min_uV > 3100000) - val = 3; - else /* 2900mV ~ 3100mV / 100mV */ - val = (min_uV - 2800001) / 100000; - break; - case PM8607_ID_LDO7: - case PM8607_ID_LDO8: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; /* 1800mv */ - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else - val = -EINVAL; - } - break; - case PM8607_ID_LDO10: - if (min_uV > 2850000) - val = 7; - else if (min_uV <= 1200000) - val = 8; - else if (min_uV < 2700000) /* 1800mV ~ 1900mV / 50mV */ - val = (min_uV - 1750001) / 50000; - else { /* 2700mV ~ 2850mV / 50mV */ - val = (min_uV - 2650001) / 50000; - val += 3; - } - break; - case PM8607_ID_LDO12: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 100mV */ - if (min_uV <= 1200000) - val = 8; /* 1200mV */ - else if (min_uV <= 1800000) - val = 0; /* 1800mV */ - else if (min_uV <= 1900000) - val = (min_uV - 1700001) / 100000; - else - val = 2; /* 2700mV */ - } else { /* 2700mV ~ 3100mV / 100mV */ - if (min_uV <= 3100000) { - val = (min_uV - 2600001) / 100000; - val += 2; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; - case PM8607_ID_LDO2: - case PM8607_ID_LDO3: - case PM8607_ID_LDO9: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2850mV / 50mV */ - if (min_uV <= 2850000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; - case PM8607_ID_LDO4: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2800mV / 50mV */ - if (min_uV <= 2850000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else if (min_uV <= 2900000) - val = 6; - else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; - case PM8607_ID_LDO6: - if (min_uV < 2600000) { /* 1800mV ~ 1850mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1850000) - val = (min_uV - 1750001) / 50000; - else - val = 2; /* 2600mV */ - } else { /* 2600mV ~ 2800mV / 50mV */ - if (min_uV <= 2800000) { - val = (min_uV - 2550001) / 50000; - val += 2; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; - case PM8607_ID_LDO14: - if (min_uV < 2700000) { /* 1800mV ~ 1850mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1850000) - val = (min_uV - 1750001) / 50000; - else - val = 2; /* 2700mV */ - } else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 2; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; + if (info->slope_double) { + min_uV = min_uV >> 1; + max_uV = max_uV >> 1; } - if (val >= 0) { - ret = pm8607_list_voltage(rdev, val); - if (ret > max_uV) { - pr_err("exceed voltage range (%d %d) uV", - min_uV, max_uV); - return -EINVAL; + if (info->vol_table) { + for (i = 0; i < (2 << info->vol_nbits); i++) { + if (!info->vol_table[i]) + break; + if ((min_uV <= info->vol_table[i]) + && (max_uV >= info->vol_table[i])) { + ret = i; + break; + } } - } else - pr_err("invalid voltage range (%d %d) uV", min_uV, max_uV); - return val; + } + if (ret < 0) + pr_err("invalid voltage range (%d %d) uV\n", min_uV, max_uV); + return ret; } static int pm8607_set_voltage(struct regulator_dev *rdev, @@ -297,7 +255,7 @@ static int pm8607_set_voltage(struct regulator_dev *rdev, uint8_t val, mask; int ret; - if (check_range(info, min_uV, max_uV)) { + if (min_uV > max_uV) { pr_err("invalid voltage range (%d, %d) uV\n", min_uV, max_uV); return -EINVAL; } @@ -375,18 +333,15 @@ static struct regulator_ops pm8607_regulator_ops = { .is_enabled = pm8607_is_enabled, }; -#define PM8607_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ +#define PM8607_DVC(vreg, nbits, ureg, ubit, ereg, ebit) \ { \ .desc = { \ - .name = "BUCK" #_id, \ + .name = #vreg, \ .ops = &pm8607_regulator_ops, \ .type = REGULATOR_VOLTAGE, \ - .id = PM8607_ID_BUCK##_id, \ + .id = PM8607_ID_##vreg, \ .owner = THIS_MODULE, \ }, \ - .min_uV = (min) * 1000, \ - .max_uV = (max) * 1000, \ - .step_uV = (step) * 1000, \ .vol_reg = PM8607_##vreg, \ .vol_shift = (0), \ .vol_nbits = (nbits), \ @@ -395,9 +350,11 @@ static struct regulator_ops pm8607_regulator_ops = { .enable_reg = PM8607_##ereg, \ .enable_bit = (ebit), \ .slope_double = (0), \ + .vol_table = (unsigned int *)&vreg##_table, \ + .vol_suspend = (unsigned int *)&vreg##_suspend_table, \ } -#define PM8607_LDO(_id, min, max, step, vreg, shift, nbits, ereg, ebit) \ +#define PM8607_LDO(_id, vreg, shift, nbits, ereg, ebit) \ { \ .desc = { \ .name = "LDO" #_id, \ @@ -406,33 +363,34 @@ static struct regulator_ops pm8607_regulator_ops = { .id = PM8607_ID_LDO##_id, \ .owner = THIS_MODULE, \ }, \ - .min_uV = (min) * 1000, \ - .max_uV = (max) * 1000, \ - .step_uV = (step) * 1000, \ .vol_reg = PM8607_##vreg, \ .vol_shift = (shift), \ .vol_nbits = (nbits), \ .enable_reg = PM8607_##ereg, \ .enable_bit = (ebit), \ .slope_double = (0), \ + .vol_table = (unsigned int *)&LDO##_id##_table, \ + .vol_suspend = (unsigned int *)&LDO##_id##_suspend_table, \ } static struct pm8607_regulator_info pm8607_regulator_info[] = { - PM8607_DVC(1, 0, 1500, 25, BUCK1, 6, GO, 0, SUPPLIES_EN11, 0), - PM8607_DVC(3, 0, 1500, 25, BUCK3, 6, GO, 2, SUPPLIES_EN11, 2), - - PM8607_LDO(1 , 1200, 2800, 0, LDO1 , 0, 2, SUPPLIES_EN11, 3), - PM8607_LDO(2 , 1800, 3300, 0, LDO2 , 0, 3, SUPPLIES_EN11, 4), - PM8607_LDO(3 , 1800, 3300, 0, LDO3 , 0, 3, SUPPLIES_EN11, 5), - PM8607_LDO(4 , 1800, 3300, 0, LDO4 , 0, 3, SUPPLIES_EN11, 6), - PM8607_LDO(5 , 2900, 3300, 0, LDO5 , 0, 2, SUPPLIES_EN11, 7), - PM8607_LDO(6 , 1800, 3300, 0, LDO6 , 0, 3, SUPPLIES_EN12, 0), - PM8607_LDO(7 , 1800, 2900, 0, LDO7 , 0, 3, SUPPLIES_EN12, 1), - PM8607_LDO(8 , 1800, 2900, 0, LDO8 , 0, 3, SUPPLIES_EN12, 2), - PM8607_LDO(9 , 1800, 3300, 0, LDO9 , 0, 3, SUPPLIES_EN12, 3), - PM8607_LDO(10, 1200, 3300, 0, LDO10, 0, 4, SUPPLIES_EN11, 4), - PM8607_LDO(12, 1200, 3300, 0, LDO12, 0, 4, SUPPLIES_EN11, 5), - PM8607_LDO(14, 1800, 3300, 0, LDO14, 0, 3, SUPPLIES_EN11, 6), + PM8607_DVC(BUCK1, 6, GO, 0, SUPPLIES_EN11, 0), + PM8607_DVC(BUCK2, 6, GO, 1, SUPPLIES_EN11, 1), + PM8607_DVC(BUCK3, 6, GO, 2, SUPPLIES_EN11, 2), + + PM8607_LDO( 1, LDO1, 0, 2, SUPPLIES_EN11, 3), + PM8607_LDO( 2, LDO2, 0, 3, SUPPLIES_EN11, 4), + PM8607_LDO( 3, LDO3, 0, 3, SUPPLIES_EN11, 5), + PM8607_LDO( 4, LDO4, 0, 3, SUPPLIES_EN11, 6), + PM8607_LDO( 5, LDO5, 0, 2, SUPPLIES_EN11, 7), + PM8607_LDO( 6, LDO6, 0, 3, SUPPLIES_EN12, 0), + PM8607_LDO( 7, LDO7, 0, 3, SUPPLIES_EN12, 1), + PM8607_LDO( 8, LDO8, 0, 3, SUPPLIES_EN12, 2), + PM8607_LDO( 9, LDO9, 0, 3, SUPPLIES_EN12, 3), + PM8607_LDO(10, LDO10, 0, 3, SUPPLIES_EN12, 4), + PM8607_LDO(12, LDO12, 0, 4, SUPPLIES_EN12, 5), + PM8607_LDO(13, VIBRATOR_SET, 1, 3, VIBRATOR_SET, 0), + PM8607_LDO(14, LDO14, 0, 4, SUPPLIES_EN12, 6), }; static inline struct pm8607_regulator_info *find_regulator_info(int id) diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index 73f92c5feea2..e3c4ff8c3e38 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -132,6 +132,7 @@ enum { PM8607_ID_LDO9, PM8607_ID_LDO10, PM8607_ID_LDO12, + PM8607_ID_LDO13, PM8607_ID_LDO14, PM8607_ID_RG_MAX, @@ -309,7 +310,7 @@ struct pm860x_chip { }; -#define PM8607_MAX_REGULATOR 15 /* 3 Bucks, 12 LDOs */ +#define PM8607_MAX_REGULATOR PM8607_ID_RG_MAX /* 3 Bucks, 13 LDOs */ enum { GI2C_PORT = 0, -- cgit v1.2.3 From 7fdcef8a414eaeb367b3696005b25283d62d195d Mon Sep 17 00:00:00 2001 From: Philippe Rétornaz Date: Wed, 19 May 2010 09:24:31 +0200 Subject: leds: Add mc13783 LED support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This add basic led support for Freescale MC13783 PMIC. Signed-off-by: Philippe Rétornaz Signed-off-by: Richard Purdie --- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-mc13783.c | 403 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/mc13783-core.c | 4 + include/linux/mfd/mc13783.h | 66 ++++++++ 5 files changed, 481 insertions(+) create mode 100644 drivers/leds/leds-mc13783.c (limited to 'drivers/mfd') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ecb6932c3636..f98d17a7108b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -295,6 +295,13 @@ config LEDS_DELL_NETBOOKS This adds support for the Latitude 2100 and similar notebooks that have an external LED. +config LEDS_MC13783 + tristate "LED Support for MC13783 PMIC" + depends on MFD_MC13783 + help + This option enable support for on-chip LED drivers found + on Freescale Semiconductor MC13783 PMIC. + config LEDS_TRIGGERS bool "LED Trigger support" help diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 77119f2e26b3..2493de499374 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o +obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c new file mode 100644 index 000000000000..f05bb08d0f09 --- /dev/null +++ b/drivers/leds/leds-mc13783.c @@ -0,0 +1,403 @@ +/* + * LEDs driver for Freescale MC13783 + * + * Copyright (C) 2010 Philippe Rétornaz + * + * Based on leds-da903x: + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mc13783_led { + struct led_classdev cdev; + struct work_struct work; + struct mc13783 *master; + enum led_brightness new_brightness; + int id; +}; + +#define MC13783_REG_LED_CONTROL_0 51 +#define MC13783_LED_C0_ENABLE_BIT (1 << 0) +#define MC13783_LED_C0_TRIODE_MD_BIT (1 << 7) +#define MC13783_LED_C0_TRIODE_AD_BIT (1 << 8) +#define MC13783_LED_C0_TRIODE_KP_BIT (1 << 9) +#define MC13783_LED_C0_BOOST_BIT (1 << 10) +#define MC13783_LED_C0_ABMODE_MASK 0x7 +#define MC13783_LED_C0_ABMODE 11 +#define MC13783_LED_C0_ABREF_MASK 0x3 +#define MC13783_LED_C0_ABREF 14 + +#define MC13783_REG_LED_CONTROL_1 52 +#define MC13783_LED_C1_TC1HALF_BIT (1 << 18) + +#define MC13783_REG_LED_CONTROL_2 53 +#define MC13783_LED_C2_BL_P_MASK 0xf +#define MC13783_LED_C2_MD_P 9 +#define MC13783_LED_C2_AD_P 13 +#define MC13783_LED_C2_KP_P 17 +#define MC13783_LED_C2_BL_C_MASK 0x7 +#define MC13783_LED_C2_MD_C 0 +#define MC13783_LED_C2_AD_C 3 +#define MC13783_LED_C2_KP_C 6 + +#define MC13783_REG_LED_CONTROL_3 54 +#define MC13783_LED_C3_TC_P 6 +#define MC13783_LED_C3_TC_P_MASK 0x1f + +#define MC13783_REG_LED_CONTROL_4 55 +#define MC13783_REG_LED_CONTROL_5 56 + +#define MC13783_LED_Cx_PERIOD 21 +#define MC13783_LED_Cx_PERIOD_MASK 0x3 +#define MC13783_LED_Cx_SLEWLIM_BIT (1 << 23) +#define MC13783_LED_Cx_TRIODE_TC_BIT (1 << 23) +#define MC13783_LED_Cx_TC_C_MASK 0x3 + +static void mc13783_led_work(struct work_struct *work) +{ + struct mc13783_led *led = container_of(work, struct mc13783_led, work); + int reg = 0; + int mask = 0; + int value = 0; + int bank, off, shift; + + switch (led->id) { + case MC13783_LED_MD: + reg = MC13783_REG_LED_CONTROL_2; + mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_MD_P; + value = (led->new_brightness >> 4) << MC13783_LED_C2_MD_P; + break; + case MC13783_LED_AD: + reg = MC13783_REG_LED_CONTROL_2; + mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_AD_P; + value = (led->new_brightness >> 4) << MC13783_LED_C2_AD_P; + break; + case MC13783_LED_KP: + reg = MC13783_REG_LED_CONTROL_2; + mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_KP_P; + value = (led->new_brightness >> 4) << MC13783_LED_C2_KP_P; + break; + case MC13783_LED_R1: + case MC13783_LED_G1: + case MC13783_LED_B1: + case MC13783_LED_R2: + case MC13783_LED_G2: + case MC13783_LED_B2: + case MC13783_LED_R3: + case MC13783_LED_G3: + case MC13783_LED_B3: + off = led->id - MC13783_LED_R1; + bank = off/3; + reg = MC13783_REG_LED_CONTROL_3 + off/3; + shift = (off - bank * 3) * 5 + MC13783_LED_C3_TC_P; + value = (led->new_brightness >> 3) << shift; + mask = MC13783_LED_C3_TC_P_MASK << shift; + break; + } + + mc13783_lock(led->master); + + mc13783_reg_rmw(led->master, reg, mask, value); + + mc13783_unlock(led->master); +} + +static void mc13783_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct mc13783_led *led; + + led = container_of(led_cdev, struct mc13783_led, cdev); + led->new_brightness = value; + schedule_work(&led->work); +} + +static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current) +{ + int shift = 0; + int mask = 0; + int value = 0; + int reg = 0; + int ret, bank; + + switch (led->id) { + case MC13783_LED_MD: + shift = MC13783_LED_C2_MD_C; + mask = MC13783_LED_C2_BL_C_MASK; + value = max_current & MC13783_LED_C2_BL_C_MASK; + reg = MC13783_REG_LED_CONTROL_2; + break; + case MC13783_LED_AD: + shift = MC13783_LED_C2_AD_C; + mask = MC13783_LED_C2_BL_C_MASK; + value = max_current & MC13783_LED_C2_BL_C_MASK; + reg = MC13783_REG_LED_CONTROL_2; + break; + case MC13783_LED_KP: + shift = MC13783_LED_C2_KP_C; + mask = MC13783_LED_C2_BL_C_MASK; + value = max_current & MC13783_LED_C2_BL_C_MASK; + reg = MC13783_REG_LED_CONTROL_2; + break; + case MC13783_LED_R1: + case MC13783_LED_G1: + case MC13783_LED_B1: + case MC13783_LED_R2: + case MC13783_LED_G2: + case MC13783_LED_B2: + case MC13783_LED_R3: + case MC13783_LED_G3: + case MC13783_LED_B3: + bank = (led->id - MC13783_LED_R1)/3; + reg = MC13783_REG_LED_CONTROL_3 + bank; + shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2; + mask = MC13783_LED_Cx_TC_C_MASK; + value = max_current & MC13783_LED_Cx_TC_C_MASK; + break; + } + + mc13783_lock(led->master); + + ret = mc13783_reg_rmw(led->master, reg, mask << shift, + value << shift); + + mc13783_unlock(led->master); + return ret; +} + +static int __devinit mc13783_leds_prepare(struct platform_device *pdev) +{ + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); + int ret = 0; + int reg = 0; + + mc13783_lock(dev); + + if (pdata->flags & MC13783_LED_TC1HALF) + reg |= MC13783_LED_C1_TC1HALF_BIT; + + if (pdata->flags & MC13783_LED_SLEWLIMTC) + reg |= MC13783_LED_Cx_SLEWLIM_BIT; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_1, reg); + if (ret) + goto out; + + reg = (pdata->bl_period & MC13783_LED_Cx_PERIOD_MASK) << + MC13783_LED_Cx_PERIOD; + + if (pdata->flags & MC13783_LED_SLEWLIMBL) + reg |= MC13783_LED_Cx_SLEWLIM_BIT; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_2, reg); + if (ret) + goto out; + + reg = (pdata->tc1_period & MC13783_LED_Cx_PERIOD_MASK) << + MC13783_LED_Cx_PERIOD; + + if (pdata->flags & MC13783_LED_TRIODE_TC1) + reg |= MC13783_LED_Cx_TRIODE_TC_BIT; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_3, reg); + if (ret) + goto out; + + reg = (pdata->tc2_period & MC13783_LED_Cx_PERIOD_MASK) << + MC13783_LED_Cx_PERIOD; + + if (pdata->flags & MC13783_LED_TRIODE_TC2) + reg |= MC13783_LED_Cx_TRIODE_TC_BIT; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_4, reg); + if (ret) + goto out; + + reg = (pdata->tc3_period & MC13783_LED_Cx_PERIOD_MASK) << + MC13783_LED_Cx_PERIOD; + + if (pdata->flags & MC13783_LED_TRIODE_TC3) + reg |= MC13783_LED_Cx_TRIODE_TC_BIT;; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_5, reg); + if (ret) + goto out; + + reg = MC13783_LED_C0_ENABLE_BIT; + if (pdata->flags & MC13783_LED_TRIODE_MD) + reg |= MC13783_LED_C0_TRIODE_MD_BIT; + if (pdata->flags & MC13783_LED_TRIODE_AD) + reg |= MC13783_LED_C0_TRIODE_AD_BIT; + if (pdata->flags & MC13783_LED_TRIODE_KP) + reg |= MC13783_LED_C0_TRIODE_KP_BIT; + if (pdata->flags & MC13783_LED_BOOST_EN) + reg |= MC13783_LED_C0_BOOST_BIT; + + reg |= (pdata->abmode & MC13783_LED_C0_ABMODE_MASK) << + MC13783_LED_C0_ABMODE; + reg |= (pdata->abref & MC13783_LED_C0_ABREF_MASK) << + MC13783_LED_C0_ABREF; + + ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_0, reg); + +out: + mc13783_unlock(dev); + return ret; +} + +static int __devinit mc13783_led_probe(struct platform_device *pdev) +{ + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783_led_platform_data *led_cur; + struct mc13783_led *led, *led_dat; + int ret, i; + int init_led = 0; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } + + if (pdata->num_leds < 1 || pdata->num_leds > MC13783_LED_MAX) { + dev_err(&pdev->dev, "Invalid led count %d\n", pdata->num_leds); + return -EINVAL; + } + + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + ret = mc13783_leds_prepare(pdev); + if (ret) { + dev_err(&pdev->dev, "unable to init led driver\n"); + goto err_free; + } + + for (i = 0; i < pdata->num_leds; i++) { + led_dat = &led[i]; + led_cur = &pdata->led[i]; + + if (led_cur->id > MC13783_LED_MAX || led_cur->id < 0) { + dev_err(&pdev->dev, "invalid id %d\n", led_cur->id); + ret = -EINVAL; + goto err_register; + } + + if (init_led & (1 << led_cur->id)) { + dev_err(&pdev->dev, "led %d already initialized\n", + led_cur->id); + ret = -EINVAL; + goto err_register; + } + + init_led |= 1 << led_cur->id; + led_dat->cdev.name = led_cur->name; + led_dat->cdev.default_trigger = led_cur->default_trigger; + led_dat->cdev.brightness_set = mc13783_led_set; + led_dat->cdev.brightness = LED_OFF; + led_dat->id = led_cur->id; + led_dat->master = dev_get_drvdata(pdev->dev.parent); + + INIT_WORK(&led_dat->work, mc13783_led_work); + + ret = led_classdev_register(pdev->dev.parent, &led_dat->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register led %d\n", + led_dat->id); + goto err_register; + } + + ret = mc13783_led_setup(led_dat, led_cur->max_current); + if (ret) { + dev_err(&pdev->dev, "unable to init led %d\n", + led_dat->id); + i++; + goto err_register; + } + } + + platform_set_drvdata(pdev, led); + return 0; + +err_register: + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + +err_free: + kfree(led); + return ret; +} + +static int __devexit mc13783_led_remove(struct platform_device *pdev) +{ + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783_led *led = platform_get_drvdata(pdev); + struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); + int i; + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + + mc13783_lock(dev); + + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_0, 0); + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_1, 0); + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_2, 0); + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_3, 0); + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_4, 0); + mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_5, 0); + + mc13783_unlock(dev); + + kfree(led); + return 0; +} + +static struct platform_driver mc13783_led_driver = { + .driver = { + .name = "mc13783-led", + .owner = THIS_MODULE, + }, + .probe = mc13783_led_probe, + .remove = __devexit_p(mc13783_led_remove), +}; + +static int __init mc13783_led_init(void) +{ + return platform_driver_register(&mc13783_led_driver); +} +module_init(mc13783_led_init); + +static void __exit mc13783_led_exit(void) +{ + platform_driver_unregister(&mc13783_led_driver); +} +module_exit(mc13783_led_exit); + +MODULE_DESCRIPTION("LEDs driver for Freescale MC13783 PMIC"); +MODULE_AUTHOR("Philippe Retornaz "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mc13783-led"); diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c index 1f68ecadddc2..fecf38a4f025 100644 --- a/drivers/mfd/mc13783-core.c +++ b/drivers/mfd/mc13783-core.c @@ -679,6 +679,10 @@ err_revision: if (pdata->flags & MC13783_USE_TOUCHSCREEN) mc13783_add_subdevice(mc13783, "mc13783-ts"); + if (pdata->flags & MC13783_USE_LED) + mc13783_add_subdevice_pdata(mc13783, "mc13783-led", + pdata->leds, sizeof(*pdata->leds)); + return 0; } diff --git a/include/linux/mfd/mc13783.h b/include/linux/mfd/mc13783.h index 8895d9d8879c..4a894f688549 100644 --- a/include/linux/mfd/mc13783.h +++ b/include/linux/mfd/mc13783.h @@ -64,6 +64,70 @@ static inline int mc13783_ackirq(struct mc13783 *mc13783, int irq) MC13783_ADC0_TSMOD1 | \ MC13783_ADC0_TSMOD2) +struct mc13783_led_platform_data { +#define MC13783_LED_MD 0 +#define MC13783_LED_AD 1 +#define MC13783_LED_KP 2 +#define MC13783_LED_R1 3 +#define MC13783_LED_G1 4 +#define MC13783_LED_B1 5 +#define MC13783_LED_R2 6 +#define MC13783_LED_G2 7 +#define MC13783_LED_B2 8 +#define MC13783_LED_R3 9 +#define MC13783_LED_G3 10 +#define MC13783_LED_B3 11 +#define MC13783_LED_MAX MC13783_LED_B3 + int id; + const char *name; + const char *default_trigger; + +/* Three or two bits current selection depending on the led */ + char max_current; +}; + +struct mc13783_leds_platform_data { + int num_leds; + struct mc13783_led_platform_data *led; + +#define MC13783_LED_TRIODE_MD (1 << 0) +#define MC13783_LED_TRIODE_AD (1 << 1) +#define MC13783_LED_TRIODE_KP (1 << 2) +#define MC13783_LED_BOOST_EN (1 << 3) +#define MC13783_LED_TC1HALF (1 << 4) +#define MC13783_LED_SLEWLIMTC (1 << 5) +#define MC13783_LED_SLEWLIMBL (1 << 6) +#define MC13783_LED_TRIODE_TC1 (1 << 7) +#define MC13783_LED_TRIODE_TC2 (1 << 8) +#define MC13783_LED_TRIODE_TC3 (1 << 9) + int flags; + +#define MC13783_LED_AB_DISABLED 0 +#define MC13783_LED_AB_MD1 1 +#define MC13783_LED_AB_MD12 2 +#define MC13783_LED_AB_MD123 3 +#define MC13783_LED_AB_MD1234 4 +#define MC13783_LED_AB_MD1234_AD1 5 +#define MC13783_LED_AB_MD1234_AD12 6 +#define MC13783_LED_AB_MD1_AD 7 + char abmode; + +#define MC13783_LED_ABREF_200MV 0 +#define MC13783_LED_ABREF_400MV 1 +#define MC13783_LED_ABREF_600MV 2 +#define MC13783_LED_ABREF_800MV 3 + char abref; + +#define MC13783_LED_PERIOD_10MS 0 +#define MC13783_LED_PERIOD_100MS 1 +#define MC13783_LED_PERIOD_500MS 2 +#define MC13783_LED_PERIOD_2S 3 + char bl_period; + char tc1_period; + char tc2_period; + char tc3_period; +}; + /* to be cleaned up */ struct regulator_init_data; @@ -80,12 +144,14 @@ struct mc13783_regulator_platform_data { struct mc13783_platform_data { int num_regulators; struct mc13783_regulator_init_data *regulators; + struct mc13783_leds_platform_data *leds; #define MC13783_USE_TOUCHSCREEN (1 << 0) #define MC13783_USE_CODEC (1 << 1) #define MC13783_USE_ADC (1 << 2) #define MC13783_USE_RTC (1 << 3) #define MC13783_USE_REGULATOR (1 << 4) +#define MC13783_USE_LED (1 << 5) unsigned int flags; }; -- cgit v1.2.3 From f5bf403a9dc944bf560f49dd029195e54fcbc41c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 12 May 2010 02:44:33 +0200 Subject: backlight: pcf50633: Register a pcf50633-backlight device in pcf50633 core driver. Register a device newly added pcf50633-backlight driver as a child device in the pcf50633 core driver. Signed-off-by: Lars-Peter Clausen Acked-by: Samuel Ortiz Signed-off-by: Richard Purdie --- drivers/mfd/pcf50633-core.c | 3 +++ include/linux/mfd/pcf50633/core.h | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 63a614d696c1..dc95ddb708f1 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -620,6 +620,9 @@ static int __devinit pcf50633_probe(struct i2c_client *client, &pcf->mbc_pdev); pcf50633_client_dev_register(pcf, "pcf50633-adc", &pcf->adc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-backlight", + &pcf->bl_pdev); + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { struct platform_device *pdev; diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h index 3398bd9aab11..ad411a78870c 100644 --- a/include/linux/mfd/pcf50633/core.h +++ b/include/linux/mfd/pcf50633/core.h @@ -18,6 +18,7 @@ #include #include #include +#include struct pcf50633; @@ -43,6 +44,8 @@ struct pcf50633_platform_data { void (*force_shutdown)(struct pcf50633 *); u8 resumers[5]; + + struct pcf50633_bl_platform_data *backlight_data; }; struct pcf50633_irq { @@ -152,6 +155,7 @@ struct pcf50633 { struct platform_device *mbc_pdev; struct platform_device *adc_pdev; struct platform_device *input_pdev; + struct platform_device *bl_pdev; struct platform_device *regulator_pdev[PCF50633_NUM_REGULATORS]; }; -- cgit v1.2.3 From 9ad285d6db6e04c1c51c79152d2214fffefc92ab Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 19 Mar 2010 12:46:28 +0100 Subject: mfd: Check t7l66 platform_data pointer We should check for pdata being not NULL before dereferencing it. Reported-by: Dan Carpenter Signed-off-by: Samuel Ortiz --- drivers/mfd/t7l66xb.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index da6383a934ac..5041d33adf0b 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -318,6 +318,9 @@ static int t7l66xb_probe(struct platform_device *dev) struct resource *iomem, *rscr; int ret; + if (pdata == NULL) + return -EINVAL; + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); if (!iomem) return -EINVAL; -- cgit v1.2.3 From d84027bc4d176a0c06e8f62a9f7a002bdd444012 Mon Sep 17 00:00:00 2001 From: Richard Röjfors Date: Tue, 16 Mar 2010 10:43:28 +0100 Subject: mfd: Add in XIIC to some configurations of timberdale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds in the Xilinx I2C bus driver to some of the configurations of the timberdale MFD. It provides the I2C devices to the XIIC via platform data in a similar way as done to the ocores driver. Signed-off-by: Richard Röjfors Signed-off-by: Samuel Ortiz --- drivers/mfd/timberdale.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 7f478ec4184b..1ac77d16dce9 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -69,6 +70,12 @@ static struct i2c_board_info timberdale_i2c_board_info[] = { }, }; +static __devinitdata struct xiic_i2c_platform_data +timberdale_xiic_platform_data = { + .devices = timberdale_i2c_board_info, + .num_devices = ARRAY_SIZE(timberdale_i2c_board_info) +}; + static __devinitdata struct ocores_i2c_platform_data timberdale_ocores_platform_data = { .regstep = 4, @@ -77,6 +84,19 @@ timberdale_ocores_platform_data = { .num_devices = ARRAY_SIZE(timberdale_i2c_board_info) }; +const static __devinitconst struct resource timberdale_xiic_resources[] = { + { + .start = XIICOFFSET, + .end = XIICEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_I2C, + .end = IRQ_TIMBERDALE_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + const static __devinitconst struct resource timberdale_ocores_resources[] = { { .start = OCORESOFFSET, @@ -269,6 +289,13 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .num_resources = ARRAY_SIZE(timberdale_uart_resources), .resources = timberdale_uart_resources, }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .data_size = sizeof(timberdale_xiic_platform_data), + }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), @@ -313,6 +340,13 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .num_resources = ARRAY_SIZE(timberdale_uartlite_resources), .resources = timberdale_uartlite_resources, }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .data_size = sizeof(timberdale_xiic_platform_data), + }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), @@ -357,6 +391,13 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .num_resources = ARRAY_SIZE(timberdale_uart_resources), .resources = timberdale_uart_resources, }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .data_size = sizeof(timberdale_xiic_platform_data), + }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), -- cgit v1.2.3 From f322d5f0097333343bfd92b47258ee997c889263 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 20 Mar 2010 15:12:54 +0100 Subject: mfd: Fix dangling pointers Fix I2C-drivers which missed setting clientdata to NULL before freeing the structure it points to. Also fix drivers which do this _after_ the structure was freed already. Signed-off-by: Wolfram Sang Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-i2c.c | 1 + drivers/mfd/ab3100-core.c | 2 ++ drivers/mfd/da903x.c | 1 + drivers/mfd/menelaus.c | 3 ++- drivers/mfd/pcf50633-core.c | 1 + drivers/mfd/tps65010.c | 2 +- drivers/mfd/wm8350-i2c.c | 2 ++ 7 files changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index 4a6e7186334e..b0bade1881d4 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -202,6 +202,7 @@ static int __devexit pm860x_remove(struct i2c_client *client) i2c_unregister_device(chip->companion); i2c_set_clientdata(chip->companion, NULL); i2c_set_clientdata(chip->client, NULL); + i2c_set_clientdata(client, NULL); kfree(chip); return 0; } diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index e4ca5909e424..16898211cd1a 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -920,6 +920,7 @@ static int __init ab3100_probe(struct i2c_client *client, i2c_unregister_device(ab3100->testreg_client); exit_no_testreg_client: exit_no_detect: + i2c_set_clientdata(client, NULL); kfree(ab3100); return err; } @@ -941,6 +942,7 @@ static int __exit ab3100_remove(struct i2c_client *client) * their notifiers so deactivate IRQ */ free_irq(client->irq, ab3100); + i2c_set_clientdata(client, NULL); kfree(ab3100); return 0; } diff --git a/drivers/mfd/da903x.c b/drivers/mfd/da903x.c index 67181b147ab3..3ad915d0589c 100644 --- a/drivers/mfd/da903x.c +++ b/drivers/mfd/da903x.c @@ -544,6 +544,7 @@ static int __devexit da903x_remove(struct i2c_client *client) struct da903x_chip *chip = i2c_get_clientdata(client); da903x_remove_subdevs(chip); + i2c_set_clientdata(client, NULL); kfree(chip); return 0; } diff --git a/drivers/mfd/menelaus.c b/drivers/mfd/menelaus.c index a94b131a18ef..721948be12c7 100644 --- a/drivers/mfd/menelaus.c +++ b/drivers/mfd/menelaus.c @@ -1228,6 +1228,7 @@ fail2: free_irq(client->irq, menelaus); flush_scheduled_work(); fail1: + i2c_set_clientdata(client, NULL); kfree(menelaus); return err; } @@ -1237,8 +1238,8 @@ static int __exit menelaus_remove(struct i2c_client *client) struct menelaus_chip *menelaus = i2c_get_clientdata(client); free_irq(client->irq, menelaus); - kfree(menelaus); i2c_set_clientdata(client, NULL); + kfree(menelaus); the_menelaus = NULL; return 0; } diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index dc95ddb708f1..710e417e0eec 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -679,6 +679,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) for (i = 0; i < PCF50633_NUM_REGULATORS; i++) platform_device_unregister(pcf->regulator_pdev[i]); + i2c_set_clientdata(client, NULL); kfree(pcf); return 0; diff --git a/drivers/mfd/tps65010.c b/drivers/mfd/tps65010.c index e5955306c2fa..9b22a77f70f5 100644 --- a/drivers/mfd/tps65010.c +++ b/drivers/mfd/tps65010.c @@ -530,8 +530,8 @@ static int __exit tps65010_remove(struct i2c_client *client) cancel_delayed_work(&tps->work); flush_scheduled_work(); debugfs_remove(tps->file); - kfree(tps); i2c_set_clientdata(client, NULL); + kfree(tps); the_tps = NULL; return 0; } diff --git a/drivers/mfd/wm8350-i2c.c b/drivers/mfd/wm8350-i2c.c index 65830f57c093..aa3ba0974ee5 100644 --- a/drivers/mfd/wm8350-i2c.c +++ b/drivers/mfd/wm8350-i2c.c @@ -82,6 +82,7 @@ static int wm8350_i2c_probe(struct i2c_client *i2c, return ret; err: + i2c_set_clientdata(i2c, NULL); kfree(wm8350); return ret; } @@ -91,6 +92,7 @@ static int wm8350_i2c_remove(struct i2c_client *i2c) struct wm8350 *wm8350 = i2c_get_clientdata(i2c); wm8350_device_exit(wm8350); + i2c_set_clientdata(i2c, NULL); kfree(wm8350); return 0; -- cgit v1.2.3 From e090d506c3f1b314059fb77b177cd4193bb81d6e Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Sun, 21 Mar 2010 01:06:05 +0100 Subject: mfd: Add support for the RDC321x southbridge This patch adds a new MFD driver for the RDC321x southbridge. This southbridge is always present in the RDC321x System-on-a-Chip and provides access to some GPIOs as well as a watchdog. Access to these two functions is done using the southbridge PCI device configuration space. Signed-off-by: Florian Fainelli Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 +++ drivers/mfd/Makefile | 3 +- drivers/mfd/rdc321x-southbridge.c | 123 ++++++++++++++++++++++++++++++++++++++ include/linux/mfd/rdc321x.h | 26 ++++++++ 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/rdc321x-southbridge.c create mode 100644 include/linux/mfd/rdc321x.h (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 3c6a9860dd9c..93d0e31c0438 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -409,6 +409,15 @@ config LPC_SCH LPC bridge function of the Intel SCH provides support for System Management Bus and General Purpose I/O. +config MFD_RDC321X + tristate "Support for RDC-R321x southbridge" + select MFD_CORE + depends on PCI + help + Say yes here if you want to have support for the RDC R-321x SoC + southbridge which provides access to GPIOs and Watchdog using the + southbridge PCI device configuration space. + endmenu menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 87935f967aa0..f177e064b31b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -63,4 +63,5 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o -obj-$(CONFIG_LPC_SCH) += lpc_sch.o \ No newline at end of file +obj-$(CONFIG_LPC_SCH) += lpc_sch.o +obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o diff --git a/drivers/mfd/rdc321x-southbridge.c b/drivers/mfd/rdc321x-southbridge.c new file mode 100644 index 000000000000..256dd560bd48 --- /dev/null +++ b/drivers/mfd/rdc321x-southbridge.c @@ -0,0 +1,123 @@ +/* + * RDC321x MFD southbrige driver + * + * Copyright (C) 2007-2010 Florian Fainelli + * Copyright (C) 2010 Bernhard Loos + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include +#include +#include +#include +#include +#include +#include + +static struct rdc321x_wdt_pdata rdc321x_wdt_pdata; + +static struct resource rdc321x_wdt_resource[] = { + { + .name = "wdt-reg", + .start = RDC321X_WDT_CTRL, + .end = RDC321X_WDT_CTRL + 0x3, + .flags = IORESOURCE_MEM, + } +}; + +static struct rdc321x_gpio_pdata rdc321x_gpio_pdata = { + .max_gpios = RDC321X_MAX_GPIO, +}; + +static struct resource rdc321x_gpio_resources[] = { + { + .name = "gpio-reg1", + .start = RDC321X_GPIO_CTRL_REG1, + .end = RDC321X_GPIO_CTRL_REG1 + 0x7, + .flags = IORESOURCE_MEM, + }, { + .name = "gpio-reg2", + .start = RDC321X_GPIO_CTRL_REG2, + .end = RDC321X_GPIO_CTRL_REG2 + 0x7, + .flags = IORESOURCE_MEM, + } +}; + +static struct mfd_cell rdc321x_sb_cells[] = { + { + .name = "rdc321x-wdt", + .resources = rdc321x_wdt_resource, + .num_resources = ARRAY_SIZE(rdc321x_wdt_resource), + .driver_data = &rdc321x_wdt_pdata, + }, { + .name = "rdc321x-gpio", + .resources = rdc321x_gpio_resources, + .num_resources = ARRAY_SIZE(rdc321x_gpio_resources), + .driver_data = &rdc321x_gpio_pdata, + }, +}; + +static int __devinit rdc321x_sb_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable device\n"); + return err; + } + + rdc321x_gpio_pdata.sb_pdev = pdev; + rdc321x_wdt_pdata.sb_pdev = pdev; + + return mfd_add_devices(&pdev->dev, -1, + rdc321x_sb_cells, ARRAY_SIZE(rdc321x_sb_cells), NULL, 0); +} + +static void __devexit rdc321x_sb_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); +} + +static DEFINE_PCI_DEVICE_TABLE(rdc321x_sb_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_RDC, PCI_DEVICE_ID_RDC_R6030) }, + {} +}; + +static struct pci_driver rdc321x_sb_driver = { + .name = "RDC321x Southbridge", + .id_table = rdc321x_sb_table, + .probe = rdc321x_sb_probe, + .remove = __devexit_p(rdc321x_sb_remove), +}; + +static int __init rdc321x_sb_init(void) +{ + return pci_register_driver(&rdc321x_sb_driver); +} + +static void __exit rdc321x_sb_exit(void) +{ + pci_unregister_driver(&rdc321x_sb_driver); +} + +module_init(rdc321x_sb_init); +module_exit(rdc321x_sb_exit); + +MODULE_AUTHOR("Florian Fainelli "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RDC R-321x MFD southbridge driver"); diff --git a/include/linux/mfd/rdc321x.h b/include/linux/mfd/rdc321x.h new file mode 100644 index 000000000000..4bdf19c8eedf --- /dev/null +++ b/include/linux/mfd/rdc321x.h @@ -0,0 +1,26 @@ +#ifndef __RDC321X_MFD_H +#define __RDC321X_MFD_H + +#include +#include + +/* Offsets to be accessed in the southbridge PCI + * device configuration register */ +#define RDC321X_WDT_CTRL 0x44 +#define RDC321X_GPIO_CTRL_REG1 0x48 +#define RDC321X_GPIO_DATA_REG1 0x4c +#define RDC321X_GPIO_CTRL_REG2 0x84 +#define RDC321X_GPIO_DATA_REG2 0x88 + +#define RDC321X_MAX_GPIO 58 + +struct rdc321x_gpio_pdata { + struct pci_dev *sb_pdev; + unsigned max_gpios; +}; + +struct rdc321x_wdt_pdata { + struct pci_dev *sb_pdev; +}; + +#endif /* __RDC321X_MFD_H */ -- cgit v1.2.3 From 5f7df57eeca93fc931b32e7723fe8f964aaa63df Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 22 Mar 2010 15:12:42 +0300 Subject: mfd: Off by one calculating size for timberdale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm pretty sure that it should be + 1 here. It's an off by one, because we start counting at zero. Signed-off-by: Dan Carpenter Acked-by: Richard Röjfors Signed-off-by: Samuel Ortiz --- drivers/mfd/timberdale.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/timberdale.h b/drivers/mfd/timberdale.h index 8d27ffabc25d..902c45120241 100644 --- a/drivers/mfd/timberdale.h +++ b/drivers/mfd/timberdale.h @@ -66,7 +66,7 @@ #define CHIPCTLOFFSET 0x800 #define CHIPCTLEND 0x8ff -#define CHIPCTLSIZE (CHIPCTLEND - CHIPCTLOFFSET) +#define CHIPCTLSIZE (CHIPCTLEND - CHIPCTLOFFSET + 1) #define INTCOFFSET 0xc00 #define INTCEND 0xfff -- cgit v1.2.3 From f03cfcbc843ae6854c69dbc771762b83b3bea15f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 26 Mar 2010 01:09:04 +0100 Subject: mfd: Check for mem_base when building IORESOURCE_MEM resources If mem_base is NULL, then we fall back to the default case, just copying the original resource. Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 8ffbb7a85a7e..7dd76bceaae8 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -48,7 +48,7 @@ static int mfd_add_device(struct device *parent, int id, res[r].flags = cell->resources[r].flags; /* Find out base to use */ - if (cell->resources[r].flags & IORESOURCE_MEM) { + if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) { res[r].parent = mem_base; res[r].start = mem_base->start + cell->resources[r].start; -- cgit v1.2.3 From 872c1b14e78661086ade8b501888850da2636eee Mon Sep 17 00:00:00 2001 From: Henrik Kretzschmar Date: Fri, 26 Mar 2010 02:40:13 +0100 Subject: mfd: Section cleanup of 88pm860x driver This patch fixes three section mismatches. WARNING: drivers/mfd/88pm860x.o(.text+0x12): Section mismatch in reference from the function pm860x_device_exit() to the function .devexit.text:device_irq_exit() The function pm860x_device_exit() references a function in an exit section. Often the function device_irq_exit() has valid usage outside the exit section and the fix is to remove the __devexit annotation of device_irq_exit. WARNING: drivers/mfd/88pm860x.o(.text+0xb0): Section mismatch in reference from the function pm860x_device_init() to the function .devinit.text:device_8606_init() The function pm860x_device_init() references the function __devinit device_8606_init(). This is often because pm860x_device_init lacks a __devinit annotation or the annotation of device_8606_init is wrong. WARNING: drivers/mfd/88pm860x.o(.text+0xbe): Section mismatch in reference from the function pm860x_device_init() to the function .devinit.text:device_8607_init() The function pm860x_device_init() references the function __devinit device_8607_init(). This is often because pm860x_device_init lacks a __devinit annotation or the annotation of device_8607_init is wrong. Signed-off-by: Henrik Kretzschmar Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 6 +++--- include/linux/mfd/88pm860x.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 405d2d5183cf..2c65a2c57294 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -566,7 +566,7 @@ out: return ret; } -static void __devexit device_irq_exit(struct pm860x_chip *chip) +static void device_irq_exit(struct pm860x_chip *chip) { if (chip->core_irq) free_irq(chip->core_irq, chip); @@ -703,7 +703,7 @@ out: return; } -int pm860x_device_init(struct pm860x_chip *chip, +int __devinit pm860x_device_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) { chip->core_irq = 0; @@ -731,7 +731,7 @@ int pm860x_device_init(struct pm860x_chip *chip, return 0; } -void pm860x_device_exit(struct pm860x_chip *chip) +void __devexit pm860x_device_exit(struct pm860x_chip *chip) { device_irq_exit(chip); mfd_remove_devices(chip->dev); diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index e3c4ff8c3e38..bfd23bef7363 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -370,7 +370,7 @@ extern int pm860x_set_bits(struct i2c_client *, int, unsigned char, unsigned char); extern int pm860x_device_init(struct pm860x_chip *chip, - struct pm860x_platform_data *pdata); -extern void pm860x_device_exit(struct pm860x_chip *chip); + struct pm860x_platform_data *pdata) __devinit ; +extern void pm860x_device_exit(struct pm860x_chip *chip) __devexit ; #endif /* __LINUX_MFD_88PM860X_H */ -- cgit v1.2.3 From dc64f30fa557ab1108e97e499a9acc550baa38bc Mon Sep 17 00:00:00 2001 From: Richard Röjfors Date: Thu, 25 Mar 2010 19:44:23 +0100 Subject: mfd: Add timb-dma to all configurations of timberdale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add platform data for timb-dma, and add it in to timb-dma in all configurations of timberdale. Also incremented the version number. Signed-off-by: Richard Röjfors Signed-off-by: Samuel Ortiz --- drivers/mfd/timberdale.c | 97 +++++++++++++++++++++++++++++++++++++++++------- drivers/mfd/timberdale.h | 14 ++++++- 2 files changed, 96 insertions(+), 15 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 1ac77d16dce9..2d4691acd033 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -41,6 +41,8 @@ #include +#include + #include "timberdale.h" #define DRIVER_NAME "timberdale" @@ -270,6 +272,65 @@ static __devinitdata struct timb_radio_platform_data } }; +static __devinitdata struct timb_dma_platform_data timb_dma_platform_data = { + .nr_channels = 10, + .channels = { + { + /* UART RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* UART TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* MLB RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* MLB TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* Video RX */ + .rx = true, + .bytes_per_line = 1440, + .descriptors = 2, + .descriptor_elements = 16 + }, + { + /* Video framedrop */ + }, + { + /* SDHCI RX */ + .rx = true, + }, + { + /* SDHCI TX */ + }, + { + /* ETH RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* ETH TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + } +}; + const static __devinitconst struct resource timberdale_dma_resources[] = { { .start = DMAOFFSET, @@ -284,6 +345,13 @@ const static __devinitconst struct resource timberdale_dma_resources[] = { }; static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { + { + .name = "timb-dma", + .num_resources = ARRAY_SIZE(timberdale_dma_resources), + .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .data_size = sizeof(timb_dma_platform_data), + }, { .name = "timb-uart", .num_resources = ARRAY_SIZE(timberdale_uart_resources), @@ -322,14 +390,16 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, }, +}; + +static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .data_size = sizeof(timb_dma_platform_data), }, -}; - -static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { { .name = "timb-uart", .num_resources = ARRAY_SIZE(timberdale_uart_resources), @@ -378,14 +448,16 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, }, +}; + +static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .data_size = sizeof(timb_dma_platform_data), }, -}; - -static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { { .name = "timb-uart", .num_resources = ARRAY_SIZE(timberdale_uart_resources), @@ -419,14 +491,16 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .platform_data = &timberdale_xspi_platform_data, .data_size = sizeof(timberdale_xspi_platform_data), }, +}; + +static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .data_size = sizeof(timb_dma_platform_data), }, -}; - -static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { { .name = "timb-uart", .num_resources = ARRAY_SIZE(timberdale_uart_resources), @@ -465,11 +539,6 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, }, - { - .name = "timb-dma", - .num_resources = ARRAY_SIZE(timberdale_dma_resources), - .resources = timberdale_dma_resources, - }, }; static const __devinitconst struct resource timberdale_sdhc_resources[] = { diff --git a/drivers/mfd/timberdale.h b/drivers/mfd/timberdale.h index 902c45120241..c11bf6ebfe00 100644 --- a/drivers/mfd/timberdale.h +++ b/drivers/mfd/timberdale.h @@ -23,7 +23,7 @@ #ifndef MFD_TIMBERDALE_H #define MFD_TIMBERDALE_H -#define DRV_VERSION "0.1" +#define DRV_VERSION "0.2" /* This driver only support versions >= 3.8 and < 4.0 */ #define TIMB_SUPPORTED_MAJOR 3 @@ -127,4 +127,16 @@ #define GPIO_PIN_BT_RST 15 #define GPIO_NR_PINS 16 +/* DMA Channels */ +#define DMA_UART_RX 0 +#define DMA_UART_TX 1 +#define DMA_MLB_RX 2 +#define DMA_MLB_TX 3 +#define DMA_VIDEO_RX 4 +#define DMA_VIDEO_DROP 5 +#define DMA_SDHCI_RX 6 +#define DMA_SDHCI_TX 7 +#define DMA_ETH_RX 8 +#define DMA_ETH_TX 9 + #endif -- cgit v1.2.3 From 2a0cb351001bebf195d3c43d0f70441eb6a62652 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 26 Mar 2010 14:47:04 +0000 Subject: mfd: Remove unused wm831x IRQ handler data This was used by the old, pre-genirq IRQ implementation but is no longer required. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-irq.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 4c1122ceb443..ec10004200c9 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -39,8 +39,6 @@ struct wm831x_irq_data { int primary; int reg; int mask; - irq_handler_t handler; - void *handler_data; }; static struct wm831x_irq_data wm831x_irqs[] = { -- cgit v1.2.3 From 165bce9783ab307368e56b7c1a168520a3791266 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sun, 28 Mar 2010 14:28:08 +0300 Subject: mfd: Proper pcf50633 irq check "pcf->irq_handler" has PCF50633_NUM_IRQ elements. Signed-off-by: Dan Carpenter Signed-off-by: Samuel Ortiz --- drivers/mfd/pcf50633-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 710e417e0eec..5439282804cc 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -218,7 +218,7 @@ static struct attribute_group pcf_attr_group = { int pcf50633_register_irq(struct pcf50633 *pcf, int irq, void (*handler) (int, void *), void *data) { - if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler) + if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler) return -EINVAL; if (WARN_ON(pcf->irq_handler[irq].handler)) @@ -235,7 +235,7 @@ EXPORT_SYMBOL_GPL(pcf50633_register_irq); int pcf50633_free_irq(struct pcf50633 *pcf, int irq) { - if (irq < 0 || irq > PCF50633_NUM_IRQ) + if (irq < 0 || irq >= PCF50633_NUM_IRQ) return -EINVAL; mutex_lock(&pcf->lock); -- cgit v1.2.3 From bd3581323cc02aefc79a21780a4ca8c578642892 Mon Sep 17 00:00:00 2001 From: "Ira W. Snyder" Date: Wed, 7 Apr 2010 09:43:00 +0200 Subject: mfd: Janz CMOD-IO PCI MODULbus Carrier Board support The Janz CMOD-IO PCI MODULbus carrier board is a PCI to MODULbus bridge, which may host many different types of MODULbus daughterboards, including CAN and GPIO controllers. Signed-off-by: Ira W. Snyder Reviewed-by: Wolfgang Grandegger Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 10 ++ drivers/mfd/Makefile | 1 + drivers/mfd/janz-cmodio.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/janz.h | 54 ++++++++ 4 files changed, 369 insertions(+) create mode 100644 drivers/mfd/janz-cmodio.c create mode 100644 include/linux/mfd/janz.h (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 93d0e31c0438..05c137d16b04 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -418,6 +418,16 @@ config MFD_RDC321X southbridge which provides access to GPIOs and Watchdog using the southbridge PCI device configuration space. +config MFD_JANZ_CMODIO + tristate "Support for Janz CMOD-IO PCI MODULbus Carrier Board" + select MFD_CORE + depends on PCI + help + This is the core driver for the Janz CMOD-IO PCI MODULbus + carrier board. This device is a PCI to MODULbus bridge which may + host many different types of MODULbus daughterboards, including + CAN and GPIO controllers. + endmenu menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f177e064b31b..21eff85b288a 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -65,3 +65,4 @@ obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o +obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c new file mode 100644 index 000000000000..9ed630799acc --- /dev/null +++ b/drivers/mfd/janz-cmodio.c @@ -0,0 +1,304 @@ +/* + * Janz CMOD-IO MODULbus Carrier Board PCI Driver + * + * Copyright (c) 2010 Ira W. Snyder + * + * Lots of inspiration and code was copied from drivers/mfd/sm501.c + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_NAME "janz-cmodio" + +/* Size of each MODULbus module in PCI BAR4 */ +#define CMODIO_MODULBUS_SIZE 0x200 + +/* Maximum number of MODULbus modules on a CMOD-IO carrier board */ +#define CMODIO_MAX_MODULES 4 + +/* Module Parameters */ +static unsigned int num_modules = CMODIO_MAX_MODULES; +static unsigned char *modules[CMODIO_MAX_MODULES] = { + "empty", "empty", "empty", "empty", +}; + +module_param_array(modules, charp, &num_modules, S_IRUGO); +MODULE_PARM_DESC(modules, "MODULbus modules attached to the carrier board"); + +/* Unique Device Id */ +static unsigned int cmodio_id; + +struct cmodio_device { + /* Parent PCI device */ + struct pci_dev *pdev; + + /* PLX control registers */ + struct janz_cmodio_onboard_regs __iomem *ctrl; + + /* hex switch position */ + u8 hex; + + /* mfd-core API */ + struct mfd_cell cells[CMODIO_MAX_MODULES]; + struct resource resources[3 * CMODIO_MAX_MODULES]; + struct janz_platform_data pdata[CMODIO_MAX_MODULES]; +}; + +/* + * Subdevices using the mfd-core API + */ + +static int __devinit cmodio_setup_subdevice(struct cmodio_device *priv, + char *name, unsigned int devno, + unsigned int modno) +{ + struct janz_platform_data *pdata; + struct mfd_cell *cell; + struct resource *res; + struct pci_dev *pci; + + pci = priv->pdev; + cell = &priv->cells[devno]; + res = &priv->resources[devno * 3]; + pdata = &priv->pdata[devno]; + + cell->name = name; + cell->resources = res; + cell->num_resources = 3; + + /* Setup the subdevice ID -- must be unique */ + cell->id = cmodio_id++; + + /* Add platform data */ + pdata->modno = modno; + cell->platform_data = pdata; + cell->data_size = sizeof(*pdata); + + /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */ + res->flags = IORESOURCE_MEM; + res->parent = &pci->resource[3]; + res->start = pci->resource[3].start + (CMODIO_MODULBUS_SIZE * modno); + res->end = res->start + CMODIO_MODULBUS_SIZE - 1; + res++; + + /* PLX Control Registers -- PCI BAR4 is interrupt and other registers */ + res->flags = IORESOURCE_MEM; + res->parent = &pci->resource[4]; + res->start = pci->resource[4].start; + res->end = pci->resource[4].end; + res++; + + /* + * IRQ + * + * The start and end fields are used as an offset to the irq_base + * parameter passed into the mfd_add_devices() function call. All + * devices share the same IRQ. + */ + res->flags = IORESOURCE_IRQ; + res->parent = NULL; + res->start = 0; + res->end = 0; + res++; + + return 0; +} + +/* Probe each submodule using kernel parameters */ +static int __devinit cmodio_probe_submodules(struct cmodio_device *priv) +{ + struct pci_dev *pdev = priv->pdev; + unsigned int num_probed = 0; + char *name; + int i; + + for (i = 0; i < num_modules; i++) { + name = modules[i]; + if (!strcmp(name, "") || !strcmp(name, "empty")) + continue; + + dev_dbg(&priv->pdev->dev, "MODULbus %d: name %s\n", i, name); + cmodio_setup_subdevice(priv, name, num_probed, i); + num_probed++; + } + + /* print an error message if no modules were probed */ + if (num_probed == 0) { + dev_err(&priv->pdev->dev, "no MODULbus modules specified, " + "please set the ``modules'' kernel " + "parameter according to your " + "hardware configuration\n"); + return -ENODEV; + } + + return mfd_add_devices(&pdev->dev, 0, priv->cells, + num_probed, NULL, pdev->irq); +} + +/* + * SYSFS Attributes + */ + +static ssize_t mbus_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cmodio_device *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%x\n", priv->hex); +} + +static DEVICE_ATTR(modulbus_number, S_IRUGO, mbus_show, NULL); + +static struct attribute *cmodio_sysfs_attrs[] = { + &dev_attr_modulbus_number.attr, + NULL, +}; + +static const struct attribute_group cmodio_sysfs_attr_group = { + .attrs = cmodio_sysfs_attrs, +}; + +/* + * PCI Driver + */ + +static int __devinit cmodio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct cmodio_device *priv; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&dev->dev, "unable to allocate private data\n"); + ret = -ENOMEM; + goto out_return; + } + + pci_set_drvdata(dev, priv); + priv->pdev = dev; + + /* Hardware Initialization */ + ret = pci_enable_device(dev); + if (ret) { + dev_err(&dev->dev, "unable to enable device\n"); + goto out_free_priv; + } + + pci_set_master(dev); + ret = pci_request_regions(dev, DRV_NAME); + if (ret) { + dev_err(&dev->dev, "unable to request regions\n"); + goto out_pci_disable_device; + } + + /* Onboard configuration registers */ + priv->ctrl = pci_ioremap_bar(dev, 4); + if (!priv->ctrl) { + dev_err(&dev->dev, "unable to remap onboard regs\n"); + ret = -ENOMEM; + goto out_pci_release_regions; + } + + /* Read the hex switch on the carrier board */ + priv->hex = ioread8(&priv->ctrl->int_enable); + + /* Add the MODULbus number (hex switch value) to the device's sysfs */ + ret = sysfs_create_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); + if (ret) { + dev_err(&dev->dev, "unable to create sysfs attributes\n"); + goto out_unmap_ctrl; + } + + /* + * Disable all interrupt lines, each submodule will enable its + * own interrupt line if needed + */ + iowrite8(0xf, &priv->ctrl->int_disable); + + /* Register drivers for all submodules */ + ret = cmodio_probe_submodules(priv); + if (ret) { + dev_err(&dev->dev, "unable to probe submodules\n"); + goto out_sysfs_remove_group; + } + + return 0; + +out_sysfs_remove_group: + sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); +out_unmap_ctrl: + iounmap(priv->ctrl); +out_pci_release_regions: + pci_release_regions(dev); +out_pci_disable_device: + pci_disable_device(dev); +out_free_priv: + kfree(priv); +out_return: + return ret; +} + +static void __devexit cmodio_pci_remove(struct pci_dev *dev) +{ + struct cmodio_device *priv = pci_get_drvdata(dev); + + mfd_remove_devices(&dev->dev); + sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); + iounmap(priv->ctrl); + pci_release_regions(dev); + pci_disable_device(dev); + kfree(priv); +} + +#define PCI_VENDOR_ID_JANZ 0x13c3 + +/* The list of devices that this module will support */ +static DEFINE_PCI_DEVICE_TABLE(cmodio_pci_ids) = { + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9030, PCI_VENDOR_ID_JANZ, 0x0101 }, + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_JANZ, 0x0100 }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, cmodio_pci_ids); + +static struct pci_driver cmodio_pci_driver = { + .name = DRV_NAME, + .id_table = cmodio_pci_ids, + .probe = cmodio_pci_probe, + .remove = __devexit_p(cmodio_pci_remove), +}; + +/* + * Module Init / Exit + */ + +static int __init cmodio_init(void) +{ + return pci_register_driver(&cmodio_pci_driver); +} + +static void __exit cmodio_exit(void) +{ + pci_unregister_driver(&cmodio_pci_driver); +} + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("Janz CMOD-IO PCI MODULbus Carrier Board Driver"); +MODULE_LICENSE("GPL"); + +module_init(cmodio_init); +module_exit(cmodio_exit); diff --git a/include/linux/mfd/janz.h b/include/linux/mfd/janz.h new file mode 100644 index 000000000000..e9994c469803 --- /dev/null +++ b/include/linux/mfd/janz.h @@ -0,0 +1,54 @@ +/* + * Common Definitions for Janz MODULbus devices + * + * Copyright (c) 2010 Ira W. Snyder + * + * 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; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef JANZ_H +#define JANZ_H + +struct janz_platform_data { + /* MODULbus Module Number */ + unsigned int modno; +}; + +/* PLX bridge chip onboard registers */ +struct janz_cmodio_onboard_regs { + u8 unused1; + + /* + * Read access: interrupt status + * Write access: interrupt disable + */ + u8 int_disable; + u8 unused2; + + /* + * Read access: MODULbus number (hex switch) + * Write access: interrupt enable + */ + u8 int_enable; + u8 unused3; + + /* write-only */ + u8 reset_assert; + u8 unused4; + + /* write-only */ + u8 reset_deassert; + u8 unused5; + + /* read-write access to serial EEPROM */ + u8 eep; + u8 unused6; + + /* write-only access to EEPROM chip select */ + u8 enid; +}; + +#endif /* JANZ_H */ -- cgit v1.2.3 From 7cc1392aabc31d2af9bd3cc5febbd91977452181 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 2 Apr 2010 16:31:03 +0100 Subject: mfd: Improve WM831x AUXADC completion handling Currently completion of WM831x AUXADC conversions is monitored by checking for convertor enable. Due to the mechanism used to ensure data corruption is avoided when reading AUXADC data there may under heavy I/O be a window where this bit has cleared but the conversion results have not been updated. Data availability is only guaranteed after the AUXADC data interrupt has been asserted. Avoid this by always using the interrupt to detect completion. If the chip IRQ is not set up then we poll the IRQ status register for up to 5ms. If it is set up then we rely on the data done interrupt with a vastly increased timeout, failing the conversion if the interrupt is not generated. This also saves a register read when using interrupts. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 65 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 17 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index f2ab025ad97a..da00ca9e0c71 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -322,7 +322,11 @@ EXPORT_SYMBOL_GPL(wm831x_set_bits); */ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) { - int ret, src; + int ret, src, irq_masked, timeout; + + /* Are we using the interrupt? */ + irq_masked = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_1_MASK); + irq_masked &= WM831X_AUXADC_DATA_EINT; mutex_lock(&wm831x->auxadc_lock); @@ -342,6 +346,9 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) goto out; } + /* Clear any notification from a very late arriving interrupt */ + try_wait_for_completion(&wm831x->auxadc_done); + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); if (ret < 0) { @@ -349,22 +356,46 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) goto disable; } - /* If an interrupt arrived late clean up after it */ - try_wait_for_completion(&wm831x->auxadc_done); - - /* Ignore the result to allow us to soldier on without IRQ hookup */ - wait_for_completion_timeout(&wm831x->auxadc_done, msecs_to_jiffies(5)); - - ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL); - if (ret < 0) { - dev_err(wm831x->dev, "AUXADC status read failed: %d\n", ret); - goto disable; - } - - if (ret & WM831X_AUX_CVT_ENA) { - dev_err(wm831x->dev, "Timed out reading AUXADC\n"); - ret = -EBUSY; - goto disable; + if (irq_masked) { + /* If we're not using interrupts then poll the + * interrupt status register */ + timeout = 5; + while (timeout) { + msleep(1); + + ret = wm831x_reg_read(wm831x, + WM831X_INTERRUPT_STATUS_1); + if (ret < 0) { + dev_err(wm831x->dev, + "ISR 1 read failed: %d\n", ret); + goto disable; + } + + /* Did it complete? */ + if (ret & WM831X_AUXADC_DATA_EINT) { + wm831x_reg_write(wm831x, + WM831X_INTERRUPT_STATUS_1, + WM831X_AUXADC_DATA_EINT); + break; + } else { + dev_err(wm831x->dev, + "AUXADC conversion timeout\n"); + ret = -EBUSY; + goto disable; + } + } + } else { + /* If we are using interrupts then wait for the + * interrupt to complete. Use an extremely long + * timeout to handle situations with heavy load where + * the notification of the interrupt may be delayed by + * threaded IRQ handling. */ + if (!wait_for_completion_timeout(&wm831x->auxadc_done, + msecs_to_jiffies(500))) { + dev_err(wm831x->dev, "Timed out waiting for AUXADC\n"); + ret = -EBUSY; + goto disable; + } } ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); -- cgit v1.2.3 From 31dd6a2672e337f5de188df3e5169ee732798236 Mon Sep 17 00:00:00 2001 From: Todd Fischer Date: Thu, 8 Apr 2010 09:04:55 +0200 Subject: mfd: Add TPS6507x support TPS6507x are multi function (PM, touchscreen) chipsets from TI. This commit also changes the corresponding regulator driver from being standalone to an MFD subdevice. Signed-off-by: Todd Fischer Acked-by: Mark Brown Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 12 +++ drivers/mfd/Makefile | 1 + drivers/mfd/tps6507x.c | 156 +++++++++++++++++++++++++++++++++ drivers/regulator/tps6507x-regulator.c | 153 ++++++++++++++++---------------- include/linux/mfd/tps6507x.h | 22 +++++ 5 files changed, 265 insertions(+), 79 deletions(-) create mode 100644 drivers/mfd/tps6507x.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 05c137d16b04..40e0a0a7df5b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -116,6 +116,18 @@ config TPS65010 This driver can also be built as a module. If so, the module will be called tps65010. +config TPS6507X + tristate "TPS6507x Power Management / Touch Screen chips" + select MFD_CORE + depends on I2C + help + If you say yes here you get support for the TPS6507x series of + Power Management / Touch Screen chips. These include voltage + regulators, lithium ion/polymer battery charging, touch screen + and other features that are often used in portable devices. + This driver can also be built as a module. If so, the module + will be called tps6507x. + config MENELAUS bool "Texas Instruments TWL92330/Menelaus PM chip" depends on I2C=y && ARCH_OMAP2 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 21eff85b288a..abf6f1766dcd 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o obj-$(CONFIG_TPS65010) += tps65010.o +obj-$(CONFIG_TPS6507X) += tps6507x.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o diff --git a/drivers/mfd/tps6507x.c b/drivers/mfd/tps6507x.c new file mode 100644 index 000000000000..c59f74570b3b --- /dev/null +++ b/drivers/mfd/tps6507x.c @@ -0,0 +1,156 @@ +/* + * tps6507x.c -- TPS6507x chip family multi-function driver + * + * Copyright (c) 2010 RidgeRun (todd.fischer@ridgerun.com) + * + * Author: Todd Fischer + * todd.fischer@ridgerun.com + * + * Credits: + * + * Using code from wm831x-*.c, wm8400-core, Wolfson Microelectronics PLC. + * + * For licencing details see kernel-base/COPYING + * + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct mfd_cell tps6507x_devs[] = { + { + .name = "tps6507x-pmic", + }, +}; + + +static int tps6507x_i2c_read_device(struct tps6507x_dev *tps6507x, char reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = tps6507x->i2c_client; + struct i2c_msg xfer[2]; + int ret; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = bytes; + xfer[1].buf = dest; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +static int tps6507x_i2c_write_device(struct tps6507x_dev *tps6507x, char reg, + int bytes, void *src) +{ + struct i2c_client *i2c = tps6507x->i2c_client; + /* we add 1 byte for device register */ + u8 msg[TPS6507X_MAX_REGISTER + 1]; + int ret; + + if (bytes > (TPS6507X_MAX_REGISTER + 1)) + return -EINVAL; + + msg[0] = reg; + memcpy(&msg[1], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 1); + if (ret < 0) + return ret; + if (ret != bytes + 1) + return -EIO; + return 0; +} + +static int tps6507x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tps6507x_dev *tps6507x; + int ret = 0; + + tps6507x = kzalloc(sizeof(struct tps6507x_dev), GFP_KERNEL); + if (tps6507x == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, tps6507x); + tps6507x->dev = &i2c->dev; + tps6507x->i2c_client = i2c; + tps6507x->read_dev = tps6507x_i2c_read_device; + tps6507x->write_dev = tps6507x_i2c_write_device; + + ret = mfd_add_devices(tps6507x->dev, -1, + tps6507x_devs, ARRAY_SIZE(tps6507x_devs), + NULL, 0); + + if (ret < 0) + goto err; + + return ret; + +err: + mfd_remove_devices(tps6507x->dev); + kfree(tps6507x); + return ret; +} + +static int tps6507x_i2c_remove(struct i2c_client *i2c) +{ + struct tps6507x_dev *tps6507x = i2c_get_clientdata(i2c); + + mfd_remove_devices(tps6507x->dev); + kfree(tps6507x); + + return 0; +} + +static const struct i2c_device_id tps6507x_i2c_id[] = { + { "tps6507x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6507x_i2c_id); + + +static struct i2c_driver tps6507x_i2c_driver = { + .driver = { + .name = "tps6507x", + .owner = THIS_MODULE, + }, + .probe = tps6507x_i2c_probe, + .remove = tps6507x_i2c_remove, + .id_table = tps6507x_i2c_id, +}; + +static int __init tps6507x_i2c_init(void) +{ + return i2c_add_driver(&tps6507x_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps6507x_i2c_init); + +static void __exit tps6507x_i2c_exit(void) +{ + i2c_del_driver(&tps6507x_i2c_driver); +} +module_exit(tps6507x_i2c_exit); + +MODULE_DESCRIPTION("TPS6507x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c index ea44741fcd47..14b4576281c5 100644 --- a/drivers/regulator/tps6507x-regulator.c +++ b/drivers/regulator/tps6507x-regulator.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -104,22 +103,67 @@ struct tps_info { const u16 *table; }; +static const struct tps_info tps6507x_pmic_regs[] = { + { + .name = "VDCDC1", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC2", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC3", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "LDO1", + .min_uV = 1000000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(LDO1_VSEL_table), + .table = LDO1_VSEL_table, + }, + { + .name = "LDO2", + .min_uV = 725000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(LDO2_VSEL_table), + .table = LDO2_VSEL_table, + }, +}; + struct tps6507x_pmic { struct regulator_desc desc[TPS6507X_NUM_REGULATOR]; - struct i2c_client *client; + struct tps6507x_dev *mfd; struct regulator_dev *rdev[TPS6507X_NUM_REGULATOR]; const struct tps_info *info[TPS6507X_NUM_REGULATOR]; struct mutex io_lock; }; - static inline int tps6507x_pmic_read(struct tps6507x_pmic *tps, u8 reg) { - return i2c_smbus_read_byte_data(tps->client, reg); + u8 val; + int err; + + err = tps->mfd->read_dev(tps->mfd, reg, 1, &val); + + if (err) + return err; + + return val; } static inline int tps6507x_pmic_write(struct tps6507x_pmic *tps, u8 reg, u8 val) { - return i2c_smbus_write_byte_data(tps->client, reg, val); + return tps->mfd->write_dev(tps->mfd, reg, 1, &val); } static int tps6507x_pmic_set_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) @@ -130,7 +174,7 @@ static int tps6507x_pmic_set_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) data = tps6507x_pmic_read(tps, reg); if (data < 0) { - dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); err = data; goto out; } @@ -138,7 +182,7 @@ static int tps6507x_pmic_set_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) data |= mask; err = tps6507x_pmic_write(tps, reg, data); if (err) - dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); out: mutex_unlock(&tps->io_lock); @@ -153,7 +197,7 @@ static int tps6507x_pmic_clear_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) data = tps6507x_pmic_read(tps, reg); if (data < 0) { - dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); err = data; goto out; } @@ -161,7 +205,7 @@ static int tps6507x_pmic_clear_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) data &= ~mask; err = tps6507x_pmic_write(tps, reg, data); if (err) - dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); out: mutex_unlock(&tps->io_lock); @@ -176,7 +220,7 @@ static int tps6507x_pmic_reg_read(struct tps6507x_pmic *tps, u8 reg) data = tps6507x_pmic_read(tps, reg); if (data < 0) - dev_err(&tps->client->dev, "Read from reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); mutex_unlock(&tps->io_lock); return data; @@ -190,7 +234,7 @@ static int tps6507x_pmic_reg_write(struct tps6507x_pmic *tps, u8 reg, u8 val) err = tps6507x_pmic_write(tps, reg, val); if (err < 0) - dev_err(&tps->client->dev, "Write for reg 0x%x failed\n", reg); + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); mutex_unlock(&tps->io_lock); return err; @@ -483,11 +527,12 @@ static struct regulator_ops tps6507x_pmic_ldo_ops = { .list_voltage = tps6507x_pmic_ldo_list_voltage, }; -static int __devinit tps6507x_pmic_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static __devinit +int tps6507x_pmic_probe(struct platform_device *pdev) { + struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent); static int desc_id; - const struct tps_info *info = (void *)id->driver_data; + const struct tps_info *info = &tps6507x_pmic_regs[0]; struct regulator_init_data *init_data; struct regulator_dev *rdev; struct tps6507x_pmic *tps; @@ -495,16 +540,12 @@ static int __devinit tps6507x_pmic_probe(struct i2c_client *client, int i; int error; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA)) - return -EIO; - /** * tps_board points to pmic related constants * coming from the board-evm file. */ - tps_board = dev_get_platdata(&client->dev); + tps_board = dev_get_platdata(tps6507x_dev->dev); if (!tps_board) return -EINVAL; @@ -523,7 +564,7 @@ static int __devinit tps6507x_pmic_probe(struct i2c_client *client, mutex_init(&tps->io_lock); /* common for all regulators */ - tps->client = client; + tps->mfd = tps6507x_dev; for (i = 0; i < TPS6507X_NUM_REGULATOR; i++, info++, init_data++) { /* Register the regulators */ @@ -537,10 +578,11 @@ static int __devinit tps6507x_pmic_probe(struct i2c_client *client, tps->desc[i].owner = THIS_MODULE; rdev = regulator_register(&tps->desc[i], - &client->dev, init_data, tps); + tps6507x_dev->dev, init_data, tps); if (IS_ERR(rdev)) { - dev_err(&client->dev, "failed to register %s\n", - id->name); + dev_err(tps6507x_dev->dev, + "failed to register %s regulator\n", + pdev->name); error = PTR_ERR(rdev); goto fail; } @@ -549,7 +591,7 @@ static int __devinit tps6507x_pmic_probe(struct i2c_client *client, tps->rdev[i] = rdev; } - i2c_set_clientdata(client, tps); + tps6507x_dev->pmic = tps; return 0; @@ -567,14 +609,12 @@ fail: * * Unregister TPS driver as an i2c client device driver */ -static int __devexit tps6507x_pmic_remove(struct i2c_client *client) +static int __devexit tps6507x_pmic_remove(struct platform_device *pdev) { - struct tps6507x_pmic *tps = i2c_get_clientdata(client); + struct tps6507x_dev *tps6507x_dev = platform_get_drvdata(pdev); + struct tps6507x_pmic *tps = tps6507x_dev->pmic; int i; - /* clear the client data in i2c */ - i2c_set_clientdata(client, NULL); - for (i = 0; i < TPS6507X_NUM_REGULATOR; i++) regulator_unregister(tps->rdev[i]); @@ -583,59 +623,13 @@ static int __devexit tps6507x_pmic_remove(struct i2c_client *client) return 0; } -static const struct tps_info tps6507x_pmic_regs[] = { - { - .name = "VDCDC1", - .min_uV = 725000, - .max_uV = 3300000, - .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), - .table = VDCDCx_VSEL_table, - }, - { - .name = "VDCDC2", - .min_uV = 725000, - .max_uV = 3300000, - .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), - .table = VDCDCx_VSEL_table, - }, - { - .name = "VDCDC3", - .min_uV = 725000, - .max_uV = 3300000, - .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), - .table = VDCDCx_VSEL_table, - }, - { - .name = "LDO1", - .min_uV = 1000000, - .max_uV = 3300000, - .table_len = ARRAY_SIZE(LDO1_VSEL_table), - .table = LDO1_VSEL_table, - }, - { - .name = "LDO2", - .min_uV = 725000, - .max_uV = 3300000, - .table_len = ARRAY_SIZE(LDO2_VSEL_table), - .table = LDO2_VSEL_table, - }, -}; - -static const struct i2c_device_id tps6507x_pmic_id[] = { - {.name = "tps6507x", - .driver_data = (unsigned long) tps6507x_pmic_regs,}, - { }, -}; -MODULE_DEVICE_TABLE(i2c, tps6507x_pmic_id); - -static struct i2c_driver tps6507x_i2c_driver = { +static struct platform_driver tps6507x_pmic_driver = { .driver = { - .name = "tps6507x", + .name = "tps6507x-pmic", .owner = THIS_MODULE, }, .probe = tps6507x_pmic_probe, .remove = __devexit_p(tps6507x_pmic_remove), - .id_table = tps6507x_pmic_id, }; /** @@ -645,7 +639,7 @@ static struct i2c_driver tps6507x_i2c_driver = { */ static int __init tps6507x_pmic_init(void) { - return i2c_add_driver(&tps6507x_i2c_driver); + return platform_driver_register(&tps6507x_pmic_driver); } subsys_initcall(tps6507x_pmic_init); @@ -656,10 +650,11 @@ subsys_initcall(tps6507x_pmic_init); */ static void __exit tps6507x_pmic_cleanup(void) { - i2c_del_driver(&tps6507x_i2c_driver); + platform_driver_unregister(&tps6507x_pmic_driver); } module_exit(tps6507x_pmic_cleanup); MODULE_AUTHOR("Texas Instruments"); MODULE_DESCRIPTION("TPS6507x voltage regulator driver"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps6507x-pmic"); diff --git a/include/linux/mfd/tps6507x.h b/include/linux/mfd/tps6507x.h index fd73af5fb95a..9543cb716428 100644 --- a/include/linux/mfd/tps6507x.h +++ b/include/linux/mfd/tps6507x.h @@ -131,6 +131,8 @@ /* VDCDC MASK */ #define TPS6507X_DEFDCDCX_DCDC_MASK 0X3F +#define TPS6507X_MAX_REGISTER 0X19 + /** * struct tps6507x_board - packages regulator and touchscreen init data * @tps6507x_regulator_data: regulator initialization values @@ -142,4 +144,24 @@ struct tps6507x_board { struct regulator_init_data *tps6507x_pmic_init_data; }; +/** + * struct tps6507x_dev - tps6507x sub-driver chip access routines + * @read_dev() - I2C register read function + * @write_dev() - I2C register write function + * + * Device data may be used to access the TPS6507x chip + */ + +struct tps6507x_dev { + struct device *dev; + struct i2c_client *i2c_client; + int (*read_dev)(struct tps6507x_dev *tps6507x, char reg, int size, + void *dest); + int (*write_dev)(struct tps6507x_dev *tps6507x, char reg, int size, + void *src); + + /* Client devices */ + struct tps6507x_pmic *pmic; +}; + #endif /* __LINUX_MFD_TPS6507X_H */ -- cgit v1.2.3 From 752599667048898b6969e06e4637f906b04ec752 Mon Sep 17 00:00:00 2001 From: Todd Fischer Date: Mon, 5 Apr 2010 17:53:12 -0600 Subject: input: Touchscreen driver for TPS6507x Add touch screen input driver for TPS6507x family of multi-function chips. Uses the TPS6507x MFD driver. No interrupt support due to testing limitations of current hardware. Signed-off-by: Todd Fischer Signed-off-by: Samuel Ortiz --- drivers/input/touchscreen/Kconfig | 13 ++ drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/tps6507x-ts.c | 400 ++++++++++++++++++++++++++++++++ drivers/mfd/tps6507x.c | 3 + include/linux/input/tps6507x-ts.h | 24 ++ include/linux/mfd/tps6507x.h | 2 + 6 files changed, 443 insertions(+) create mode 100644 drivers/input/touchscreen/tps6507x-ts.c create mode 100644 include/linux/input/tps6507x-ts.h (limited to 'drivers/mfd') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index b9f58ca82fd1..6703c6b9800a 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -590,4 +590,17 @@ config TOUCHSCREEN_PCAP To compile this driver as a module, choose M here: the module will be called pcap_ts. + +config TOUCHSCREEN_TPS6507X + tristate "TPS6507x based touchscreens" + depends on I2C + help + Say Y here if you have a TPS6507x based touchscreen + controller. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called tps6507x_ts. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 8ad36eef90a2..497964a7a214 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -46,3 +46,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o +obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o diff --git a/drivers/input/touchscreen/tps6507x-ts.c b/drivers/input/touchscreen/tps6507x-ts.c new file mode 100644 index 000000000000..5de80a1a730b --- /dev/null +++ b/drivers/input/touchscreen/tps6507x-ts.c @@ -0,0 +1,400 @@ +/* + * drivers/input/touchscreen/tps6507x_ts.c + * + * Touchscreen driver for the tps6507x chip. + * + * Copyright (c) 2009 RidgeRun (todd.fischer@ridgerun.com) + * + * Credits: + * + * Using code from tsc2007, MtekVision Co., Ltd. + * + * For licencing details see kernel-base/COPYING + * + * TPS65070, TPS65073, TPS650731, and TPS650732 support + * 10 bit touch screen interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TSC_DEFAULT_POLL_PERIOD 30 /* ms */ +#define TPS_DEFAULT_MIN_PRESSURE 0x30 +#define MAX_10BIT ((1 << 10) - 1) + +#define TPS6507X_ADCONFIG_CONVERT_TS (TPS6507X_ADCONFIG_AD_ENABLE | \ + TPS6507X_ADCONFIG_START_CONVERSION | \ + TPS6507X_ADCONFIG_INPUT_REAL_TSC) +#define TPS6507X_ADCONFIG_POWER_DOWN_TS (TPS6507X_ADCONFIG_INPUT_REAL_TSC) + +struct ts_event { + u16 x; + u16 y; + u16 pressure; +}; + +struct tps6507x_ts { + struct input_dev *input_dev; + struct device *dev; + char phys[32]; + struct workqueue_struct *wq; + struct delayed_work work; + unsigned polling; /* polling is active */ + struct ts_event tc; + struct tps6507x_dev *mfd; + u16 model; + unsigned pendown; + int irq; + void (*clear_penirq)(void); + unsigned long poll_period; /* ms */ + u16 min_pressure; + int vref; /* non-zero to leave vref on */ +}; + +static int tps6507x_read_u8(struct tps6507x_ts *tsc, u8 reg, u8 *data) +{ + int err; + + err = tsc->mfd->read_dev(tsc->mfd, reg, 1, data); + + if (err) + return err; + + return 0; +} + +static int tps6507x_write_u8(struct tps6507x_ts *tsc, u8 reg, u8 data) +{ + return tsc->mfd->write_dev(tsc->mfd, reg, 1, &data); +} + +static s32 tps6507x_adc_conversion(struct tps6507x_ts *tsc, + u8 tsc_mode, u16 *value) +{ + s32 ret; + u8 adc_status; + u8 result; + + /* Route input signal to A/D converter */ + + ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, tsc_mode); + if (ret) { + dev_err(tsc->dev, "TSC mode read failed\n"); + goto err; + } + + /* Start A/D conversion */ + + ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG, + TPS6507X_ADCONFIG_CONVERT_TS); + if (ret) { + dev_err(tsc->dev, "ADC config write failed\n"); + return ret; + } + + do { + ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADCONFIG, + &adc_status); + if (ret) { + dev_err(tsc->dev, "ADC config read failed\n"); + goto err; + } + } while (adc_status & TPS6507X_ADCONFIG_START_CONVERSION); + + ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_2, &result); + if (ret) { + dev_err(tsc->dev, "ADC result 2 read failed\n"); + goto err; + } + + *value = (result & TPS6507X_REG_ADRESULT_2_MASK) << 8; + + ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_1, &result); + if (ret) { + dev_err(tsc->dev, "ADC result 1 read failed\n"); + goto err; + } + + *value |= result; + + dev_dbg(tsc->dev, "TSC channel %d = 0x%X\n", tsc_mode, *value); + +err: + return ret; +} + +/* Need to call tps6507x_adc_standby() after using A/D converter for the + * touch screen interrupt to work properly. + */ + +static s32 tps6507x_adc_standby(struct tps6507x_ts *tsc) +{ + s32 ret; + s32 loops = 0; + u8 val; + + ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG, + TPS6507X_ADCONFIG_INPUT_TSC); + if (ret) + return ret; + + ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, + TPS6507X_TSCMODE_STANDBY); + if (ret) + return ret; + + ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val); + if (ret) + return ret; + + while (val & TPS6507X_REG_TSC_INT) { + mdelay(10); + ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val); + if (ret) + return ret; + loops++; + } + + return ret; +} + +static void tps6507x_ts_handler(struct work_struct *work) +{ + struct tps6507x_ts *tsc = container_of(work, + struct tps6507x_ts, work.work); + struct input_dev *input_dev = tsc->input_dev; + int pendown; + int schd; + int poll = 0; + s32 ret; + + ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_PRESSURE, + &tsc->tc.pressure); + if (ret) + goto done; + + pendown = tsc->tc.pressure > tsc->min_pressure; + + if (unlikely(!pendown && tsc->pendown)) { + dev_dbg(tsc->dev, "UP\n"); + input_report_key(input_dev, BTN_TOUCH, 0); + input_report_abs(input_dev, ABS_PRESSURE, 0); + input_sync(input_dev); + tsc->pendown = 0; + } + + if (pendown) { + + if (!tsc->pendown) { + dev_dbg(tsc->dev, "DOWN\n"); + input_report_key(input_dev, BTN_TOUCH, 1); + } else + dev_dbg(tsc->dev, "still down\n"); + + ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_X_POSITION, + &tsc->tc.x); + if (ret) + goto done; + + ret = tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_Y_POSITION, + &tsc->tc.y); + if (ret) + goto done; + + input_report_abs(input_dev, ABS_X, tsc->tc.x); + input_report_abs(input_dev, ABS_Y, tsc->tc.y); + input_report_abs(input_dev, ABS_PRESSURE, tsc->tc.pressure); + input_sync(input_dev); + tsc->pendown = 1; + poll = 1; + } + +done: + /* always poll if not using interrupts */ + poll = 1; + + if (poll) { + schd = queue_delayed_work(tsc->wq, &tsc->work, + tsc->poll_period * HZ / 1000); + if (schd) + tsc->polling = 1; + else { + tsc->polling = 0; + dev_err(tsc->dev, "re-schedule failed"); + } + } else + tsc->polling = 0; + + ret = tps6507x_adc_standby(tsc); +} + +static int tps6507x_ts_probe(struct platform_device *pdev) +{ + int error; + struct tps6507x_ts *tsc; + struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent); + struct touchscreen_init_data *init_data; + struct input_dev *input_dev; + struct tps6507x_board *tps_board; + int schd; + + /** + * tps_board points to pmic related constants + * coming from the board-evm file. + */ + + tps_board = (struct tps6507x_board *)tps6507x_dev->dev->platform_data; + + if (!tps_board) { + dev_err(tps6507x_dev->dev, + "Could not find tps6507x platform data\n"); + return -EIO; + } + + /** + * init_data points to array of regulator_init structures + * coming from the board-evm file. + */ + + init_data = tps_board->tps6507x_ts_init_data; + + tsc = kzalloc(sizeof(struct tps6507x_ts), GFP_KERNEL); + if (!tsc) { + dev_err(tps6507x_dev->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err0; + } + + tps6507x_dev->ts = tsc; + tsc->mfd = tps6507x_dev; + tsc->dev = tps6507x_dev->dev; + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(tsc->dev, "Failed to allocate input device.\n"); + error = -ENOMEM; + goto err1; + } + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, MAX_10BIT, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, MAX_10BIT, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_10BIT, 0, 0); + + input_dev->name = "TPS6507x Touchscreen"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = tsc->dev; + + snprintf(tsc->phys, sizeof(tsc->phys), + "%s/input0", dev_name(tsc->dev)); + input_dev->phys = tsc->phys; + + dev_dbg(tsc->dev, "device: %s\n", input_dev->phys); + + input_set_drvdata(input_dev, tsc); + + tsc->input_dev = input_dev; + + INIT_DELAYED_WORK(&tsc->work, tps6507x_ts_handler); + tsc->wq = create_workqueue("TPS6507x Touchscreen"); + + if (init_data) { + tsc->poll_period = init_data->poll_period; + tsc->vref = init_data->vref; + tsc->min_pressure = init_data->min_pressure; + input_dev->id.vendor = init_data->vendor; + input_dev->id.product = init_data->product; + input_dev->id.version = init_data->version; + } else { + tsc->poll_period = TSC_DEFAULT_POLL_PERIOD; + tsc->min_pressure = TPS_DEFAULT_MIN_PRESSURE; + } + + error = tps6507x_adc_standby(tsc); + if (error) + goto err2; + + error = input_register_device(input_dev); + if (error) + goto err2; + + schd = queue_delayed_work(tsc->wq, &tsc->work, + tsc->poll_period * HZ / 1000); + + if (schd) + tsc->polling = 1; + else { + tsc->polling = 0; + dev_err(tsc->dev, "schedule failed"); + goto err2; + } + + return 0; + +err2: + cancel_delayed_work(&tsc->work); + flush_workqueue(tsc->wq); + destroy_workqueue(tsc->wq); + tsc->wq = 0; + input_free_device(input_dev); +err1: + kfree(tsc); + tps6507x_dev->ts = NULL; +err0: + return error; +} + +static int __devexit tps6507x_ts_remove(struct platform_device *pdev) +{ + struct tps6507x_dev *tps6507x_dev = platform_get_drvdata(pdev); + struct tps6507x_ts *tsc = tps6507x_dev->ts; + struct input_dev *input_dev = tsc->input_dev; + + if (!tsc) + return 0; + + cancel_delayed_work(&tsc->work); + flush_workqueue(tsc->wq); + destroy_workqueue(tsc->wq); + tsc->wq = 0; + + input_free_device(input_dev); + + tps6507x_dev->ts = NULL; + kfree(tsc); + + return 0; +} + +static struct platform_driver tps6507x_ts_driver = { + .driver = { + .name = "tps6507x-ts", + .owner = THIS_MODULE, + }, + .probe = tps6507x_ts_probe, + .remove = __devexit_p(tps6507x_ts_remove), +}; + +static int __init tps6507x_ts_init(void) +{ + return platform_driver_register(&tps6507x_ts_driver); +} +module_init(tps6507x_ts_init); + +static void __exit tps6507x_ts_exit(void) +{ + platform_driver_unregister(&tps6507x_ts_driver); +} +module_exit(tps6507x_ts_exit); + +MODULE_AUTHOR("Todd Fischer "); +MODULE_DESCRIPTION("TPS6507x - TouchScreen driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps6507x-tsc"); diff --git a/drivers/mfd/tps6507x.c b/drivers/mfd/tps6507x.c index c59f74570b3b..d859dffed39f 100644 --- a/drivers/mfd/tps6507x.c +++ b/drivers/mfd/tps6507x.c @@ -26,6 +26,9 @@ static struct mfd_cell tps6507x_devs[] = { { .name = "tps6507x-pmic", }, + { + .name = "tps6507x-ts", + }, }; diff --git a/include/linux/input/tps6507x-ts.h b/include/linux/input/tps6507x-ts.h new file mode 100644 index 000000000000..ab1440313924 --- /dev/null +++ b/include/linux/input/tps6507x-ts.h @@ -0,0 +1,24 @@ +/* linux/i2c/tps6507x-ts.h + * + * Functions to access TPS65070 touch screen chip. + * + * Copyright (c) 2009 RidgeRun (todd.fischer@ridgerun.com) + * + * + * For licencing details see kernel-base/COPYING + */ + +#ifndef __LINUX_I2C_TPS6507X_TS_H +#define __LINUX_I2C_TPS6507X_TS_H + +/* Board specific touch screen initial values */ +struct touchscreen_init_data { + int poll_period; /* ms */ + int vref; /* non-zero to leave vref on */ + __u16 min_pressure; /* min reading to be treated as a touch */ + __u16 vendor; + __u16 product; + __u16 version; +}; + +#endif /* __LINUX_I2C_TPS6507X_TS_H */ diff --git a/include/linux/mfd/tps6507x.h b/include/linux/mfd/tps6507x.h index 9543cb716428..c923e4864f55 100644 --- a/include/linux/mfd/tps6507x.h +++ b/include/linux/mfd/tps6507x.h @@ -142,6 +142,7 @@ struct tps6507x_board { struct regulator_init_data *tps6507x_pmic_init_data; + struct touchscreen_init_data *tps6507x_ts_init_data; }; /** @@ -162,6 +163,7 @@ struct tps6507x_dev { /* Client devices */ struct tps6507x_pmic *pmic; + struct tps6507x_ts *ts; }; #endif /* __LINUX_MFD_TPS6507X_H */ -- cgit v1.2.3 From 0d7e0e399d7fcd5ddc3313a1aa2135fab3226d8f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 5 Apr 2010 16:14:17 +0100 Subject: mfd: Initialise WM831x IRQ masks on chip even if interrupts not in use Ensure that the hardware has interrupts masked if we are not using the interrupt controller on the WM831x by initialising the masks before we check for the setup data required for the IRQ line. This avoids signalling an unused IRQ line and improves the robustness of checks that the IRQ is in use. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-irq.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index ec10004200c9..7dabe4dbd373 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -490,6 +490,14 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq) mutex_init(&wm831x->irq_lock); + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + wm831x->irq_masks_cur[i] = 0xffff; + wm831x->irq_masks_cache[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + if (!irq) { dev_warn(wm831x->dev, "No interrupt specified - functionality limited\n"); @@ -505,14 +513,6 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq) wm831x->irq = irq; wm831x->irq_base = pdata->irq_base; - /* Mask the individual interrupt sources */ - for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { - wm831x->irq_masks_cur[i] = 0xffff; - wm831x->irq_masks_cache[i] = 0xffff; - wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, - 0xffff); - } - /* Register them with genirq */ for (cur_irq = wm831x->irq_base; cur_irq < ARRAY_SIZE(wm831x_irqs) + wm831x->irq_base; -- cgit v1.2.3 From b03b4d7cdd8f18395494478634707c6f132080d6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 8 Apr 2010 10:02:39 +0200 Subject: mfd: Ensure WM831x charger interrupts are acknowledged when suspending The charger interrupts on the WM831x are unconditionally a wake source for the system. If the power driver is not able to monitor them (for example, due to the IRQ line not having been wired up on the system) then any charger interrupt will prevent the system suspending for any meaningful amount of time since nothing will ack them. Avoid this issue by manually acknowledging these interrupts when we suspend the WM831x core device if they are masked. If software is actually using the interrupts then they will be unmasked and this change will have no effect. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 47 +++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/wm831x/core.h | 5 +++-- 2 files changed, 50 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index da00ca9e0c71..1a968f34d679 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -1494,6 +1494,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) case WM8310: parent = WM8310; wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; if (rev > 0) { wm831x->has_gpio_ena = 1; wm831x->has_cs_sts = 1; @@ -1505,6 +1506,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) case WM8311: parent = WM8311; wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; if (rev > 0) { wm831x->has_gpio_ena = 1; wm831x->has_cs_sts = 1; @@ -1516,6 +1518,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) case WM8312: parent = WM8312; wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; if (rev > 0) { wm831x->has_gpio_ena = 1; wm831x->has_cs_sts = 1; @@ -1654,6 +1657,42 @@ static void wm831x_device_exit(struct wm831x *wm831x) kfree(wm831x); } +static int wm831x_device_suspend(struct wm831x *wm831x) +{ + int reg, mask; + + /* If the charger IRQs are a wake source then make sure we ack + * them even if they're not actively being used (eg, no power + * driver or no IRQ line wired up) then acknowledge the + * interrupts otherwise suspend won't last very long. + */ + if (wm831x->charger_irq_wake) { + reg = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_2_MASK); + + mask = WM831X_CHG_BATT_HOT_EINT | + WM831X_CHG_BATT_COLD_EINT | + WM831X_CHG_BATT_FAIL_EINT | + WM831X_CHG_OV_EINT | WM831X_CHG_END_EINT | + WM831X_CHG_TO_EINT | WM831X_CHG_MODE_EINT | + WM831X_CHG_START_EINT; + + /* If any of the interrupts are masked read the statuses */ + if (reg & mask) + reg = wm831x_reg_read(wm831x, + WM831X_INTERRUPT_STATUS_2); + + if (reg & mask) { + dev_info(wm831x->dev, + "Acknowledging masked charger IRQs: %x\n", + reg & mask); + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_2, + reg & mask); + } + } + + return 0; +} + static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg, int bytes, void *dest) { @@ -1728,6 +1767,13 @@ static int wm831x_i2c_remove(struct i2c_client *i2c) return 0; } +static int wm831x_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg) +{ + struct wm831x *wm831x = i2c_get_clientdata(i2c); + + return wm831x_device_suspend(wm831x); +} + static const struct i2c_device_id wm831x_i2c_id[] = { { "wm8310", WM8310 }, { "wm8311", WM8311 }, @@ -1745,6 +1791,7 @@ static struct i2c_driver wm831x_i2c_driver = { }, .probe = wm831x_i2c_probe, .remove = wm831x_i2c_remove, + .suspend = wm831x_i2c_suspend, .id_table = wm831x_i2c_id, }; diff --git a/include/linux/mfd/wm831x/core.h b/include/linux/mfd/wm831x/core.h index 5915f6e3d9ab..eb5bd4e0e03c 100644 --- a/include/linux/mfd/wm831x/core.h +++ b/include/linux/mfd/wm831x/core.h @@ -256,8 +256,9 @@ struct wm831x { int irq_masks_cache[WM831X_NUM_IRQ_REGS]; /* Cached hardware value */ /* Chip revision based flags */ - unsigned has_gpio_ena:1; /* Has GPIO enable bit */ - unsigned has_cs_sts:1; /* Has current sink status bit */ + unsigned has_gpio_ena:1; /* Has GPIO enable bit */ + unsigned has_cs_sts:1; /* Has current sink status bit */ + unsigned charger_irq_wake:1; /* Are charger IRQs a wake source? */ int num_gpio; -- cgit v1.2.3 From fffba64ca37e8f06020f89e878f0d76a8e121c4e Mon Sep 17 00:00:00 2001 From: Phil Carmody Date: Fri, 16 Apr 2010 15:00:09 +0300 Subject: mfd: Fix error in wm8400 reg cache access check Accessing num_reg elements in the interval [reg .. reg+num_regs) is permitted if (reg+numregs <= array size), so barf when that excluded upper bound is > array size. The prior -1 would give access to one too many elements. Signed-off-by: Phil Carmody Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8400-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8400-core.c b/drivers/mfd/wm8400-core.c index 865ce013a821..e08aafa663dc 100644 --- a/drivers/mfd/wm8400-core.c +++ b/drivers/mfd/wm8400-core.c @@ -118,7 +118,7 @@ static int wm8400_read(struct wm8400 *wm8400, u8 reg, int num_regs, u16 *dest) { int i, ret = 0; - BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache)); + BUG_ON(reg + num_regs > ARRAY_SIZE(wm8400->reg_cache)); /* If there are any volatile reads then read back the entire block */ for (i = reg; i < reg + num_regs; i++) @@ -144,7 +144,7 @@ static int wm8400_write(struct wm8400 *wm8400, u8 reg, int num_regs, { int ret, i; - BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache)); + BUG_ON(reg + num_regs > ARRAY_SIZE(wm8400->reg_cache)); for (i = 0; i < num_regs; i++) { BUG_ON(!reg_data[reg + i].writable); -- cgit v1.2.3 From e47a3bbe4ffd4afe2b24c912598aee7ea1ecf738 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Wed, 21 Apr 2010 21:06:05 +0530 Subject: mfd: Remove incorrect wm8350 kfree The i2c_client received in probe() should not be kfree()'d. Signed-off-by: Rabin Vincent Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8350-i2c.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8350-i2c.c b/drivers/mfd/wm8350-i2c.c index aa3ba0974ee5..7795af4b1fe1 100644 --- a/drivers/mfd/wm8350-i2c.c +++ b/drivers/mfd/wm8350-i2c.c @@ -64,10 +64,8 @@ static int wm8350_i2c_probe(struct i2c_client *i2c, int ret = 0; wm8350 = kzalloc(sizeof(struct wm8350), GFP_KERNEL); - if (wm8350 == NULL) { - kfree(i2c); + if (wm8350 == NULL) return -ENOMEM; - } i2c_set_clientdata(i2c, wm8350); wm8350->dev = &i2c->dev; -- cgit v1.2.3 From 68e488d965a9055c63c0eac4ad1e6568b07e8ee1 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Tue, 27 Apr 2010 14:55:28 -0400 Subject: mfd: Delay to mask tsc irq in max8925 There're two IRQ pins output from MAX8925. One is PMIC interrupt, the other is TSC interrupt. But they're sharing one irq chip. After initializing MAX8925 interrupts, unexpected TSC interrupt may occur and it can't be cleared if touch driver isn't loaded. Now move the operation of masking TSC interrupt behind requesting PMIC interrupt. If touch driver isn't loaded, this interrupt is always masked. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/max8925-core.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index 85d63c04749b..f621bcea3d02 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -508,7 +508,7 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq, max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ2); max8925_reg_read(chip->rtc, MAX8925_RTC_IRQ); max8925_reg_read(chip->adc, MAX8925_TSC_IRQ); - /* mask all interrupts */ + /* mask all interrupts except for TSC */ max8925_reg_write(chip->rtc, MAX8925_ALARM0_CNTL, 0); max8925_reg_write(chip->rtc, MAX8925_ALARM1_CNTL, 0); max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, 0xff); @@ -516,7 +516,6 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq, max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff); max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, 0xff); max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, 0xff); - max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0xff); mutex_init(&chip->irq_lock); chip->core_irq = irq; @@ -547,7 +546,11 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq, dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret); chip->core_irq = 0; } + tsc_irq: + /* mask TSC interrupt */ + max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0x0f); + if (!pdata->tsc_irq) { dev_warn(chip->dev, "No interrupt support on TSC IRQ\n"); return 0; -- cgit v1.2.3 From b4ecd326b789f1029c5d4a5239d9bd12ecac353d Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Mon, 10 May 2010 23:39:47 +0200 Subject: mfd: Add Toshiba's TC35892 MFD core The TC35892 I/O Expander provides 24 GPIOs, a keypad controller, timers, and a rotator wheel interface. This patch adds the MFD core. Acked-by: Linus Walleij Signed-off-by: Rabin Vincent Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 11 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tc35892.c | 347 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/tc35892.h | 132 +++++++++++++++++ 4 files changed, 491 insertions(+) create mode 100644 drivers/mfd/tc35892.c create mode 100644 include/linux/mfd/tc35892.h (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 40e0a0a7df5b..614fa7b8a054 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -171,6 +171,17 @@ config TWL4030_CODEC select MFD_CORE default n +config MFD_TC35892 + bool "Support Toshiba TC35892" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the Toshiba TC35892 I/O Expander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index abf6f1766dcd..5eb79794d5a5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o +obj-$(CONFIG_MFD_TC35892) += tc35892.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c new file mode 100644 index 000000000000..715f095dd7a6 --- /dev/null +++ b/drivers/mfd/tc35892.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad for ST-Ericsson + * Author: Rabin Vincent for ST-Ericsson + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * tc35892_reg_read() - read a single TC35892 register + * @tc35892: Device to read from + * @reg: Register to read + */ +int tc35892_reg_read(struct tc35892 *tc35892, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(tc35892->i2c, reg); + if (ret < 0) + dev_err(tc35892->dev, "failed to read reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_read); + +/** + * tc35892_reg_read() - write a single TC35892 register + * @tc35892: Device to write to + * @reg: Register to read + * @data: Value to write + */ +int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data); + if (ret < 0) + dev_err(tc35892->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_write); + +/** + * tc35892_block_read() - read multiple TC35892 registers + * @tc35892: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values); + if (ret < 0) + dev_err(tc35892->dev, "failed to read regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_read); + +/** + * tc35892_block_write() - write multiple TC35892 registers + * @tc35892: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length, + values); + if (ret < 0) + dev_err(tc35892->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_write); + +/** + * tc35892_set_bits() - set the value of a bitfield in a TC35892 register + * @tc35892: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @values: Value to set + */ +int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&tc35892->lock); + + ret = tc35892_reg_read(tc35892, reg); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= val; + + ret = tc35892_reg_write(tc35892, reg, ret); + +out: + mutex_unlock(&tc35892->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_set_bits); + +static struct resource gpio_resources[] = { + { + .start = TC35892_INT_GPIIRQ, + .end = TC35892_INT_GPIIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tc35892_devs[] = { + { + .name = "tc35892-gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = &gpio_resources[0], + }, +}; + +static irqreturn_t tc35892_irq(int irq, void *data) +{ + struct tc35892 *tc35892 = data; + int status; + + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status < 0) + return IRQ_NONE; + + while (status) { + int bit = __ffs(status); + + handle_nested_irq(tc35892->irq_base + bit); + status &= ~(1 << bit); + } + + /* + * A dummy read or write (to any register) appears to be necessary to + * have the last interrupt clear (for example, GPIO IC write) take + * effect. + */ + tc35892_reg_read(tc35892, TC35892_IRQST); + + return IRQ_HANDLED; +} + +static void tc35892_irq_dummy(unsigned int irq) +{ + /* No mask/unmask at this level */ +} + +static struct irq_chip tc35892_irq_chip = { + .name = "tc35892", + .mask = tc35892_irq_dummy, + .unmask = tc35892_irq_dummy, +}; + +static int tc35892_irq_init(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { + set_irq_chip_data(irq, tc35892); + set_irq_chip_and_handler(irq, &tc35892_irq_chip, + handle_edge_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void tc35892_irq_remove(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + +static int tc35892_chip_init(struct tc35892 *tc35892) +{ + int manf, ver, ret; + + manf = tc35892_reg_read(tc35892, TC35892_MANFCODE); + if (manf < 0) + return manf; + + ver = tc35892_reg_read(tc35892, TC35892_VERSION); + if (ver < 0) + return ver; + + if (manf != TC35892_MANFCODE_MAGIC) { + dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf); + return -EINVAL; + } + + dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); + + /* Put everything except the IRQ module into reset */ + ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, + TC35892_RSTCTRL_TIMRST + | TC35892_RSTCTRL_ROTRST + | TC35892_RSTCTRL_KBDRST + | TC35892_RSTCTRL_GPIRST); + if (ret < 0) + return ret; + + /* Clear the reset interrupt. */ + return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1); +} + +static int __devinit tc35892_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tc35892_platform_data *pdata = i2c->dev.platform_data; + struct tc35892 *tc35892; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL); + if (!tc35892) + return -ENOMEM; + + mutex_init(&tc35892->lock); + + tc35892->dev = &i2c->dev; + tc35892->i2c = i2c; + tc35892->pdata = pdata; + tc35892->irq_base = pdata->irq_base; + tc35892->num_gpio = id->driver_data; + + i2c_set_clientdata(i2c, tc35892); + + ret = tc35892_chip_init(tc35892); + if (ret) + goto out_free; + + ret = tc35892_irq_init(tc35892); + if (ret) + goto out_free; + + ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tc35892", tc35892); + if (ret) { + dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret); + goto out_removeirq; + } + + ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs, + ARRAY_SIZE(tc35892_devs), NULL, + tc35892->irq_base); + if (ret) { + dev_err(tc35892->dev, "failed to add children\n"); + goto out_freeirq; + } + + return 0; + +out_freeirq: + free_irq(tc35892->i2c->irq, tc35892); +out_removeirq: + tc35892_irq_remove(tc35892); +out_free: + i2c_set_clientdata(i2c, NULL); + kfree(tc35892); + return ret; +} + +static int __devexit tc35892_remove(struct i2c_client *client) +{ + struct tc35892 *tc35892 = i2c_get_clientdata(client); + + mfd_remove_devices(tc35892->dev); + + free_irq(tc35892->i2c->irq, tc35892); + tc35892_irq_remove(tc35892); + + i2c_set_clientdata(client, NULL); + kfree(tc35892); + + return 0; +} + +static const struct i2c_device_id tc35892_id[] = { + { "tc35892", 24 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc35892_id); + +static struct i2c_driver tc35892_driver = { + .driver.name = "tc35892", + .driver.owner = THIS_MODULE, + .probe = tc35892_probe, + .remove = __devexit_p(tc35892_remove), + .id_table = tc35892_id, +}; + +static int __init tc35892_init(void) +{ + return i2c_add_driver(&tc35892_driver); +} +subsys_initcall(tc35892_init); + +static void __exit tc35892_exit(void) +{ + i2c_del_driver(&tc35892_driver); +} +module_exit(tc35892_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC35892 MFD core driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/include/linux/mfd/tc35892.h b/include/linux/mfd/tc35892.h new file mode 100644 index 000000000000..e47f770d3068 --- /dev/null +++ b/include/linux/mfd/tc35892.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + */ + +#ifndef __LINUX_MFD_TC35892_H +#define __LINUX_MFD_TC35892_H + +#include + +#define TC35892_RSTCTRL_IRQRST (1 << 4) +#define TC35892_RSTCTRL_TIMRST (1 << 3) +#define TC35892_RSTCTRL_ROTRST (1 << 2) +#define TC35892_RSTCTRL_KBDRST (1 << 1) +#define TC35892_RSTCTRL_GPIRST (1 << 0) + +#define TC35892_IRQST 0x91 + +#define TC35892_MANFCODE_MAGIC 0x03 +#define TC35892_MANFCODE 0x80 +#define TC35892_VERSION 0x81 +#define TC35892_IOCFG 0xA7 + +#define TC35892_CLKMODE 0x88 +#define TC35892_CLKCFG 0x89 +#define TC35892_CLKEN 0x8A + +#define TC35892_RSTCTRL 0x82 +#define TC35892_EXTRSTN 0x83 +#define TC35892_RSTINTCLR 0x84 + +#define TC35892_GPIOIS0 0xC9 +#define TC35892_GPIOIS1 0xCA +#define TC35892_GPIOIS2 0xCB +#define TC35892_GPIOIBE0 0xCC +#define TC35892_GPIOIBE1 0xCD +#define TC35892_GPIOIBE2 0xCE +#define TC35892_GPIOIEV0 0xCF +#define TC35892_GPIOIEV1 0xD0 +#define TC35892_GPIOIEV2 0xD1 +#define TC35892_GPIOIE0 0xD2 +#define TC35892_GPIOIE1 0xD3 +#define TC35892_GPIOIE2 0xD4 +#define TC35892_GPIORIS0 0xD6 +#define TC35892_GPIORIS1 0xD7 +#define TC35892_GPIORIS2 0xD8 +#define TC35892_GPIOMIS0 0xD9 +#define TC35892_GPIOMIS1 0xDA +#define TC35892_GPIOMIS2 0xDB +#define TC35892_GPIOIC0 0xDC +#define TC35892_GPIOIC1 0xDD +#define TC35892_GPIOIC2 0xDE + +#define TC35892_GPIODATA0 0xC0 +#define TC35892_GPIOMASK0 0xc1 +#define TC35892_GPIODATA1 0xC2 +#define TC35892_GPIOMASK1 0xc3 +#define TC35892_GPIODATA2 0xC4 +#define TC35892_GPIOMASK2 0xC5 + +#define TC35892_GPIODIR0 0xC6 +#define TC35892_GPIODIR1 0xC7 +#define TC35892_GPIODIR2 0xC8 + +#define TC35892_GPIOSYNC0 0xE6 +#define TC35892_GPIOSYNC1 0xE7 +#define TC35892_GPIOSYNC2 0xE8 + +#define TC35892_GPIOWAKE0 0xE9 +#define TC35892_GPIOWAKE1 0xEA +#define TC35892_GPIOWAKE2 0xEB + +#define TC35892_GPIOODM0 0xE0 +#define TC35892_GPIOODE0 0xE1 +#define TC35892_GPIOODM1 0xE2 +#define TC35892_GPIOODE1 0xE3 +#define TC35892_GPIOODM2 0xE4 +#define TC35892_GPIOODE2 0xE5 + +#define TC35892_INT_GPIIRQ 0 +#define TC35892_INT_TI0IRQ 1 +#define TC35892_INT_TI1IRQ 2 +#define TC35892_INT_TI2IRQ 3 +#define TC35892_INT_ROTIRQ 5 +#define TC35892_INT_KBDIRQ 6 +#define TC35892_INT_PORIRQ 7 + +#define TC35892_NR_INTERNAL_IRQS 8 +#define TC35892_INT_GPIO(x) (TC35892_NR_INTERNAL_IRQS + (x)) + +struct tc35892 { + struct mutex lock; + struct device *dev; + struct i2c_client *i2c; + + int irq_base; + int num_gpio; + struct tc35892_platform_data *pdata; +}; + +extern int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data); +extern int tc35892_reg_read(struct tc35892 *tc35892, u8 reg); +extern int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, + u8 *values); +extern int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values); +extern int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val); + +/** + * struct tc35892_gpio_platform_data - TC35892 GPIO platform data + * @gpio_base: first gpio number assigned to TC35892. A maximum of + * %TC35892_NR_GPIOS GPIOs will be allocated. + */ +struct tc35892_gpio_platform_data { + int gpio_base; +}; + +/** + * struct tc35892_platform_data - TC35892 platform data + * @irq_base: base IRQ number. %TC35892_NR_IRQS irqs will be used. + * @gpio: GPIO-specific platform data + */ +struct tc35892_platform_data { + int irq_base; + struct tc35892_gpio_platform_data *gpio; +}; + +#define TC35892_NR_GPIOS 24 +#define TC35892_NR_IRQS TC35892_INT_GPIO(TC35892_NR_GPIOS) + +#endif -- cgit v1.2.3 From 812f9e9d424dde9ccb35975c0281edb6f8543735 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 1 May 2010 18:26:07 +0200 Subject: mfd: Renamed ab3100.h to abx500.h The goal here is to make way for a more general interface for the analog baseband chips ab3100 ab3550 ab550 and future chips. This patch have been divided into two parts since both changing name and content of a file is not recommended in git. Signed-off-by: Mattias Wallin Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- arch/arm/mach-u300/i2c.c | 2 +- drivers/mfd/ab3100-core.c | 2 +- drivers/mfd/ab3100-otp.c | 2 +- drivers/regulator/ab3100.c | 2 +- drivers/rtc/rtc-ab3100.c | 2 +- include/linux/mfd/ab3100.h | 129 --------------------------------------------- include/linux/mfd/abx500.h | 129 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 134 deletions(-) delete mode 100644 include/linux/mfd/ab3100.h create mode 100644 include/linux/mfd/abx500.h (limited to 'drivers/mfd') diff --git a/arch/arm/mach-u300/i2c.c b/arch/arm/mach-u300/i2c.c index c73ed06b6065..d893ee035c23 100644 --- a/arch/arm/mach-u300/i2c.c +++ b/arch/arm/mach-u300/i2c.c @@ -9,7 +9,7 @@ */ #include #include -#include +#include #include #include #include diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 16898211cd1a..f8c4a333ff1c 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include /* These are the only registers inside AB3100 used in this main file */ diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 2d14655fdebd..7093f1aef1e5 100644 --- a/drivers/mfd/ab3100-otp.c +++ b/drivers/mfd/ab3100-otp.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c index 1afd008ca957..04ca7f62c57e 100644 --- a/drivers/regulator/ab3100.c +++ b/drivers/regulator/ab3100.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include /* LDO registers and some handy masking definitions for AB3100 */ #define AB3100_LDO_A 0x40 diff --git a/drivers/rtc/rtc-ab3100.c b/drivers/rtc/rtc-ab3100.c index 4704aac2b5af..b46b85d581b0 100644 --- a/drivers/rtc/rtc-ab3100.c +++ b/drivers/rtc/rtc-ab3100.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include /* Clock rate in Hz */ #define AB3100_RTC_CLOCK_RATE 32768 diff --git a/include/linux/mfd/ab3100.h b/include/linux/mfd/ab3100.h deleted file mode 100644 index 9a881c305a50..000000000000 --- a/include/linux/mfd/ab3100.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2007-2009 ST-Ericsson AB - * License terms: GNU General Public License (GPL) version 2 - * AB3100 core access functions - * Author: Linus Walleij - */ - -#include -#include - -#ifndef MFD_AB3100_H -#define MFD_AB3100_H - -#define ABUNKNOWN 0 -#define AB3000 1 -#define AB3100 2 - -/* - * AB3100, EVENTA1, A2 and A3 event register flags - * these are catenated into a single 32-bit flag in the code - * for event notification broadcasts. - */ -#define AB3100_EVENTA1_ONSWA (0x01<<16) -#define AB3100_EVENTA1_ONSWB (0x02<<16) -#define AB3100_EVENTA1_ONSWC (0x04<<16) -#define AB3100_EVENTA1_DCIO (0x08<<16) -#define AB3100_EVENTA1_OVER_TEMP (0x10<<16) -#define AB3100_EVENTA1_SIM_OFF (0x20<<16) -#define AB3100_EVENTA1_VBUS (0x40<<16) -#define AB3100_EVENTA1_VSET_USB (0x80<<16) - -#define AB3100_EVENTA2_READY_TX (0x01<<8) -#define AB3100_EVENTA2_READY_RX (0x02<<8) -#define AB3100_EVENTA2_OVERRUN_ERROR (0x04<<8) -#define AB3100_EVENTA2_FRAMING_ERROR (0x08<<8) -#define AB3100_EVENTA2_CHARG_OVERCURRENT (0x10<<8) -#define AB3100_EVENTA2_MIDR (0x20<<8) -#define AB3100_EVENTA2_BATTERY_REM (0x40<<8) -#define AB3100_EVENTA2_ALARM (0x80<<8) - -#define AB3100_EVENTA3_ADC_TRIG5 (0x01) -#define AB3100_EVENTA3_ADC_TRIG4 (0x02) -#define AB3100_EVENTA3_ADC_TRIG3 (0x04) -#define AB3100_EVENTA3_ADC_TRIG2 (0x08) -#define AB3100_EVENTA3_ADC_TRIGVBAT (0x10) -#define AB3100_EVENTA3_ADC_TRIGVTX (0x20) -#define AB3100_EVENTA3_ADC_TRIG1 (0x40) -#define AB3100_EVENTA3_ADC_TRIG0 (0x80) - -/* AB3100, STR register flags */ -#define AB3100_STR_ONSWA (0x01) -#define AB3100_STR_ONSWB (0x02) -#define AB3100_STR_ONSWC (0x04) -#define AB3100_STR_DCIO (0x08) -#define AB3100_STR_BOOT_MODE (0x10) -#define AB3100_STR_SIM_OFF (0x20) -#define AB3100_STR_BATT_REMOVAL (0x40) -#define AB3100_STR_VBUS (0x80) - -/* - * AB3100 contains 8 regulators, one external regulator controller - * and a buck converter, further the LDO E and buck converter can - * have separate settings if they are in sleep mode, this is - * modeled as a separate regulator. - */ -#define AB3100_NUM_REGULATORS 10 - -/** - * struct ab3100 - * @access_mutex: lock out concurrent accesses to the AB3100 registers - * @dev: pointer to the containing device - * @i2c_client: I2C client for this chip - * @testreg_client: secondary client for test registers - * @chip_name: name of this chip variant - * @chip_id: 8 bit chip ID for this chip variant - * @event_subscribers: event subscribers are listed here - * @startup_events: a copy of the first reading of the event registers - * @startup_events_read: whether the first events have been read - * - * This struct is PRIVATE and devices using it should NOT - * access ANY fields. It is used as a token for calling the - * AB3100 functions. - */ -struct ab3100 { - struct mutex access_mutex; - struct device *dev; - struct i2c_client *i2c_client; - struct i2c_client *testreg_client; - char chip_name[32]; - u8 chip_id; - struct blocking_notifier_head event_subscribers; - u32 startup_events; - bool startup_events_read; -}; - -/** - * struct ab3100_platform_data - * Data supplied to initialize board connections to the AB3100 - * @reg_constraints: regulator constraints for target board - * the order of these constraints are: LDO A, C, D, E, - * F, G, H, K, EXT and BUCK. - * @reg_initvals: initial values for the regulator registers - * plus two sleep settings for LDO E and the BUCK converter. - * exactly AB3100_NUM_REGULATORS+2 values must be sent in. - * Order: LDO A, C, E, E sleep, F, G, H, K, EXT, BUCK, - * BUCK sleep, LDO D. (LDO D need to be initialized last.) - * @external_voltage: voltage level of the external regulator. - */ -struct ab3100_platform_data { - struct regulator_init_data reg_constraints[AB3100_NUM_REGULATORS]; - u8 reg_initvals[AB3100_NUM_REGULATORS+2]; - int external_voltage; -}; - -int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval); -int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval); -int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, - u8 first_reg, u8 *regvals, u8 numregs); -int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, - u8 reg, u8 andmask, u8 ormask); -u8 ab3100_get_chip_type(struct ab3100 *ab3100); -int ab3100_event_register(struct ab3100 *ab3100, - struct notifier_block *nb); -int ab3100_event_unregister(struct ab3100 *ab3100, - struct notifier_block *nb); -int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, - u32 *fatevent); - -#endif diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h new file mode 100644 index 000000000000..9a881c305a50 --- /dev/null +++ b/include/linux/mfd/abx500.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * AB3100 core access functions + * Author: Linus Walleij + */ + +#include +#include + +#ifndef MFD_AB3100_H +#define MFD_AB3100_H + +#define ABUNKNOWN 0 +#define AB3000 1 +#define AB3100 2 + +/* + * AB3100, EVENTA1, A2 and A3 event register flags + * these are catenated into a single 32-bit flag in the code + * for event notification broadcasts. + */ +#define AB3100_EVENTA1_ONSWA (0x01<<16) +#define AB3100_EVENTA1_ONSWB (0x02<<16) +#define AB3100_EVENTA1_ONSWC (0x04<<16) +#define AB3100_EVENTA1_DCIO (0x08<<16) +#define AB3100_EVENTA1_OVER_TEMP (0x10<<16) +#define AB3100_EVENTA1_SIM_OFF (0x20<<16) +#define AB3100_EVENTA1_VBUS (0x40<<16) +#define AB3100_EVENTA1_VSET_USB (0x80<<16) + +#define AB3100_EVENTA2_READY_TX (0x01<<8) +#define AB3100_EVENTA2_READY_RX (0x02<<8) +#define AB3100_EVENTA2_OVERRUN_ERROR (0x04<<8) +#define AB3100_EVENTA2_FRAMING_ERROR (0x08<<8) +#define AB3100_EVENTA2_CHARG_OVERCURRENT (0x10<<8) +#define AB3100_EVENTA2_MIDR (0x20<<8) +#define AB3100_EVENTA2_BATTERY_REM (0x40<<8) +#define AB3100_EVENTA2_ALARM (0x80<<8) + +#define AB3100_EVENTA3_ADC_TRIG5 (0x01) +#define AB3100_EVENTA3_ADC_TRIG4 (0x02) +#define AB3100_EVENTA3_ADC_TRIG3 (0x04) +#define AB3100_EVENTA3_ADC_TRIG2 (0x08) +#define AB3100_EVENTA3_ADC_TRIGVBAT (0x10) +#define AB3100_EVENTA3_ADC_TRIGVTX (0x20) +#define AB3100_EVENTA3_ADC_TRIG1 (0x40) +#define AB3100_EVENTA3_ADC_TRIG0 (0x80) + +/* AB3100, STR register flags */ +#define AB3100_STR_ONSWA (0x01) +#define AB3100_STR_ONSWB (0x02) +#define AB3100_STR_ONSWC (0x04) +#define AB3100_STR_DCIO (0x08) +#define AB3100_STR_BOOT_MODE (0x10) +#define AB3100_STR_SIM_OFF (0x20) +#define AB3100_STR_BATT_REMOVAL (0x40) +#define AB3100_STR_VBUS (0x80) + +/* + * AB3100 contains 8 regulators, one external regulator controller + * and a buck converter, further the LDO E and buck converter can + * have separate settings if they are in sleep mode, this is + * modeled as a separate regulator. + */ +#define AB3100_NUM_REGULATORS 10 + +/** + * struct ab3100 + * @access_mutex: lock out concurrent accesses to the AB3100 registers + * @dev: pointer to the containing device + * @i2c_client: I2C client for this chip + * @testreg_client: secondary client for test registers + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @event_subscribers: event subscribers are listed here + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + * + * This struct is PRIVATE and devices using it should NOT + * access ANY fields. It is used as a token for calling the + * AB3100 functions. + */ +struct ab3100 { + struct mutex access_mutex; + struct device *dev; + struct i2c_client *i2c_client; + struct i2c_client *testreg_client; + char chip_name[32]; + u8 chip_id; + struct blocking_notifier_head event_subscribers; + u32 startup_events; + bool startup_events_read; +}; + +/** + * struct ab3100_platform_data + * Data supplied to initialize board connections to the AB3100 + * @reg_constraints: regulator constraints for target board + * the order of these constraints are: LDO A, C, D, E, + * F, G, H, K, EXT and BUCK. + * @reg_initvals: initial values for the regulator registers + * plus two sleep settings for LDO E and the BUCK converter. + * exactly AB3100_NUM_REGULATORS+2 values must be sent in. + * Order: LDO A, C, E, E sleep, F, G, H, K, EXT, BUCK, + * BUCK sleep, LDO D. (LDO D need to be initialized last.) + * @external_voltage: voltage level of the external regulator. + */ +struct ab3100_platform_data { + struct regulator_init_data reg_constraints[AB3100_NUM_REGULATORS]; + u8 reg_initvals[AB3100_NUM_REGULATORS+2]; + int external_voltage; +}; + +int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval); +int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval); +int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, + u8 first_reg, u8 *regvals, u8 numregs); +int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 andmask, u8 ormask); +u8 ab3100_get_chip_type(struct ab3100 *ab3100); +int ab3100_event_register(struct ab3100 *ab3100, + struct notifier_block *nb); +int ab3100_event_unregister(struct ab3100 *ab3100, + struct notifier_block *nb); +int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, + u32 *fatevent); + +#endif -- cgit v1.2.3 From fa661258a27aa74aaf741882053d195291cefb75 Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Sat, 1 May 2010 18:26:20 +0200 Subject: mfd: AB3100 register access change to abx500 API The interface for the AB3100 is changed to make way for the ABX500 family of chips: AB3550, AB5500 and future ST-Ericsson Analog Baseband chips. The register access functions are moved out to a separate struct abx500_ops. In this way the interface is moved from the implementation and the sub functionality drivers can keep their interface intact when chip infrastructure and communication mechanisms changes. We also define the AB3550 device IDs and the AB3550 platform data struct and convert the catenated 32bit event to an array of 3 x 8bits. Signed-off-by: Mattias Wallin Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 12 +++- drivers/mfd/Makefile | 1 + drivers/mfd/ab3100-core.c | 95 ++++++++++++++++++--------- drivers/mfd/ab3100-otp.c | 11 ++-- drivers/mfd/abx500-core.c | 157 +++++++++++++++++++++++++++++++++++++++++++++ drivers/regulator/ab3100.c | 33 +++++----- drivers/rtc/rtc-ab3100.c | 39 +++++------ include/linux/mfd/abx500.h | 134 +++++++++++++++++++++++++++++++++----- 8 files changed, 390 insertions(+), 92 deletions(-) create mode 100644 drivers/mfd/abx500-core.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 614fa7b8a054..1c3d737ec406 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -374,9 +374,19 @@ config PCF50633_GPIO Say yes here if you want to include support GPIO for pins on the PCF50633 chip. +config ABX500_CORE + bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" + default y if ARCH_U300 + help + Say yes here if you have the ABX500 Mixed Signal IC family + chips. This core driver expose register access functions. + Functionality specific drivers using these functions can + remain unchanged when IC changes. Binding of the functions to + actual register access is done by the IC core driver. + config AB3100_CORE bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" - depends on I2C=y + depends on I2C=y && ABX500_CORE default y if ARCH_U300 help Select this to enable the AB3100 Mixed Signal IC core diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5eb79794d5a5..4b8ad04ff8e2 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o +obj-$(CONFIG_ABX500_CORE) += abx500-core.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index f8c4a333ff1c..53ebfee548fa 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -59,24 +59,15 @@ * The AB3100 is usually assigned address 0x48 (7-bit) * The chip is defined in the platform i2c_board_data section. */ - -u8 ab3100_get_chip_type(struct ab3100 *ab3100) +static int ab3100_get_chip_id(struct device *dev) { - u8 chip = ABUNKNOWN; - - switch (ab3100->chip_id & 0xf0) { - case 0xa0: - chip = AB3000; - break; - case 0xc0: - chip = AB3100; - break; - } - return chip; + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return (int)ab3100->chip_id; } -EXPORT_SYMBOL(ab3100_get_chip_type); -int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) +static int ab3100_set_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 regval) { u8 regandval[2] = {reg, regval}; int err; @@ -108,8 +99,14 @@ int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_set_register_interruptible); +static int set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 value) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_set_register_interruptible(ab3100, reg, value); +} /* * The test registers exist at an I2C bus address up one @@ -148,8 +145,8 @@ static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100, return err; } - -int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval) +static int ab3100_get_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 *regval) { int err; @@ -203,10 +200,16 @@ int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval) mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register_interruptible); +static int get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_get_register_interruptible(ab3100, reg, value); +} -int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, +static int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, u8 first_reg, u8 *regvals, u8 numregs) { int err; @@ -260,10 +263,17 @@ int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register_page_interruptible); +static int get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_get_register_page_interruptible(ab3100, + first_reg, regvals, numregs); +} -int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, +static int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 andmask, u8 ormask) { u8 regandval[2] = {reg, 0}; @@ -331,8 +341,15 @@ int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible); +static int mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_mask_and_set_register_interruptible(ab3100, + reg, bitmask, (bitmask & bitvalues)); +} /* * Register a simple callback for handling any AB3100 events. @@ -357,15 +374,27 @@ int ab3100_event_unregister(struct ab3100 *ab3100, EXPORT_SYMBOL(ab3100_event_unregister); -int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, - u32 *fatevent) +static int ab3100_event_registers_startup_state_get(struct device *dev, + u8 *event) { + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); if (!ab3100->startup_events_read) return -EAGAIN; /* Try again later */ - *fatevent = ab3100->startup_events; + memcpy(event, ab3100->startup_events, 3); return 0; } -EXPORT_SYMBOL(ab3100_event_registers_startup_state_get); + +static struct abx500_ops ab3100_ops = { + .get_chip_id = ab3100_get_chip_id, + .set_register = set_register_interruptible, + .get_register = get_register_interruptible, + .get_register_page = get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab3100_event_registers_startup_state_get, + .startup_irq_enabled = NULL, +}; /* * This is a threaded interrupt handler so we can make some @@ -390,7 +419,9 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data) event_regs[2]; if (!ab3100->startup_events_read) { - ab3100->startup_events = fatevent; + ab3100->startup_events[0] = event_regs[0]; + ab3100->startup_events[1] = event_regs[1]; + ab3100->startup_events[2] = event_regs[2]; ab3100->startup_events_read = true; } /* @@ -703,7 +734,8 @@ static int __init ab3100_setup(struct ab3100 *ab3100) dev_warn(ab3100->dev, "AB3100 P1E variant detected, " "forcing chip to 32KHz\n"); - err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08); + err = ab3100_set_test_register_interruptible(ab3100, + 0x02, 0x08); } exit_no_setup: @@ -898,6 +930,10 @@ static int __init ab3100_probe(struct i2c_client *client, if (err) goto exit_no_irq; + err = abx500_register_ops(&client->dev, &ab3100_ops); + if (err) + goto exit_no_ops; + /* Set parent and a pointer back to the container in device data */ for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) { ab3100_platform_devs[i]->dev.parent = @@ -915,6 +951,7 @@ static int __init ab3100_probe(struct i2c_client *client, return 0; + exit_no_ops: exit_no_irq: exit_no_setup: i2c_unregister_device(ab3100->testreg_client); diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 7093f1aef1e5..63d2b727ddbb 100644 --- a/drivers/mfd/ab3100-otp.c +++ b/drivers/mfd/ab3100-otp.c @@ -30,7 +30,6 @@ /** * struct ab3100_otp * @dev containing device - * @ab3100 a pointer to the parent ab3100 device struct * @locked whether the OTP is locked, after locking, no more bits * can be changed but before locking it is still possible * to change bits from 1->0. @@ -49,7 +48,6 @@ */ struct ab3100_otp { struct device *dev; - struct ab3100 *ab3100; bool locked; u32 freq; bool paf; @@ -63,19 +61,19 @@ struct ab3100_otp { static int __init ab3100_otp_read(struct ab3100_otp *otp) { - struct ab3100 *ab = otp->ab3100; u8 otpval[8]; u8 otpp; int err; - err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp); + err = abx500_get_register_interruptible(otp->dev, 0, + AB3100_OTPP, &otpp); if (err) { dev_err(otp->dev, "unable to read OTPP register\n"); return err; } - err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0, - otpval, 8); + err = abx500_get_register_page_interruptible(otp->dev, 0, + AB3100_OTP0, otpval, 8); if (err) { dev_err(otp->dev, "unable to read OTP register page\n"); return err; @@ -197,7 +195,6 @@ static int __init ab3100_otp_probe(struct platform_device *pdev) otp->dev = &pdev->dev; /* Replace platform data coming in with a local struct */ - otp->ab3100 = platform_get_drvdata(pdev); platform_set_drvdata(pdev, otp); err = ab3100_otp_read(otp); diff --git a/drivers/mfd/abx500-core.c b/drivers/mfd/abx500-core.c new file mode 100644 index 000000000000..3b3b97ec32a7 --- /dev/null +++ b/drivers/mfd/abx500-core.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Register access functions for the ABX500 Mixed Signal IC family. + * Author: Mattias Wallin + */ + +#include +#include +#include +#include + +static LIST_HEAD(abx500_list); + +struct abx500_device_entry { + struct list_head list; + struct abx500_ops ops; + struct device *dev; +}; + +static void lookup_ops(struct device *dev, struct abx500_ops **ops) +{ + struct abx500_device_entry *dev_entry; + + *ops = NULL; + list_for_each_entry(dev_entry, &abx500_list, list) { + if (dev_entry->dev == dev) { + *ops = &dev_entry->ops; + return; + } + } +} + +int abx500_register_ops(struct device *dev, struct abx500_ops *ops) +{ + struct abx500_device_entry *dev_entry; + + dev_entry = kzalloc(sizeof(struct abx500_device_entry), GFP_KERNEL); + if (IS_ERR(dev_entry)) { + dev_err(dev, "register_ops kzalloc failed"); + return -ENOMEM; + } + dev_entry->dev = dev; + memcpy(&dev_entry->ops, ops, sizeof(struct abx500_ops)); + + list_add_tail(&dev_entry->list, &abx500_list); + return 0; +} +EXPORT_SYMBOL(abx500_register_ops); + +void abx500_remove_ops(struct device *dev) +{ + struct abx500_device_entry *dev_entry, *tmp; + + list_for_each_entry_safe(dev_entry, tmp, &abx500_list, list) + { + if (dev_entry->dev == dev) { + list_del(&dev_entry->list); + kfree(dev_entry); + } + } +} +EXPORT_SYMBOL(abx500_remove_ops); + +int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->set_register != NULL)) + return ops->set_register(dev, bank, reg, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_set_register_interruptible); + +int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_register != NULL)) + return ops->get_register(dev, bank, reg, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_register_interruptible); + +int abx500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_register_page != NULL)) + return ops->get_register_page(dev, bank, + first_reg, regvals, numregs); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_register_page_interruptible); + +int abx500_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->mask_and_set_register != NULL)) + return ops->mask_and_set_register(dev, bank, + reg, bitmask, bitvalues); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_mask_and_set_register_interruptible); + +int abx500_get_chip_id(struct device *dev) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_chip_id != NULL)) + return ops->get_chip_id(dev); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_chip_id); + +int abx500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->event_registers_startup_state_get != NULL)) + return ops->event_registers_startup_state_get(dev, event); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_event_registers_startup_state_get); + +int abx500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->startup_irq_enabled != NULL)) + return ops->startup_irq_enabled(dev, irq); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_startup_irq_enabled); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("ABX500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c index 04ca7f62c57e..7b14a67bdca2 100644 --- a/drivers/regulator/ab3100.c +++ b/drivers/regulator/ab3100.c @@ -41,7 +41,7 @@ * struct ab3100_regulator * A struct passed around the individual regulator functions * @platform_device: platform device holding this regulator - * @ab3100: handle to the AB3100 parent chip + * @dev: handle to the device * @plfdata: AB3100 platform data passed in at probe time * @regreg: regulator register number in the AB3100 * @fixed_voltage: a fixed voltage for this regulator, if this @@ -52,7 +52,7 @@ */ struct ab3100_regulator { struct regulator_dev *rdev; - struct ab3100 *ab3100; + struct device *dev; struct ab3100_platform_data *plfdata; u8 regreg; int fixed_voltage; @@ -183,7 +183,7 @@ static int ab3100_enable_regulator(struct regulator_dev *reg) int err; u8 regval; - err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, ®val); if (err) { dev_warn(®->dev, "failed to get regid %d value\n", @@ -197,7 +197,7 @@ static int ab3100_enable_regulator(struct regulator_dev *reg) regval |= AB3100_REG_ON_MASK; - err = ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + err = abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, regval); if (err) { dev_warn(®->dev, "failed to set regid %d value\n", @@ -245,14 +245,14 @@ static int ab3100_disable_regulator(struct regulator_dev *reg) if (abreg->regreg == AB3100_LDO_D) { dev_info(®->dev, "disabling LDO D - shut down system\n"); /* Setting LDO D to 0x00 cuts the power to the SoC */ - return ab3100_set_register_interruptible(abreg->ab3100, + return abx500_set_register_interruptible(abreg->dev, 0, AB3100_LDO_D, 0x00U); } /* * All other regulators are handled here */ - err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, ®val); if (err) { dev_err(®->dev, "unable to get register 0x%x\n", @@ -260,7 +260,7 @@ static int ab3100_disable_regulator(struct regulator_dev *reg) return err; } regval &= ~AB3100_REG_ON_MASK; - return ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + return abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, regval); } @@ -270,7 +270,7 @@ static int ab3100_is_enabled_regulator(struct regulator_dev *reg) u8 regval; int err; - err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, ®val); if (err) { dev_err(®->dev, "unable to get register 0x%x\n", @@ -305,7 +305,7 @@ static int ab3100_get_voltage_regulator(struct regulator_dev *reg) * For variable types, read out setting and index into * supplied voltage list. */ - err = ab3100_get_register_interruptible(abreg->ab3100, + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, ®val); if (err) { dev_warn(®->dev, @@ -373,7 +373,7 @@ static int ab3100_set_voltage_regulator(struct regulator_dev *reg, if (bestindex < 0) return bestindex; - err = ab3100_get_register_interruptible(abreg->ab3100, + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, ®val); if (err) { dev_warn(®->dev, @@ -386,7 +386,7 @@ static int ab3100_set_voltage_regulator(struct regulator_dev *reg, regval &= ~0xE0; regval |= (bestindex << 5); - err = ab3100_set_register_interruptible(abreg->ab3100, + err = abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, regval); if (err) dev_warn(®->dev, "failed to set regulator register %02x\n", @@ -414,7 +414,7 @@ static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, /* LDO E and BUCK have special suspend voltages you can set */ bestindex = ab3100_get_best_voltage_index(reg, uV, uV); - err = ab3100_get_register_interruptible(abreg->ab3100, + err = abx500_get_register_interruptible(abreg->dev, 0, targetreg, ®val); if (err) { dev_warn(®->dev, @@ -427,7 +427,7 @@ static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, regval &= ~0xE0; regval |= (bestindex << 5); - err = ab3100_set_register_interruptible(abreg->ab3100, + err = abx500_set_register_interruptible(abreg->dev, 0, targetreg, regval); if (err) dev_warn(®->dev, "failed to set regulator register %02x\n", @@ -574,13 +574,12 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { static int __devinit ab3100_regulators_probe(struct platform_device *pdev) { struct ab3100_platform_data *plfdata = pdev->dev.platform_data; - struct ab3100 *ab3100 = platform_get_drvdata(pdev); int err = 0; u8 data; int i; /* Check chip state */ - err = ab3100_get_register_interruptible(ab3100, + err = abx500_get_register_interruptible(&pdev->dev, 0, AB3100_LDO_D, &data); if (err) { dev_err(&pdev->dev, "could not read initial status of LDO_D\n"); @@ -595,7 +594,7 @@ static int __devinit ab3100_regulators_probe(struct platform_device *pdev) /* Set up regulators */ for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { - err = ab3100_set_register_interruptible(ab3100, + err = abx500_set_register_interruptible(&pdev->dev, 0, ab3100_reg_init_order[i], plfdata->reg_initvals[i]); if (err) { @@ -617,7 +616,7 @@ static int __devinit ab3100_regulators_probe(struct platform_device *pdev) * see what it looks like for a certain machine, go * into the machine I2C setup. */ - reg->ab3100 = ab3100; + reg->dev = &pdev->dev; reg->plfdata = plfdata; /* diff --git a/drivers/rtc/rtc-ab3100.c b/drivers/rtc/rtc-ab3100.c index b46b85d581b0..d26780ea254b 100644 --- a/drivers/rtc/rtc-ab3100.c +++ b/drivers/rtc/rtc-ab3100.c @@ -45,7 +45,6 @@ */ static int ab3100_rtc_set_mmss(struct device *dev, unsigned long secs) { - struct ab3100 *ab3100_data = dev_get_drvdata(dev); u8 regs[] = {AB3100_TI0, AB3100_TI1, AB3100_TI2, AB3100_TI3, AB3100_TI4, AB3100_TI5}; unsigned char buf[6]; @@ -61,27 +60,26 @@ static int ab3100_rtc_set_mmss(struct device *dev, unsigned long secs) buf[5] = (fat_time >> 40) & 0xFF; for (i = 0; i < 6; i++) { - err = ab3100_set_register_interruptible(ab3100_data, + err = abx500_set_register_interruptible(dev, 0, regs[i], buf[i]); if (err) return err; } /* Set the flag to mark that the clock is now set */ - return ab3100_mask_and_set_register_interruptible(ab3100_data, + return abx500_mask_and_set_register_interruptible(dev, 0, AB3100_RTC, - 0xFE, 0x01); + 0x01, 0x01); } static int ab3100_rtc_read_time(struct device *dev, struct rtc_time *tm) { - struct ab3100 *ab3100_data = dev_get_drvdata(dev); unsigned long time; u8 rtcval; int err; - err = ab3100_get_register_interruptible(ab3100_data, + err = abx500_get_register_interruptible(dev, 0, AB3100_RTC, &rtcval); if (err) return err; @@ -94,7 +92,7 @@ static int ab3100_rtc_read_time(struct device *dev, struct rtc_time *tm) u8 buf[6]; /* Read out time registers */ - err = ab3100_get_register_page_interruptible(ab3100_data, + err = abx500_get_register_page_interruptible(dev, 0, AB3100_TI0, buf, 6); if (err != 0) @@ -114,7 +112,6 @@ static int ab3100_rtc_read_time(struct device *dev, struct rtc_time *tm) static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) { - struct ab3100 *ab3100_data = dev_get_drvdata(dev); unsigned long time; u64 fat_time; u8 buf[6]; @@ -122,7 +119,7 @@ static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) int err; /* Figure out if alarm is enabled or not */ - err = ab3100_get_register_interruptible(ab3100_data, + err = abx500_get_register_interruptible(dev, 0, AB3100_RTC, &rtcval); if (err) return err; @@ -133,7 +130,7 @@ static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) /* No idea how this could be represented */ alarm->pending = 0; /* Read out alarm registers, only 4 bytes */ - err = ab3100_get_register_page_interruptible(ab3100_data, + err = abx500_get_register_page_interruptible(dev, 0, AB3100_AL0, buf, 4); if (err) return err; @@ -148,7 +145,6 @@ static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) static int ab3100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) { - struct ab3100 *ab3100_data = dev_get_drvdata(dev); u8 regs[] = {AB3100_AL0, AB3100_AL1, AB3100_AL2, AB3100_AL3}; unsigned char buf[4]; unsigned long secs; @@ -165,21 +161,19 @@ static int ab3100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) /* Set the alarm */ for (i = 0; i < 4; i++) { - err = ab3100_set_register_interruptible(ab3100_data, + err = abx500_set_register_interruptible(dev, 0, regs[i], buf[i]); if (err) return err; } /* Then enable the alarm */ - return ab3100_mask_and_set_register_interruptible(ab3100_data, - AB3100_RTC, ~(1 << 2), + return abx500_mask_and_set_register_interruptible(dev, 0, + AB3100_RTC, (1 << 2), alarm->enabled << 2); } static int ab3100_rtc_irq_enable(struct device *dev, unsigned int enabled) { - struct ab3100 *ab3100_data = dev_get_drvdata(dev); - /* * It's not possible to enable/disable the alarm IRQ for this RTC. * It does not actually trigger any IRQ: instead its only function is @@ -188,12 +182,12 @@ static int ab3100_rtc_irq_enable(struct device *dev, unsigned int enabled) * and need to be handled there instead. */ if (enabled) - return ab3100_mask_and_set_register_interruptible(ab3100_data, - AB3100_RTC, ~(1 << 2), + return abx500_mask_and_set_register_interruptible(dev, 0, + AB3100_RTC, (1 << 2), 1 << 2); else - return ab3100_mask_and_set_register_interruptible(ab3100_data, - AB3100_RTC, ~(1 << 2), + return abx500_mask_and_set_register_interruptible(dev, 0, + AB3100_RTC, (1 << 2), 0); } @@ -210,10 +204,9 @@ static int __init ab3100_rtc_probe(struct platform_device *pdev) int err; u8 regval; struct rtc_device *rtc; - struct ab3100 *ab3100_data = platform_get_drvdata(pdev); /* The first RTC register needs special treatment */ - err = ab3100_get_register_interruptible(ab3100_data, + err = abx500_get_register_interruptible(&pdev->dev, 0, AB3100_RTC, ®val); if (err) { dev_err(&pdev->dev, "unable to read RTC register\n"); @@ -231,7 +224,7 @@ static int __init ab3100_rtc_probe(struct platform_device *pdev) * This bit remains until RTC power is lost. */ regval = 1 | RTC_SETTING; - err = ab3100_set_register_interruptible(ab3100_data, + err = abx500_set_register_interruptible(&pdev->dev, 0, AB3100_RTC, regval); /* Ignore any error on this write */ } diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 9a881c305a50..390726fcbcb1 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -3,17 +3,37 @@ * License terms: GNU General Public License (GPL) version 2 * AB3100 core access functions * Author: Linus Walleij + * + * ABX500 core access functions. + * The abx500 interface is used for the Analog Baseband chip + * ab3100, ab3550, ab5500 and possibly comming. It is not used for + * ab4500 and ab8500 since they are another family of chip. + * + * Author: Mattias Wallin + * Author: Mattias Nilsson + * Author: Bengt Jonsson + * Author: Rickard Andersson */ #include #include -#ifndef MFD_AB3100_H -#define MFD_AB3100_H +#ifndef MFD_ABX500_H +#define MFD_ABX500_H -#define ABUNKNOWN 0 -#define AB3000 1 -#define AB3100 2 +#define AB3100_P1A 0xc0 +#define AB3100_P1B 0xc1 +#define AB3100_P1C 0xc2 +#define AB3100_P1D 0xc3 +#define AB3100_P1E 0xc4 +#define AB3100_P1F 0xc5 +#define AB3100_P1G 0xc6 +#define AB3100_R2A 0xc7 +#define AB3100_R2B 0xc8 +#define AB3550_P1A 0x10 +#define AB5500_1_0 0x20 +#define AB5500_2_0 0x21 +#define AB5500_2_1 0x22 /* * AB3100, EVENTA1, A2 and A3 event register flags @@ -89,7 +109,7 @@ struct ab3100 { char chip_name[32]; u8 chip_id; struct blocking_notifier_head event_subscribers; - u32 startup_events; + u8 startup_events[3]; bool startup_events_read; }; @@ -112,18 +132,102 @@ struct ab3100_platform_data { int external_voltage; }; -int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval); -int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval); -int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, - u8 first_reg, u8 *regvals, u8 numregs); -int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, - u8 reg, u8 andmask, u8 ormask); -u8 ab3100_get_chip_type(struct ab3100 *ab3100); int ab3100_event_register(struct ab3100 *ab3100, struct notifier_block *nb); int ab3100_event_unregister(struct ab3100 *ab3100, struct notifier_block *nb); -int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, - u32 *fatevent); +/* AB3550, STR register flags */ +#define AB3550_STR_ONSWA (0x01) +#define AB3550_STR_ONSWB (0x02) +#define AB3550_STR_ONSWC (0x04) +#define AB3550_STR_DCIO (0x08) +#define AB3550_STR_BOOT_MODE (0x10) +#define AB3550_STR_SIM_OFF (0x20) +#define AB3550_STR_BATT_REMOVAL (0x40) +#define AB3550_STR_VBUS (0x80) + +/* Interrupt mask registers */ +#define AB3550_IMR1 0x29 +#define AB3550_IMR2 0x2a +#define AB3550_IMR3 0x2b +#define AB3550_IMR4 0x2c +#define AB3550_IMR5 0x2d + +enum ab3550_devid { + AB3550_DEVID_ADC, + AB3550_DEVID_DAC, + AB3550_DEVID_LEDS, + AB3550_DEVID_POWER, + AB3550_DEVID_REGULATORS, + AB3550_DEVID_SIM, + AB3550_DEVID_UART, + AB3550_DEVID_RTC, + AB3550_DEVID_CHARGER, + AB3550_DEVID_FUELGAUGE, + AB3550_DEVID_VIBRATOR, + AB3550_DEVID_CODEC, + AB3550_NUM_DEVICES, +}; + +/** + * struct abx500_init_setting + * Initial value of the registers for driver to use during setup. + */ +struct abx500_init_settings { + u8 bank; + u8 reg; + u8 setting; +}; + +/** + * struct ab3550_platform_data + * Data supplied to initialize board connections to the AB3550 + */ +struct ab3550_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB3550_NUM_DEVICES]; + size_t dev_data_sz[AB3550_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; +}; + +int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value); +int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value); +int abx500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs); +int abx500_set_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs); +/** + * abx500_mask_and_set_register_inerruptible() - Modifies selected bits of a + * target register + * + * @dev: The AB sub device. + * @bank: The i2c bank number. + * @bitmask: The bit mask to use. + * @bitvalues: The new bit values. + * + * Updates the value of an AB register: + * value -> ((value & ~bitmask) | (bitvalues & bitmask)) + */ +int abx500_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues); +int abx500_get_chip_id(struct device *dev); +int abx500_event_registers_startup_state_get(struct device *dev, u8 *event); +int abx500_startup_irq_enabled(struct device *dev, unsigned int irq); + +struct abx500_ops { + int (*get_chip_id) (struct device *); + int (*get_register) (struct device *, u8, u8, u8 *); + int (*set_register) (struct device *, u8, u8, u8); + int (*get_register_page) (struct device *, u8, u8, u8 *, u8); + int (*set_register_page) (struct device *, u8, u8, u8 *, u8); + int (*mask_and_set_register) (struct device *, u8, u8, u8, u8); + int (*event_registers_startup_state_get) (struct device *, u8 *); + int (*startup_irq_enabled) (struct device *, unsigned int); +}; + +int abx500_register_ops(struct device *core_dev, struct abx500_ops *ops); #endif -- cgit v1.2.3 From 09bcb3f3369e164bf51b3b566b865f2514b23e8c Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Tue, 11 May 2010 00:25:29 +0200 Subject: mfd: AB3550 core driver This adds a core driver for the AB3550 mixed-signal circuit found in the ST-Ericsson U300 platforms. This driver is a singleton proxy for all access to the AB3550 sub functionality drivers which can be added on top of this one: RTC, regulators, battery and system power control, vibrator, LEDs and an ALSA codec. Signed-off-by: Mattias Wallin Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 14 + drivers/mfd/Makefile | 1 + drivers/mfd/ab3550-core.c | 1401 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1416 insertions(+) create mode 100644 drivers/mfd/ab3550-core.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 1c3d737ec406..a20fa230d354 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -423,6 +423,20 @@ config AB4500_CORE read/write functions for the devices to get access to this chip. This chip embeds various other multimedia funtionalities as well. +config AB3550_CORE + bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions" + select MFD_CORE + depends on I2C=y && GENERIC_HARDIRQS && ABX500_CORE + help + Select this to enable the AB3550 Mixed Signal IC core + functionality. This connects to a AB3550 on the I2C bus + and expose a number of symbols needed for dependent devices + to read and write registers and subscribe to events from + this multi-functional IC. This is needed to use other features + of the AB3550 such as battery-backed RTC, charging control, + LEDs, vibrator, system power and temperature, power management + and ALSA sound. + config MFD_TIMBERDALE tristate "Support for the Timberdale FPGA" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4b8ad04ff8e2..4c684c137d43 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_ABX500_CORE) += abx500-core.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o +obj-$(CONFIG_AB3550_CORE) += ab3550-core.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c new file mode 100644 index 000000000000..1060f8e1c40a --- /dev/null +++ b/drivers/mfd/ab3550-core.c @@ -0,0 +1,1401 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB3550 IC on the I2C bus + * and some basic chip-configuration. + * Author: Bengt Jonsson + * Author: Mattias Nilsson + * Author: Mattias Wallin + * Author: Rickard Andersson + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB3550_NAME_STRING "ab3550" +#define AB3550_ID_FORMAT_STRING "AB3550 %s" +#define AB3550_NUM_BANKS 2 +#define AB3550_NUM_EVENT_REG 5 + +/* These are the only registers inside AB3550 used in this main file */ + +/* Chip ID register */ +#define AB3550_CID_REG 0x20 + +/* Interrupt event registers */ +#define AB3550_EVENT_BANK 0 +#define AB3550_EVENT_REG 0x22 + +/* Read/write operation values. */ +#define AB3550_PERM_RD (0x01) +#define AB3550_PERM_WR (0x02) + +/* Read/write permissions. */ +#define AB3550_PERM_RO (AB3550_PERM_RD) +#define AB3550_PERM_RW (AB3550_PERM_RD | AB3550_PERM_WR) + +/** + * struct ab3550 + * @access_mutex: lock out concurrent accesses to the AB registers + * @i2c_client: I2C client for this chip + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @mask_work: a worker for writing to mask registers + * @event_lock: a lock to protect the event_mask + * @event_mask: a local copy of the mask event registers + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab3550 { + struct mutex access_mutex; + struct i2c_client *i2c_client[AB3550_NUM_BANKS]; + char chip_name[32]; + u8 chip_id; + struct work_struct mask_work; + spinlock_t event_lock; + u8 event_mask[AB3550_NUM_EVENT_REG]; + u8 startup_events[AB3550_NUM_EVENT_REG]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +/** + * struct ab3550_reg_range + * @first: the first address of the range + * @last: the last address of the range + * @perm: access permissions for the range + */ +struct ab3550_reg_range { + u8 first; + u8 last; + u8 perm; +}; + +/** + * struct ab3550_reg_ranges + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab3550_reg_ranges { + u8 count; + const struct ab3550_reg_range *range; +}; + +/* + * Permissible register ranges for reading and writing per device and bank. + * + * The ranges must be listed in increasing address order, and no overlaps are + * allowed. It is assumed that write permission implies read permission + * (i.e. only RO and RW permissions should be used). Ranges with write + * permission must not be split up. + */ + +#define NO_RANGE {.count = 0, .range = NULL,} + +static struct +ab3550_reg_ranges ab3550_reg_ranges[AB3550_NUM_DEVICES][AB3550_NUM_BANKS] = { + [AB3550_DEVID_DAC] = { + NO_RANGE, + { + .count = 2, + .range = (struct ab3550_reg_range[]) { + { + .first = 0xb0, + .last = 0xba, + .perm = AB3550_PERM_RW, + }, + { + .first = 0xbc, + .last = 0xc3, + .perm = AB3550_PERM_RW, + }, + }, + }, + }, + [AB3550_DEVID_LEDS] = { + NO_RANGE, + { + .count = 2, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x5a, + .last = 0x88, + .perm = AB3550_PERM_RW, + }, + { + .first = 0x8a, + .last = 0xad, + .perm = AB3550_PERM_RW, + }, + } + }, + }, + [AB3550_DEVID_POWER] = { + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x21, + .last = 0x21, + .perm = AB3550_PERM_RO, + }, + } + }, + NO_RANGE, + }, + [AB3550_DEVID_REGULATORS] = { + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x69, + .last = 0xa3, + .perm = AB3550_PERM_RW, + }, + } + }, + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x14, + .last = 0x16, + .perm = AB3550_PERM_RW, + }, + } + }, + }, + [AB3550_DEVID_SIM] = { + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x21, + .last = 0x21, + .perm = AB3550_PERM_RO, + }, + } + }, + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x14, + .last = 0x17, + .perm = AB3550_PERM_RW, + }, + } + + }, + }, + [AB3550_DEVID_UART] = { + NO_RANGE, + NO_RANGE, + }, + [AB3550_DEVID_RTC] = { + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x00, + .last = 0x0c, + .perm = AB3550_PERM_RW, + }, + } + }, + NO_RANGE, + }, + [AB3550_DEVID_CHARGER] = { + { + .count = 2, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x10, + .last = 0x1a, + .perm = AB3550_PERM_RW, + }, + { + .first = 0x21, + .last = 0x21, + .perm = AB3550_PERM_RO, + }, + } + }, + NO_RANGE, + }, + [AB3550_DEVID_ADC] = { + NO_RANGE, + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x20, + .last = 0x56, + .perm = AB3550_PERM_RW, + }, + + } + }, + }, + [AB3550_DEVID_FUELGAUGE] = { + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x21, + .last = 0x21, + .perm = AB3550_PERM_RO, + }, + } + }, + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x00, + .last = 0x0e, + .perm = AB3550_PERM_RW, + }, + } + }, + }, + [AB3550_DEVID_VIBRATOR] = { + NO_RANGE, + { + .count = 1, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + .perm = AB3550_PERM_RW, + }, + + } + }, + }, + [AB3550_DEVID_CODEC] = { + { + .count = 2, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x31, + .last = 0x63, + .perm = AB3550_PERM_RW, + }, + { + .first = 0x65, + .last = 0x68, + .perm = AB3550_PERM_RW, + }, + } + }, + NO_RANGE, + }, +}; + +static struct mfd_cell ab3550_devs[AB3550_NUM_DEVICES] = { + [AB3550_DEVID_DAC] = { + .name = "ab3550-dac", + .id = AB3550_DEVID_DAC, + .num_resources = 0, + }, + [AB3550_DEVID_LEDS] = { + .name = "ab3550-leds", + .id = AB3550_DEVID_LEDS, + }, + [AB3550_DEVID_POWER] = { + .name = "ab3550-power", + .id = AB3550_DEVID_POWER, + }, + [AB3550_DEVID_REGULATORS] = { + .name = "ab3550-regulators", + .id = AB3550_DEVID_REGULATORS, + }, + [AB3550_DEVID_SIM] = { + .name = "ab3550-sim", + .id = AB3550_DEVID_SIM, + }, + [AB3550_DEVID_UART] = { + .name = "ab3550-uart", + .id = AB3550_DEVID_UART, + }, + [AB3550_DEVID_RTC] = { + .name = "ab3550-rtc", + .id = AB3550_DEVID_RTC, + }, + [AB3550_DEVID_CHARGER] = { + .name = "ab3550-charger", + .id = AB3550_DEVID_CHARGER, + }, + [AB3550_DEVID_ADC] = { + .name = "ab3550-adc", + .id = AB3550_DEVID_ADC, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = 16, + .end = 16, + }, + { + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = 17, + .end = 17, + }, + { + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = 18, + .end = 18, + }, + { + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = 19, + .end = 19, + }, + { + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = 20, + .end = 20, + }, + { + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = 21, + .end = 21, + }, + { + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = 22, + .end = 22, + }, + { + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = 23, + .end = 23, + }, + { + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = 13, + .end = 13, + }, + { + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = 12, + .end = 12, + }, + }, + }, + [AB3550_DEVID_FUELGAUGE] = { + .name = "ab3550-fuelgauge", + .id = AB3550_DEVID_FUELGAUGE, + }, + [AB3550_DEVID_VIBRATOR] = { + .name = "ab3550-vibrator", + .id = AB3550_DEVID_VIBRATOR, + }, + [AB3550_DEVID_CODEC] = { + .name = "ab3550-codec", + .id = AB3550_DEVID_CODEC, + }, +}; + +/* + * I2C transactions with error messages. + */ +static int ab3550_i2c_master_send(struct ab3550 *ab, u8 bank, u8 *data, + u8 count) +{ + int err; + + err = i2c_master_send(ab->i2c_client[bank], data, count); + if (err < 0) { + dev_err(&ab->i2c_client[0]->dev, "send error: %d\n", err); + return err; + } + return 0; +} + +static int ab3550_i2c_master_recv(struct ab3550 *ab, u8 bank, u8 *data, + u8 count) +{ + int err; + + err = i2c_master_recv(ab->i2c_client[bank], data, count); + if (err < 0) { + dev_err(&ab->i2c_client[0]->dev, "receive error: %d\n", err); + return err; + } + return 0; +} + +/* + * Functionality for getting/setting register values. + */ +static int get_register_interruptible(struct ab3550 *ab, u8 bank, u8 reg, + u8 *value) +{ + int err; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + err = ab3550_i2c_master_send(ab, bank, ®, 1); + if (!err) + err = ab3550_i2c_master_recv(ab, bank, value, 1); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct ab3550 *ab, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + err = ab3550_i2c_master_send(ab, bank, &first_reg, 1); + if (!err) + err = ab3550_i2c_master_recv(ab, bank, regvals, numregs); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct ab3550 *ab, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int err = 0; + + if (likely(bitmask)) { + u8 reg_bits[2] = {reg, 0}; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + if (bitmask == 0xFF) /* No need to read in this case. */ + reg_bits[1] = bitvalues; + else { /* Read and modify the register value. */ + u8 bits; + + err = ab3550_i2c_master_send(ab, bank, ®, 1); + if (err) + goto unlock_and_return; + err = ab3550_i2c_master_recv(ab, bank, &bits, 1); + if (err) + goto unlock_and_return; + reg_bits[1] = ((~bitmask & bits) | + (bitmask & bitvalues)); + } + /* Write the new value. */ + err = ab3550_i2c_master_send(ab, bank, reg_bits, 2); +unlock_and_return: + mutex_unlock(&ab->access_mutex); + } + return err; +} + +/* + * Read/write permission checking functions. + */ +static bool page_write_allowed(const struct ab3550_reg_ranges *ranges, + u8 first_reg, u8 last_reg) +{ + u8 i; + + if (last_reg < first_reg) + return false; + + for (i = 0; i < ranges->count; i++) { + if (first_reg < ranges->range[i].first) + break; + if ((last_reg <= ranges->range[i].last) && + (ranges->range[i].perm & AB3550_PERM_WR)) + return true; + } + return false; +} + +static bool reg_write_allowed(const struct ab3550_reg_ranges *ranges, u8 reg) +{ + return page_write_allowed(ranges, reg, reg); +} + +static bool page_read_allowed(const struct ab3550_reg_ranges *ranges, + u8 first_reg, u8 last_reg) +{ + u8 i; + + if (last_reg < first_reg) + return false; + /* Find the range (if it exists in the list) that includes first_reg. */ + for (i = 0; i < ranges->count; i++) { + if (first_reg < ranges->range[i].first) + return false; + if (first_reg <= ranges->range[i].last) + break; + } + /* Make sure that the entire range up to and including last_reg is + * readable. This may span several of the ranges in the list. + */ + while ((i < ranges->count) && + (ranges->range[i].perm & AB3550_PERM_RD)) { + if (last_reg <= ranges->range[i].last) + return true; + if ((++i >= ranges->count) || + (ranges->range[i].first != + (ranges->range[i - 1].last + 1))) { + break; + } + } + return false; +} + +static bool reg_read_allowed(const struct ab3550_reg_ranges *ranges, u8 reg) +{ + return page_read_allowed(ranges, reg, reg); +} + +/* + * The exported register access functionality. + */ +int ab3550_get_chip_id(struct device *dev) +{ + struct ab3550 *ab = dev_get_drvdata(dev->parent); + return (int)ab->chip_id; +} + +int ab3550_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab3550 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB3550_NUM_BANKS <= bank) || + !reg_write_allowed(&ab3550_reg_ranges[pdev->id][bank], reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return mask_and_set_register_interruptible(ab, bank, reg, + bitmask, bitvalues); +} + +int ab3550_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value) +{ + return ab3550_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, + value); +} + +int ab3550_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct ab3550 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB3550_NUM_BANKS <= bank) || + !reg_read_allowed(&ab3550_reg_ranges[pdev->id][bank], reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_interruptible(ab, bank, reg, value); +} + +int ab3550_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab3550 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB3550_NUM_BANKS <= bank) || + !page_read_allowed(&ab3550_reg_ranges[pdev->id][bank], + first_reg, (first_reg + numregs - 1))) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_page_interruptible(ab, bank, first_reg, regvals, + numregs); +} + +int ab3550_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct ab3550 *ab; + + ab = dev_get_drvdata(dev->parent); + if (!ab->startup_events_read) + return -EAGAIN; /* Try again later */ + + memcpy(event, ab->startup_events, AB3550_NUM_EVENT_REG); + return 0; +} + +int ab3550_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct ab3550 *ab; + struct ab3550_platform_data *plf_data; + bool val; + + ab = get_irq_chip_data(irq); + plf_data = ab->i2c_client[0]->dev.platform_data; + irq -= plf_data->irq.base; + val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); + + return val; +} + +static struct abx500_ops ab3550_ops = { + .get_chip_id = ab3550_get_chip_id, + .get_register = ab3550_get_register_interruptible, + .set_register = ab3550_set_register_interruptible, + .get_register_page = ab3550_get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = ab3550_mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab3550_event_registers_startup_state_get, + .startup_irq_enabled = ab3550_startup_irq_enabled, +}; + +static irqreturn_t ab3550_irq_handler(int irq, void *data) +{ + struct ab3550 *ab = data; + int err; + unsigned int i; + u8 e[AB3550_NUM_EVENT_REG]; + u8 *events; + unsigned long flags; + + events = (ab->startup_events_read ? e : ab->startup_events); + + err = get_register_page_interruptible(ab, AB3550_EVENT_BANK, + AB3550_EVENT_REG, events, AB3550_NUM_EVENT_REG); + if (err) + goto err_event_rd; + + if (!ab->startup_events_read) { + dev_info(&ab->i2c_client[0]->dev, + "startup events 0x%x,0x%x,0x%x,0x%x,0x%x\n", + ab->startup_events[0], ab->startup_events[1], + ab->startup_events[2], ab->startup_events[3], + ab->startup_events[4]); + ab->startup_events_read = true; + goto out; + } + + /* The two highest bits in event[4] are not used. */ + events[4] &= 0x3f; + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB3550_NUM_EVENT_REG; i++) + events[i] &= ~ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + for (i = 0; i < AB3550_NUM_EVENT_REG; i++) { + u8 bit; + u8 event_reg; + + dev_dbg(&ab->i2c_client[0]->dev, "IRQ Event[%d]: 0x%2x\n", + i, events[i]); + + event_reg = events[i]; + for (bit = 0; event_reg; bit++, event_reg /= 2) { + if (event_reg % 2) { + unsigned int irq; + struct ab3550_platform_data *plf_data; + + plf_data = ab->i2c_client[0]->dev.platform_data; + irq = plf_data->irq.base + (i * 8) + bit; + handle_nested_irq(irq); + } + } + } +out: + return IRQ_HANDLED; + +err_event_rd: + dev_dbg(&ab->i2c_client[0]->dev, "error reading event registers\n"); + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS +static struct ab3550_reg_ranges debug_ranges[AB3550_NUM_BANKS] = { + { + .count = 6, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x00, + .last = 0x0e, + }, + { + .first = 0x10, + .last = 0x1a, + }, + { + .first = 0x1e, + .last = 0x4f, + }, + { + .first = 0x51, + .last = 0x63, + }, + { + .first = 0x65, + .last = 0xa3, + }, + { + .first = 0xa5, + .last = 0xa8, + }, + } + }, + { + .count = 8, + .range = (struct ab3550_reg_range[]) { + { + .first = 0x00, + .last = 0x0e, + }, + { + .first = 0x10, + .last = 0x17, + }, + { + .first = 0x1a, + .last = 0x1c, + }, + { + .first = 0x20, + .last = 0x56, + }, + { + .first = 0x5a, + .last = 0x88, + }, + { + .first = 0x8a, + .last = 0xad, + }, + { + .first = 0xb0, + .last = 0xba, + }, + { + .first = 0xbc, + .last = 0xc3, + }, + } + }, +}; + +static int ab3550_registers_print(struct seq_file *s, void *p) +{ + struct ab3550 *ab = s->private; + int bank; + + seq_printf(s, AB3550_NAME_STRING " register values:\n"); + + for (bank = 0; bank < AB3550_NUM_BANKS; bank++) { + unsigned int i; + + seq_printf(s, " bank %d:\n", bank); + for (i = 0; i < debug_ranges[bank].count; i++) { + u8 reg; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + get_register_interruptible(ab, bank, reg, + &value); + seq_printf(s, " [%d/0x%02X]: 0x%02X\n", bank, + reg, value); + } + } + } + return 0; +} + +static int ab3550_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3550_registers_print, inode->i_private); +} + +static const struct file_operations ab3550_registers_fops = { + .open = ab3550_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab3550_bank_print(struct seq_file *s, void *p) +{ + struct ab3550 *ab = s->private; + + seq_printf(s, "%d\n", ab->debug_bank); + return 0; +} + +static int ab3550_bank_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3550_bank_print, inode->i_private); +} + +static ssize_t ab3550_bank_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_bank; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_bank); + if (err) + return -EINVAL; + + if (user_bank >= AB3550_NUM_BANKS) { + dev_err(&ab->i2c_client[0]->dev, + "debugfs error input > number of banks\n"); + return -EINVAL; + } + + ab->debug_bank = user_bank; + + return buf_size; +} + +static int ab3550_address_print(struct seq_file *s, void *p) +{ + struct ab3550 *ab = s->private; + + seq_printf(s, "0x%02X\n", ab->debug_address); + return 0; +} + +static int ab3550_address_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3550_address_print, inode->i_private); +} + +static ssize_t ab3550_address_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_address; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_address); + if (err) + return -EINVAL; + if (user_address > 0xff) { + dev_err(&ab->i2c_client[0]->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + ab->debug_address = user_address; + return buf_size; +} + +static int ab3550_val_print(struct seq_file *s, void *p) +{ + struct ab3550 *ab = s->private; + int err; + u8 regvalue; + + err = get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) + return -EINVAL; + seq_printf(s, "0x%02X\n", regvalue); + + return 0; +} + +static int ab3550_val_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3550_val_print, inode->i_private); +} + +static ssize_t ab3550_val_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + u8 regvalue; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val > 0xff) { + dev_err(&ab->i2c_client[0]->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + err = mask_and_set_register_interruptible( + ab, (u8)ab->debug_bank, + (u8)ab->debug_address, 0xFF, (u8)user_val); + if (err) + return -EINVAL; + + get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) + return -EINVAL; + + return buf_size; +} + +static const struct file_operations ab3550_bank_fops = { + .open = ab3550_bank_open, + .write = ab3550_bank_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab3550_address_fops = { + .open = ab3550_address_open, + .write = ab3550_address_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab3550_val_fops = { + .open = ab3550_val_open, + .write = ab3550_val_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab3550_dir; +static struct dentry *ab3550_reg_file; +static struct dentry *ab3550_bank_file; +static struct dentry *ab3550_address_file; +static struct dentry *ab3550_val_file; + +static inline void ab3550_setup_debugfs(struct ab3550 *ab) +{ + ab->debug_bank = 0; + ab->debug_address = 0x00; + + ab3550_dir = debugfs_create_dir(AB3550_NAME_STRING, NULL); + if (!ab3550_dir) + goto exit_no_debugfs; + + ab3550_reg_file = debugfs_create_file("all-registers", + S_IRUGO, ab3550_dir, ab, &ab3550_registers_fops); + if (!ab3550_reg_file) + goto exit_destroy_dir; + + ab3550_bank_file = debugfs_create_file("register-bank", + (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_bank_fops); + if (!ab3550_bank_file) + goto exit_destroy_reg; + + ab3550_address_file = debugfs_create_file("register-address", + (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_address_fops); + if (!ab3550_address_file) + goto exit_destroy_bank; + + ab3550_val_file = debugfs_create_file("register-value", + (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_val_fops); + if (!ab3550_val_file) + goto exit_destroy_address; + + return; + +exit_destroy_address: + debugfs_remove(ab3550_address_file); +exit_destroy_bank: + debugfs_remove(ab3550_bank_file); +exit_destroy_reg: + debugfs_remove(ab3550_reg_file); +exit_destroy_dir: + debugfs_remove(ab3550_dir); +exit_no_debugfs: + dev_err(&ab->i2c_client[0]->dev, "failed to create debugfs entries.\n"); + return; +} + +static inline void ab3550_remove_debugfs(void) +{ + debugfs_remove(ab3550_val_file); + debugfs_remove(ab3550_address_file); + debugfs_remove(ab3550_bank_file); + debugfs_remove(ab3550_reg_file); + debugfs_remove(ab3550_dir); +} + +#else /* !CONFIG_DEBUG_FS */ +static inline void ab3550_setup_debugfs(struct ab3550 *ab) +{ +} +static inline void ab3550_remove_debugfs(void) +{ +} +#endif + +/* + * Basic set-up, datastructure creation/destruction and I2C interface. + * This sets up a default config in the AB3550 chip so that it + * will work as expected. + */ +static int __init ab3550_setup(struct ab3550 *ab) +{ + int err = 0; + int i; + struct ab3550_platform_data *plf_data; + struct abx500_init_settings *settings; + + plf_data = ab->i2c_client[0]->dev.platform_data; + settings = plf_data->init_settings; + + for (i = 0; i < plf_data->init_settings_sz; i++) { + err = mask_and_set_register_interruptible(ab, + settings[i].bank, + settings[i].reg, + 0xFF, settings[i].setting); + if (err) + goto exit_no_setup; + + /* If event mask register update the event mask in ab3550 */ + if ((settings[i].bank == 0) && + (AB3550_IMR1 <= settings[i].reg) && + (settings[i].reg <= AB3550_IMR5)) { + ab->event_mask[settings[i].reg - AB3550_IMR1] = + settings[i].setting; + } + } +exit_no_setup: + return err; +} + +static void ab3550_mask_work(struct work_struct *work) +{ + struct ab3550 *ab = container_of(work, struct ab3550, mask_work); + int i; + unsigned long flags; + u8 mask[AB3550_NUM_EVENT_REG]; + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB3550_NUM_EVENT_REG; i++) + mask[i] = ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + for (i = 0; i < AB3550_NUM_EVENT_REG; i++) { + int err; + + err = mask_and_set_register_interruptible(ab, 0, + (AB3550_IMR1 + i), ~0, mask[i]); + if (err) + dev_err(&ab->i2c_client[0]->dev, + "ab3550_mask_work failed 0x%x,0x%x\n", + (AB3550_IMR1 + i), mask[i]); + } +} + +static void ab3550_mask(unsigned int irq) +{ + unsigned long flags; + struct ab3550 *ab; + struct ab3550_platform_data *plf_data; + + ab = get_irq_chip_data(irq); + plf_data = ab->i2c_client[0]->dev.platform_data; + irq -= plf_data->irq.base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] |= BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void ab3550_unmask(unsigned int irq) +{ + unsigned long flags; + struct ab3550 *ab; + struct ab3550_platform_data *plf_data; + + ab = get_irq_chip_data(irq); + plf_data = ab->i2c_client[0]->dev.platform_data; + irq -= plf_data->irq.base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] &= ~BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void noop(unsigned int irq) +{ +} + +static struct irq_chip ab3550_irq_chip = { + .name = "ab3550-core", /* Keep the same name as the request */ + .startup = NULL, /* defaults to enable */ + .shutdown = NULL, /* defaults to disable */ + .enable = NULL, /* defaults to unmask */ + .disable = ab3550_mask, /* No default to mask in chip.c */ + .ack = noop, + .mask = ab3550_mask, + .unmask = ab3550_unmask, + .end = NULL, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] __initdata = { + /* AB3550 */ + { + .id = AB3550_P1A, + .name = "P1A" + }, + /* Terminator */ + { + .id = 0x00, + } +}; + +static int __init ab3550_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ab3550 *ab; + struct ab3550_platform_data *ab3550_plf_data = + client->dev.platform_data; + int err; + int i; + int num_i2c_clients = 0; + + ab = kzalloc(sizeof(struct ab3550), GFP_KERNEL); + if (!ab) { + dev_err(&client->dev, + "could not allocate " AB3550_NAME_STRING " device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab->access_mutex); + spin_lock_init(&ab->event_lock); + ab->i2c_client[0] = client; + + i2c_set_clientdata(client, ab); + + /* Read chip ID register */ + err = get_register_interruptible(ab, 0, AB3550_CID_REG, &ab->chip_id); + if (err) { + dev_err(&client->dev, "could not communicate with the analog " + "baseband chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab->chip_id) { + snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1, + AB3550_ID_FORMAT_STRING, ids[i].name); + break; + } + } + + if (ids[i].id == 0x0) { + dev_err(&client->dev, "unknown analog baseband chip id: 0x%x\n", + ab->chip_id); + dev_err(&client->dev, "driver not started!\n"); + goto exit_no_detect; + } + + dev_info(&client->dev, "detected AB chip: %s\n", &ab->chip_name[0]); + + /* Attach other dummy I2C clients. */ + while (++num_i2c_clients < AB3550_NUM_BANKS) { + ab->i2c_client[num_i2c_clients] = + i2c_new_dummy(client->adapter, + (client->addr + num_i2c_clients)); + if (!ab->i2c_client[num_i2c_clients]) { + err = -ENOMEM; + goto exit_no_dummy_client; + } + strlcpy(ab->i2c_client[num_i2c_clients]->name, id->name, + sizeof(ab->i2c_client[num_i2c_clients]->name)); + } + + err = ab3550_setup(ab); + if (err) + goto exit_no_setup; + + INIT_WORK(&ab->mask_work, ab3550_mask_work); + + for (i = 0; i < ab3550_plf_data->irq.count; i++) { + unsigned int irq; + + irq = ab3550_plf_data->irq.base + i; + set_irq_chip_data(irq, ab); + set_irq_chip_and_handler(irq, &ab3550_irq_chip, + handle_simple_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + err = request_threaded_irq(client->irq, NULL, ab3550_irq_handler, + IRQF_ONESHOT, "ab3550-core", ab); + /* This real unpredictable IRQ is of course sampled for entropy */ + rand_initialize_irq(client->irq); + + if (err) + goto exit_no_irq; + + err = abx500_register_ops(&client->dev, &ab3550_ops); + if (err) + goto exit_no_ops; + + /* Set up and register the platform devices. */ + for (i = 0; i < AB3550_NUM_DEVICES; i++) { + ab3550_devs[i].platform_data = ab3550_plf_data->dev_data[i]; + ab3550_devs[i].data_size = ab3550_plf_data->dev_data_sz[i]; + } + + err = mfd_add_devices(&client->dev, 0, ab3550_devs, + ARRAY_SIZE(ab3550_devs), NULL, + ab3550_plf_data->irq.base); + + ab3550_setup_debugfs(ab); + + return 0; + +exit_no_ops: +exit_no_irq: +exit_no_setup: +exit_no_dummy_client: + /* Unregister the dummy i2c clients. */ + while (--num_i2c_clients) + i2c_unregister_device(ab->i2c_client[num_i2c_clients]); +exit_no_detect: + kfree(ab); + return err; +} + +static int __exit ab3550_remove(struct i2c_client *client) +{ + struct ab3550 *ab = i2c_get_clientdata(client); + int num_i2c_clients = AB3550_NUM_BANKS; + + mfd_remove_devices(&client->dev); + ab3550_remove_debugfs(); + + while (--num_i2c_clients) + i2c_unregister_device(ab->i2c_client[num_i2c_clients]); + + /* + * At this point, all subscribers should have unregistered + * their notifiers so deactivate IRQ + */ + free_irq(client->irq, ab); + i2c_set_clientdata(client, NULL); + kfree(ab); + return 0; +} + +static const struct i2c_device_id ab3550_id[] = { + {AB3550_NAME_STRING, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ab3550_id); + +static struct i2c_driver ab3550_driver = { + .driver = { + .name = AB3550_NAME_STRING, + .owner = THIS_MODULE, + }, + .id_table = ab3550_id, + .probe = ab3550_probe, + .remove = __exit_p(ab3550_remove), +}; + +static int __init ab3550_i2c_init(void) +{ + return i2c_add_driver(&ab3550_driver); +} + +static void __exit ab3550_i2c_exit(void) +{ + i2c_del_driver(&ab3550_driver); +} + +subsys_initcall(ab3550_i2c_init); +module_exit(ab3550_i2c_exit); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("AB3550 core driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 38270644cc2f2c1fdf150dcc8c4b96136eb4e1f7 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 11 May 2010 01:07:34 +0200 Subject: mfd: Use menuconfig for quicker config editing Change MFD 'menu' to 'menuconfig' to facilitate easy (one-click) disabling of all MFD drivers. Signed-off-by: Randy Dunlap Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a20fa230d354..b84b7078e909 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2,8 +2,14 @@ # Multifunction miscellaneous devices # -menu "Multifunction device drivers" +menuconfig MFD_SUPPORT + bool "Multifunction device drivers" depends on HAS_IOMEM + default y + help + Configure MFD device drivers. + +if MFD_SUPPORT config MFD_CORE tristate @@ -475,7 +481,7 @@ config MFD_JANZ_CMODIO host many different types of MODULbus daughterboards, including CAN and GPIO controllers. -endmenu +endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100 -- cgit v1.2.3 From 0aeee5d4f6aa9bd28373907727937b7935d0434c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 12 May 2010 02:10:53 +0200 Subject: mfd: Fix pcf50633 bitfield logic in interrupt handler Those constants are alreay bitfields. Signed-off-by: Lars-Peter Clausen Signed-off-by: Samuel Ortiz --- drivers/mfd/pcf50633-core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 5439282804cc..37618b2be9a2 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -354,18 +354,18 @@ static void pcf50633_irq_worker(struct work_struct *work) if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM); + pcf_int[0] &= ~PCF50633_INT1_USBREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS); + pcf_int[0] &= ~PCF50633_INT1_USBINS; } /* Make sure only one of ADPINS or ADPREM is set */ if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM); + pcf_int[0] &= ~PCF50633_INT1_ADPREM; else - pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS); + pcf_int[0] &= ~PCF50633_INT1_ADPINS; } dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " -- cgit v1.2.3 From 6438a694b670cd188c2fd2f75e0cd6b0ae21bea9 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 12 May 2010 02:10:54 +0200 Subject: mfd: pcf50633-adc: Fix potential race in pcf50633_adc_sync_read Currently it's not guaranteed that request struct is not already freed when reading from it. Fix this by moving synced request related fields from the pcf50633_adc_request struct to its own struct and store it on the functions stack. Signed-off-by: Lars-Peter Clausen Signed-off-by: Samuel Ortiz --- drivers/mfd/pcf50633-adc.c | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c index fe8f922f6654..aed0d2a9b032 100644 --- a/drivers/mfd/pcf50633-adc.c +++ b/drivers/mfd/pcf50633-adc.c @@ -30,13 +30,13 @@ struct pcf50633_adc_request { int mux; int avg; - int result; void (*callback)(struct pcf50633 *, void *, int); void *callback_param; +}; - /* Used in case of sync requests */ +struct pcf50633_adc_sync_request { + int result; struct completion completion; - }; #define PCF50633_MAX_ADC_FIFO_DEPTH 8 @@ -109,10 +109,10 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) return 0; } -static void -pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) +static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, + int result) { - struct pcf50633_adc_request *req = param; + struct pcf50633_adc_sync_request *req = param; req->result = result; complete(&req->completion); @@ -120,28 +120,19 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) { - struct pcf50633_adc_request *req; - int err; + struct pcf50633_adc_sync_request req; + int ret; - /* req is freed when the result is ready, in interrupt handler */ - req = kzalloc(sizeof(*req), GFP_KERNEL); - if (!req) - return -ENOMEM; - - req->mux = mux; - req->avg = avg; - req->callback = pcf50633_adc_sync_read_callback; - req->callback_param = req; + init_completion(&req.completion); - init_completion(&req->completion); - err = adc_enqueue_request(pcf, req); - if (err) - return err; + ret = pcf50633_adc_async_read(pcf, mux, avg, + pcf50633_adc_sync_read_callback, &req); + if (ret) + return ret; - wait_for_completion(&req->completion); + wait_for_completion(&req.completion); - /* FIXME by this time req might be already freed */ - return req->result; + return req.result; } EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); -- cgit v1.2.3 From f7b2a77fe6f7b13b9cbf1909f032adef0be63ce1 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 12 May 2010 02:10:55 +0200 Subject: mfd: Use threaded irq for pcf50633 Use threaded oneshot irq handler instead of normal irq handler and a workqueue. Signed-off-by: Lars-Peter Clausen Signed-off-by: Samuel Ortiz --- drivers/mfd/Makefile | 2 +- drivers/mfd/pcf50633-core.c | 57 +++++++-------------------------------------- 2 files changed, 10 insertions(+), 49 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4c684c137d43..85934966a593 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -57,7 +57,7 @@ obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o -obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o +obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_ABX500_CORE) += abx500-core.o diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 37618b2be9a2..b3bef4998744 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -325,14 +325,12 @@ static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) /* Maximum amount of time ONKEY is held before emergency action is taken */ #define PCF50633_ONKEY1S_TIMEOUT 8 -static void pcf50633_irq_worker(struct work_struct *work) +static irqreturn_t pcf50633_irq(int irq, void *data) { - struct pcf50633 *pcf; + struct pcf50633 *pcf = data; int ret, i, j; u8 pcf_int[5], chgstat; - pcf = container_of(work, struct pcf50633, irq_work); - /* Read the 5 INT regs in one transaction */ ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, ARRAY_SIZE(pcf_int), pcf_int); @@ -436,21 +434,7 @@ static void pcf50633_irq_worker(struct work_struct *work) } out: - put_device(pcf->dev); - enable_irq(pcf->irq); -} - -static irqreturn_t pcf50633_irq(int irq, void *data) -{ - struct pcf50633 *pcf = data; - - dev_dbg(pcf->dev, "pcf50633_irq\n"); - - get_device(pcf->dev); - disable_irq_nosync(pcf->irq); - queue_work(pcf->work_queue, &pcf->irq_work); - - return IRQ_HANDLED; + return IRQ_HANDLED } static void @@ -488,9 +472,6 @@ static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) * henceforth */ disable_irq(pcf->irq); - /* Make sure that any running IRQ worker has quit */ - cancel_work_sync(&pcf->irq_work); - /* Save the masks */ ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, ARRAY_SIZE(pcf->suspend_irq_masks), @@ -531,16 +512,7 @@ static int pcf50633_resume(struct i2c_client *client) if (ret < 0) dev_err(pcf->dev, "Error restoring saved suspend masks\n"); - /* Restore regulators' state */ - - - get_device(pcf->dev); - - /* - * Clear any pending interrupts and set resume reason if any. - * This will leave with enable_irq() - */ - pcf50633_irq_worker(&pcf->irq_work); + enable_irq(pcf->irq); return 0; } @@ -574,22 +546,13 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf->dev = &client->dev; pcf->i2c_client = client; pcf->irq = client->irq; - pcf->work_queue = create_singlethread_workqueue("pcf50633"); - - if (!pcf->work_queue) { - dev_err(&client->dev, "Failed to alloc workqueue\n"); - ret = -ENOMEM; - goto err_free; - } - - INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); version = pcf50633_reg_read(pcf, 0); variant = pcf50633_reg_read(pcf, 1); if (version < 0 || variant < 0) { dev_err(pcf->dev, "Unable to probe pcf50633\n"); ret = -ENODEV; - goto err_destroy_workqueue; + goto err_free; } dev_info(pcf->dev, "Probed device version %d variant %d\n", @@ -603,12 +566,13 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); - ret = request_irq(client->irq, pcf50633_irq, - IRQF_TRIGGER_LOW, "pcf50633", pcf); + ret = request_threaded_irq(client->irq, NULL, pcf50633_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pcf50633", pcf); if (ret) { dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err_destroy_workqueue; + goto err_free; } /* Create sub devices */ @@ -654,8 +618,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, return 0; -err_destroy_workqueue: - destroy_workqueue(pcf->work_queue); err_free: i2c_set_clientdata(client, NULL); kfree(pcf); @@ -669,7 +631,6 @@ static int __devexit pcf50633_remove(struct i2c_client *client) int i; free_irq(pcf->irq, pcf); - destroy_workqueue(pcf->work_queue); platform_device_unregister(pcf->input_pdev); platform_device_unregister(pcf->rtc_pdev); -- cgit v1.2.3 From 380c09f6489f1fd773a697e9e2a156c083a34fc5 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 12 May 2010 02:10:56 +0200 Subject: mfd: Move pcf50633 irq related functions to its own file. This reduces code clutter a bit and will ease an migration to genirq. Signed-off-by: Lars-Peter Clausen Signed-off-by: Samuel Ortiz --- drivers/mfd/Makefile | 3 +- drivers/mfd/pcf50633-core.c | 303 ++--------------------------------------- drivers/mfd/pcf50633-irq.c | 318 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+), 294 deletions(-) create mode 100644 drivers/mfd/pcf50633-irq.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 85934966a593..ca1517e948af 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -57,7 +57,8 @@ obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o -obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o pcf50633-irq.o +pcf50633-objs := pcf50633-core.o pcf50633-irq.o +obj-$(CONFIG_MFD_PCF50633) += pcf50633.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_ABX500_CORE) += abx500-core.o diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index b3bef4998744..704736e6e9b9 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -21,16 +21,16 @@ #include #include #include -#include #include #include -/* Two MBCS registers used during cold start */ -#define PCF50633_REG_MBCS1 0x4b -#define PCF50633_REG_MBCS2 0x4c -#define PCF50633_MBCS1_USBPRES 0x01 -#define PCF50633_MBCS1_ADAPTPRES 0x01 +int pcf50633_irq_init(struct pcf50633 *pcf, int irq); +void pcf50633_irq_free(struct pcf50633 *pcf); +#ifdef CONFIG_PM +int pcf50633_irq_suspend(struct pcf50633 *pcf); +int pcf50633_irq_resume(struct pcf50633 *pcf); +#endif static int __pcf50633_read(struct pcf50633 *pcf, u8 reg, int num, u8 *data) { @@ -215,228 +215,6 @@ static struct attribute_group pcf_attr_group = { .attrs = pcf_sysfs_entries, }; -int pcf50633_register_irq(struct pcf50633 *pcf, int irq, - void (*handler) (int, void *), void *data) -{ - if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler) - return -EINVAL; - - if (WARN_ON(pcf->irq_handler[irq].handler)) - return -EBUSY; - - mutex_lock(&pcf->lock); - pcf->irq_handler[irq].handler = handler; - pcf->irq_handler[irq].data = data; - mutex_unlock(&pcf->lock); - - return 0; -} -EXPORT_SYMBOL_GPL(pcf50633_register_irq); - -int pcf50633_free_irq(struct pcf50633 *pcf, int irq) -{ - if (irq < 0 || irq >= PCF50633_NUM_IRQ) - return -EINVAL; - - mutex_lock(&pcf->lock); - pcf->irq_handler[irq].handler = NULL; - mutex_unlock(&pcf->lock); - - return 0; -} -EXPORT_SYMBOL_GPL(pcf50633_free_irq); - -static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) -{ - u8 reg, bits, tmp; - int ret = 0, idx; - - idx = irq >> 3; - reg = PCF50633_REG_INT1M + idx; - bits = 1 << (irq & 0x07); - - mutex_lock(&pcf->lock); - - if (mask) { - ret = __pcf50633_read(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - tmp |= bits; - - ret = __pcf50633_write(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - pcf->mask_regs[idx] &= ~bits; - pcf->mask_regs[idx] |= bits; - } else { - ret = __pcf50633_read(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - tmp &= ~bits; - - ret = __pcf50633_write(pcf, reg, 1, &tmp); - if (ret < 0) - goto out; - - pcf->mask_regs[idx] &= ~bits; - } -out: - mutex_unlock(&pcf->lock); - - return ret; -} - -int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) -{ - dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); - - return __pcf50633_irq_mask_set(pcf, irq, 1); -} -EXPORT_SYMBOL_GPL(pcf50633_irq_mask); - -int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) -{ - dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); - - return __pcf50633_irq_mask_set(pcf, irq, 0); -} -EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); - -int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) -{ - u8 reg, bits; - - reg = irq >> 3; - bits = 1 << (irq & 0x07); - - return pcf->mask_regs[reg] & bits; -} -EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); - -static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) -{ - if (pcf->irq_handler[irq].handler) - pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); -} - -/* Maximum amount of time ONKEY is held before emergency action is taken */ -#define PCF50633_ONKEY1S_TIMEOUT 8 - -static irqreturn_t pcf50633_irq(int irq, void *data) -{ - struct pcf50633 *pcf = data; - int ret, i, j; - u8 pcf_int[5], chgstat; - - /* Read the 5 INT regs in one transaction */ - ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, - ARRAY_SIZE(pcf_int), pcf_int); - if (ret != ARRAY_SIZE(pcf_int)) { - dev_err(pcf->dev, "Error reading INT registers\n"); - - /* - * If this doesn't ACK the interrupt to the chip, we'll be - * called once again as we're level triggered. - */ - goto out; - } - - /* defeat 8s death from lowsys on A5 */ - pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); - - /* We immediately read the usb and adapter status. We thus make sure - * only of USBINS/USBREM IRQ handlers are called */ - if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { - chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); - if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~PCF50633_INT1_USBREM; - else - pcf_int[0] &= ~PCF50633_INT1_USBINS; - } - - /* Make sure only one of ADPINS or ADPREM is set */ - if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { - chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); - if (chgstat & (0x3 << 4)) - pcf_int[0] &= ~PCF50633_INT1_ADPREM; - else - pcf_int[0] &= ~PCF50633_INT1_ADPINS; - } - - dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " - "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], - pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); - - /* Some revisions of the chip don't have a 8s standby mode on - * ONKEY1S press. We try to manually do it in such cases. */ - if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { - dev_info(pcf->dev, "ONKEY1S held for %d secs\n", - pcf->onkey1s_held); - if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) - if (pcf->pdata->force_shutdown) - pcf->pdata->force_shutdown(pcf); - } - - if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { - dev_info(pcf->dev, "ONKEY1S held\n"); - pcf->onkey1s_held = 1 ; - - /* Unmask IRQ_SECOND */ - pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, - PCF50633_INT1_SECOND); - - /* Unmask IRQ_ONKEYR */ - pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, - PCF50633_INT2_ONKEYR); - } - - if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { - pcf->onkey1s_held = 0; - - /* Mask SECOND and ONKEYR interrupts */ - if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) - pcf50633_reg_set_bit_mask(pcf, - PCF50633_REG_INT1M, - PCF50633_INT1_SECOND, - PCF50633_INT1_SECOND); - - if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) - pcf50633_reg_set_bit_mask(pcf, - PCF50633_REG_INT2M, - PCF50633_INT2_ONKEYR, - PCF50633_INT2_ONKEYR); - } - - /* Have we just resumed ? */ - if (pcf->is_suspended) { - pcf->is_suspended = 0; - - /* Set the resume reason filtering out non resumers */ - for (i = 0; i < ARRAY_SIZE(pcf_int); i++) - pcf->resume_reason[i] = pcf_int[i] & - pcf->pdata->resumers[i]; - - /* Make sure we don't pass on any ONKEY events to - * userspace now */ - pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); - } - - for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { - /* Unset masked interrupts */ - pcf_int[i] &= ~pcf->mask_regs[i]; - - for (j = 0; j < 8 ; j++) - if (pcf_int[i] & (1 << j)) - pcf50633_irq_call_handler(pcf, (i * 8) + j); - } - -out: - return IRQ_HANDLED -} - static void pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, struct platform_device **pdev) @@ -463,58 +241,17 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, static int pcf50633_suspend(struct i2c_client *client, pm_message_t state) { struct pcf50633 *pcf; - int ret = 0, i; - u8 res[5]; - pcf = i2c_get_clientdata(client); - /* Make sure our interrupt handlers are not called - * henceforth */ - disable_irq(pcf->irq); - - /* Save the masks */ - ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(pcf->suspend_irq_masks), - pcf->suspend_irq_masks); - if (ret < 0) { - dev_err(pcf->dev, "error saving irq masks\n"); - goto out; - } - - /* Write wakeup irq masks */ - for (i = 0; i < ARRAY_SIZE(res); i++) - res[i] = ~pcf->pdata->resumers[i]; - - ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(res), &res[0]); - if (ret < 0) { - dev_err(pcf->dev, "error writing wakeup irq masks\n"); - goto out; - } - - pcf->is_suspended = 1; - -out: - return ret; + return pcf50633_irq_suspend(pcf); } static int pcf50633_resume(struct i2c_client *client) { struct pcf50633 *pcf; - int ret; - pcf = i2c_get_clientdata(client); - /* Write the saved mask registers */ - ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, - ARRAY_SIZE(pcf->suspend_irq_masks), - pcf->suspend_irq_masks); - if (ret < 0) - dev_err(pcf->dev, "Error restoring saved suspend masks\n"); - - enable_irq(pcf->irq); - - return 0; + return pcf50633_irq_resume(pcf); } #else #define pcf50633_suspend NULL @@ -545,7 +282,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, i2c_set_clientdata(client, pcf); pcf->dev = &client->dev; pcf->i2c_client = client; - pcf->irq = client->irq; version = pcf50633_reg_read(pcf, 0); variant = pcf50633_reg_read(pcf, 1); @@ -558,22 +294,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, dev_info(pcf->dev, "Probed device version %d variant %d\n", version, variant); - /* Enable all interrupts except RTC SECOND */ - pcf->mask_regs[0] = 0x80; - pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); - pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); - pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); - - ret = request_threaded_irq(client->irq, NULL, pcf50633_irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "pcf50633", pcf); - - if (ret) { - dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); - goto err_free; - } + pcf50633_irq_init(pcf, client->irq); /* Create sub devices */ pcf50633_client_dev_register(pcf, "pcf50633-input", @@ -605,10 +326,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client, platform_device_add(pdev); } - if (enable_irq_wake(client->irq) < 0) - dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" - "in this hardware revision", client->irq); - ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); if (ret) dev_err(pcf->dev, "error creating sysfs entries\n"); @@ -630,7 +347,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) struct pcf50633 *pcf = i2c_get_clientdata(client); int i; - free_irq(pcf->irq, pcf); + pcf50633_irq_free(pcf); platform_device_unregister(pcf->input_pdev); platform_device_unregister(pcf->rtc_pdev); diff --git a/drivers/mfd/pcf50633-irq.c b/drivers/mfd/pcf50633-irq.c new file mode 100644 index 000000000000..1b0192f1efff --- /dev/null +++ b/drivers/mfd/pcf50633-irq.c @@ -0,0 +1,318 @@ +/* NXP PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Harald Welte + * Balaji Rao + * All rights reserved. + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include + +#include + +/* Two MBCS registers used during cold start */ +#define PCF50633_REG_MBCS1 0x4b +#define PCF50633_REG_MBCS2 0x4c +#define PCF50633_MBCS1_USBPRES 0x01 +#define PCF50633_MBCS1_ADAPTPRES 0x01 + +int pcf50633_register_irq(struct pcf50633 *pcf, int irq, + void (*handler) (int, void *), void *data) +{ + if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler) + return -EINVAL; + + if (WARN_ON(pcf->irq_handler[irq].handler)) + return -EBUSY; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = handler; + pcf->irq_handler[irq].data = data; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_register_irq); + +int pcf50633_free_irq(struct pcf50633 *pcf, int irq) +{ + if (irq < 0 || irq >= PCF50633_NUM_IRQ) + return -EINVAL; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = NULL; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_free_irq); + +static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) +{ + u8 reg, bit; + int ret = 0, idx; + + idx = irq >> 3; + reg = PCF50633_REG_INT1M + idx; + bit = 1 << (irq & 0x07); + + pcf50633_reg_set_bit_mask(pcf, reg, bit, mask ? bit : 0); + + mutex_lock(&pcf->lock); + + if (mask) + pcf->mask_regs[idx] |= bit; + else + pcf->mask_regs[idx] &= ~bit; + + mutex_unlock(&pcf->lock); + + return ret; +} + +int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 1); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask); + +int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 0); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); + +int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) +{ + u8 reg, bits; + + reg = irq >> 3; + bits = 1 << (irq & 0x07); + + return pcf->mask_regs[reg] & bits; +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); + +static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) +{ + if (pcf->irq_handler[irq].handler) + pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); +} + +/* Maximum amount of time ONKEY is held before emergency action is taken */ +#define PCF50633_ONKEY1S_TIMEOUT 8 + +static irqreturn_t pcf50633_irq(int irq, void *data) +{ + struct pcf50633 *pcf = data; + int ret, i, j; + u8 pcf_int[5], chgstat; + + /* Read the 5 INT regs in one transaction */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, + ARRAY_SIZE(pcf_int), pcf_int); + if (ret != ARRAY_SIZE(pcf_int)) { + dev_err(pcf->dev, "Error reading INT registers\n"); + + /* + * If this doesn't ACK the interrupt to the chip, we'll be + * called once again as we're level triggered. + */ + goto out; + } + + /* defeat 8s death from lowsys on A5 */ + pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); + + /* We immediately read the usb and adapter status. We thus make sure + * only of USBINS/USBREM IRQ handlers are called */ + if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~PCF50633_INT1_USBREM; + else + pcf_int[0] &= ~PCF50633_INT1_USBINS; + } + + /* Make sure only one of ADPINS or ADPREM is set */ + if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~PCF50633_INT1_ADPREM; + else + pcf_int[0] &= ~PCF50633_INT1_ADPINS; + } + + dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " + "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], + pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); + + /* Some revisions of the chip don't have a 8s standby mode on + * ONKEY1S press. We try to manually do it in such cases. */ + if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { + dev_info(pcf->dev, "ONKEY1S held for %d secs\n", + pcf->onkey1s_held); + if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) + if (pcf->pdata->force_shutdown) + pcf->pdata->force_shutdown(pcf); + } + + if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { + dev_info(pcf->dev, "ONKEY1S held\n"); + pcf->onkey1s_held = 1 ; + + /* Unmask IRQ_SECOND */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND); + + /* Unmask IRQ_ONKEYR */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR); + } + + if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { + pcf->onkey1s_held = 0; + + /* Mask SECOND and ONKEYR interrupts */ + if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, + PCF50633_INT1_SECOND); + + if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR, + PCF50633_INT2_ONKEYR); + } + + /* Have we just resumed ? */ + if (pcf->is_suspended) { + pcf->is_suspended = 0; + + /* Set the resume reason filtering out non resumers */ + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) + pcf->resume_reason[i] = pcf_int[i] & + pcf->pdata->resumers[i]; + + /* Make sure we don't pass on any ONKEY events to + * userspace now */ + pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); + } + + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { + /* Unset masked interrupts */ + pcf_int[i] &= ~pcf->mask_regs[i]; + + for (j = 0; j < 8 ; j++) + if (pcf_int[i] & (1 << j)) + pcf50633_irq_call_handler(pcf, (i * 8) + j); + } + +out: + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +int pcf50633_irq_suspend(struct pcf50633 *pcf) +{ + int ret; + int i; + u8 res[5]; + + + /* Make sure our interrupt handlers are not called + * henceforth */ + disable_irq(pcf->irq); + + /* Save the masks */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) { + dev_err(pcf->dev, "error saving irq masks\n"); + goto out; + } + + /* Write wakeup irq masks */ + for (i = 0; i < ARRAY_SIZE(res); i++) + res[i] = ~pcf->pdata->resumers[i]; + + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(res), &res[0]); + if (ret < 0) { + dev_err(pcf->dev, "error writing wakeup irq masks\n"); + goto out; + } + + pcf->is_suspended = 1; + +out: + return ret; +} + +int pcf50633_irq_resume(struct pcf50633 *pcf) +{ + int ret; + + /* Write the saved mask registers */ + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) + dev_err(pcf->dev, "Error restoring saved suspend masks\n"); + + enable_irq(pcf->irq); + + return ret; +} + +#endif + +int pcf50633_irq_init(struct pcf50633 *pcf, int irq) +{ + int ret; + + pcf->irq = irq; + + /* Enable all interrupts except RTC SECOND */ + pcf->mask_regs[0] = 0x80; + pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); + pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + + ret = request_threaded_irq(irq, NULL, pcf50633_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pcf50633", pcf); + + if (ret) + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + + if (enable_irq_wake(irq) < 0) + dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + "in this hardware revision", irq); + + return ret; +} + +void pcf50633_irq_free(struct pcf50633 *pcf) +{ + free_irq(pcf->irq, pcf); +} -- cgit v1.2.3 From 8deca39e5caf7a20fc3786548dc9bfb7f0ae14b6 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Sat, 15 May 2010 22:58:27 +0200 Subject: mfd: Change rdc321x resources flags to IORESOURCE_IO The rdc321x southbridge PCI device has no MEM PCI resources that we could pass to mfd_add_devices. Since 33254dd5, mfd_add_device checks for the mem_base argument that we set to NULL. Changing the resources passed to our MFD cells from IORESOURCE_MEM to IORESOURCE_IO fixes that. Since we use those resources as offsets to the PCI configuration space base address of the southbridge device this is also more adequate. Signed-off-by: Florian Fainelli Signed-off-by: Samuel Ortiz --- drivers/gpio/rdc321x-gpio.c | 4 ++-- drivers/mfd/rdc321x-southbridge.c | 6 +++--- drivers/watchdog/rdc321x_wdt.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/gpio/rdc321x-gpio.c b/drivers/gpio/rdc321x-gpio.c index e344907da5b0..22f31dc50104 100644 --- a/drivers/gpio/rdc321x-gpio.c +++ b/drivers/gpio/rdc321x-gpio.c @@ -147,7 +147,7 @@ static int __devinit rdc321x_gpio_probe(struct platform_device *pdev) return -ENOMEM; } - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpio-reg1"); + r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg1"); if (!r) { dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n"); err = -ENODEV; @@ -159,7 +159,7 @@ static int __devinit rdc321x_gpio_probe(struct platform_device *pdev) rdc321x_gpio_dev->reg1_ctrl_base = r->start; rdc321x_gpio_dev->reg1_data_base = r->start + 0x4; - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpio-reg2"); + r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg2"); if (!r) { dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n"); err = -ENODEV; diff --git a/drivers/mfd/rdc321x-southbridge.c b/drivers/mfd/rdc321x-southbridge.c index 256dd560bd48..50922975bda3 100644 --- a/drivers/mfd/rdc321x-southbridge.c +++ b/drivers/mfd/rdc321x-southbridge.c @@ -34,7 +34,7 @@ static struct resource rdc321x_wdt_resource[] = { .name = "wdt-reg", .start = RDC321X_WDT_CTRL, .end = RDC321X_WDT_CTRL + 0x3, - .flags = IORESOURCE_MEM, + .flags = IORESOURCE_IO, } }; @@ -47,12 +47,12 @@ static struct resource rdc321x_gpio_resources[] = { .name = "gpio-reg1", .start = RDC321X_GPIO_CTRL_REG1, .end = RDC321X_GPIO_CTRL_REG1 + 0x7, - .flags = IORESOURCE_MEM, + .flags = IORESOURCE_IO, }, { .name = "gpio-reg2", .start = RDC321X_GPIO_CTRL_REG2, .end = RDC321X_GPIO_CTRL_REG2 + 0x7, - .flags = IORESOURCE_MEM, + .flags = IORESOURCE_IO, } }; diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c index 4733a99955dd..428f8a1583e8 100644 --- a/drivers/watchdog/rdc321x_wdt.c +++ b/drivers/watchdog/rdc321x_wdt.c @@ -237,7 +237,7 @@ static int __devinit rdc321x_wdt_probe(struct platform_device *pdev) return -ENODEV; } - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-reg"); + r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg"); if (!r) { dev_err(&pdev->dev, "failed to get wdt-reg resource\n"); return -ENODEV; -- cgit v1.2.3 From 62579266cf9caca5b999560be2b5ceee42fc4d4d Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Wed, 19 May 2010 11:39:02 +0200 Subject: mfd: New AB8500 driver Add a new driver to support the AB8500 Power Management chip, replacing the current AB4500. The new driver replaces the old one, instead of an incremental modification, because this is a substantial overhaul including: - Split of the driver into -core and -spi portions, to allow another interface layer to be added - Addition of interrupt support - Switch to MFD core API for handling subdevices - Simplification of the APIs to remove a redundant block parameter - Rename of the APIs and macros from ab4500_* to ab8500_* - Rename of the files from ab4500* to ab8500* - Change of the driver name from ab4500 to ab8500 Acked-by: Linus Walleij Acked-by: Srinidhi Kasagar Signed-off-by: Rabin Vincent Signed-off-by: Samuel Ortiz --- arch/arm/mach-ux500/board-mop500.c | 2 +- drivers/mfd/Kconfig | 9 +- drivers/mfd/Makefile | 2 +- drivers/mfd/ab4500-core.c | 209 ----------------- drivers/mfd/ab8500-core.c | 444 +++++++++++++++++++++++++++++++++++++ drivers/mfd/ab8500-spi.c | 133 +++++++++++ include/linux/mfd/ab4500.h | 262 ---------------------- include/linux/mfd/ab8500.h | 128 +++++++++++ 8 files changed, 712 insertions(+), 477 deletions(-) delete mode 100644 drivers/mfd/ab4500-core.c create mode 100644 drivers/mfd/ab8500-core.c create mode 100644 drivers/mfd/ab8500-spi.c delete mode 100644 include/linux/mfd/ab4500.h create mode 100644 include/linux/mfd/ab8500.h (limited to 'drivers/mfd') diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 072196c57263..bb8d7b771817 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -50,7 +50,7 @@ struct pl022_config_chip ab4500_chip_info = { static struct spi_board_info u8500_spi_devices[] = { { - .modalias = "ab4500", + .modalias = "ab8500", .controller_data = &ab4500_chip_info, .max_speed_hz = 12000000, .bus_num = 0, diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index b84b7078e909..9da0e504bbe9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -420,11 +420,12 @@ config EZX_PCAP This enables the PCAP ASIC present on EZX Phones. This is needed for MMC, TouchScreen, Sound, USB, etc.. -config AB4500_CORE - tristate "ST-Ericsson's AB4500 Mixed Signal Power management chip" - depends on SPI +config AB8500_CORE + bool "ST-Ericsson AB8500 Mixed Signal Power Management chip" + depends on SPI=y && GENERIC_HARDIRQS + select MFD_CORE help - Select this option to enable access to AB4500 power management + Select this option to enable access to AB8500 power management chip. This connects to U8500 on the SSP/SPI bus and exports read/write functions for the devices to get access to this chip. This chip embeds various other multimedia funtionalities as well. diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index ca1517e948af..fb503e77dc60 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -64,8 +64,8 @@ obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_ABX500_CORE) += abx500-core.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o -obj-$(CONFIG_AB4500_CORE) += ab4500-core.o obj-$(CONFIG_AB3550_CORE) += ab3550-core.o +obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-spi.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab4500-core.c b/drivers/mfd/ab4500-core.c deleted file mode 100644 index c275daa3ab1a..000000000000 --- a/drivers/mfd/ab4500-core.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2009 ST-Ericsson - * - * Author: Srinidhi KASAGAR - * - * This program is free software; you can redistribute it - * and/or modify it under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation. - * - * AB4500 is a companion power management chip used with U8500. - * On this platform, this is interfaced with SSP0 controller - * which is a ARM primecell pl022. - * - * At the moment the module just exports read/write features. - * Interrupt management to be added - TODO. - */ -#include -#include -#include -#include -#include -#include -#include - -/* just required if probe fails, we need to - * unregister the device - */ -static struct spi_driver ab4500_driver; - -/* - * This funtion writes to any AB4500 registers using - * SPI protocol & before it writes it packs the data - * in the below 24 bit frame format - * - * *|------------------------------------| - * *| 23|22...18|17.......10|9|8|7......0| - * *| r/w bank adr data | - * * ------------------------------------ - * - * This function shouldn't be called from interrupt - * context - */ -int ab4500_write(struct ab4500 *ab4500, unsigned char block, - unsigned long addr, unsigned char data) -{ - struct spi_transfer xfer; - struct spi_message msg; - int err; - unsigned long spi_data = - block << 18 | addr << 10 | data; - - mutex_lock(&ab4500->lock); - ab4500->tx_buf[0] = spi_data; - ab4500->rx_buf[0] = 0; - - xfer.tx_buf = ab4500->tx_buf; - xfer.rx_buf = NULL; - xfer.len = sizeof(unsigned long); - - spi_message_init(&msg); - spi_message_add_tail(&xfer, &msg); - - err = spi_sync(ab4500->spi, &msg); - mutex_unlock(&ab4500->lock); - - return err; -} -EXPORT_SYMBOL(ab4500_write); - -int ab4500_read(struct ab4500 *ab4500, unsigned char block, - unsigned long addr) -{ - struct spi_transfer xfer; - struct spi_message msg; - unsigned long spi_data = - 1 << 23 | block << 18 | addr << 10; - - mutex_lock(&ab4500->lock); - ab4500->tx_buf[0] = spi_data; - ab4500->rx_buf[0] = 0; - - xfer.tx_buf = ab4500->tx_buf; - xfer.rx_buf = ab4500->rx_buf; - xfer.len = sizeof(unsigned long); - - spi_message_init(&msg); - spi_message_add_tail(&xfer, &msg); - - spi_sync(ab4500->spi, &msg); - mutex_unlock(&ab4500->lock); - - return ab4500->rx_buf[0]; -} -EXPORT_SYMBOL(ab4500_read); - -/* ref: ab3100 core */ -#define AB4500_DEVICE(devname, devid) \ -static struct platform_device ab4500_##devname##_device = { \ - .name = devid, \ - .id = -1, \ -} - -/* list of childern devices of ab4500 - all are - * not populated here - TODO - */ -AB4500_DEVICE(charger, "ab4500-charger"); -AB4500_DEVICE(audio, "ab4500-audio"); -AB4500_DEVICE(usb, "ab4500-usb"); -AB4500_DEVICE(tvout, "ab4500-tvout"); -AB4500_DEVICE(sim, "ab4500-sim"); -AB4500_DEVICE(gpadc, "ab4500-gpadc"); -AB4500_DEVICE(clkmgt, "ab4500-clkmgt"); -AB4500_DEVICE(misc, "ab4500-misc"); - -static struct platform_device *ab4500_platform_devs[] = { - &ab4500_charger_device, - &ab4500_audio_device, - &ab4500_usb_device, - &ab4500_tvout_device, - &ab4500_sim_device, - &ab4500_gpadc_device, - &ab4500_clkmgt_device, - &ab4500_misc_device, -}; - -static int __init ab4500_probe(struct spi_device *spi) -{ - struct ab4500 *ab4500; - unsigned char revision; - int err = 0; - int i; - - ab4500 = kzalloc(sizeof *ab4500, GFP_KERNEL); - if (!ab4500) { - dev_err(&spi->dev, "could not allocate AB4500\n"); - err = -ENOMEM; - goto not_detect; - } - - ab4500->spi = spi; - spi_set_drvdata(spi, ab4500); - - mutex_init(&ab4500->lock); - - /* read the revision register */ - revision = ab4500_read(ab4500, AB4500_MISC, AB4500_REV_REG); - - /* revision id 0x0 is for early drop, 0x10 is for cut1.0 */ - if (revision == 0x0 || revision == 0x10) - dev_info(&spi->dev, "Detected chip: %s, revision = %x\n", - ab4500_driver.driver.name, revision); - else { - dev_err(&spi->dev, "unknown chip: 0x%x\n", revision); - goto not_detect; - } - - for (i = 0; i < ARRAY_SIZE(ab4500_platform_devs); i++) { - ab4500_platform_devs[i]->dev.parent = - &spi->dev; - platform_set_drvdata(ab4500_platform_devs[i], ab4500); - } - - /* register the ab4500 platform devices */ - platform_add_devices(ab4500_platform_devs, - ARRAY_SIZE(ab4500_platform_devs)); - - return err; - - not_detect: - spi_unregister_driver(&ab4500_driver); - kfree(ab4500); - return err; -} - -static int __devexit ab4500_remove(struct spi_device *spi) -{ - struct ab4500 *ab4500 = - spi_get_drvdata(spi); - - kfree(ab4500); - - return 0; -} - -static struct spi_driver ab4500_driver = { - .driver = { - .name = "ab4500", - .owner = THIS_MODULE, - }, - .probe = ab4500_probe, - .remove = __devexit_p(ab4500_remove) -}; - -static int __devinit ab4500_init(void) -{ - return spi_register_driver(&ab4500_driver); -} - -static void __exit ab4500_exit(void) -{ - spi_unregister_driver(&ab4500_driver); -} - -subsys_initcall(ab4500_init); -module_exit(ab4500_exit); - -MODULE_AUTHOR("Srinidhi KASAGAR + * Author: Rabin Vincent + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE1_REG 0x0E00 +#define AB8500_IT_SOURCE2_REG 0x0E01 +#define AB8500_IT_SOURCE3_REG 0x0E02 +#define AB8500_IT_SOURCE4_REG 0x0E03 +#define AB8500_IT_SOURCE5_REG 0x0E04 +#define AB8500_IT_SOURCE6_REG 0x0E05 +#define AB8500_IT_SOURCE7_REG 0x0E06 +#define AB8500_IT_SOURCE8_REG 0x0E07 +#define AB8500_IT_SOURCE19_REG 0x0E12 +#define AB8500_IT_SOURCE20_REG 0x0E13 +#define AB8500_IT_SOURCE21_REG 0x0E14 +#define AB8500_IT_SOURCE22_REG 0x0E15 +#define AB8500_IT_SOURCE23_REG 0x0E16 +#define AB8500_IT_SOURCE24_REG 0x0E17 + +/* + * latch registers + */ +#define AB8500_IT_LATCH1_REG 0x0E20 +#define AB8500_IT_LATCH2_REG 0x0E21 +#define AB8500_IT_LATCH3_REG 0x0E22 +#define AB8500_IT_LATCH4_REG 0x0E23 +#define AB8500_IT_LATCH5_REG 0x0E24 +#define AB8500_IT_LATCH6_REG 0x0E25 +#define AB8500_IT_LATCH7_REG 0x0E26 +#define AB8500_IT_LATCH8_REG 0x0E27 +#define AB8500_IT_LATCH9_REG 0x0E28 +#define AB8500_IT_LATCH10_REG 0x0E29 +#define AB8500_IT_LATCH19_REG 0x0E32 +#define AB8500_IT_LATCH20_REG 0x0E33 +#define AB8500_IT_LATCH21_REG 0x0E34 +#define AB8500_IT_LATCH22_REG 0x0E35 +#define AB8500_IT_LATCH23_REG 0x0E36 +#define AB8500_IT_LATCH24_REG 0x0E37 + +/* + * mask registers + */ + +#define AB8500_IT_MASK1_REG 0x0E40 +#define AB8500_IT_MASK2_REG 0x0E41 +#define AB8500_IT_MASK3_REG 0x0E42 +#define AB8500_IT_MASK4_REG 0x0E43 +#define AB8500_IT_MASK5_REG 0x0E44 +#define AB8500_IT_MASK6_REG 0x0E45 +#define AB8500_IT_MASK7_REG 0x0E46 +#define AB8500_IT_MASK8_REG 0x0E47 +#define AB8500_IT_MASK9_REG 0x0E48 +#define AB8500_IT_MASK10_REG 0x0E49 +#define AB8500_IT_MASK11_REG 0x0E4A +#define AB8500_IT_MASK12_REG 0x0E4B +#define AB8500_IT_MASK13_REG 0x0E4C +#define AB8500_IT_MASK14_REG 0x0E4D +#define AB8500_IT_MASK15_REG 0x0E4E +#define AB8500_IT_MASK16_REG 0x0E4F +#define AB8500_IT_MASK17_REG 0x0E50 +#define AB8500_IT_MASK18_REG 0x0E51 +#define AB8500_IT_MASK19_REG 0x0E52 +#define AB8500_IT_MASK20_REG 0x0E53 +#define AB8500_IT_MASK21_REG 0x0E54 +#define AB8500_IT_MASK22_REG 0x0E55 +#define AB8500_IT_MASK23_REG 0x0E56 +#define AB8500_IT_MASK24_REG 0x0E57 + +#define AB8500_REV_REG 0x1080 + +/* + * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt + * numbers are indexed into this array with (num / 8). + * + * This is one off from the register names, i.e. AB8500_IT_MASK1_REG is at + * offset 0. + */ +static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, 6, 7, 8, 9, 18, 19, 20, 21, +}; + +static int __ab8500_write(struct ab8500 *ab8500, u16 addr, u8 data) +{ + int ret; + + dev_vdbg(ab8500->dev, "wr: addr %#x <= %#x\n", addr, data); + + ret = ab8500->write(ab8500, addr, data); + if (ret < 0) + dev_err(ab8500->dev, "failed to write reg %#x: %d\n", + addr, ret); + + return ret; +} + +/** + * ab8500_write() - write an AB8500 register + * @ab8500: device to write to + * @addr: address of the register + * @data: value to write + */ +int ab8500_write(struct ab8500 *ab8500, u16 addr, u8 data) +{ + int ret; + + mutex_lock(&ab8500->lock); + ret = __ab8500_write(ab8500, addr, data); + mutex_unlock(&ab8500->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(ab8500_write); + +static int __ab8500_read(struct ab8500 *ab8500, u16 addr) +{ + int ret; + + ret = ab8500->read(ab8500, addr); + if (ret < 0) + dev_err(ab8500->dev, "failed to read reg %#x: %d\n", + addr, ret); + + dev_vdbg(ab8500->dev, "rd: addr %#x => data %#x\n", addr, ret); + + return ret; +} + +/** + * ab8500_read() - read an AB8500 register + * @ab8500: device to read from + * @addr: address of the register + */ +int ab8500_read(struct ab8500 *ab8500, u16 addr) +{ + int ret; + + mutex_lock(&ab8500->lock); + ret = __ab8500_read(ab8500, addr); + mutex_unlock(&ab8500->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(ab8500_read); + +/** + * ab8500_set_bits() - set a bitfield in an AB8500 register + * @ab8500: device to read from + * @addr: address of the register + * @mask: mask of the bitfield to modify + * @data: value to set to the bitfield + */ +int ab8500_set_bits(struct ab8500 *ab8500, u16 addr, u8 mask, u8 data) +{ + int ret; + + mutex_lock(&ab8500->lock); + + ret = __ab8500_read(ab8500, addr); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= data; + + ret = __ab8500_write(ab8500, addr, ret); + +out: + mutex_unlock(&ab8500->lock); + return ret; +} +EXPORT_SYMBOL_GPL(ab8500_set_bits); + +static void ab8500_irq_lock(unsigned int irq) +{ + struct ab8500 *ab8500 = get_irq_chip_data(irq); + + mutex_lock(&ab8500->irq_lock); +} + +static void ab8500_irq_sync_unlock(unsigned int irq) +{ + struct ab8500 *ab8500 = get_irq_chip_data(irq); + int i; + + for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) { + u8 old = ab8500->oldmask[i]; + u8 new = ab8500->mask[i]; + int reg; + + if (new == old) + continue; + + ab8500->oldmask[i] = new; + + reg = AB8500_IT_MASK1_REG + ab8500_irq_regoffset[i]; + ab8500_write(ab8500, reg, new); + } + + mutex_unlock(&ab8500->irq_lock); +} + +static void ab8500_irq_mask(unsigned int irq) +{ + struct ab8500 *ab8500 = get_irq_chip_data(irq); + int offset = irq - ab8500->irq_base; + int index = offset / 8; + int mask = 1 << (offset % 8); + + ab8500->mask[index] |= mask; +} + +static void ab8500_irq_unmask(unsigned int irq) +{ + struct ab8500 *ab8500 = get_irq_chip_data(irq); + int offset = irq - ab8500->irq_base; + int index = offset / 8; + int mask = 1 << (offset % 8); + + ab8500->mask[index] &= ~mask; +} + +static struct irq_chip ab8500_irq_chip = { + .name = "ab8500", + .bus_lock = ab8500_irq_lock, + .bus_sync_unlock = ab8500_irq_sync_unlock, + .mask = ab8500_irq_mask, + .unmask = ab8500_irq_unmask, +}; + +static irqreturn_t ab8500_irq(int irq, void *dev) +{ + struct ab8500 *ab8500 = dev; + int i; + + dev_vdbg(ab8500->dev, "interrupt\n"); + + for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) { + int regoffset = ab8500_irq_regoffset[i]; + int status; + + status = ab8500_read(ab8500, AB8500_IT_LATCH1_REG + regoffset); + if (status <= 0) + continue; + + do { + int bit = __ffs(status); + int line = i * 8 + bit; + + handle_nested_irq(ab8500->irq_base + line); + status &= ~(1 << bit); + } while (status); + } + + return IRQ_HANDLED; +} + +static int ab8500_irq_init(struct ab8500 *ab8500) +{ + int base = ab8500->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NR_IRQS; irq++) { + set_irq_chip_data(irq, ab8500); + set_irq_chip_and_handler(irq, &ab8500_irq_chip, + handle_simple_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void ab8500_irq_remove(struct ab8500 *ab8500) +{ + int base = ab8500->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NR_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + +static struct resource ab8500_gpadc_resources[] = { + { + .name = "HW_CONV_END", + .start = AB8500_INT_GP_HW_ADC_CONV_END, + .end = AB8500_INT_GP_HW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "SW_CONV_END", + .start = AB8500_INT_GP_SW_ADC_CONV_END, + .end = AB8500_INT_GP_SW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_rtc_resources[] = { + { + .name = "60S", + .start = AB8500_INT_RTC_60S, + .end = AB8500_INT_RTC_60S, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALARM", + .start = AB8500_INT_RTC_ALARM, + .end = AB8500_INT_RTC_ALARM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell ab8500_devs[] = { + { + .name = "ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), + .resources = ab8500_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { .name = "ab8500-charger", }, + { .name = "ab8500-audio", }, + { .name = "ab8500-usb", }, + { .name = "ab8500-pwm", }, +}; + +int __devinit ab8500_init(struct ab8500 *ab8500) +{ + struct ab8500_platform_data *plat = dev_get_platdata(ab8500->dev); + int ret; + int i; + + if (plat) + ab8500->irq_base = plat->irq_base; + + mutex_init(&ab8500->lock); + mutex_init(&ab8500->irq_lock); + + ret = ab8500_read(ab8500, AB8500_REV_REG); + if (ret < 0) + return ret; + + /* + * 0x0 - Early Drop + * 0x10 - Cut 1.0 + * 0x11 - Cut 1.1 + */ + if (ret == 0x0 || ret == 0x10 || ret == 0x11) { + ab8500->revision = ret; + dev_info(ab8500->dev, "detected chip, revision: %#x\n", ret); + } else { + dev_err(ab8500->dev, "unknown chip, revision: %#x\n", ret); + return -EINVAL; + } + + if (plat && plat->init) + plat->init(ab8500); + + /* Clear and mask all interrupts */ + for (i = 0; i < 10; i++) { + ab8500_read(ab8500, AB8500_IT_LATCH1_REG + i); + ab8500_write(ab8500, AB8500_IT_MASK1_REG + i, 0xff); + } + + for (i = 18; i < 24; i++) { + ab8500_read(ab8500, AB8500_IT_LATCH1_REG + i); + ab8500_write(ab8500, AB8500_IT_MASK1_REG + i, 0xff); + } + + for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) + ab8500->mask[i] = ab8500->oldmask[i] = 0xff; + + if (ab8500->irq_base) { + ret = ab8500_irq_init(ab8500); + if (ret) + return ret; + + ret = request_threaded_irq(ab8500->irq, NULL, ab8500_irq, + IRQF_ONESHOT, "ab8500", ab8500); + if (ret) + goto out_removeirq; + } + + ret = mfd_add_devices(ab8500->dev, -1, ab8500_devs, + ARRAY_SIZE(ab8500_devs), NULL, + ab8500->irq_base); + if (ret) + goto out_freeirq; + + return ret; + +out_freeirq: + if (ab8500->irq_base) { + free_irq(ab8500->irq, ab8500); +out_removeirq: + ab8500_irq_remove(ab8500); + } + return ret; +} + +int __devexit ab8500_exit(struct ab8500 *ab8500) +{ + mfd_remove_devices(ab8500->dev); + if (ab8500->irq_base) { + free_irq(ab8500->irq, ab8500); + ab8500_irq_remove(ab8500); + } + + return 0; +} + +MODULE_AUTHOR("Srinidhi Kasagar, Rabin Vincent"); +MODULE_DESCRIPTION("AB8500 MFD core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/ab8500-spi.c b/drivers/mfd/ab8500-spi.c new file mode 100644 index 000000000000..b81d4f768ef6 --- /dev/null +++ b/drivers/mfd/ab8500-spi.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Srinidhi Kasagar + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * This funtion writes to any AB8500 registers using + * SPI protocol & before it writes it packs the data + * in the below 24 bit frame format + * + * *|------------------------------------| + * *| 23|22...18|17.......10|9|8|7......0| + * *| r/w bank adr data | + * * ------------------------------------ + * + * This function shouldn't be called from interrupt + * context + */ +static int ab8500_spi_write(struct ab8500 *ab8500, u16 addr, u8 data) +{ + struct spi_device *spi = container_of(ab8500->dev, struct spi_device, + dev); + unsigned long spi_data = addr << 10 | data; + struct spi_transfer xfer; + struct spi_message msg; + + ab8500->tx_buf[0] = spi_data; + ab8500->rx_buf[0] = 0; + + xfer.tx_buf = ab8500->tx_buf; + xfer.rx_buf = NULL; + xfer.len = sizeof(unsigned long); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(spi, &msg); +} + +static int ab8500_spi_read(struct ab8500 *ab8500, u16 addr) +{ + struct spi_device *spi = container_of(ab8500->dev, struct spi_device, + dev); + unsigned long spi_data = 1 << 23 | addr << 10; + struct spi_transfer xfer; + struct spi_message msg; + int ret; + + ab8500->tx_buf[0] = spi_data; + ab8500->rx_buf[0] = 0; + + xfer.tx_buf = ab8500->tx_buf; + xfer.rx_buf = ab8500->rx_buf; + xfer.len = sizeof(unsigned long); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(spi, &msg); + if (!ret) + ret = ab8500->rx_buf[0]; + + return ret; +} + +static int __devinit ab8500_spi_probe(struct spi_device *spi) +{ + struct ab8500 *ab8500; + int ret; + + ab8500 = kzalloc(sizeof *ab8500, GFP_KERNEL); + if (!ab8500) + return -ENOMEM; + + ab8500->dev = &spi->dev; + ab8500->irq = spi->irq; + + ab8500->read = ab8500_spi_read; + ab8500->write = ab8500_spi_write; + + spi_set_drvdata(spi, ab8500); + + ret = ab8500_init(ab8500); + if (ret) + kfree(ab8500); + + return ret; +} + +static int __devexit ab8500_spi_remove(struct spi_device *spi) +{ + struct ab8500 *ab8500 = spi_get_drvdata(spi); + + ab8500_exit(ab8500); + kfree(ab8500); + + return 0; +} + +static struct spi_driver ab8500_spi_driver = { + .driver = { + .name = "ab8500", + .owner = THIS_MODULE, + }, + .probe = ab8500_spi_probe, + .remove = __devexit_p(ab8500_spi_remove) +}; + +static int __init ab8500_spi_init(void) +{ + return spi_register_driver(&ab8500_spi_driver); +} +subsys_initcall(ab8500_spi_init); + +static void __exit ab8500_spi_exit(void) +{ + spi_unregister_driver(&ab8500_spi_driver); +} +module_exit(ab8500_spi_exit); + +MODULE_AUTHOR("Srinidhi KASAGAR - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, as - * published by the Free Software Foundation. - * - * AB4500 device core funtions, for client access - */ -#ifndef MFD_AB4500_H -#define MFD_AB4500_H - -#include - -/* - * AB4500 bank addresses - */ -#define AB4500_SYS_CTRL1_BLOCK 0x1 -#define AB4500_SYS_CTRL2_BLOCK 0x2 -#define AB4500_REGU_CTRL1 0x3 -#define AB4500_REGU_CTRL2 0x4 -#define AB4500_USB 0x5 -#define AB4500_TVOUT 0x6 -#define AB4500_DBI 0x7 -#define AB4500_ECI_AV_ACC 0x8 -#define AB4500_RESERVED 0x9 -#define AB4500_GPADC 0xA -#define AB4500_CHARGER 0xB -#define AB4500_GAS_GAUGE 0xC -#define AB4500_AUDIO 0xD -#define AB4500_INTERRUPT 0xE -#define AB4500_RTC 0xF -#define AB4500_MISC 0x10 -#define AB4500_DEBUG 0x12 -#define AB4500_PROD_TEST 0x13 -#define AB4500_OTP_EMUL 0x15 - -/* - * System control 1 register offsets. - * Bank = 0x01 - */ -#define AB4500_TURNON_STAT_REG 0x0100 -#define AB4500_RESET_STAT_REG 0x0101 -#define AB4500_PONKEY1_PRESS_STAT_REG 0x0102 - -#define AB4500_FSM_STAT1_REG 0x0140 -#define AB4500_FSM_STAT2_REG 0x0141 -#define AB4500_SYSCLK_REQ_STAT_REG 0x0142 -#define AB4500_USB_STAT1_REG 0x0143 -#define AB4500_USB_STAT2_REG 0x0144 -#define AB4500_STATUS_SPARE1_REG 0x0145 -#define AB4500_STATUS_SPARE2_REG 0x0146 - -#define AB4500_CTRL1_REG 0x0180 -#define AB4500_CTRL2_REG 0x0181 - -/* - * System control 2 register offsets. - * bank = 0x02 - */ -#define AB4500_CTRL3_REG 0x0200 -#define AB4500_MAIN_WDOG_CTRL_REG 0x0201 -#define AB4500_MAIN_WDOG_TIMER_REG 0x0202 -#define AB4500_LOW_BAT_REG 0x0203 -#define AB4500_BATT_OK_REG 0x0204 -#define AB4500_SYSCLK_TIMER_REG 0x0205 -#define AB4500_SMPSCLK_CTRL_REG 0x0206 -#define AB4500_SMPSCLK_SEL1_REG 0x0207 -#define AB4500_SMPSCLK_SEL2_REG 0x0208 -#define AB4500_SMPSCLK_SEL3_REG 0x0209 -#define AB4500_SYSULPCLK_CONF_REG 0x020A -#define AB4500_SYSULPCLK_CTRL1_REG 0x020B -#define AB4500_SYSCLK_CTRL_REG 0x020C -#define AB4500_SYSCLK_REQ1_VALID_REG 0x020D -#define AB4500_SYSCLK_REQ_VALID_REG 0x020E -#define AB4500_SYSCTRL_SPARE_REG 0x020F -#define AB4500_PAD_CONF_REG 0x0210 - -/* - * Regu control1 register offsets - * Bank = 0x03 - */ -#define AB4500_REGU_SERIAL_CTRL1_REG 0x0300 -#define AB4500_REGU_SERIAL_CTRL2_REG 0x0301 -#define AB4500_REGU_SERIAL_CTRL3_REG 0x0302 -#define AB4500_REGU_REQ_CTRL1_REG 0x0303 -#define AB4500_REGU_REQ_CTRL2_REG 0x0304 -#define AB4500_REGU_REQ_CTRL3_REG 0x0305 -#define AB4500_REGU_REQ_CTRL4_REG 0x0306 -#define AB4500_REGU_MISC1_REG 0x0380 -#define AB4500_REGU_OTGSUPPLY_CTRL_REG 0x0381 -#define AB4500_REGU_VUSB_CTRL_REG 0x0382 -#define AB4500_REGU_VAUDIO_SUPPLY_REG 0x0383 -#define AB4500_REGU_CTRL1_SPARE_REG 0x0384 - -/* - * Regu control2 Vmod register offsets - */ -#define AB4500_REGU_VMOD_REGU_REG 0x0440 -#define AB4500_REGU_VMOD_SEL1_REG 0x0441 -#define AB4500_REGU_VMOD_SEL2_REG 0x0442 -#define AB4500_REGU_CTRL_DISCH_REG 0x0443 -#define AB4500_REGU_CTRL_DISCH2_REG 0x0444 - -/* - * USB/ULPI register offsets - * Bank : 0x5 - */ -#define AB4500_USB_LINE_STAT_REG 0x0580 -#define AB4500_USB_LINE_CTRL1_REG 0x0581 -#define AB4500_USB_LINE_CTRL2_REG 0x0582 -#define AB4500_USB_LINE_CTRL3_REG 0x0583 -#define AB4500_USB_LINE_CTRL4_REG 0x0584 -#define AB4500_USB_LINE_CTRL5_REG 0x0585 -#define AB4500_USB_OTG_CTRL_REG 0x0587 -#define AB4500_USB_OTG_STAT_REG 0x0588 -#define AB4500_USB_OTG_STAT_REG 0x0588 -#define AB4500_USB_CTRL_SPARE_REG 0x0589 -#define AB4500_USB_PHY_CTRL_REG 0x058A - -/* - * TVOUT / CTRL register offsets - * Bank : 0x06 - */ -#define AB4500_TVOUT_CTRL_REG 0x0680 - -/* - * DBI register offsets - * Bank : 0x07 - */ -#define AB4500_DBI_REG1_REG 0x0700 -#define AB4500_DBI_REG2_REG 0x0701 - -/* - * ECI regsiter offsets - * Bank : 0x08 - */ -#define AB4500_ECI_CTRL_REG 0x0800 -#define AB4500_ECI_HOOKLEVEL_REG 0x0801 -#define AB4500_ECI_DATAOUT_REG 0x0802 -#define AB4500_ECI_DATAIN_REG 0x0803 - -/* - * AV Connector register offsets - * Bank : 0x08 - */ -#define AB4500_AV_CONN_REG 0x0840 - -/* - * Accessory detection register offsets - * Bank : 0x08 - */ -#define AB4500_ACC_DET_DB1_REG 0x0880 -#define AB4500_ACC_DET_DB2_REG 0x0881 - -/* - * GPADC register offsets - * Bank : 0x0A - */ -#define AB4500_GPADC_CTRL1_REG 0x0A00 -#define AB4500_GPADC_CTRL2_REG 0x0A01 -#define AB4500_GPADC_CTRL3_REG 0x0A02 -#define AB4500_GPADC_AUTO_TIMER_REG 0x0A03 -#define AB4500_GPADC_STAT_REG 0x0A04 -#define AB4500_GPADC_MANDATAL_REG 0x0A05 -#define AB4500_GPADC_MANDATAH_REG 0x0A06 -#define AB4500_GPADC_AUTODATAL_REG 0x0A07 -#define AB4500_GPADC_AUTODATAH_REG 0x0A08 -#define AB4500_GPADC_MUX_CTRL_REG 0x0A09 - -/* - * Charger / status register offfsets - * Bank : 0x0B - */ -#define AB4500_CH_STATUS1_REG 0x0B00 -#define AB4500_CH_STATUS2_REG 0x0B01 -#define AB4500_CH_USBCH_STAT1_REG 0x0B02 -#define AB4500_CH_USBCH_STAT2_REG 0x0B03 -#define AB4500_CH_FSM_STAT_REG 0x0B04 -#define AB4500_CH_STAT_REG 0x0B05 - -/* - * Charger / control register offfsets - * Bank : 0x0B - */ -#define AB4500_CH_VOLT_LVL_REG 0x0B40 - -/* - * Charger / main control register offfsets - * Bank : 0x0B - */ -#define AB4500_MCH_CTRL1 0x0B80 -#define AB4500_MCH_CTRL2 0x0B81 -#define AB4500_MCH_IPT_CURLVL_REG 0x0B82 -#define AB4500_CH_WD_REG 0x0B83 - -/* - * Charger / USB control register offsets - * Bank : 0x0B - */ -#define AB4500_USBCH_CTRL1_REG 0x0BC0 -#define AB4500_USBCH_CTRL2_REG 0x0BC1 -#define AB4500_USBCH_IPT_CRNTLVL_REG 0x0BC2 - -/* - * RTC bank register offsets - * Bank : 0xF - */ -#define AB4500_RTC_SOFF_STAT_REG 0x0F00 -#define AB4500_RTC_CC_CONF_REG 0x0F01 -#define AB4500_RTC_READ_REQ_REG 0x0F02 -#define AB4500_RTC_WATCH_TSECMID_REG 0x0F03 -#define AB4500_RTC_WATCH_TSECHI_REG 0x0F04 -#define AB4500_RTC_WATCH_TMIN_LOW_REG 0x0F05 -#define AB4500_RTC_WATCH_TMIN_MID_REG 0x0F06 -#define AB4500_RTC_WATCH_TMIN_HI_REG 0x0F07 -#define AB4500_RTC_ALRM_MIN_LOW_REG 0x0F08 -#define AB4500_RTC_ALRM_MIN_MID_REG 0x0F09 -#define AB4500_RTC_ALRM_MIN_HI_REG 0x0F0A -#define AB4500_RTC_STAT_REG 0x0F0B -#define AB4500_RTC_BKUP_CHG_REG 0x0F0C -#define AB4500_RTC_FORCE_BKUP_REG 0x0F0D -#define AB4500_RTC_CALIB_REG 0x0F0E -#define AB4500_RTC_SWITCH_STAT_REG 0x0F0F - -/* - * PWM Out generators - * Bank: 0x10 - */ -#define AB4500_PWM_OUT_CTRL1_REG 0x1060 -#define AB4500_PWM_OUT_CTRL2_REG 0x1061 -#define AB4500_PWM_OUT_CTRL3_REG 0x1062 -#define AB4500_PWM_OUT_CTRL4_REG 0x1063 -#define AB4500_PWM_OUT_CTRL5_REG 0x1064 -#define AB4500_PWM_OUT_CTRL6_REG 0x1065 -#define AB4500_PWM_OUT_CTRL7_REG 0x1066 - -#define AB4500_I2C_PAD_CTRL_REG 0x1067 -#define AB4500_REV_REG 0x1080 - -/** - * struct ab4500 - * @spi: spi device structure - * @tx_buf: transmit buffer - * @rx_buf: receive buffer - * @lock: sync primitive - */ -struct ab4500 { - struct spi_device *spi; - unsigned long tx_buf[4]; - unsigned long rx_buf[4]; - struct mutex lock; -}; - -int ab4500_write(struct ab4500 *ab4500, unsigned char block, - unsigned long addr, unsigned char data); -int ab4500_read(struct ab4500 *ab4500, unsigned char block, - unsigned long addr); - -#endif /* MFD_AB4500_H */ diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h new file mode 100644 index 000000000000..b63ff3ba3351 --- /dev/null +++ b/include/linux/mfd/ab8500.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Srinidhi Kasagar + */ +#ifndef MFD_AB8500_H +#define MFD_AB8500_H + +#include + +/* + * Interrupts + */ + +#define AB8500_INT_MAIN_EXT_CH_NOT_OK 0 +#define AB8500_INT_UN_PLUG_TV_DET 1 +#define AB8500_INT_PLUG_TV_DET 2 +#define AB8500_INT_TEMP_WARM 3 +#define AB8500_INT_PON_KEY2DB_F 4 +#define AB8500_INT_PON_KEY2DB_R 5 +#define AB8500_INT_PON_KEY1DB_F 6 +#define AB8500_INT_PON_KEY1DB_R 7 +#define AB8500_INT_BATT_OVV 8 +#define AB8500_INT_MAIN_CH_UNPLUG_DET 10 +#define AB8500_INT_MAIN_CH_PLUG_DET 11 +#define AB8500_INT_USB_ID_DET_F 12 +#define AB8500_INT_USB_ID_DET_R 13 +#define AB8500_INT_VBUS_DET_F 14 +#define AB8500_INT_VBUS_DET_R 15 +#define AB8500_INT_VBUS_CH_DROP_END 16 +#define AB8500_INT_RTC_60S 17 +#define AB8500_INT_RTC_ALARM 18 +#define AB8500_INT_BAT_CTRL_INDB 20 +#define AB8500_INT_CH_WD_EXP 21 +#define AB8500_INT_VBUS_OVV 22 +#define AB8500_INT_MAIN_CH_DROP_END 23 +#define AB8500_INT_CCN_CONV_ACC 24 +#define AB8500_INT_INT_AUD 25 +#define AB8500_INT_CCEOC 26 +#define AB8500_INT_CC_INT_CALIB 27 +#define AB8500_INT_LOW_BAT_F 28 +#define AB8500_INT_LOW_BAT_R 29 +#define AB8500_INT_BUP_CHG_NOT_OK 30 +#define AB8500_INT_BUP_CHG_OK 31 +#define AB8500_INT_GP_HW_ADC_CONV_END 32 +#define AB8500_INT_ACC_DETECT_1DB_F 33 +#define AB8500_INT_ACC_DETECT_1DB_R 34 +#define AB8500_INT_ACC_DETECT_22DB_F 35 +#define AB8500_INT_ACC_DETECT_22DB_R 36 +#define AB8500_INT_ACC_DETECT_21DB_F 37 +#define AB8500_INT_ACC_DETECT_21DB_R 38 +#define AB8500_INT_GP_SW_ADC_CONV_END 39 +#define AB8500_INT_BTEMP_LOW 72 +#define AB8500_INT_BTEMP_LOW_MEDIUM 73 +#define AB8500_INT_BTEMP_MEDIUM_HIGH 74 +#define AB8500_INT_BTEMP_HIGH 75 +#define AB8500_INT_USB_CHARGER_NOT_OK 81 +#define AB8500_INT_ID_WAKEUP_R 82 +#define AB8500_INT_ID_DET_R1R 84 +#define AB8500_INT_ID_DET_R2R 85 +#define AB8500_INT_ID_DET_R3R 86 +#define AB8500_INT_ID_DET_R4R 87 +#define AB8500_INT_ID_WAKEUP_F 88 +#define AB8500_INT_ID_DET_R1F 90 +#define AB8500_INT_ID_DET_R2F 91 +#define AB8500_INT_ID_DET_R3F 92 +#define AB8500_INT_ID_DET_R4F 93 +#define AB8500_INT_USB_CHG_DET_DONE 94 +#define AB8500_INT_USB_CH_TH_PROT_F 96 +#define AB8500_INT_USB_CH_TH_PROP_R 97 +#define AB8500_INT_MAIN_CH_TH_PROP_F 98 +#define AB8500_INT_MAIN_CH_TH_PROT_R 99 +#define AB8500_INT_USB_CHARGER_NOT_OKF 103 + +#define AB8500_NR_IRQS 104 +#define AB8500_NUM_IRQ_REGS 13 + +/** + * struct ab8500 - ab8500 internal structure + * @dev: parent device + * @lock: read/write operations lock + * @irq_lock: genirq bus lock + * @revision: chip revision + * @irq: irq line + * @write: register write + * @read: register read + * @rx_buf: rx buf for SPI + * @tx_buf: tx buf for SPI + * @mask: cache of IRQ regs for bus lock + * @oldmask: cache of previous IRQ regs for bus lock + */ +struct ab8500 { + struct device *dev; + struct mutex lock; + struct mutex irq_lock; + int revision; + int irq_base; + int irq; + + int (*write) (struct ab8500 *a8500, u16 addr, u8 data); + int (*read) (struct ab8500 *a8500, u16 addr); + + unsigned long tx_buf[4]; + unsigned long rx_buf[4]; + + u8 mask[AB8500_NUM_IRQ_REGS]; + u8 oldmask[AB8500_NUM_IRQ_REGS]; +}; + +/** + * struct ab8500_platform_data - AB8500 platform data + * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used + * @init: board-specific initialization after detection of ab8500 + */ +struct ab8500_platform_data { + int irq_base; + void (*init) (struct ab8500 *); +}; + +extern int ab8500_write(struct ab8500 *a8500, u16 addr, u8 data); +extern int ab8500_read(struct ab8500 *a8500, u16 addr); +extern int ab8500_set_bits(struct ab8500 *a8500, u16 addr, u8 mask, u8 data); + +extern int __devinit ab8500_init(struct ab8500 *ab8500); +extern int __devexit ab8500_exit(struct ab8500 *ab8500); + +#endif /* MFD_AB8500_H */ -- cgit v1.2.3 From 28ade0f217a3a3ff992b01e06e6e425c250a8406 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 21 May 2010 00:50:17 +0200 Subject: mfd: Remove unneeded and dangerous clearing of clientdata Unlike real i2c-devices which get detached from the driver, dummy-devices get truly unregistered. So, there has never been a need to clear the clientdata because the device will go away anyhow. For the occasions fixed here, clearing clientdata was even dangerous as the structure was freed already. Signed-off-by: Wolfram Sang Acked-by: Jean Delvare Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-i2c.c | 1 - drivers/mfd/max8925-i2c.c | 2 -- 2 files changed, 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index b0bade1881d4..c933b64d1283 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -200,7 +200,6 @@ static int __devexit pm860x_remove(struct i2c_client *client) pm860x_device_exit(chip); i2c_unregister_device(chip->companion); - i2c_set_clientdata(chip->companion, NULL); i2c_set_clientdata(chip->client, NULL); i2c_set_clientdata(client, NULL); kfree(chip); diff --git a/drivers/mfd/max8925-i2c.c b/drivers/mfd/max8925-i2c.c index d9fd8785da4d..e73f3f5252a8 100644 --- a/drivers/mfd/max8925-i2c.c +++ b/drivers/mfd/max8925-i2c.c @@ -173,8 +173,6 @@ static int __devexit max8925_remove(struct i2c_client *client) max8925_device_exit(chip); i2c_unregister_device(chip->adc); i2c_unregister_device(chip->rtc); - i2c_set_clientdata(chip->adc, NULL); - i2c_set_clientdata(chip->rtc, NULL); i2c_set_clientdata(chip->i2c, NULL); kfree(chip); return 0; -- cgit v1.2.3 From ae9f52f05c8223c8b4e18b042febf7a7bb46332a Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 20 May 2010 10:39:00 +0200 Subject: mfd: Storage class for timberdale should be before const qualifier The C99 specification states in section 6.11.5: The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature. Signed-off-by: Tobias Klauser Signed-off-by: Samuel Ortiz --- drivers/mfd/timberdale.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 2d4691acd033..ac5995026c88 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -86,7 +86,7 @@ timberdale_ocores_platform_data = { .num_devices = ARRAY_SIZE(timberdale_i2c_board_info) }; -const static __devinitconst struct resource timberdale_xiic_resources[] = { +static const __devinitconst struct resource timberdale_xiic_resources[] = { { .start = XIICOFFSET, .end = XIICEND, @@ -99,7 +99,7 @@ const static __devinitconst struct resource timberdale_xiic_resources[] = { }, }; -const static __devinitconst struct resource timberdale_ocores_resources[] = { +static const __devinitconst struct resource timberdale_ocores_resources[] = { { .start = OCORESOFFSET, .end = OCORESEND, @@ -148,7 +148,7 @@ static __devinitdata struct xspi_platform_data timberdale_xspi_platform_data = { */ }; -const static __devinitconst struct resource timberdale_spi_resources[] = { +static const __devinitconst struct resource timberdale_spi_resources[] = { { .start = SPIOFFSET, .end = SPIEND, @@ -161,7 +161,7 @@ const static __devinitconst struct resource timberdale_spi_resources[] = { }, }; -const static __devinitconst struct resource timberdale_eth_resources[] = { +static const __devinitconst struct resource timberdale_eth_resources[] = { { .start = ETHOFFSET, .end = ETHEND, @@ -181,7 +181,7 @@ static __devinitdata struct timbgpio_platform_data .irq_base = 200, }; -const static __devinitconst struct resource timberdale_gpio_resources[] = { +static const __devinitconst struct resource timberdale_gpio_resources[] = { { .start = GPIOOFFSET, .end = GPIOEND, @@ -194,7 +194,7 @@ const static __devinitconst struct resource timberdale_gpio_resources[] = { }, }; -const static __devinitconst struct resource timberdale_mlogicore_resources[] = { +static const __devinitconst struct resource timberdale_mlogicore_resources[] = { { .start = MLCOREOFFSET, .end = MLCOREEND, @@ -212,7 +212,7 @@ const static __devinitconst struct resource timberdale_mlogicore_resources[] = { }, }; -const static __devinitconst struct resource timberdale_uart_resources[] = { +static const __devinitconst struct resource timberdale_uart_resources[] = { { .start = UARTOFFSET, .end = UARTEND, @@ -225,7 +225,7 @@ const static __devinitconst struct resource timberdale_uart_resources[] = { }, }; -const static __devinitconst struct resource timberdale_uartlite_resources[] = { +static const __devinitconst struct resource timberdale_uartlite_resources[] = { { .start = UARTLITEOFFSET, .end = UARTLITEEND, @@ -238,7 +238,7 @@ const static __devinitconst struct resource timberdale_uartlite_resources[] = { }, }; -const static __devinitconst struct resource timberdale_radio_resources[] = { +static const __devinitconst struct resource timberdale_radio_resources[] = { { .start = RDSOFFSET, .end = RDSEND, @@ -331,7 +331,7 @@ static __devinitdata struct timb_dma_platform_data timb_dma_platform_data = { } }; -const static __devinitconst struct resource timberdale_dma_resources[] = { +static const __devinitconst struct resource timberdale_dma_resources[] = { { .start = DMAOFFSET, .end = DMAEND, -- cgit v1.2.3 From 191211f50f35deb5b3b80bc7b620cfd4b0a4a2d9 Mon Sep 17 00:00:00 2001 From: Ilkka Koskinen Date: Thu, 20 May 2010 13:04:20 +0300 Subject: mfd: Rename twl5031 sih modules Fix the names of twl5031 specific sih modules to match the documentation. Signed-off-by: Ilkka Koskinen Signed-off-by: Samuel Ortiz --- drivers/mfd/twl4030-irq.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index 202bdd59632d..097f24d8bceb 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c @@ -232,10 +232,11 @@ static const struct sih sih_modules_twl5031[8] = { }, [6] = { /* - * ACI doesn't use the same SIH organization. - * For example, it supports only one interrupt line + * ECI/DBI doesn't use the same SIH organization. + * For example, it supports only one interrupt output line. + * That is, the interrupts are seen on both INT1 and INT2 lines. */ - .name = "aci", + .name = "eci_dbi", .module = TWL5031_MODULE_ACCESSORY, .bits = 9, .bytes_ixr = 2, @@ -247,8 +248,8 @@ static const struct sih sih_modules_twl5031[8] = { }, [7] = { - /* Accessory */ - .name = "acc", + /* Audio accessory */ + .name = "audio", .module = TWL5031_MODULE_ACCESSORY, .control_offset = TWL5031_ACCSIHCTRL, .bits = 2, -- cgit v1.2.3