From 3ad2f3fbb961429d2aa627465ae4829758bc7e07 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Wed, 3 Feb 2010 08:01:28 +0800 Subject: tree-wide: Assorted spelling fixes In particular, several occurances of funny versions of 'success', 'unknown', 'therefore', 'acknowledge', 'argument', 'achieve', 'address', 'beginning', 'desirable', 'separate' and 'necessary' are fixed. Signed-off-by: Daniel Mack Cc: Joe Perches Cc: Junio C Hamano Signed-off-by: Jiri Kosina --- drivers/mfd/sm501.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 0cc5eeff5ee8..10491e4e305d 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -523,7 +523,7 @@ unsigned long sm501_set_clock(struct device *dev, unsigned long clock = readl(sm->regs + SM501_CURRENT_CLOCK); unsigned char reg; unsigned int pll_reg = 0; - unsigned long sm501_freq; /* the actual frequency acheived */ + unsigned long sm501_freq; /* the actual frequency achieved */ struct sm501_clock to; @@ -533,7 +533,7 @@ unsigned long sm501_set_clock(struct device *dev, switch (clksrc) { case SM501_CLOCK_P2XCLK: - /* This clock is divided in half so to achive the + /* This clock is divided in half so to achieve the * requested frequency the value must be multiplied by * 2. This clock also has an additional pre divisor */ @@ -562,7 +562,7 @@ unsigned long sm501_set_clock(struct device *dev, break; case SM501_CLOCK_V2XCLK: - /* This clock is divided in half so to achive the + /* This clock is divided in half so to achieve the * requested frequency the value must be multiplied by 2. */ sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); @@ -648,7 +648,7 @@ unsigned long sm501_find_clock(struct device *dev, unsigned long req_freq) { struct sm501_devdata *sm = dev_get_drvdata(dev); - unsigned long sm501_freq; /* the frequency achiveable by the 501 */ + unsigned long sm501_freq; /* the frequency achieveable by the 501 */ struct sm501_clock to; switch (clksrc) { -- cgit v1.2.3 From 984b3f5746ed2cde3d184651dabf26980f2b66e5 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Fri, 5 Mar 2010 13:41:37 -0800 Subject: bitops: rename for_each_bit() to for_each_set_bit() Rename for_each_bit to for_each_set_bit in the kernel source tree. To permit for_each_clear_bit(), should that ever be added. The patch includes a macro to map the old for_each_bit() onto the new for_each_set_bit(). This is a (very) temporary thing to ease the migration. [akpm@linux-foundation.org: add temporary for_each_bit()] Suggested-by: Alexey Dobriyan Suggested-by: Andrew Morton Signed-off-by: Akinobu Mita Cc: "David S. Miller" Cc: Russell King Cc: David Woodhouse Cc: Artem Bityutskiy Cc: Stephen Rothwell Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/htc-egpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/htc-egpio.c b/drivers/mfd/htc-egpio.c index aa266e1f69b2..addb846c1e34 100644 --- a/drivers/mfd/htc-egpio.c +++ b/drivers/mfd/htc-egpio.c @@ -108,7 +108,7 @@ static void egpio_handler(unsigned int irq, struct irq_desc *desc) ack_irqs(ei); /* Process all set pins. */ readval &= ei->irqs_enabled; - for_each_bit(irqpin, &readval, ei->nirqs) { + for_each_set_bit(irqpin, &readval, ei->nirqs) { /* Run irq handler */ pr_debug("got IRQ %d\n", irqpin); irq = ei->irq_start + irqpin; -- cgit v1.2.3 From 57205026da070b59e9546df352fe465f1aeacf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 5 Mar 2010 13:44:25 -0800 Subject: mc13783: rename mc13783_{{un,}mask,ack_irq} to have a mc13783_irq prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the source file group these functions together. The mc13783 header file provides fallback implementations for the old names to prevent build failures. When all users of the old names are fixed to use the new names these can go away. Signed-off-by: Uwe Kleine-König Cc: Alessandro Zummo Cc: Paul Gortmaker Cc: Valentin Longchamp Cc: Sascha Hauer Cc: Samuel Ortiz Cc: Dmitry Torokhov Cc: Luotao Fu Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/mc13783-core.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c index 735c8a4d164f..a2bd44552042 100644 --- a/drivers/mfd/mc13783-core.c +++ b/drivers/mfd/mc13783-core.c @@ -225,7 +225,7 @@ int mc13783_reg_rmw(struct mc13783 *mc13783, unsigned int offset, } EXPORT_SYMBOL(mc13783_reg_rmw); -int mc13783_mask(struct mc13783 *mc13783, int irq) +int mc13783_irq_mask(struct mc13783 *mc13783, int irq) { int ret; unsigned int offmask = irq < 24 ? MC13783_IRQMASK0 : MC13783_IRQMASK1; @@ -245,9 +245,9 @@ int mc13783_mask(struct mc13783 *mc13783, int irq) return mc13783_reg_write(mc13783, offmask, mask | irqbit); } -EXPORT_SYMBOL(mc13783_mask); +EXPORT_SYMBOL(mc13783_irq_mask); -int mc13783_unmask(struct mc13783 *mc13783, int irq) +int mc13783_irq_unmask(struct mc13783 *mc13783, int irq) { int ret; unsigned int offmask = irq < 24 ? MC13783_IRQMASK0 : MC13783_IRQMASK1; @@ -267,7 +267,18 @@ int mc13783_unmask(struct mc13783 *mc13783, int irq) return mc13783_reg_write(mc13783, offmask, mask & ~irqbit); } -EXPORT_SYMBOL(mc13783_unmask); +EXPORT_SYMBOL(mc13783_irq_unmask); + +int mc13783_irq_ack(struct mc13783 *mc13783, int irq) +{ + unsigned int offstat = irq < 24 ? MC13783_IRQSTAT0 : MC13783_IRQSTAT1; + unsigned int val = 1 << (irq < 24 ? irq : irq - 24); + + BUG_ON(irq < 0 || irq >= MC13783_NUM_IRQ); + + return mc13783_reg_write(mc13783, offstat, val); +} +EXPORT_SYMBOL(mc13783_irq_ack); int mc13783_irq_request_nounmask(struct mc13783 *mc13783, int irq, irq_handler_t handler, const char *name, void *dev) @@ -297,7 +308,7 @@ int mc13783_irq_request(struct mc13783 *mc13783, int irq, if (ret) return ret; - ret = mc13783_unmask(mc13783, irq); + ret = mc13783_irq_unmask(mc13783, irq); if (ret) { mc13783->irqhandler[irq] = NULL; mc13783->irqdata[irq] = NULL; @@ -317,7 +328,7 @@ int mc13783_irq_free(struct mc13783 *mc13783, int irq, void *dev) mc13783->irqdata[irq] != dev) return -EINVAL; - ret = mc13783_mask(mc13783, irq); + ret = mc13783_irq_mask(mc13783, irq); if (ret) return ret; @@ -333,17 +344,6 @@ static inline irqreturn_t mc13783_irqhandler(struct mc13783 *mc13783, int irq) return mc13783->irqhandler[irq](irq, mc13783->irqdata[irq]); } -int mc13783_ackirq(struct mc13783 *mc13783, int irq) -{ - unsigned int offstat = irq < 24 ? MC13783_IRQSTAT0 : MC13783_IRQSTAT1; - unsigned int val = 1 << (irq < 24 ? irq : irq - 24); - - BUG_ON(irq < 0 || irq >= MC13783_NUM_IRQ); - - return mc13783_reg_write(mc13783, offstat, val); -} -EXPORT_SYMBOL(mc13783_ackirq); - /* * returns: number of handled irqs or negative error * locking: holds mc13783->lock @@ -422,7 +422,7 @@ static irqreturn_t mc13783_handler_adcdone(int irq, void *data) { struct mc13783_adcdone_data *adcdone_data = data; - mc13783_ackirq(adcdone_data->mc13783, irq); + mc13783_irq_ack(adcdone_data->mc13783, irq); complete_all(&adcdone_data->done); @@ -486,7 +486,7 @@ int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode, dev_dbg(&mc13783->spidev->dev, "%s: request irq\n", __func__); mc13783_irq_request(mc13783, MC13783_IRQ_ADCDONE, mc13783_handler_adcdone, __func__, &adcdone_data); - mc13783_ackirq(mc13783, MC13783_IRQ_ADCDONE); + mc13783_irq_ack(mc13783, MC13783_IRQ_ADCDONE); mc13783_reg_write(mc13783, MC13783_REG_ADC_0, adc0); mc13783_reg_write(mc13783, MC13783_REG_ADC_1, adc1); -- cgit v1.2.3 From 86c3400810a7a33e176bf33b6b074d881e829374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 5 Mar 2010 13:44:29 -0800 Subject: mfd/mc13783: new function reading irq mask and status register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver for the mc13783 rtc needs to know if the TODA irq is pending. Instead of tracking in the rtc driver if the irq is enabled provide that information, too. Signed-off-by: Uwe Kleine-König Cc: Alessandro Zummo Cc: Paul Gortmaker Cc: Valentin Longchamp Cc: Sascha Hauer Cc: Samuel Ortiz Cc: Dmitry Torokhov Cc: Luotao Fu Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/mc13783-core.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c index a2bd44552042..62a847e4c2d8 100644 --- a/drivers/mfd/mc13783-core.c +++ b/drivers/mfd/mc13783-core.c @@ -269,6 +269,41 @@ int mc13783_irq_unmask(struct mc13783 *mc13783, int irq) } EXPORT_SYMBOL(mc13783_irq_unmask); +int mc13783_irq_status(struct mc13783 *mc13783, int irq, + int *enabled, int *pending) +{ + int ret; + unsigned int offmask = irq < 24 ? MC13783_IRQMASK0 : MC13783_IRQMASK1; + unsigned int offstat = irq < 24 ? MC13783_IRQSTAT0 : MC13783_IRQSTAT1; + u32 irqbit = 1 << (irq < 24 ? irq : irq - 24); + + if (irq < 0 || irq >= MC13783_NUM_IRQ) + return -EINVAL; + + if (enabled) { + u32 mask; + + ret = mc13783_reg_read(mc13783, offmask, &mask); + if (ret) + return ret; + + *enabled = mask & irqbit; + } + + if (pending) { + u32 stat; + + ret = mc13783_reg_read(mc13783, offstat, &stat); + if (ret) + return ret; + + *pending = stat & irqbit; + } + + return 0; +} +EXPORT_SYMBOL(mc13783_irq_status); + int mc13783_irq_ack(struct mc13783 *mc13783, int irq) { unsigned int offstat = irq < 24 ? MC13783_IRQSTAT0 : MC13783_IRQSTAT1; -- cgit v1.2.3 From 311e54c07e784fe63d421f724dcc597d1f82ccad Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Tue, 5 Jan 2010 19:55:54 +0100 Subject: mfd: Use resource_size() in sm501 The requested memory region is smaller than the actual ioremap(). Use resource_size() to get the correct size. Signed-off-by: H Hartley Sweeten Cc: Ben Dooks Signed-off-by: Samuel Ortiz --- drivers/mfd/sm501.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 0cc5eeff5ee8..dc9ea95c0561 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -1430,7 +1430,7 @@ static int __devinit sm501_plat_probe(struct platform_device *dev) } sm->regs_claim = request_mem_region(sm->io_res->start, - 0x100, "sm501"); + resource_size(sm->io_res), "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); @@ -1440,8 +1440,7 @@ static int __devinit sm501_plat_probe(struct platform_device *dev) platform_set_drvdata(dev, sm); - sm->regs = ioremap(sm->io_res->start, - (sm->io_res->end - sm->io_res->start) - 1); + sm->regs = ioremap(sm->io_res->start, resource_size(sm->io_res)); if (sm->regs == NULL) { dev_err(&dev->dev, "cannot remap registers\n"); @@ -1645,7 +1644,7 @@ static int __devinit sm501_pci_probe(struct pci_dev *dev, sm->mem_res = &dev->resource[0]; sm->regs_claim = request_mem_region(sm->io_res->start, - 0x100, "sm501"); + resource_size(sm->io_res), "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); err= -EBUSY; -- cgit v1.2.3 From c02e6a5ff43d24d73cf7830c2925e213fc72e037 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Tue, 5 Jan 2010 19:58:34 +0100 Subject: mfd: Use resource_size() for t7l66xb Use resource_size() for ioremap. Signed-off-by: H Hartley Sweeten Cc: Ian Molton Signed-off-by: Samuel Ortiz --- drivers/mfd/t7l66xb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index bcf4687d4af5..e0bbddd7aac2 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -360,7 +360,7 @@ static int t7l66xb_probe(struct platform_device *dev) if (ret) goto err_request_scr; - t7l66xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + t7l66xb->scr = ioremap(rscr->start, resource_size(rscr)); if (!t7l66xb->scr) { ret = -ENOMEM; goto err_ioremap; -- cgit v1.2.3 From 1ecc09e765d3ae16ef42a4d454836d9ed804fd18 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Tue, 5 Jan 2010 20:01:16 +0100 Subject: mfd: Use resouce_size for tc6393xb Use resource_size() for ioremap. Signed-off-by: H Hartley Sweeten Cc: Ian Molton Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6393xb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index 4bc5a08a2b09..c59e5c5737d0 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -647,7 +647,7 @@ static int __devinit tc6393xb_probe(struct platform_device *dev) if (ret) goto err_request_scr; - tc6393xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + tc6393xb->scr = ioremap(rscr->start, resource_size(rscr)); if (!tc6393xb->scr) { ret = -ENOMEM; goto err_ioremap; -- cgit v1.2.3 From 0e820ab60118e06db62ef4e55b6dd96db807a34e Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Tue, 22 Dec 2009 21:31:43 +0100 Subject: mfd: Correct use after free for t7l66xb The structure t7l66xb should not be freed before the subsequent references to its fields in the arguments to clk_put. Furthermore, this structure is allocated near the beginning of the function, and a goto to the label err_noirq appears after a successful allocation, so it would seem that the kfree should be moved down below this label. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression x,e; identifier f; iterator I; statement S; @@ *kfree(x); ... when != &x when != x = e when != I(x,...) S *x->f // Signed-off-by: Julia Lawall --- drivers/mfd/t7l66xb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index e0bbddd7aac2..26d9176fca91 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -403,12 +403,12 @@ static int t7l66xb_probe(struct platform_device *dev) err_ioremap: release_resource(&t7l66xb->rscr); err_request_scr: - kfree(t7l66xb); clk_put(t7l66xb->clk48m); err_clk48m_get: clk_put(t7l66xb->clk32k); err_clk32k_get: err_noirq: + kfree(t7l66xb); return ret; } -- cgit v1.2.3 From f99344fc69c3df46786a39ea4283a4175ea40b3f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Jan 2010 13:59:07 +0000 Subject: mfd: Add a data argument to the WM8350 IRQ free function To better match genirq. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8350-irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c index 9025f29e2707..655836bc69a0 100644 --- a/drivers/mfd/wm8350-irq.c +++ b/drivers/mfd/wm8350-irq.c @@ -451,7 +451,7 @@ int wm8350_register_irq(struct wm8350 *wm8350, int irq, } EXPORT_SYMBOL_GPL(wm8350_register_irq); -int wm8350_free_irq(struct wm8350 *wm8350, int irq) +int wm8350_free_irq(struct wm8350 *wm8350, int irq, void *data) { if (irq < 0 || irq >= WM8350_NUM_IRQ) return -EINVAL; -- cgit v1.2.3 From 760e4518788df6762700e6bb9dd8692379f11168 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Jan 2010 13:59:09 +0000 Subject: mfd: Convert WM8350 to genirq This gives us use of the diagnostic facilities genirq provides and will allow implementation of interrupt support for the WM8350 GPIOs. Stub functions are provided to ease the transition of the individual drivers, probably after additional work to pass the IRQ numbers via the struct devices. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8350-irq.c | 155 ++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 68 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c index 655836bc69a0..f56c9adf9493 100644 --- a/drivers/mfd/wm8350-irq.c +++ b/drivers/mfd/wm8350-irq.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -29,8 +29,6 @@ #include #include -#define WM8350_NUM_IRQ_REGS 7 - #define WM8350_INT_OFFSET_1 0 #define WM8350_INT_OFFSET_2 1 #define WM8350_POWER_UP_INT_OFFSET 2 @@ -366,19 +364,10 @@ static struct wm8350_irq_data wm8350_irqs[] = { }, }; -static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq) +static inline struct wm8350_irq_data *irq_to_wm8350_irq(struct wm8350 *wm8350, + int irq) { - mutex_lock(&wm8350->irq_mutex); - - if (wm8350->irq[irq].handler) - wm8350->irq[irq].handler(irq, wm8350->irq[irq].data); - else { - dev_err(wm8350->dev, "irq %d nobody cared. now masked.\n", - irq); - wm8350_mask_irq(wm8350, irq); - } - - mutex_unlock(&wm8350->irq_mutex); + return &wm8350_irqs[irq - wm8350->irq_base]; } /* @@ -386,7 +375,9 @@ static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq) * interrupts are clear on read the IRQ line will be reasserted and * the physical IRQ will be handled again if another interrupt is * asserted while we run - in the normal course of events this is a - * rare occurrence so we save I2C/SPI reads. + * rare occurrence so we save I2C/SPI reads. We're also assuming that + * it's rare to get lots of interrupts firing simultaneously so try to + * minimise I/O. */ static irqreturn_t wm8350_irq(int irq, void *irq_data) { @@ -397,7 +388,6 @@ static irqreturn_t wm8350_irq(int irq, void *irq_data) struct wm8350_irq_data *data; int i; - /* TODO: Use block reads to improve performance? */ level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS) & ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK); @@ -416,93 +406,101 @@ static irqreturn_t wm8350_irq(int irq, void *irq_data) sub_reg[data->reg] = wm8350_reg_read(wm8350, WM8350_INT_STATUS_1 + data->reg); - sub_reg[data->reg] &= - ~wm8350_reg_read(wm8350, - WM8350_INT_STATUS_1_MASK + - data->reg); + sub_reg[data->reg] &= ~wm8350->irq_masks[data->reg]; read_done[data->reg] = 1; } if (sub_reg[data->reg] & data->mask) - wm8350_irq_call_handler(wm8350, i); + handle_nested_irq(wm8350->irq_base + i); } return IRQ_HANDLED; } -int wm8350_register_irq(struct wm8350 *wm8350, int irq, - irq_handler_t handler, unsigned long flags, - const char *name, void *data) +static void wm8350_irq_lock(unsigned int irq) { - if (irq < 0 || irq >= WM8350_NUM_IRQ || !handler) - return -EINVAL; - - if (wm8350->irq[irq].handler) - return -EBUSY; - - mutex_lock(&wm8350->irq_mutex); - wm8350->irq[irq].handler = handler; - wm8350->irq[irq].data = data; - mutex_unlock(&wm8350->irq_mutex); - - wm8350_unmask_irq(wm8350, irq); + struct wm8350 *wm8350 = get_irq_chip_data(irq); - return 0; + mutex_lock(&wm8350->irq_lock); } -EXPORT_SYMBOL_GPL(wm8350_register_irq); -int wm8350_free_irq(struct wm8350 *wm8350, int irq, void *data) +static void wm8350_irq_sync_unlock(unsigned int irq) { - if (irq < 0 || irq >= WM8350_NUM_IRQ) - return -EINVAL; + struct wm8350 *wm8350 = get_irq_chip_data(irq); + int i; - wm8350_mask_irq(wm8350, irq); + for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + if (wm8350->irq_masks[i] != + wm8350->reg_cache[WM8350_INT_STATUS_1_MASK + i]) + WARN_ON(wm8350_reg_write(wm8350, + WM8350_INT_STATUS_1_MASK + i, + wm8350->irq_masks[i])); + } - mutex_lock(&wm8350->irq_mutex); - wm8350->irq[irq].handler = NULL; - mutex_unlock(&wm8350->irq_mutex); - return 0; + mutex_unlock(&wm8350->irq_lock); } -EXPORT_SYMBOL_GPL(wm8350_free_irq); -int wm8350_mask_irq(struct wm8350 *wm8350, int irq) +static void wm8350_irq_enable(unsigned int irq) { - return wm8350_set_bits(wm8350, WM8350_INT_STATUS_1_MASK + - wm8350_irqs[irq].reg, - wm8350_irqs[irq].mask); + struct wm8350 *wm8350 = get_irq_chip_data(irq); + struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, irq); + + wm8350->irq_masks[irq_data->reg] &= ~irq_data->mask; } -EXPORT_SYMBOL_GPL(wm8350_mask_irq); -int wm8350_unmask_irq(struct wm8350 *wm8350, int irq) +static void wm8350_irq_disable(unsigned int irq) { - return wm8350_clear_bits(wm8350, WM8350_INT_STATUS_1_MASK + - wm8350_irqs[irq].reg, - wm8350_irqs[irq].mask); + struct wm8350 *wm8350 = get_irq_chip_data(irq); + struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, irq); + + wm8350->irq_masks[irq_data->reg] |= irq_data->mask; } -EXPORT_SYMBOL_GPL(wm8350_unmask_irq); + +static struct irq_chip wm8350_irq_chip = { + .name = "wm8350", + .bus_lock = wm8350_irq_lock, + .bus_sync_unlock = wm8350_irq_sync_unlock, + .disable = wm8350_irq_disable, + .enable = wm8350_irq_enable, +}; int wm8350_irq_init(struct wm8350 *wm8350, int irq, struct wm8350_platform_data *pdata) { - int ret; + int ret, cur_irq, i; int flags = IRQF_ONESHOT; if (!irq) { - dev_err(wm8350->dev, "No IRQ configured\n"); - return -EINVAL; + dev_warn(wm8350->dev, "No interrupt support, no core IRQ\n"); + return 0; + } + + if (!pdata || !pdata->irq_base) { + dev_warn(wm8350->dev, "No interrupt support, no IRQ base\n"); + return 0; } + /* Mask top level interrupts */ wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0xFFFF); - wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK, 0xFFFF); - wm8350_reg_write(wm8350, WM8350_INT_STATUS_2_MASK, 0xFFFF); - wm8350_reg_write(wm8350, WM8350_UNDER_VOLTAGE_INT_STATUS_MASK, 0xFFFF); - wm8350_reg_write(wm8350, WM8350_GPIO_INT_STATUS_MASK, 0xFFFF); - wm8350_reg_write(wm8350, WM8350_COMPARATOR_INT_STATUS_MASK, 0xFFFF); - mutex_init(&wm8350->irq_mutex); + /* Mask all individual interrupts by default and cache the + * masks. We read the masks back since there are unwritable + * bits in the mask registers. */ + for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) { + wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK + i, + 0xFFFF); + wm8350->irq_masks[i] = + wm8350_reg_read(wm8350, + WM8350_INT_STATUS_1_MASK + i); + } + + mutex_init(&wm8350->irq_lock); wm8350->chip_irq = irq; + wm8350->irq_base = pdata->irq_base; - if (pdata && pdata->irq_high) { + if (pdata->irq_high) { flags |= IRQF_TRIGGER_HIGH; wm8350_set_bits(wm8350, WM8350_SYSTEM_CONTROL_1, @@ -514,11 +512,32 @@ int wm8350_irq_init(struct wm8350 *wm8350, int irq, WM8350_IRQ_POL); } + /* Register with genirq */ + for (cur_irq = wm8350->irq_base; + cur_irq < ARRAY_SIZE(wm8350_irqs) + wm8350->irq_base; + cur_irq++) { + set_irq_chip_data(cur_irq, wm8350); + set_irq_chip_and_handler(cur_irq, &wm8350_irq_chip, + handle_edge_irq); + set_irq_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + } + ret = request_threaded_irq(irq, NULL, wm8350_irq, flags, "wm8350", wm8350); if (ret != 0) dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret); + /* Allow interrupts to fire */ + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0); + return ret; } -- cgit v1.2.3 From ecf926bcda5ba1da7269dcaa71a1b6920dc48349 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Jan 2010 20:40:47 +0000 Subject: mfd: Don't allow WM8350 to be built modular The genirq implementation does not allow modules to implement irq_chips so the conversion of WM8350 to genirq means we can no longer allow the driver to be built as a module. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index b670d10d5c92..d957373d77e4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -205,7 +205,7 @@ config MFD_WM831X functionality of the device. config MFD_WM8350 - tristate + bool config MFD_WM8350_CONFIG_MODE_0 bool @@ -256,9 +256,9 @@ config MFD_WM8352_CONFIG_MODE_3 depends on MFD_WM8350 config MFD_WM8350_I2C - tristate "Support Wolfson Microelectronics WM8350 with I2C" + bool "Support Wolfson Microelectronics WM8350 with I2C" select MFD_WM8350 - depends on I2C + depends on I2C=y help The WM8350 is an integrated audio and power management subsystem with watchdog and RTC functionality for embedded -- cgit v1.2.3 From 0df883df8e8aea79b501f6262b595e66dec175dc Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 8 Jan 2010 10:44:16 +0100 Subject: mfd: Convert AB3100 driver to threaded IRQ This converts the AB3100 core MFD driver to use a threaded interrupt handler instead of the explicit top/bottom-half construction with a workqueue. This saves some code and make it more similar to other modern MFD drivers. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3100-core.c | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index fd42a80e7bf9..aa3824a1b4f2 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -365,10 +365,13 @@ int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100, } EXPORT_SYMBOL(ab3100_event_registers_startup_state_get); -/* Interrupt handling worker */ -static void ab3100_work(struct work_struct *work) +/* + * This is a threaded interrupt handler so we can make some + * I2C calls etc. + */ +static irqreturn_t ab3100_irq_handler(int irq, void *data) { - struct ab3100 *ab3100 = container_of(work, struct ab3100, work); + struct ab3100 *ab3100 = data; u8 event_regs[3]; u32 fatevent; int err; @@ -376,7 +379,7 @@ static void ab3100_work(struct work_struct *work) err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, event_regs, 3); if (err) - goto err_event_wq; + goto err_event; fatevent = (event_regs[0] << 16) | (event_regs[1] << 8) | @@ -398,29 +401,11 @@ static void ab3100_work(struct work_struct *work) dev_dbg(ab3100->dev, "IRQ Event: 0x%08x\n", fatevent); - /* By now the IRQ should be acked and deasserted so enable it again */ - enable_irq(ab3100->i2c_client->irq); - return; + return IRQ_HANDLED; - err_event_wq: + err_event: dev_dbg(ab3100->dev, - "error in event workqueue\n"); - /* Enable the IRQ anyway, what choice do we have? */ - enable_irq(ab3100->i2c_client->irq); - return; -} - -static irqreturn_t ab3100_irq_handler(int irq, void *data) -{ - struct ab3100 *ab3100 = data; - /* - * Disable the IRQ and dispatch a worker to handle the - * event. Since the chip resides on I2C this is slow - * stuff and we will re-enable the interrupts once th - * worker has finished. - */ - disable_irq_nosync(irq); - schedule_work(&ab3100->work); + "error reading event status\n"); return IRQ_HANDLED; } @@ -904,12 +889,10 @@ static int __init ab3100_probe(struct i2c_client *client, if (err) goto exit_no_setup; - INIT_WORK(&ab3100->work, ab3100_work); - /* This real unpredictable IRQ is of course sampled for entropy */ - err = request_irq(client->irq, ab3100_irq_handler, - IRQF_DISABLED | IRQF_SAMPLE_RANDOM, - "AB3100 IRQ", ab3100); + err = request_threaded_irq(client->irq, NULL, ab3100_irq_handler, + IRQF_ONESHOT, + "ab3100-core", ab3100); if (err) goto exit_no_irq; -- cgit v1.2.3 From bbd51b1ff1bf57b9ed7f062486a415509968d4d9 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 6 Jan 2010 17:04:18 -0500 Subject: mfd: Split 88pm8607 driver Create 88pm8607-i2c driver to support all I2C operation of 88PM8607. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm8607.c | 302 -------------------------------------------- drivers/mfd/88pm860x-core.c | 134 ++++++++++++++++++++ drivers/mfd/88pm860x-i2c.c | 202 +++++++++++++++++++++++++++++ drivers/mfd/Makefile | 2 +- 4 files changed, 337 insertions(+), 303 deletions(-) delete mode 100644 drivers/mfd/88pm8607.c create mode 100644 drivers/mfd/88pm860x-core.c create mode 100644 drivers/mfd/88pm860x-i2c.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm8607.c b/drivers/mfd/88pm8607.c deleted file mode 100644 index 7e3f65907993..000000000000 --- a/drivers/mfd/88pm8607.c +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Base driver for Marvell 88PM8607 - * - * Copyright (C) 2009 Marvell International Ltd. - * Haojian Zhuang - * - * 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 - - -#define PM8607_REG_RESOURCE(_start, _end) \ -{ \ - .start = PM8607_##_start, \ - .end = PM8607_##_end, \ - .flags = IORESOURCE_IO, \ -} - -static struct resource pm8607_regulator_resources[] = { - PM8607_REG_RESOURCE(BUCK1, BUCK1), - PM8607_REG_RESOURCE(BUCK2, BUCK2), - PM8607_REG_RESOURCE(BUCK3, BUCK3), - PM8607_REG_RESOURCE(LDO1, LDO1), - PM8607_REG_RESOURCE(LDO2, LDO2), - PM8607_REG_RESOURCE(LDO3, LDO3), - PM8607_REG_RESOURCE(LDO4, LDO4), - PM8607_REG_RESOURCE(LDO5, LDO5), - PM8607_REG_RESOURCE(LDO6, LDO6), - PM8607_REG_RESOURCE(LDO7, LDO7), - PM8607_REG_RESOURCE(LDO8, LDO8), - PM8607_REG_RESOURCE(LDO9, LDO9), - PM8607_REG_RESOURCE(LDO10, LDO10), - PM8607_REG_RESOURCE(LDO12, LDO12), - PM8607_REG_RESOURCE(LDO14, LDO14), -}; - -#define PM8607_REG_DEVS(_name, _id) \ -{ \ - .name = "88pm8607-" #_name, \ - .num_resources = 1, \ - .resources = &pm8607_regulator_resources[PM8607_ID_##_id], \ -} - -static struct mfd_cell pm8607_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), -}; - -static inline int pm8607_read_device(struct pm8607_chip *chip, - int reg, int bytes, void *dest) -{ - struct i2c_client *i2c = chip->client; - unsigned char data; - int ret; - - data = (unsigned char)reg; - ret = i2c_master_send(i2c, &data, 1); - if (ret < 0) - return ret; - - ret = i2c_master_recv(i2c, dest, bytes); - if (ret < 0) - return ret; - return 0; -} - -static inline int pm8607_write_device(struct pm8607_chip *chip, - int reg, int bytes, void *src) -{ - struct i2c_client *i2c = chip->client; - unsigned char buf[bytes + 1]; - int ret; - - buf[0] = (unsigned char)reg; - memcpy(&buf[1], src, bytes); - - ret = i2c_master_send(i2c, buf, bytes + 1); - if (ret < 0) - return ret; - return 0; -} - -int pm8607_reg_read(struct pm8607_chip *chip, int reg) -{ - unsigned char data; - int ret; - - mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, 1, &data); - mutex_unlock(&chip->io_lock); - - if (ret < 0) - return ret; - else - return (int)data; -} -EXPORT_SYMBOL(pm8607_reg_read); - -int pm8607_reg_write(struct pm8607_chip *chip, int reg, - unsigned char data) -{ - int ret; - - mutex_lock(&chip->io_lock); - ret = chip->write(chip, reg, 1, &data); - mutex_unlock(&chip->io_lock); - - return ret; -} -EXPORT_SYMBOL(pm8607_reg_write); - -int pm8607_bulk_read(struct pm8607_chip *chip, int reg, - int count, unsigned char *buf) -{ - int ret; - - mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, count, buf); - mutex_unlock(&chip->io_lock); - - return ret; -} -EXPORT_SYMBOL(pm8607_bulk_read); - -int pm8607_bulk_write(struct pm8607_chip *chip, int reg, - int count, unsigned char *buf) -{ - int ret; - - mutex_lock(&chip->io_lock); - ret = chip->write(chip, reg, count, buf); - mutex_unlock(&chip->io_lock); - - return ret; -} -EXPORT_SYMBOL(pm8607_bulk_write); - -int pm8607_set_bits(struct pm8607_chip *chip, int reg, - unsigned char mask, unsigned char data) -{ - unsigned char value; - int ret; - - mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, 1, &value); - if (ret < 0) - goto out; - value &= ~mask; - value |= data; - ret = chip->write(chip, reg, 1, &value); -out: - mutex_unlock(&chip->io_lock); - return ret; -} -EXPORT_SYMBOL(pm8607_set_bits); - - -static const struct i2c_device_id pm8607_id_table[] = { - { "88PM8607", 0 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, pm8607_id_table); - - -static int __devinit pm8607_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct pm8607_platform_data *pdata = client->dev.platform_data; - struct pm8607_chip *chip; - int i, count; - int ret; - - chip = kzalloc(sizeof(struct pm8607_chip), GFP_KERNEL); - if (chip == NULL) - return -ENOMEM; - - chip->client = client; - chip->dev = &client->dev; - chip->read = pm8607_read_device; - chip->write = pm8607_write_device; - i2c_set_clientdata(client, chip); - - mutex_init(&chip->io_lock); - dev_set_drvdata(chip->dev, chip); - - ret = pm8607_reg_read(chip, PM8607_CHIP_ID); - if (ret < 0) { - dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); - goto out; - } - if ((ret & CHIP_ID_MASK) == CHIP_ID) - dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", - ret); - else { - dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " - "Chip ID: %02x\n", ret); - goto out; - } - chip->chip_id = ret; - - ret = pm8607_reg_read(chip, PM8607_BUCK3); - if (ret < 0) { - dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); - goto out; - } - if (ret & PM8607_BUCK3_DOUBLE) - chip->buck3_double = 1; - - ret = pm8607_reg_read(chip, PM8607_MISC1); - if (ret < 0) { - dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); - goto out; - } - if (pdata->i2c_port == PI2C_PORT) - ret |= PM8607_MISC1_PI2C; - else - ret &= ~PM8607_MISC1_PI2C; - ret = pm8607_reg_write(chip, PM8607_MISC1, ret); - if (ret < 0) { - dev_err(chip->dev, "Failed to write MISC1 register: %d\n", ret); - goto out; - } - - - count = ARRAY_SIZE(pm8607_devs); - for (i = 0; i < count; i++) { - ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], - 1, NULL, 0); - if (ret != 0) { - dev_err(chip->dev, "Failed to add subdevs\n"); - goto out; - } - } - - return 0; - -out: - i2c_set_clientdata(client, NULL); - kfree(chip); - return ret; -} - -static int __devexit pm8607_remove(struct i2c_client *client) -{ - struct pm8607_chip *chip = i2c_get_clientdata(client); - - mfd_remove_devices(chip->dev); - kfree(chip); - return 0; -} - -static struct i2c_driver pm8607_driver = { - .driver = { - .name = "88PM8607", - .owner = THIS_MODULE, - }, - .probe = pm8607_probe, - .remove = __devexit_p(pm8607_remove), - .id_table = pm8607_id_table, -}; - -static int __init pm8607_init(void) -{ - int ret; - ret = i2c_add_driver(&pm8607_driver); - if (ret != 0) - pr_err("Failed to register 88PM8607 I2C driver: %d\n", ret); - return ret; -} -subsys_initcall(pm8607_init); - -static void __exit pm8607_exit(void) -{ - i2c_del_driver(&pm8607_driver); -} -module_exit(pm8607_exit); - -MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM8607"); -MODULE_AUTHOR("Haojian Zhuang "); -MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c new file mode 100644 index 000000000000..d1464e54e656 --- /dev/null +++ b/drivers/mfd/88pm860x-core.c @@ -0,0 +1,134 @@ +/* + * Base driver for Marvell 88PM8607 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang + * + * 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 + + +#define PM8607_REG_RESOURCE(_start, _end) \ +{ \ + .start = PM8607_##_start, \ + .end = PM8607_##_end, \ + .flags = IORESOURCE_IO, \ +} + +static struct resource pm8607_regulator_resources[] = { + PM8607_REG_RESOURCE(BUCK1, BUCK1), + PM8607_REG_RESOURCE(BUCK2, BUCK2), + PM8607_REG_RESOURCE(BUCK3, BUCK3), + PM8607_REG_RESOURCE(LDO1, LDO1), + PM8607_REG_RESOURCE(LDO2, LDO2), + PM8607_REG_RESOURCE(LDO3, LDO3), + PM8607_REG_RESOURCE(LDO4, LDO4), + PM8607_REG_RESOURCE(LDO5, LDO5), + PM8607_REG_RESOURCE(LDO6, LDO6), + PM8607_REG_RESOURCE(LDO7, LDO7), + PM8607_REG_RESOURCE(LDO8, LDO8), + PM8607_REG_RESOURCE(LDO9, LDO9), + PM8607_REG_RESOURCE(LDO10, LDO10), + PM8607_REG_RESOURCE(LDO12, LDO12), + PM8607_REG_RESOURCE(LDO14, LDO14), +}; + +#define PM8607_REG_DEVS(_name, _id) \ +{ \ + .name = "88pm8607-" #_name, \ + .num_resources = 1, \ + .resources = &pm8607_regulator_resources[PM8607_ID_##_id], \ +} + +static struct mfd_cell pm8607_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), +}; + +int pm860x_device_init(struct pm8607_chip *chip, + struct pm8607_platform_data *pdata) +{ + int i, count; + int ret; + + ret = pm8607_reg_read(chip, PM8607_CHIP_ID); + if (ret < 0) { + dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); + goto out; + } + if ((ret & PM8607_ID_MASK) == PM8607_ID) + dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", + ret); + else { + dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " + "Chip ID: %02x\n", ret); + goto out; + } + chip->chip_id = ret; + + ret = pm8607_reg_read(chip, PM8607_BUCK3); + if (ret < 0) { + dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); + goto out; + } + if (ret & PM8607_BUCK3_DOUBLE) + chip->buck3_double = 1; + + ret = pm8607_reg_read(chip, PM8607_MISC1); + if (ret < 0) { + dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); + goto out; + } + if (pdata->i2c_port == PI2C_PORT) + ret |= PM8607_MISC1_PI2C; + else + ret &= ~PM8607_MISC1_PI2C; + ret = pm8607_reg_write(chip, PM8607_MISC1, ret); + if (ret < 0) { + dev_err(chip->dev, "Failed to write MISC1 register: %d\n", ret); + goto out; + } + + count = ARRAY_SIZE(pm8607_devs); + for (i = 0; i < count; i++) { + ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], + 1, NULL, 0); + if (ret != 0) { + dev_err(chip->dev, "Failed to add subdevs\n"); + goto out; + } + } +out: + return ret; +} + +void pm8607_device_exit(struct pm8607_chip *chip) +{ + mfd_remove_devices(chip->dev); +} + +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM8607"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c new file mode 100644 index 000000000000..dda23cbfe415 --- /dev/null +++ b/drivers/mfd/88pm860x-i2c.c @@ -0,0 +1,202 @@ +/* + * I2C driver for Marvell 88PM8607 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang + * + * 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 + +static inline int pm8607_read_device(struct pm8607_chip *chip, + int reg, int bytes, void *dest) +{ + struct i2c_client *i2c = chip->client; + unsigned char data; + int ret; + + data = (unsigned char)reg; + ret = i2c_master_send(i2c, &data, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + return 0; +} + +static inline int pm8607_write_device(struct pm8607_chip *chip, + int reg, int bytes, void *src) +{ + struct i2c_client *i2c = chip->client; + unsigned char buf[bytes + 1]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int pm8607_reg_read(struct pm8607_chip *chip, int reg) +{ + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(pm8607_reg_read); + +int pm8607_reg_write(struct pm8607_chip *chip, int reg, + unsigned char data) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->write(chip, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_reg_write); + +int pm8607_bulk_read(struct pm8607_chip *chip, int reg, + int count, unsigned char *buf) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_bulk_read); + +int pm8607_bulk_write(struct pm8607_chip *chip, int reg, + int count, unsigned char *buf) +{ + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->write(chip, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm8607_bulk_write); + +int pm8607_set_bits(struct pm8607_chip *chip, int reg, + unsigned char mask, unsigned char data) +{ + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = chip->read(chip, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = chip->write(chip, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm8607_set_bits); + + +static const struct i2c_device_id pm860x_id_table[] = { + { "88PM8607", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm860x_id_table); + +static int __devinit pm860x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pm8607_platform_data *pdata = client->dev.platform_data; + struct pm8607_chip *chip; + int ret; + + chip = kzalloc(sizeof(struct pm8607_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->read = pm8607_read_device; + chip->write = pm8607_write_device; + memcpy(&chip->id, id, sizeof(struct i2c_device_id)); + i2c_set_clientdata(client, chip); + + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + + ret = pm860x_device_init(chip, pdata); + if (ret < 0) + goto out; + + + return 0; + +out: + i2c_set_clientdata(client, NULL); + kfree(chip); + return ret; +} + +static int __devexit pm860x_remove(struct i2c_client *client) +{ + struct pm8607_chip *chip = i2c_get_clientdata(client); + + kfree(chip); + return 0; +} + +static struct i2c_driver pm860x_driver = { + .driver = { + .name = "88PM860x", + .owner = THIS_MODULE, + }, + .probe = pm860x_probe, + .remove = __devexit_p(pm860x_remove), + .id_table = pm860x_id_table, +}; + +static int __init pm860x_i2c_init(void) +{ + int ret; + ret = i2c_add_driver(&pm860x_driver); + if (ret != 0) + pr_err("Failed to register 88PM860x I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(pm860x_i2c_init); + +static void __exit pm860x_i2c_exit(void) +{ + i2c_del_driver(&pm860x_driver); +} +module_exit(pm860x_i2c_exit); + +MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 78295d6a75f7..88fa200188cf 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,6 +2,7 @@ # Makefile for multifunction miscellaneous devices # +obj-$(CONFIG_MFD_88PM8607) += 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o @@ -55,5 +56,4 @@ obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB4500_CORE) += ab4500-core.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o -obj-$(CONFIG_MFD_88PM8607) += 88pm8607.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o -- cgit v1.2.3 From 53dbab7af9ca13fa95605e9a5c31bb803dcba363 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 8 Jan 2010 06:01:24 -0500 Subject: mfd: Support 88pm8606 in 860x driver 88PM8606 and 88PM8607 are two discrete chips used for power management. Hardware designer can use them together or only one of them according to requirement. There's some logic tightly linked between these two chips. For example, USB charger driver needs to access both chips by I2C interface. Now share one driver to these two devices. Only one I2C client is identified in platform init data. If another chip is also used, user should mark it in companion_addr field of platform init data. Then driver could create another I2C client for the companion chip. All I2C operations are accessed by 860x-i2c driver. In order to support both I2C client address, the read/write API is changed in below. reg_read(client, offset) reg_write(client, offset, data) The benefit is that client drivers only need one kind of read/write API. I2C and MFD driver can be shared in both 8606 and 8607. Since API is changed, update API in 8607 regulator driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 61 ++++++++++------ drivers/mfd/88pm860x-i2c.c | 172 ++++++++++++++++++++++++++++++-------------- 2 files changed, 160 insertions(+), 73 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index d1464e54e656..72b00304dc3a 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #define PM8607_REG_RESOURCE(_start, _end) \ @@ -67,18 +67,23 @@ static struct mfd_cell pm8607_devs[] = { PM8607_REG_DEVS(ldo14, LDO14), }; -int pm860x_device_init(struct pm8607_chip *chip, - struct pm8607_platform_data *pdata) +static void device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ +} + +static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, + struct pm860x_platform_data *pdata) { int i, count; int ret; - ret = pm8607_reg_read(chip, PM8607_CHIP_ID); + ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); if (ret < 0) { dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); goto out; } - if ((ret & PM8607_ID_MASK) == PM8607_ID) + if ((ret & PM8607_VERSION_MASK) == PM8607_VERSION) dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", ret); else { @@ -86,9 +91,9 @@ int pm860x_device_init(struct pm8607_chip *chip, "Chip ID: %02x\n", ret); goto out; } - chip->chip_id = ret; + chip->chip_version = ret; - ret = pm8607_reg_read(chip, PM8607_BUCK3); + ret = pm860x_reg_read(i2c, PM8607_BUCK3); if (ret < 0) { dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); goto out; @@ -96,20 +101,11 @@ int pm860x_device_init(struct pm8607_chip *chip, if (ret & PM8607_BUCK3_DOUBLE) chip->buck3_double = 1; - ret = pm8607_reg_read(chip, PM8607_MISC1); + ret = pm860x_reg_read(i2c, PM8607_MISC1); if (ret < 0) { dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); goto out; } - if (pdata->i2c_port == PI2C_PORT) - ret |= PM8607_MISC1_PI2C; - else - ret &= ~PM8607_MISC1_PI2C; - ret = pm8607_reg_write(chip, PM8607_MISC1, ret); - if (ret < 0) { - dev_err(chip->dev, "Failed to write MISC1 register: %d\n", ret); - goto out; - } count = ARRAY_SIZE(pm8607_devs); for (i = 0; i < count; i++) { @@ -121,14 +117,39 @@ int pm860x_device_init(struct pm8607_chip *chip, } } out: - return ret; + return; +} + +int pm860x_device_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + switch (chip->id) { + case CHIP_PM8606: + device_8606_init(chip, chip->client, pdata); + break; + case CHIP_PM8607: + device_8607_init(chip, chip->client, pdata); + break; + } + + if (chip->companion) { + switch (chip->id) { + case CHIP_PM8607: + device_8606_init(chip, chip->companion, pdata); + break; + case CHIP_PM8606: + device_8607_init(chip, chip->companion, pdata); + break; + } + } + return 0; } -void pm8607_device_exit(struct pm8607_chip *chip) +void pm860x_device_exit(struct pm860x_chip *chip) { mfd_remove_devices(chip->dev); } -MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM8607"); +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x"); MODULE_AUTHOR("Haojian Zhuang "); MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index dda23cbfe415..6d7dba2bce8a 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -1,5 +1,5 @@ /* - * I2C driver for Marvell 88PM8607 + * I2C driver for Marvell 88PM860x * * Copyright (C) 2009 Marvell International Ltd. * Haojian Zhuang @@ -12,12 +12,11 @@ #include #include #include -#include +#include -static inline int pm8607_read_device(struct pm8607_chip *chip, +static inline int pm860x_read_device(struct i2c_client *i2c, int reg, int bytes, void *dest) { - struct i2c_client *i2c = chip->client; unsigned char data; int ret; @@ -32,10 +31,9 @@ static inline int pm8607_read_device(struct pm8607_chip *chip, return 0; } -static inline int pm8607_write_device(struct pm8607_chip *chip, +static inline int pm860x_write_device(struct i2c_client *i2c, int reg, int bytes, void *src) { - struct i2c_client *i2c = chip->client; unsigned char buf[bytes + 1]; int ret; @@ -48,13 +46,14 @@ static inline int pm8607_write_device(struct pm8607_chip *chip, return 0; } -int pm8607_reg_read(struct pm8607_chip *chip, int reg) +int pm860x_reg_read(struct i2c_client *i2c, int reg) { + struct pm860x_chip *chip = i2c_get_clientdata(i2c); unsigned char data; int ret; mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, 1, &data); + ret = pm860x_read_device(i2c, reg, 1, &data); mutex_unlock(&chip->io_lock); if (ret < 0) @@ -62,111 +61,178 @@ int pm8607_reg_read(struct pm8607_chip *chip, int reg) else return (int)data; } -EXPORT_SYMBOL(pm8607_reg_read); +EXPORT_SYMBOL(pm860x_reg_read); -int pm8607_reg_write(struct pm8607_chip *chip, int reg, +int pm860x_reg_write(struct i2c_client *i2c, int reg, unsigned char data) { + struct pm860x_chip *chip = i2c_get_clientdata(i2c); int ret; mutex_lock(&chip->io_lock); - ret = chip->write(chip, reg, 1, &data); + ret = pm860x_write_device(i2c, reg, 1, &data); mutex_unlock(&chip->io_lock); return ret; } -EXPORT_SYMBOL(pm8607_reg_write); +EXPORT_SYMBOL(pm860x_reg_write); -int pm8607_bulk_read(struct pm8607_chip *chip, int reg, +int pm860x_bulk_read(struct i2c_client *i2c, int reg, int count, unsigned char *buf) { + struct pm860x_chip *chip = i2c_get_clientdata(i2c); int ret; mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, count, buf); + ret = pm860x_read_device(i2c, reg, count, buf); mutex_unlock(&chip->io_lock); return ret; } -EXPORT_SYMBOL(pm8607_bulk_read); +EXPORT_SYMBOL(pm860x_bulk_read); -int pm8607_bulk_write(struct pm8607_chip *chip, int reg, +int pm860x_bulk_write(struct i2c_client *i2c, int reg, int count, unsigned char *buf) { + struct pm860x_chip *chip = i2c_get_clientdata(i2c); int ret; mutex_lock(&chip->io_lock); - ret = chip->write(chip, reg, count, buf); + ret = pm860x_write_device(i2c, reg, count, buf); mutex_unlock(&chip->io_lock); return ret; } -EXPORT_SYMBOL(pm8607_bulk_write); +EXPORT_SYMBOL(pm860x_bulk_write); -int pm8607_set_bits(struct pm8607_chip *chip, int reg, +int pm860x_set_bits(struct i2c_client *i2c, int reg, unsigned char mask, unsigned char data) { + struct pm860x_chip *chip = i2c_get_clientdata(i2c); unsigned char value; int ret; mutex_lock(&chip->io_lock); - ret = chip->read(chip, reg, 1, &value); + ret = pm860x_read_device(i2c, reg, 1, &value); if (ret < 0) goto out; value &= ~mask; value |= data; - ret = chip->write(chip, reg, 1, &value); + ret = pm860x_write_device(i2c, reg, 1, &value); out: mutex_unlock(&chip->io_lock); return ret; } -EXPORT_SYMBOL(pm8607_set_bits); +EXPORT_SYMBOL(pm860x_set_bits); static const struct i2c_device_id pm860x_id_table[] = { - { "88PM8607", 0 }, + { "88PM860x", 0 }, {} }; MODULE_DEVICE_TABLE(i2c, pm860x_id_table); +static int verify_addr(struct i2c_client *i2c) +{ + unsigned short addr_8607[] = {0x30, 0x34}; + unsigned short addr_8606[] = {0x10, 0x11}; + int size, i; + + if (i2c == NULL) + return 0; + size = ARRAY_SIZE(addr_8606); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8606 + i)) + return CHIP_PM8606; + } + size = ARRAY_SIZE(addr_8607); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8607 + i)) + return CHIP_PM8607; + } + return 0; +} + static int __devinit pm860x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct pm8607_platform_data *pdata = client->dev.platform_data; - struct pm8607_chip *chip; - int ret; - - chip = kzalloc(sizeof(struct pm8607_chip), GFP_KERNEL); - if (chip == NULL) - return -ENOMEM; - - chip->client = client; - chip->dev = &client->dev; - chip->read = pm8607_read_device; - chip->write = pm8607_write_device; - memcpy(&chip->id, id, sizeof(struct i2c_device_id)); - i2c_set_clientdata(client, chip); - - mutex_init(&chip->io_lock); - dev_set_drvdata(chip->dev, chip); - - ret = pm860x_device_init(chip, pdata); - if (ret < 0) - goto out; - - + struct pm860x_platform_data *pdata = client->dev.platform_data; + static struct pm860x_chip *chip; + struct i2c_board_info i2c_info = { + .type = "88PM860x", + .platform_data = client->dev.platform_data, + }; + int addr_c, found_companion = 0; + + if (pdata == NULL) { + pr_info("No platform data in %s!\n", __func__); + return -EINVAL; + } + + /* + * Both client and companion client shares same platform driver. + * Driver distinguishes them by pdata->companion_addr. + * pdata->companion_addr is only assigned if companion chip exists. + * At the same time, the companion_addr shouldn't equal to client + * address. + */ + addr_c = pdata->companion_addr; + if (addr_c && (addr_c != client->addr)) { + i2c_info.addr = addr_c; + found_companion = 1; + } + + if (found_companion || (addr_c == 0)) { + chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->id = verify_addr(client); + chip->companion_addr = addr_c; + chip->client = client; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + + if (found_companion) { + /* + * If this driver is built in, probe function is + * recursive. + * If this driver is built as module, the next probe + * function is called after the first one finished. + */ + chip->companion = i2c_new_device(client->adapter, + &i2c_info); + } + } + + /* + * If companion chip existes, it's called by companion probe. + * If there's no companion chip, it's called by client probe. + */ + if ((addr_c == 0) || (addr_c == client->addr)) { + chip->companion = client; + i2c_set_clientdata(chip->companion, chip); + pm860x_device_init(chip, pdata); + } return 0; - -out: - i2c_set_clientdata(client, NULL); - kfree(chip); - return ret; } static int __devexit pm860x_remove(struct i2c_client *client) { - struct pm8607_chip *chip = i2c_get_clientdata(client); - + struct pm860x_chip *chip = i2c_get_clientdata(client); + + /* + * If companion existes, companion client is removed first. + * Because companion client is registered last and removed first. + */ + if (chip->companion_addr == client->addr) + return 0; + pm860x_device_exit(chip); + i2c_unregister_device(chip->companion); + i2c_set_clientdata(chip->companion, NULL); + i2c_set_clientdata(chip->client, NULL); kfree(chip); return 0; } -- cgit v1.2.3 From 2cc50bee9934deb6dfe32929a4c1742cf83d6db3 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 6 Jan 2010 17:07:13 -0500 Subject: mfd: Rename 88pm8607 to 88pm860x in mfd Rename 88PM8607 to 88PM860X in both Makefile and Kconfig under mfd directory. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 20 ++++++++++---------- drivers/mfd/Makefile | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d957373d77e4..815907eb70a4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -9,6 +9,16 @@ config MFD_CORE tristate default n +config MFD_88PM860X + bool "Support Marvell 88PM8606/88PM8607" + depends on I2C=y + select MFD_CORE + help + This supports for Marvell 88PM8606/88PM8607 Power Management IC. + This includes the I2C driver and the core APIs _only_, you have to + select individual components like voltage regulators, RTC and + battery-charger under the corresponding menus. + config MFD_SM501 tristate "Support for Silicon Motion SM501" ---help--- @@ -329,16 +339,6 @@ config EZX_PCAP This enables the PCAP ASIC present on EZX Phones. This is needed for MMC, TouchScreen, Sound, USB, etc.. -config MFD_88PM8607 - bool "Support Marvell 88PM8607" - depends on I2C=y - select MFD_CORE - help - This supports for Marvell 88PM8607 Power Management IC. This includes - the I2C driver and the core APIs _only_, you have to select - individual components like voltage regulators, RTC and - battery-charger under the corresponding menus. - config AB4500_CORE tristate "ST-Ericsson's AB4500 Mixed Signal Power management chip" depends on SPI diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 88fa200188cf..1e3ae062c1f6 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,7 +2,8 @@ # Makefile for multifunction miscellaneous devices # -obj-$(CONFIG_MFD_88PM8607) += 88pm860x-core.o 88pm860x-i2c.o +88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o +obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o -- cgit v1.2.3 From 5c42e8c4a9c86ea26ed4ecb732a842dea0dfb6b6 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Tue, 15 Dec 2009 16:01:47 -0500 Subject: mfd: Add irq support in 88pm860x 88PM860x is a complex PMIC device. It contains touch, charger, sound, rtc, backlight, led, and so on. Host communicates to 88PM860x by I2C bus. Use thread irq to support this usage case. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 226 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 219 insertions(+), 7 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 72b00304dc3a..9185f0d945f4 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -67,15 +68,209 @@ static struct mfd_cell pm8607_devs[] = { PM8607_REG_DEVS(ldo14, LDO14), }; -static void device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c, - struct pm860x_platform_data *pdata) +#define CHECK_IRQ(irq) \ +do { \ + if ((irq < 0) || (irq >= PM860X_NUM_IRQ)) \ + return -EINVAL; \ +} while (0) + +/* IRQs only occur on 88PM8607 */ +int pm860x_mask_irq(struct pm860x_chip *chip, int irq) +{ + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ + : chip->companion; + int offset, data, ret; + + CHECK_IRQ(irq); + + offset = (irq >> 3) + PM8607_INT_MASK_1; + data = 1 << (irq % 8); + ret = pm860x_set_bits(i2c, offset, data, 0); + + return ret; +} +EXPORT_SYMBOL(pm860x_mask_irq); + +int pm860x_unmask_irq(struct pm860x_chip *chip, int irq) +{ + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ + : chip->companion; + int offset, data, ret; + + CHECK_IRQ(irq); + + offset = (irq >> 3) + PM8607_INT_MASK_1; + data = 1 << (irq % 8); + ret = pm860x_set_bits(i2c, offset, data, data); + + return ret; +} +EXPORT_SYMBOL(pm860x_unmask_irq); + +#define INT_STATUS_NUM (3) + +static irqreturn_t pm8607_irq_thread(int irq, void *data) +{ + DECLARE_BITMAP(irq_status, PM860X_NUM_IRQ); + struct pm860x_chip *chip = data; + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ + : chip->companion; + unsigned char status_buf[INT_STATUS_NUM << 1]; + unsigned long value; + int i, ret; + + irq_status[0] = 0; + + /* read out status register */ + ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM << 1, status_buf); + if (ret < 0) + goto out; + if (chip->irq_mode) { + /* 0, clear by read. 1, clear by write */ + ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM, status_buf); + if (ret < 0) + goto out; + } + + /* clear masked interrupt status */ + for (i = 0, value = 0; i < INT_STATUS_NUM; i++) { + status_buf[i] &= status_buf[i + INT_STATUS_NUM]; + irq_status[0] |= status_buf[i] << (i * 8); + } + + while (!bitmap_empty(irq_status, PM860X_NUM_IRQ)) { + irq = find_first_bit(irq_status, PM860X_NUM_IRQ); + clear_bit(irq, irq_status); + dev_dbg(chip->dev, "Servicing IRQ #%d\n", irq); + + mutex_lock(&chip->irq_lock); + if (chip->irq[irq].handler) + chip->irq[irq].handler(irq, chip->irq[irq].data); + else { + pm860x_mask_irq(chip, irq); + dev_err(chip->dev, "Nobody cares IRQ %d. " + "Now mask it.\n", irq); + for (i = 0; i < (INT_STATUS_NUM << 1); i++) { + dev_err(chip->dev, "status[%d]:%x\n", i, + status_buf[i]); + } + } + mutex_unlock(&chip->irq_lock); + } +out: + return IRQ_HANDLED; +} + +int pm860x_request_irq(struct pm860x_chip *chip, int irq, + irq_handler_t handler, void *data) { + CHECK_IRQ(irq); + if (!handler) + return -EINVAL; + + mutex_lock(&chip->irq_lock); + chip->irq[irq].handler = handler; + chip->irq[irq].data = data; + mutex_unlock(&chip->irq_lock); + + return 0; } +EXPORT_SYMBOL(pm860x_request_irq); -static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, - struct pm860x_platform_data *pdata) +int pm860x_free_irq(struct pm860x_chip *chip, int irq) { - int i, count; + CHECK_IRQ(irq); + + mutex_lock(&chip->irq_lock); + chip->irq[irq].handler = NULL; + chip->irq[irq].data = NULL; + mutex_unlock(&chip->irq_lock); + + return 0; +} +EXPORT_SYMBOL(pm860x_free_irq); + +static int __devinit device_irq_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ + : chip->companion; + unsigned char status_buf[INT_STATUS_NUM]; + int data, mask, ret = -EINVAL; + + mutex_init(&chip->irq_lock); + + mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR + | PM8607_B0_MISC1_INT_MASK; + data = 0; + chip->irq_mode = 0; + if (pdata && pdata->irq_mode) { + /* + * irq_mode defines the way of clearing interrupt. If it's 1, + * clear IRQ by write. Otherwise, clear it by read. + * This control bit is valid from 88PM8607 B0 steping. + */ + data |= PM8607_B0_MISC1_INT_CLEAR; + chip->irq_mode = 1; + } + ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data); + if (ret < 0) + goto out; + + /* mask all IRQs */ + memset(status_buf, 0, INT_STATUS_NUM); + ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1, + INT_STATUS_NUM, status_buf); + if (ret < 0) + goto out; + + if (chip->irq_mode) { + /* clear interrupt status by write */ + memset(status_buf, 0xFF, INT_STATUS_NUM); + ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM, status_buf); + } else { + /* clear interrupt status by read */ + ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM, status_buf); + } + if (ret < 0) + goto out; + + memset(chip->irq, 0, sizeof(struct pm860x_irq) * PM860X_NUM_IRQ); + + ret = request_threaded_irq(i2c->irq, NULL, pm8607_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "88PM8607", chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + goto out; + } + chip->chip_irq = i2c->irq; + return 0; +out: + return ret; +} + +static void __devexit device_irq_exit(struct pm860x_chip *chip) +{ + if (chip->chip_irq >= 0) + free_irq(chip->chip_irq, chip); +} + +static void __devinit device_8606_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ +} + +static void __devinit device_8607_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ + int i, count, data; int ret; ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); @@ -91,7 +286,6 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, "Chip ID: %02x\n", ret); goto out; } - chip->chip_version = ret; ret = pm860x_reg_read(i2c, PM8607_BUCK3); if (ret < 0) { @@ -101,12 +295,26 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, if (ret & PM8607_BUCK3_DOUBLE) chip->buck3_double = 1; - ret = pm860x_reg_read(i2c, PM8607_MISC1); + ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); if (ret < 0) { dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); goto out; } + if (pdata && (pdata->i2c_port == PI2C_PORT)) + data = PM8607_B0_MISC1_PI2C; + else + data = 0; + ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data); + if (ret < 0) { + dev_err(chip->dev, "Failed to access MISC1:%d\n", ret); + goto out; + } + + ret = device_irq_init(chip, pdata); + if (ret < 0) + goto out; + count = ARRAY_SIZE(pm8607_devs); for (i = 0; i < count; i++) { ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], @@ -123,6 +331,8 @@ out: int pm860x_device_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) { + chip->chip_irq = -EINVAL; + switch (chip->id) { case CHIP_PM8606: device_8606_init(chip, chip->client, pdata); @@ -142,11 +352,13 @@ int pm860x_device_init(struct pm860x_chip *chip, break; } } + return 0; } void pm860x_device_exit(struct pm860x_chip *chip) { + device_irq_exit(chip); mfd_remove_devices(chip->dev); } -- cgit v1.2.3 From a16122bcacf050e7f83015183053cf799713cc37 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Tue, 15 Dec 2009 16:04:36 -0500 Subject: mfd: Append subdev into 88pm860x driver Append backlight, led & touch subdevs into 88pm860x driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 217 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 205 insertions(+), 12 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 9185f0d945f4..16f0dca707a7 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -17,6 +17,100 @@ #include #include +char pm860x_backlight_name[][MFD_NAME_SIZE] = { + "backlight-0", + "backlight-1", + "backlight-2", +}; +EXPORT_SYMBOL(pm860x_backlight_name); + +char pm860x_led_name[][MFD_NAME_SIZE] = { + "led0-red", + "led0-green", + "led0-blue", + "led1-red", + "led1-green", + "led1-blue", +}; +EXPORT_SYMBOL(pm860x_led_name); + +#define PM8606_BACKLIGHT_RESOURCE(_i, _x) \ +{ \ + .name = pm860x_backlight_name[_i], \ + .start = PM8606_##_x, \ + .end = PM8606_##_x, \ + .flags = IORESOURCE_IO, \ +} + +static struct resource backlight_resources[] = { + PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT1, WLED1A), + PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT2, WLED2A), + PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT3, WLED3A), +}; + +#define PM8606_BACKLIGHT_DEVS(_i) \ +{ \ + .name = "88pm860x-backlight", \ + .num_resources = 1, \ + .resources = &backlight_resources[_i], \ + .id = _i, \ +} + +static struct mfd_cell backlight_devs[] = { + PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT1), + PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT2), + PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT3), +}; + +#define PM8606_LED_RESOURCE(_i, _x) \ +{ \ + .name = pm860x_led_name[_i], \ + .start = PM8606_##_x, \ + .end = PM8606_##_x, \ + .flags = IORESOURCE_IO, \ +} + +static struct resource led_resources[] = { + PM8606_LED_RESOURCE(PM8606_LED1_RED, RGB2B), + PM8606_LED_RESOURCE(PM8606_LED1_GREEN, RGB2C), + PM8606_LED_RESOURCE(PM8606_LED1_BLUE, RGB2D), + PM8606_LED_RESOURCE(PM8606_LED2_RED, RGB1B), + PM8606_LED_RESOURCE(PM8606_LED2_GREEN, RGB1C), + PM8606_LED_RESOURCE(PM8606_LED2_BLUE, RGB1D), +}; + +#define PM8606_LED_DEVS(_i) \ +{ \ + .name = "88pm860x-led", \ + .num_resources = 1, \ + .resources = &led_resources[_i], \ + .id = _i, \ +} + +static struct mfd_cell led_devs[] = { + PM8606_LED_DEVS(PM8606_LED1_RED), + PM8606_LED_DEVS(PM8606_LED1_GREEN), + PM8606_LED_DEVS(PM8606_LED1_BLUE), + PM8606_LED_DEVS(PM8606_LED2_RED), + PM8606_LED_DEVS(PM8606_LED2_GREEN), + PM8606_LED_DEVS(PM8606_LED2_BLUE), +}; + +static struct resource touch_resources[] = { + { + .start = PM8607_IRQ_PEN, + .end = PM8607_IRQ_PEN, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell touch_devs[] = { + { + .name = "88pm860x-touch", + .num_resources = 1, + .resources = &touch_resources[0], + }, +}; #define PM8607_REG_RESOURCE(_start, _end) \ { \ @@ -25,7 +119,7 @@ .flags = IORESOURCE_IO, \ } -static struct resource pm8607_regulator_resources[] = { +static struct resource regulator_resources[] = { PM8607_REG_RESOURCE(BUCK1, BUCK1), PM8607_REG_RESOURCE(BUCK2, BUCK2), PM8607_REG_RESOURCE(BUCK3, BUCK3), @@ -47,10 +141,11 @@ static struct resource pm8607_regulator_resources[] = { { \ .name = "88pm8607-" #_name, \ .num_resources = 1, \ - .resources = &pm8607_regulator_resources[PM8607_ID_##_id], \ + .resources = ®ulator_resources[PM8607_ID_##_id], \ + .id = PM8607_ID_##_id, \ } -static struct mfd_cell pm8607_devs[] = { +static struct mfd_cell regulator_devs[] = { PM8607_REG_DEVS(buck1, BUCK1), PM8607_REG_DEVS(buck2, BUCK2), PM8607_REG_DEVS(buck3, BUCK3), @@ -192,6 +287,61 @@ int pm860x_free_irq(struct pm860x_chip *chip, int irq) } EXPORT_SYMBOL(pm860x_free_irq); +static int __devinit device_gpadc_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ + : chip->companion; + int use_gpadc = 0, data, ret; + + /* initialize GPADC without activating it */ + + if (pdata && pdata->touch) { + /* set GPADC MISC1 register */ + data = 0; + data |= (pdata->touch->gpadc_prebias << 1) + & PM8607_GPADC_PREBIAS_MASK; + data |= (pdata->touch->slot_cycle << 3) + & PM8607_GPADC_SLOT_CYCLE_MASK; + data |= (pdata->touch->off_scale << 5) + & PM8607_GPADC_OFF_SCALE_MASK; + data |= (pdata->touch->sw_cal << 7) + & PM8607_GPADC_SW_CAL_MASK; + if (data) { + ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data); + if (ret < 0) + goto out; + } + /* set tsi prebias time */ + if (pdata->touch->tsi_prebias) { + data = pdata->touch->tsi_prebias; + ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data); + if (ret < 0) + goto out; + } + /* set prebias & prechg time of pen detect */ + data = 0; + data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK; + data |= (pdata->touch->pen_prechg << 5) + & PM8607_PD_PRECHG_MASK; + if (data) { + ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data); + if (ret < 0) + goto out; + } + + use_gpadc = 1; + } + + /* turn on GPADC */ + if (use_gpadc) { + ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, + PM8607_GPADC_EN, PM8607_GPADC_EN); + } +out: + return ret; +} + static int __devinit device_irq_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) { @@ -264,14 +414,40 @@ static void __devinit device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c, struct pm860x_platform_data *pdata) { + int ret; + + if (pdata && pdata->backlight) { + ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], + ARRAY_SIZE(backlight_devs), + &backlight_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add backlight " + "subdev\n"); + goto out_dev; + } + } + + if (pdata && pdata->led) { + ret = mfd_add_devices(chip->dev, 0, &led_devs[0], + ARRAY_SIZE(led_devs), + &led_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add led " + "subdev\n"); + goto out_dev; + } + } + return; +out_dev: + mfd_remove_devices(chip->dev); + device_irq_exit(chip); } static void __devinit device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, struct pm860x_platform_data *pdata) { - int i, count, data; - int ret; + int data, ret; ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); if (ret < 0) { @@ -311,19 +487,36 @@ static void __devinit device_8607_init(struct pm860x_chip *chip, goto out; } + ret = device_gpadc_init(chip, pdata); + if (ret < 0) + goto out; + ret = device_irq_init(chip, pdata); if (ret < 0) goto out; - count = ARRAY_SIZE(pm8607_devs); - for (i = 0; i < count; i++) { - ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i], - 1, NULL, 0); - if (ret != 0) { - dev_err(chip->dev, "Failed to add subdevs\n"); - goto out; + ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], + ARRAY_SIZE(regulator_devs), + ®ulator_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + goto out_dev; + } + + if (pdata && pdata->touch) { + ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], + ARRAY_SIZE(touch_devs), + &touch_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add touch " + "subdev\n"); + goto out_dev; } } + return; +out_dev: + mfd_remove_devices(chip->dev); + device_irq_exit(chip); out: return; } -- cgit v1.2.3 From d50f8f339f6901fccc9d4292b65ce8b69d7413d4 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 8 Jan 2010 12:29:23 +0100 Subject: mfd: Initial max8925 support Basic Max8925 support, which is a power management IC from Maxim Semiconductor. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 ++ drivers/mfd/Makefile | 2 + drivers/mfd/max8925-core.c | 262 +++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/max8925-i2c.c | 210 ++++++++++++++++++++++++++++++++++++ 4 files changed, 483 insertions(+) create mode 100644 drivers/mfd/max8925-core.c create mode 100644 drivers/mfd/max8925-i2c.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 815907eb70a4..ee416eefb8e9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -194,6 +194,15 @@ config PMIC_ADP5520 individual components like LCD backlight, LEDs, GPIOs and Kepad under the corresponding menus. +config MFD_MAX8925 + tristate "Maxim Semiconductor MAX8925 PMIC Support" + depends on I2C + help + Say yes here to support for Maxim Semiconductor MAX8925. This is + a Power Management IC. This driver provies common support for + accessing the device, additional drivers must be enabled in order + to use the functionality of the device. + config MFD_WM8400 tristate "Support Wolfson Microelectronics WM8400" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 1e3ae062c1f6..261635700243 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -49,6 +49,8 @@ endif obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o 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_PCF50633_ADC) += pcf50633-adc.o diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c new file mode 100644 index 000000000000..3e26267960b1 --- /dev/null +++ b/drivers/mfd/max8925-core.c @@ -0,0 +1,262 @@ +/* + * Base driver for Maxim MAX8925 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang + * + * 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 + +#define IRQ_MODE_STATUS 0 +#define IRQ_MODE_MASK 1 + +static int __get_irq_offset(struct max8925_chip *chip, int irq, int mode, + int *offset, int *bit) +{ + if (!offset || !bit) + return -EINVAL; + + switch (chip->chip_id) { + case MAX8925_GPM: + *bit = irq % BITS_PER_BYTE; + if (irq < (BITS_PER_BYTE << 1)) { /* irq = [0,15] */ + *offset = (mode) ? MAX8925_CHG_IRQ1_MASK + : MAX8925_CHG_IRQ1; + if (irq >= BITS_PER_BYTE) + (*offset)++; + } else { /* irq = [16,31] */ + *offset = (mode) ? MAX8925_ON_OFF_IRQ1_MASK + : MAX8925_ON_OFF_IRQ1; + if (irq >= (BITS_PER_BYTE * 3)) + (*offset)++; + } + break; + case MAX8925_ADC: + *bit = irq % BITS_PER_BYTE; + *offset = (mode) ? MAX8925_TSC_IRQ_MASK : MAX8925_TSC_IRQ; + break; + default: + goto out; + } + return 0; +out: + dev_err(chip->dev, "Wrong irq #%d is assigned\n", irq); + return -EINVAL; +} + +static int __check_irq(int irq) +{ + if ((irq < 0) || (irq >= MAX8925_NUM_IRQ)) + return -EINVAL; + return 0; +} + +int max8925_mask_irq(struct max8925_chip *chip, int irq) +{ + int offset, bit, ret; + + ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); + if (ret < 0) + return ret; + ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 1 << bit); + return ret; +} + +int max8925_unmask_irq(struct max8925_chip *chip, int irq) +{ + int offset, bit, ret; + + ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); + if (ret < 0) + return ret; + ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 0); + return ret; +} + +#define INT_STATUS_NUM (MAX8925_NUM_IRQ / BITS_PER_BYTE) + +static irqreturn_t max8925_irq_thread(int irq, void *data) +{ + struct max8925_chip *chip = data; + unsigned long irq_status[INT_STATUS_NUM]; + unsigned char status_buf[INT_STATUS_NUM << 1]; + int i, ret; + + memset(irq_status, 0, sizeof(unsigned long) * INT_STATUS_NUM); + + /* all these interrupt status registers are read-only */ + switch (chip->chip_id) { + case MAX8925_GPM: + ret = max8925_bulk_read(chip->i2c, MAX8925_CHG_IRQ1, + 4, status_buf); + if (ret < 0) + goto out; + ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ1, + 2, &status_buf[4]); + if (ret < 0) + goto out; + ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ2, + 2, &status_buf[6]); + if (ret < 0) + goto out; + /* clear masked interrupt status */ + status_buf[0] &= (~status_buf[2] & CHG_IRQ1_MASK); + irq_status[0] |= status_buf[0]; + status_buf[1] &= (~status_buf[3] & CHG_IRQ2_MASK); + irq_status[0] |= (status_buf[1] << BITS_PER_BYTE); + status_buf[4] &= (~status_buf[5] & ON_OFF_IRQ1_MASK); + irq_status[0] |= (status_buf[4] << (BITS_PER_BYTE * 2)); + status_buf[6] &= (~status_buf[7] & ON_OFF_IRQ2_MASK); + irq_status[0] |= (status_buf[6] << (BITS_PER_BYTE * 3)); + break; + case MAX8925_ADC: + ret = max8925_bulk_read(chip->i2c, MAX8925_TSC_IRQ, + 2, status_buf); + if (ret < 0) + goto out; + /* clear masked interrupt status */ + status_buf[0] &= (~status_buf[1] & TSC_IRQ_MASK); + irq_status[0] |= status_buf[0]; + break; + default: + goto out; + } + + for_each_bit(i, &irq_status[0], MAX8925_NUM_IRQ) { + clear_bit(i, irq_status); + dev_dbg(chip->dev, "Servicing IRQ #%d in %s\n", i, chip->name); + + mutex_lock(&chip->irq_lock); + if (chip->irq[i].handler) + chip->irq[i].handler(i, chip->irq[i].data); + else { + max8925_mask_irq(chip, i); + dev_err(chip->dev, "Noboday cares IRQ #%d in %s. " + "Now mask it.\n", i, chip->name); + } + mutex_unlock(&chip->irq_lock); + } +out: + return IRQ_HANDLED; +} + +int max8925_request_irq(struct max8925_chip *chip, int irq, + irq_handler_t handler, void *data) +{ + if ((__check_irq(irq) < 0) || !handler) + return -EINVAL; + + mutex_lock(&chip->irq_lock); + chip->irq[irq].handler = handler; + chip->irq[irq].data = data; + mutex_unlock(&chip->irq_lock); + return 0; +} +EXPORT_SYMBOL(max8925_request_irq); + +int max8925_free_irq(struct max8925_chip *chip, int irq) +{ + if (__check_irq(irq) < 0) + return -EINVAL; + + mutex_lock(&chip->irq_lock); + chip->irq[irq].handler = NULL; + chip->irq[irq].data = NULL; + mutex_unlock(&chip->irq_lock); + return 0; +} +EXPORT_SYMBOL(max8925_free_irq); + +static int __devinit device_gpm_init(struct max8925_chip *chip, + struct i2c_client *i2c, + struct max8925_platform_data *pdata) +{ + int ret; + + /* mask all IRQs */ + ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ1_MASK, 0x7, 0x7); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ2_MASK, 0xff, 0xff); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff, 0xff); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ2_MASK, 0x3, 0x3); + if (ret < 0) + goto out; + + chip->name = "GPM"; + memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); + ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "max8925-gpm", chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + goto out; + } + chip->chip_irq = i2c->irq; + + /* enable hard-reset for ONKEY power-off */ + max8925_set_bits(i2c, MAX8925_SYSENSEL, 0x80, 0x80); +out: + return ret; +} + +static int __devinit device_adc_init(struct max8925_chip *chip, + struct i2c_client *i2c, + struct max8925_platform_data *pdata) +{ + int ret; + + /* mask all IRQs */ + ret = max8925_set_bits(i2c, MAX8925_TSC_IRQ_MASK, 3, 3); + + chip->name = "ADC"; + memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); + ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "max8925-adc", chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + goto out; + } + chip->chip_irq = i2c->irq; +out: + return ret; +} + +int __devinit max8925_device_init(struct max8925_chip *chip, + struct max8925_platform_data *pdata) +{ + switch (chip->chip_id) { + case MAX8925_GPM: + device_gpm_init(chip, chip->i2c, pdata); + break; + case MAX8925_ADC: + device_adc_init(chip, chip->i2c, pdata); + break; + } + return 0; +} + +void max8925_device_exit(struct max8925_chip *chip) +{ + if (chip->chip_irq >= 0) + free_irq(chip->chip_irq, chip); +} + +MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925"); +MODULE_AUTHOR("Haojian Zhuang + * + * 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 + +static inline int max8925_read_device(struct i2c_client *i2c, + int reg, int bytes, void *dest) +{ + unsigned char data; + unsigned char *buf; + int ret; + + buf = kzalloc(bytes + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + data = (unsigned char)reg; + ret = i2c_master_send(i2c, &data, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + memcpy(dest, buf, bytes); + return 0; +} + +static inline int max8925_write_device(struct i2c_client *i2c, + int reg, int bytes, void *src) +{ + unsigned char buf[bytes + 1]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int max8925_reg_read(struct i2c_client *i2c, int reg) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(max8925_reg_read); + +int max8925_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_reg_write); + +int max8925_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_read); + +int max8925_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_write); + +int max8925_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = max8925_write_device(i2c, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(max8925_set_bits); + + +static const struct i2c_device_id max8925_id_table[] = { + { "max8925", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max8925_id_table); + +static int __devinit max8925_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max8925_platform_data *pdata = client->dev.platform_data; + struct max8925_chip *chip; + + if (!pdata) { + pr_info("%s: platform data is missing\n", __func__); + return -EINVAL; + } + if ((pdata->chip_id <= MAX8925_INVALID) + || (pdata->chip_id >= MAX8925_MAX)) { + pr_info("#%s: wrong chip identification\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8925_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->i2c = client; + chip->chip_id = pdata->chip_id; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + max8925_device_init(chip, pdata); + + return 0; +} + +static int __devexit max8925_remove(struct i2c_client *client) +{ + struct max8925_chip *chip = i2c_get_clientdata(client); + + max8925_device_exit(chip); + i2c_set_clientdata(client, NULL); + kfree(chip); + return 0; +} + +static struct i2c_driver max8925_driver = { + .driver = { + .name = "max8925", + .owner = THIS_MODULE, + }, + .probe = max8925_probe, + .remove = __devexit_p(max8925_remove), + .id_table = max8925_id_table, +}; + +static int __init max8925_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&max8925_driver); + if (ret != 0) + pr_err("Failed to register MAX8925 I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(max8925_i2c_init); + +static void __exit max8925_i2c_exit(void) +{ + i2c_del_driver(&max8925_driver); +} +module_exit(max8925_i2c_exit); + +MODULE_DESCRIPTION("I2C Driver for Maxim 8925"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 1ad998934e9c6cbae91662a05e0cb8772b1f4f75 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 8 Jan 2010 12:43:29 -0500 Subject: mfd: Add subdevs in max8925 Add subdevs in MAX8925. MAX8925 includes regulator, backlight and touch components. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 1 + drivers/mfd/max8925-core.c | 142 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ee416eefb8e9..d63ab2eec661 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -197,6 +197,7 @@ config PMIC_ADP5520 config MFD_MAX8925 tristate "Maxim Semiconductor MAX8925 PMIC Support" depends on I2C + select MFD_CORE help Say yes here to support for Maxim Semiconductor MAX8925. This is a Power Management IC. This driver provies common support for diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index 3e26267960b1..f36c494b80f1 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -20,6 +20,109 @@ #define IRQ_MODE_STATUS 0 #define IRQ_MODE_MASK 1 +static struct resource backlight_resources[] = { + { + .name = "max8925-backlight", + .start = MAX8925_WLED_MODE_CNTL, + .end = MAX8925_WLED_CNTL, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell backlight_devs[] = { + { + .name = "max8925-backlight", + .num_resources = 1, + .resources = &backlight_resources[0], + .id = -1, + }, +}; + +static struct resource touch_resources[] = { + { + .name = "max8925-tsc", + .start = MAX8925_TSC_IRQ, + .end = MAX8925_ADC_RES_END, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell touch_devs[] = { + { + .name = "max8925-touch", + .num_resources = 1, + .resources = &touch_resources[0], + .id = -1, + }, +}; + +#define MAX8925_REG_RESOURCE(_start, _end) \ +{ \ + .start = MAX8925_##_start, \ + .end = MAX8925_##_end, \ + .flags = IORESOURCE_IO, \ +} + +static struct resource regulator_resources[] = { + MAX8925_REG_RESOURCE(SDCTL1, SDCTL1), + MAX8925_REG_RESOURCE(SDCTL2, SDCTL2), + MAX8925_REG_RESOURCE(SDCTL3, SDCTL3), + MAX8925_REG_RESOURCE(LDOCTL1, LDOCTL1), + MAX8925_REG_RESOURCE(LDOCTL2, LDOCTL2), + MAX8925_REG_RESOURCE(LDOCTL3, LDOCTL3), + MAX8925_REG_RESOURCE(LDOCTL4, LDOCTL4), + MAX8925_REG_RESOURCE(LDOCTL5, LDOCTL5), + MAX8925_REG_RESOURCE(LDOCTL6, LDOCTL6), + MAX8925_REG_RESOURCE(LDOCTL7, LDOCTL7), + MAX8925_REG_RESOURCE(LDOCTL8, LDOCTL8), + MAX8925_REG_RESOURCE(LDOCTL9, LDOCTL9), + MAX8925_REG_RESOURCE(LDOCTL10, LDOCTL10), + MAX8925_REG_RESOURCE(LDOCTL11, LDOCTL11), + MAX8925_REG_RESOURCE(LDOCTL12, LDOCTL12), + MAX8925_REG_RESOURCE(LDOCTL13, LDOCTL13), + MAX8925_REG_RESOURCE(LDOCTL14, LDOCTL14), + MAX8925_REG_RESOURCE(LDOCTL15, LDOCTL15), + MAX8925_REG_RESOURCE(LDOCTL16, LDOCTL16), + MAX8925_REG_RESOURCE(LDOCTL17, LDOCTL17), + MAX8925_REG_RESOURCE(LDOCTL18, LDOCTL18), + MAX8925_REG_RESOURCE(LDOCTL19, LDOCTL19), + MAX8925_REG_RESOURCE(LDOCTL20, LDOCTL20), +}; + +#define MAX8925_REG_DEVS(_id) \ +{ \ + .name = "max8925-regulator", \ + .num_resources = 1, \ + .resources = ®ulator_resources[MAX8925_ID_##_id], \ + .id = MAX8925_ID_##_id, \ +} + +static struct mfd_cell regulator_devs[] = { + MAX8925_REG_DEVS(SD1), + MAX8925_REG_DEVS(SD2), + MAX8925_REG_DEVS(SD3), + MAX8925_REG_DEVS(LDO1), + MAX8925_REG_DEVS(LDO2), + MAX8925_REG_DEVS(LDO3), + MAX8925_REG_DEVS(LDO4), + MAX8925_REG_DEVS(LDO5), + MAX8925_REG_DEVS(LDO6), + MAX8925_REG_DEVS(LDO7), + MAX8925_REG_DEVS(LDO8), + MAX8925_REG_DEVS(LDO9), + MAX8925_REG_DEVS(LDO10), + MAX8925_REG_DEVS(LDO11), + MAX8925_REG_DEVS(LDO12), + MAX8925_REG_DEVS(LDO13), + MAX8925_REG_DEVS(LDO14), + MAX8925_REG_DEVS(LDO15), + MAX8925_REG_DEVS(LDO16), + MAX8925_REG_DEVS(LDO17), + MAX8925_REG_DEVS(LDO18), + MAX8925_REG_DEVS(LDO19), + MAX8925_REG_DEVS(LDO20), +}; + static int __get_irq_offset(struct max8925_chip *chip, int irq, int mode, int *offset, int *bit) { @@ -210,6 +313,30 @@ static int __devinit device_gpm_init(struct max8925_chip *chip, /* enable hard-reset for ONKEY power-off */ max8925_set_bits(i2c, MAX8925_SYSENSEL, 0x80, 0x80); + + ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], + ARRAY_SIZE(regulator_devs), + ®ulator_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + goto out_irq; + } + + if (pdata && pdata->backlight) { + ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], + ARRAY_SIZE(backlight_devs), + &backlight_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add backlight subdev\n"); + goto out_dev; + } + } + return 0; +out_dev: + mfd_remove_devices(chip->dev); +out_irq: + if (chip->chip_irq) + free_irq(chip->chip_irq, chip); out: return ret; } @@ -233,6 +360,20 @@ static int __devinit device_adc_init(struct max8925_chip *chip, goto out; } chip->chip_irq = i2c->irq; + + if (pdata && pdata->touch) { + ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], + ARRAY_SIZE(touch_devs), + &touch_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add touch subdev\n"); + goto out_irq; + } + } + return 0; +out_irq: + if (chip->chip_irq) + free_irq(chip->chip_irq, chip); out: return ret; } @@ -255,6 +396,7 @@ void max8925_device_exit(struct max8925_chip *chip) { if (chip->chip_irq >= 0) free_irq(chip->chip_irq, chip); + mfd_remove_devices(chip->dev); } MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925"); -- cgit v1.2.3 From 6048a3dd2371c58611ea0ab8b306f8f1469399ae Mon Sep 17 00:00:00 2001 From: Cory Maccarrone Date: Tue, 19 Jan 2010 11:22:45 +0100 Subject: mfd: Add HTCPLD driver This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically: * it can have several sub-devices, each with its own I2C address * Each sub-device provides 8 output and 8 input pins * The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers. Signed-off-by: Cory Maccarrone Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/htc-i2cpld.c | 710 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 720 insertions(+) create mode 100644 drivers/mfd/htc-i2cpld.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d63ab2eec661..9bcd447e71e2 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -78,6 +78,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C=y + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + config UCB1400_CORE tristate "Philips UCB1400 Core driver" depends on AC97_BUS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 261635700243..142d31202b14 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o diff --git a/drivers/mfd/htc-i2cpld.c b/drivers/mfd/htc-i2cpld.c new file mode 100644 index 000000000000..37b9fdab4f36 --- /dev/null +++ b/drivers/mfd/htc-i2cpld.c @@ -0,0 +1,710 @@ +/* + * htc-i2cpld.c + * Chip driver for an unknown CPLD chip found on omap850 HTC devices like + * the HTC Wizard and HTC Herald. + * The cpld is located on the i2c bus and acts as an input/output GPIO + * extender. + * + * Copyright (C) 2009 Cory Maccarrone + * + * Based on work done in the linwizard project + * Copyright (C) 2008-2009 Angelo Arrifano + * + * 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 +#include +#include +#include + +struct htcpld_chip { + spinlock_t lock; + + /* chip info */ + u8 reset; + u8 addr; + struct device *dev; + struct i2c_client *client; + + /* Output details */ + u8 cache_out; + struct gpio_chip chip_out; + + /* Input details */ + u8 cache_in; + struct gpio_chip chip_in; + + u16 irqs_enabled; + uint irq_start; + int nirqs; + + /* + * Work structure to allow for setting values outside of any + * possible interrupt context + */ + struct work_struct set_val_work; +}; + +struct htcpld_data { + /* irq info */ + u16 irqs_enabled; + uint irq_start; + int nirqs; + uint chained_irq; + unsigned int int_reset_gpio_hi; + unsigned int int_reset_gpio_lo; + + /* htcpld info */ + struct htcpld_chip *chip; + unsigned int nchips; +}; + +/* There does not appear to be a way to proactively mask interrupts + * on the htcpld chip itself. So, we simply ignore interrupts that + * aren't desired. */ +static void htcpld_mask(unsigned int irq) +{ + struct htcpld_chip *chip = get_irq_chip_data(irq); + chip->irqs_enabled &= ~(1 << (irq - chip->irq_start)); + pr_debug("HTCPLD mask %d %04x\n", irq, chip->irqs_enabled); +} +static void htcpld_unmask(unsigned int irq) +{ + struct htcpld_chip *chip = get_irq_chip_data(irq); + chip->irqs_enabled |= 1 << (irq - chip->irq_start); + pr_debug("HTCPLD unmask %d %04x\n", irq, chip->irqs_enabled); +} + +static int htcpld_set_type(unsigned int irq, unsigned int flags) +{ + struct irq_desc *d = irq_to_desc(irq); + + if (!d) { + pr_err("HTCPLD invalid IRQ: %d\n", irq); + return -EINVAL; + } + + if (flags & ~IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + /* We only allow edge triggering */ + if (flags & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)) + return -EINVAL; + + d->status &= ~IRQ_TYPE_SENSE_MASK; + d->status |= flags; + + return 0; +} + +static struct irq_chip htcpld_muxed_chip = { + .name = "htcpld", + .mask = htcpld_mask, + .unmask = htcpld_unmask, + .set_type = htcpld_set_type, +}; + +/* To properly dispatch IRQ events, we need to read from the + * chip. This is an I2C action that could possibly sleep + * (which is bad in interrupt context) -- so we use a threaded + * interrupt handler to get around that. + */ +static irqreturn_t htcpld_handler(int irq, void *dev) +{ + struct htcpld_data *htcpld = dev; + unsigned int i; + unsigned long flags; + int irqpin; + struct irq_desc *desc; + + if (!htcpld) { + pr_debug("htcpld is null in ISR\n"); + return IRQ_HANDLED; + } + + /* + * For each chip, do a read of the chip and trigger any interrupts + * desired. The interrupts will be triggered from LSB to MSB (i.e. + * bit 0 first, then bit 1, etc.) + * + * For chips that have no interrupt range specified, just skip 'em. + */ + for (i = 0; i < htcpld->nchips; i++) { + struct htcpld_chip *chip = &htcpld->chip[i]; + struct i2c_client *client; + int val; + unsigned long uval, old_val; + + if (!chip) { + pr_debug("chip %d is null in ISR\n", i); + continue; + } + + if (chip->nirqs == 0) + continue; + + client = chip->client; + if (!client) { + pr_debug("client %d is null in ISR\n", i); + continue; + } + + /* Scan the chip */ + val = i2c_smbus_read_byte_data(client, chip->cache_out); + if (val < 0) { + /* Throw a warning and skip this chip */ + dev_warn(chip->dev, "Unable to read from chip: %d\n", + val); + continue; + } + + uval = (unsigned long)val; + + spin_lock_irqsave(&chip->lock, flags); + + /* Save away the old value so we can compare it */ + old_val = chip->cache_in; + + /* Write the new value */ + chip->cache_in = uval; + + spin_unlock_irqrestore(&chip->lock, flags); + + /* + * For each bit in the data (starting at bit 0), trigger + * associated interrupts. + */ + for (irqpin = 0; irqpin < chip->nirqs; irqpin++) { + unsigned oldb, newb; + int flags; + + irq = chip->irq_start + irqpin; + desc = irq_to_desc(irq); + flags = desc->status; + + /* Run the IRQ handler, but only if the bit value + * changed, and the proper flags are set */ + oldb = (old_val >> irqpin) & 1; + newb = (uval >> irqpin) & 1; + + if ((!oldb && newb && (flags & IRQ_TYPE_EDGE_RISING)) || + (oldb && !newb && + (flags & IRQ_TYPE_EDGE_FALLING))) { + pr_debug("fire IRQ %d\n", irqpin); + desc->handle_irq(irq, desc); + } + } + } + + /* + * In order to continue receiving interrupts, the int_reset_gpio must + * be asserted. + */ + if (htcpld->int_reset_gpio_hi) + gpio_set_value(htcpld->int_reset_gpio_hi, 1); + if (htcpld->int_reset_gpio_lo) + gpio_set_value(htcpld->int_reset_gpio_lo, 0); + + return IRQ_HANDLED; +} + +/* + * The GPIO set routines can be called from interrupt context, especially if, + * for example they're attached to the led-gpio framework and a trigger is + * enabled. As such, we declared work above in the htcpld_chip structure, + * and that work is scheduled in the set routine. The kernel can then run + * the I2C functions, which will sleep, in process context. + */ +void htcpld_chip_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct i2c_client *client; + struct htcpld_chip *chip_data; + unsigned long flags; + + chip_data = container_of(chip, struct htcpld_chip, chip_out); + if (!chip_data) + return; + + client = chip_data->client; + if (client == NULL) + return; + + spin_lock_irqsave(&chip_data->lock, flags); + if (val) + chip_data->cache_out |= (1 << offset); + else + chip_data->cache_out &= ~(1 << offset); + spin_unlock_irqrestore(&chip_data->lock, flags); + + schedule_work(&(chip_data->set_val_work)); +} + +void htcpld_chip_set_ni(struct work_struct *work) +{ + struct htcpld_chip *chip_data; + struct i2c_client *client; + + chip_data = container_of(work, struct htcpld_chip, set_val_work); + client = chip_data->client; + i2c_smbus_read_byte_data(client, chip_data->cache_out); +} + +int htcpld_chip_get(struct gpio_chip *chip, unsigned offset) +{ + struct htcpld_chip *chip_data; + int val = 0; + int is_input = 0; + + /* Try out first */ + chip_data = container_of(chip, struct htcpld_chip, chip_out); + if (!chip_data) { + /* Try in */ + is_input = 1; + chip_data = container_of(chip, struct htcpld_chip, chip_in); + if (!chip_data) + return -EINVAL; + } + + /* Determine if this is an input or output GPIO */ + if (!is_input) + /* Use the output cache */ + val = (chip_data->cache_out >> offset) & 1; + else + /* Use the input cache */ + val = (chip_data->cache_in >> offset) & 1; + + if (val) + return 1; + else + return 0; +} + +static int htcpld_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + htcpld_chip_set(chip, offset, value); + return 0; +} + +static int htcpld_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + /* + * No-op: this function can only be called on the input chip. + * We do however make sure the offset is within range. + */ + return (offset < chip->ngpio) ? 0 : -EINVAL; +} + +int htcpld_chip_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct htcpld_chip *chip_data; + + chip_data = container_of(chip, struct htcpld_chip, chip_in); + + if (offset < chip_data->nirqs) + return chip_data->irq_start + offset; + else + return -EINVAL; +} + +void htcpld_chip_reset(struct i2c_client *client) +{ + struct htcpld_chip *chip_data = i2c_get_clientdata(client); + if (!chip_data) + return; + + i2c_smbus_read_byte_data( + client, (chip_data->cache_out = chip_data->reset)); +} + +static int __devinit htcpld_setup_chip_irq( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct htcpld_chip *chip; + struct htcpld_chip_platform_data *plat_chip_data; + unsigned int irq, irq_end; + int ret = 0; + + /* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + /* Setup irq handlers */ + irq_end = chip->irq_start + chip->nirqs; + for (irq = chip->irq_start; irq < irq_end; irq++) { + set_irq_chip(irq, &htcpld_muxed_chip); + set_irq_chip_data(irq, chip); + set_irq_handler(irq, handle_simple_irq); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); +#else + set_irq_probe(irq); +#endif + } + + return ret; +} + +static int __devinit htcpld_register_chip_i2c( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct htcpld_chip *chip; + struct htcpld_chip_platform_data *plat_chip_data; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct i2c_board_info info; + + /* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + adapter = i2c_get_adapter(pdata->i2c_adapter_id); + if (adapter == NULL) { + /* Eek, no such I2C adapter! Bail out. */ + dev_warn(dev, "Chip at i2c address 0x%x: Invalid i2c adapter %d\n", + plat_chip_data->addr, pdata->i2c_adapter_id); + return -ENODEV; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + dev_warn(dev, "i2c adapter %d non-functional\n", + pdata->i2c_adapter_id); + return -EINVAL; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = plat_chip_data->addr; + strlcpy(info.type, "htcpld-chip", I2C_NAME_SIZE); + info.platform_data = chip; + + /* Add the I2C device. This calls the probe() function. */ + client = i2c_new_device(adapter, &info); + if (!client) { + /* I2C device registration failed, contineu with the next */ + dev_warn(dev, "Unable to add I2C device for 0x%x\n", + plat_chip_data->addr); + return -ENODEV; + } + + i2c_set_clientdata(client, chip); + snprintf(client->name, I2C_NAME_SIZE, "Chip_0x%d", client->addr); + chip->client = client; + + /* Reset the chip */ + htcpld_chip_reset(client); + chip->cache_in = i2c_smbus_read_byte_data(client, chip->cache_out); + + return 0; +} + +static void __devinit htcpld_unregister_chip_i2c( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct htcpld_chip *chip; + + /* Get the platform and driver data */ + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + + if (chip->client) + i2c_unregister_device(chip->client); +} + +static int __devinit htcpld_register_chip_gpio( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct htcpld_chip *chip; + struct htcpld_chip_platform_data *plat_chip_data; + struct gpio_chip *gpio_chip; + int ret = 0; + + /* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + /* Setup the GPIO chips */ + gpio_chip = &(chip->chip_out); + gpio_chip->label = "htcpld-out"; + gpio_chip->dev = dev; + gpio_chip->owner = THIS_MODULE; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = htcpld_chip_set; + gpio_chip->direction_input = NULL; + gpio_chip->direction_output = htcpld_direction_output; + gpio_chip->base = plat_chip_data->gpio_out_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + gpio_chip = &(chip->chip_in); + gpio_chip->label = "htcpld-in"; + gpio_chip->dev = dev; + gpio_chip->owner = THIS_MODULE; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = NULL; + gpio_chip->direction_input = htcpld_direction_input; + gpio_chip->direction_output = NULL; + gpio_chip->to_irq = htcpld_chip_to_irq; + gpio_chip->base = plat_chip_data->gpio_in_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + /* Add the GPIO chips */ + ret = gpiochip_add(&(chip->chip_out)); + if (ret) { + dev_warn(dev, "Unable to register output GPIOs for 0x%x: %d\n", + plat_chip_data->addr, ret); + return ret; + } + + ret = gpiochip_add(&(chip->chip_in)); + if (ret) { + int error; + + dev_warn(dev, "Unable to register input GPIOs for 0x%x: %d\n", + plat_chip_data->addr, ret); + + error = gpiochip_remove(&(chip->chip_out)); + if (error) + dev_warn(dev, "Error while trying to unregister gpio chip: %d\n", error); + + return ret; + } + + return 0; +} + +static int __devinit htcpld_setup_chips(struct platform_device *pdev) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + int i; + + /* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld = platform_get_drvdata(pdev); + + /* Setup each chip's output GPIOs */ + htcpld->nchips = pdata->num_chip; + htcpld->chip = kzalloc(sizeof(struct htcpld_chip) * htcpld->nchips, + GFP_KERNEL); + if (!htcpld->chip) { + dev_warn(dev, "Unable to allocate memory for chips\n"); + return -ENOMEM; + } + + /* Add the chips as best we can */ + for (i = 0; i < htcpld->nchips; i++) { + int ret; + + /* Setup the HTCPLD chips */ + htcpld->chip[i].reset = pdata->chip[i].reset; + htcpld->chip[i].cache_out = pdata->chip[i].reset; + htcpld->chip[i].cache_in = 0; + htcpld->chip[i].dev = dev; + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; + + INIT_WORK(&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); + spin_lock_init(&(htcpld->chip[i].lock)); + + /* Setup the interrupts for the chip */ + if (htcpld->chained_irq) { + ret = htcpld_setup_chip_irq(pdev, i); + if (ret) + continue; + } + + /* Register the chip with I2C */ + ret = htcpld_register_chip_i2c(pdev, i); + if (ret) + continue; + + + /* Register the chips with the GPIO subsystem */ + ret = htcpld_register_chip_gpio(pdev, i); + if (ret) { + /* Unregister the chip from i2c and continue */ + htcpld_unregister_chip_i2c(pdev, i); + continue; + } + + dev_info(dev, "Registered chip at 0x%x\n", pdata->chip[i].addr); + } + + return 0; +} + +static int __devinit htcpld_core_probe(struct platform_device *pdev) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct resource *res; + int ret = 0; + + if (!dev) + return -ENODEV; + + pdata = dev->platform_data; + if (!pdata) { + dev_warn(dev, "Platform data not found for htcpld core!\n"); + return -ENXIO; + } + + htcpld = kzalloc(sizeof(struct htcpld_data), GFP_KERNEL); + if (!htcpld) + return -ENOMEM; + + /* Find chained irq */ + ret = -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) { + int flags; + htcpld->chained_irq = res->start; + + /* Setup the chained interrupt handler */ + flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + ret = request_threaded_irq(htcpld->chained_irq, + NULL, htcpld_handler, + flags, pdev->name, htcpld); + if (ret) { + dev_warn(dev, "Unable to setup chained irq handler: %d\n", ret); + goto fail; + } else + device_init_wakeup(dev, 0); + } + + /* Set the driver data */ + platform_set_drvdata(pdev, htcpld); + + /* Setup the htcpld chips */ + ret = htcpld_setup_chips(pdev); + if (ret) + goto fail; + + /* Request the GPIO(s) for the int reset and set them up */ + if (pdata->int_reset_gpio_hi) { + ret = gpio_request(pdata->int_reset_gpio_hi, "htcpld-core"); + if (ret) { + /* + * If it failed, that sucks, but we can probably + * continue on without it. + */ + dev_warn(dev, "Unable to request int_reset_gpio_hi -- interrupts may not work\n"); + htcpld->int_reset_gpio_hi = 0; + } else { + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; + gpio_set_value(htcpld->int_reset_gpio_hi, 1); + } + } + + if (pdata->int_reset_gpio_lo) { + ret = gpio_request(pdata->int_reset_gpio_lo, "htcpld-core"); + if (ret) { + /* + * If it failed, that sucks, but we can probably + * continue on without it. + */ + dev_warn(dev, "Unable to request int_reset_gpio_lo -- interrupts may not work\n"); + htcpld->int_reset_gpio_lo = 0; + } else { + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; + gpio_set_value(htcpld->int_reset_gpio_lo, 0); + } + } + + dev_info(dev, "Initialized successfully\n"); + return 0; + +fail: + kfree(htcpld); + return ret; +} + +/* The I2C Driver -- used internally */ +static const struct i2c_device_id htcpld_chip_id[] = { + { "htcpld-chip", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, htcpld_chip_id); + + +static struct i2c_driver htcpld_chip_driver = { + .driver = { + .name = "htcpld-chip", + }, + .id_table = htcpld_chip_id, +}; + +/* The Core Driver */ +static struct platform_driver htcpld_core_driver = { + .driver = { + .name = "i2c-htcpld", + }, +}; + +static int __init htcpld_core_init(void) +{ + int ret; + + /* Register the I2C Chip driver */ + ret = i2c_add_driver(&htcpld_chip_driver); + if (ret) + return ret; + + /* Probe for our chips */ + return platform_driver_probe(&htcpld_core_driver, htcpld_core_probe); +} + +static void __exit htcpld_core_exit(void) +{ + i2c_del_driver(&htcpld_chip_driver); + platform_driver_unregister(&htcpld_core_driver); +} + +module_init(htcpld_core_init); +module_exit(htcpld_core_exit); + +MODULE_AUTHOR("Cory Maccarrone "); +MODULE_DESCRIPTION("I2C HTC PLD Driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 2071db4848ea809887e97248dd3dec02a2e3dd6b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 19 Jan 2010 11:41:52 +0100 Subject: mfd: Use AB3100 MFD core IRQ for interrupt randomness This reintroduces the entropy sampling of the AB3100 IRQ as the IRQF_SAMPLE_RANDOM is going out according to the feature removal schedule. I'm trying to do this the right way then, so CC:ing some random people for a quick review. We add entropy for interrupt events in the AB3100 which are truly random in nature, like external cables being connected, voltages on batteries dropping below certain ranges, ADC triggers or overheating. Signed-off-by: Linus Walleij Cc: Robin Getz Acked-by: Matt Mackall Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 4 ++-- drivers/mfd/ab3100-core.c | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9bcd447e71e2..ea2557bb0bd4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -329,8 +329,8 @@ config PCF50633_GPIO the PCF50633 chip. config AB3100_CORE - tristate "ST-Ericsson AB3100 Mixed Signal Circuit core functions" - depends on I2C + bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" + depends on I2C=y default y if ARCH_U300 help Select this to enable the AB3100 Mixed Signal IC core diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index aa3824a1b4f2..a2ce3b6af4a2 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 ST-Ericsson + * Copyright (C) 2007-2010 ST-Ericsson * License terms: GNU General Public License (GPL) version 2 * Low-level core for exclusive access to the AB3100 IC on the I2C bus * and some basic chip-configuration. @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -376,6 +377,8 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data) u32 fatevent; int err; + add_interrupt_randomness(irq); + err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, event_regs, 3); if (err) @@ -720,10 +723,7 @@ static struct platform_device ab3100_##devname##_device = { \ .id = -1, \ } -/* - * This lists all the subdevices and corresponding register - * ranges. - */ +/* This lists all the subdevices */ AB3100_DEVICE(dac, "ab3100-dac"); AB3100_DEVICE(leds, "ab3100-leds"); AB3100_DEVICE(power, "ab3100-power"); @@ -889,10 +889,11 @@ static int __init ab3100_probe(struct i2c_client *client, if (err) goto exit_no_setup; - /* This real unpredictable IRQ is of course sampled for entropy */ err = request_threaded_irq(client->irq, NULL, ab3100_irq_handler, - IRQF_ONESHOT, - "ab3100-core", ab3100); + IRQF_ONESHOT, "ab3100-core", ab3100); + /* This real unpredictable IRQ is of course sampled for entropy */ + rand_initialize_irq(client->irq); + if (err) goto exit_no_irq; -- cgit v1.2.3 From ca229f1b624a72a1c9eb1d94bbca7a0c094a1b8b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 17 Jan 2010 20:57:43 +0100 Subject: mfd: Fix debugfs code in AB3100 OTP driver This fixes the aged and unreachable debugfs code in the AB3100 OTP driver so that it's (A) reachable and (B) works. Bug detected in parallell by Christoph Egger using VAMOS and Robert P. J. Day by his kernel scanning script. Signed-off-by: Linus Walleij Cc: Christoph Egger Cc: Robert P. J. Day Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3100-otp.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 0499b2031a2c..b603469dff69 100644 --- a/drivers/mfd/ab3100-otp.c +++ b/drivers/mfd/ab3100-otp.c @@ -13,6 +13,7 @@ #include #include #include +#include /* The OTP registers */ #define AB3100_OTP0 0xb0 @@ -95,11 +96,10 @@ static int __init ab3100_otp_read(struct ab3100_otp *otp) * This is a simple debugfs human-readable file that dumps out * the contents of the OTP. */ -#ifdef CONFIG_DEBUGFS -static int show_otp(struct seq_file *s, void *v) +#ifdef CONFIG_DEBUG_FS +static int ab3100_show_otp(struct seq_file *s, void *v) { struct ab3100_otp *otp = s->private; - int err; seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); @@ -113,7 +113,7 @@ static int show_otp(struct seq_file *s, void *v) static int ab3100_otp_open(struct inode *inode, struct file *file) { - return single_open(file, ab3100_otp_show, inode->i_private); + return single_open(file, ab3100_show_otp, inode->i_private); } static const struct file_operations ab3100_otp_operations = { @@ -131,13 +131,14 @@ static int __init ab3100_otp_init_debugfs(struct device *dev, &ab3100_otp_operations); if (!otp->debugfs) { dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); - return err; + return -ENOENT; } + return 0; } static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) { - debugfs_remove_file(otp->debugfs); + debugfs_remove(otp->debugfs); } #else /* Compile this out if debugfs not selected */ -- cgit v1.2.3 From f7ea2dc59ed46dcd0f1cfaccda02211f4507207b Mon Sep 17 00:00:00 2001 From: Christoph Egger Date: Fri, 15 Jan 2010 15:33:46 +0100 Subject: mfd: Remove leftover from discontinued TWL4030 battery patch The TWL4030_BCI_BATTERY config option originates from a patch to the omap git tree. However inclusion in linux was seemingly rejected and the functionality nears inclusion under a different name so this removes the bits of the old version that made it into the mainline kernel again. Signed-off-by: Christoph Egger Signed-off-by: Samuel Ortiz --- drivers/mfd/twl-core.c | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 19a930d06241..d81003f4867e 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -58,13 +58,6 @@ #define DRIVER_NAME "twl" -#if defined(CONFIG_TWL4030_BCI_BATTERY) || \ - defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) -#define twl_has_bci() true -#else -#define twl_has_bci() false -#endif - #if defined(CONFIG_KEYBOARD_TWL4030) || defined(CONFIG_KEYBOARD_TWL4030_MODULE) #define twl_has_keypad() true #else @@ -588,18 +581,6 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) struct device *child; unsigned sub_chip_id; - if (twl_has_bci() && pdata->bci && - !(features & (TPS_SUBSET | TWL5031))) { - child = add_child(3, "twl4030_bci", - pdata->bci, sizeof(*pdata->bci), - false, - /* irq0 = CHG_PRES, irq1 = BCI */ - pdata->irq_base + BCI_PRES_INTR_OFFSET, - pdata->irq_base + BCI_INTR_OFFSET); - if (IS_ERR(child)) - return PTR_ERR(child); - } - if (twl_has_gpio() && pdata->gpio) { child = add_child(SUB_CHIP_ID1, "twl4030_gpio", pdata->gpio, sizeof(*pdata->gpio), -- cgit v1.2.3 From bef3e20c9f8ce5cb8809cf33494d95a78db07857 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 20 Jan 2010 19:55:34 -0800 Subject: mfd: htc-i2cpld depends on GPIOLIB htc-i2cpld should depend on GPIOLIB, otherwise there are many build errors: drivers/mfd/htc-i2cpld.c:50: error: field 'chip_out' has incomplete type drivers/mfd/htc-i2cpld.c:54: error: field 'chip_in' has incomplete type drivers/mfd/htc-i2cpld.c:243: warning: type defaults to 'int' in declaration of '__mptr' drivers/mfd/htc-i2cpld.c:243: warning: initialization from incompatible pointer type drivers/mfd/htc-i2cpld.c:278: warning: type defaults to 'int' in declaration of '__mptr' drivers/mfd/htc-i2cpld.c:278: warning: initialization from incompatible pointer type drivers/mfd/htc-i2cpld.c:282: warning: type defaults to 'int' in declaration of '__mptr' drivers/mfd/htc-i2cpld.c:282: warning: initialization from incompatible pointer type drivers/mfd/htc-i2cpld.c:315: error: dereferencing pointer to incomplete type drivers/mfd/htc-i2cpld.c:322: warning: type defaults to 'int' in declaration of '__mptr' drivers/mfd/htc-i2cpld.c:322: warning: initialization from incompatible pointer type drivers/mfd/htc-i2cpld.c:467+: error: dereferencing pointer to incomplete type [19X] drivers/mfd/htc-i2cpld.c:490: error: implicit declaration of function 'gpiochip_add' drivers/mfd/htc-i2cpld.c:504: error: implicit declaration of function 'gpiochip_remove' Signed-off-by: Randy Dunlap Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ea2557bb0bd4..a760dbefd27a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -80,7 +80,7 @@ config HTC_PASIC3 config HTC_I2CPLD bool "HTC I2C PLD chip support" - depends on I2C=y + depends on I2C=y && GPIOLIB help If you say yes here you get support for the supposed CPLD found on omap850 HTC devices like the HTC Wizard and HTC Herald. -- cgit v1.2.3 From 9e50108668a70a9927257298bd4e679300124420 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 29 Jan 2010 18:20:29 +0000 Subject: mfd: Add initial WM8994 support The WM8994 is a highly integrated ultra low power audio hub CODEC. Since it includes on-board regulators and GPIOs it is represented as a multi-function device, though the overwhelming majority of the functionality is provided by the ASoC CODEC driver. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 12 ++ drivers/mfd/Makefile | 1 + drivers/mfd/wm8994-core.c | 537 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 550 insertions(+) create mode 100644 drivers/mfd/wm8994-core.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a760dbefd27a..64fbe3334eb3 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -295,6 +295,18 @@ config MFD_WM8350_I2C I2C as the control interface. Additional options must be selected to enable support for the functionality of the chip. +config MFD_WM8994 + tristate "Support Wolfson Microelectronics WM8994" + select MFD_CORE + depends on I2C + help + The WM8994 is a highly integrated hi-fi CODEC designed for + smartphone applicatiosn. As well as audio functionality it + has on board GPIO and regulator functionality which is + supported via the relevant subsystems. This driver provides + core support for the WM8994, in order to use the actual + functionaltiy of the device other drivers must be enabled. + config MFD_PCF50633 tristate "Support for NXP PCF50633" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 142d31202b14..878cac691067 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -25,6 +25,7 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o wm8350-objs += wm8350-irq.o obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o +obj-$(CONFIG_MFD_WM8994) += wm8994-core.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c new file mode 100644 index 000000000000..299c1af1334e --- /dev/null +++ b/drivers/mfd/wm8994-core.c @@ -0,0 +1,537 @@ +/* + * wm8994-core.c -- Device access for Wolfson WM8994 + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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 +#include + +static int wm8994_read(struct wm8994 *wm8994, unsigned short reg, + int bytes, void *dest) +{ + int ret, i; + u16 *buf = dest; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + ret = wm8994->read_dev(wm8994, reg, bytes, dest); + if (ret < 0) + return ret; + + for (i = 0; i < bytes / 2; i++) { + buf[i] = be16_to_cpu(buf[i]); + + dev_vdbg(wm8994->dev, "Read %04x from R%d(0x%x)\n", + buf[i], reg + i, reg + i); + } + + return 0; +} + +/** + * wm8994_reg_read: Read a single WM8994 register. + * + * @wm8994: Device to read from. + * @reg: Register to read. + */ +int wm8994_reg_read(struct wm8994 *wm8994, unsigned short reg) +{ + unsigned short val; + int ret; + + mutex_lock(&wm8994->io_lock); + + ret = wm8994_read(wm8994, reg, 2, &val); + + mutex_unlock(&wm8994->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wm8994_reg_read); + +/** + * wm8994_bulk_read: Read multiple WM8994 registers + * + * @wm8994: Device to read from + * @reg: First register + * @count: Number of registers + * @buf: Buffer to fill. + */ +int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, + int count, u16 *buf) +{ + int ret; + + mutex_lock(&wm8994->io_lock); + + ret = wm8994_read(wm8994, reg, count * 2, buf); + + mutex_unlock(&wm8994->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8994_bulk_read); + +static int wm8994_write(struct wm8994 *wm8994, unsigned short reg, + int bytes, void *src) +{ + u16 *buf = src; + int i; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + for (i = 0; i < bytes / 2; i++) { + dev_vdbg(wm8994->dev, "Write %04x to R%d(0x%x)\n", + buf[i], reg + i, reg + i); + + buf[i] = cpu_to_be16(buf[i]); + } + + return wm8994->write_dev(wm8994, reg, bytes, src); +} + +/** + * wm8994_reg_write: Write a single WM8994 register. + * + * @wm8994: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + */ +int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg, + unsigned short val) +{ + int ret; + + mutex_lock(&wm8994->io_lock); + + ret = wm8994_write(wm8994, reg, 2, &val); + + mutex_unlock(&wm8994->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8994_reg_write); + +/** + * wm8994_set_bits: Set the value of a bitfield in a WM8994 register + * + * @wm8994: Device to write to. + * @reg: Register to write to. + * @mask: Mask of bits to set. + * @val: Value to set (unshifted) + */ +int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg, + unsigned short mask, unsigned short val) +{ + int ret; + u16 r; + + mutex_lock(&wm8994->io_lock); + + ret = wm8994_read(wm8994, reg, 2, &r); + if (ret < 0) + goto out; + + r &= ~mask; + r |= val; + + ret = wm8994_write(wm8994, reg, 2, &r); + +out: + mutex_unlock(&wm8994->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8994_set_bits); + +static struct mfd_cell wm8994_regulator_devs[] = { + { .name = "wm8994-ldo", .id = 1 }, + { .name = "wm8994-ldo", .id = 2 }, +}; + +static struct mfd_cell wm8994_devs[] = { + { .name = "wm8994-codec" }, + { .name = "wm8994-gpio" }, +}; + +/* + * Supplies for the main bulk of CODEC; the LDO supplies are ignored + * and should be handled via the standard regulator API supply + * management. + */ +static const char *wm8994_main_supplies[] = { + "DBVDD", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + +#ifdef CONFIG_PM +static int wm8994_device_suspend(struct device *dev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(dev); + int ret; + + /* GPIO configuration state is saved here since we may be configuring + * the GPIO alternate functions even if we're not using the gpiolib + * driver for them. + */ + ret = wm8994_read(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2, + &wm8994->gpio_regs); + if (ret < 0) + dev_err(dev, "Failed to save GPIO registers: %d\n", ret); + + /* For similar reasons we also stash the regulator states */ + ret = wm8994_read(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, + &wm8994->ldo_regs); + if (ret < 0) + dev_err(dev, "Failed to save LDO registers: %d\n", ret); + + ret = regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); + if (ret != 0) { + dev_err(dev, "Failed to disable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int wm8994_device_resume(struct device *dev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, + &wm8994->ldo_regs); + if (ret < 0) + dev_err(dev, "Failed to restore LDO registers: %d\n", ret); + + ret = wm8994_write(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2, + &wm8994->gpio_regs); + if (ret < 0) + dev_err(dev, "Failed to restore GPIO registers: %d\n", ret); + + return 0; +} +#endif + +#ifdef CONFIG_REGULATOR +static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo) +{ + struct wm8994_ldo_pdata *ldo_pdata; + + if (!pdata) + return 0; + + ldo_pdata = &pdata->ldo[ldo]; + + if (!ldo_pdata->init_data) + return 0; + + return ldo_pdata->init_data->num_consumer_supplies != 0; +} +#else +static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo) +{ + return 0; +} +#endif + +/* + * Instantiate the generic non-control parts of the device. + */ +static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) +{ + struct wm8994_pdata *pdata = wm8994->dev->platform_data; + int ret, i; + + mutex_init(&wm8994->io_lock); + dev_set_drvdata(wm8994->dev, wm8994); + + /* Add the on-chip regulators first for bootstrapping */ + ret = mfd_add_devices(wm8994->dev, -1, + wm8994_regulator_devs, + ARRAY_SIZE(wm8994_regulator_devs), + NULL, 0); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to add children: %d\n", ret); + goto err; + } + + wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) * + ARRAY_SIZE(wm8994_main_supplies), + GFP_KERNEL); + if (!wm8994->supplies) + goto err; + + for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++) + wm8994->supplies[i].supply = wm8994_main_supplies[i]; + + ret = regulator_bulk_get(wm8994->dev, ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to get supplies: %d\n", ret); + goto err_get; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to enable supplies: %d\n", ret); + goto err_supplies; + } + + ret = wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read ID register\n"); + goto err_enable; + } + if (ret != 0x8994) { + dev_err(wm8994->dev, "Device is not a WM8994, ID is %x\n", + ret); + ret = -EINVAL; + goto err_enable; + } + + ret = wm8994_reg_read(wm8994, WM8994_CHIP_REVISION); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read revision register: %d\n", + ret); + goto err_enable; + } + + switch (ret) { + case 0: + case 1: + dev_warn(wm8994->dev, "revision %c not fully supported\n", + 'A' + ret); + break; + default: + dev_info(wm8994->dev, "revision %c\n", 'A' + ret); + break; + } + + + if (pdata) { + wm8994->gpio_base = pdata->gpio_base; + + /* GPIO configuration is only applied if it's non-zero */ + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (pdata->gpio_defaults[i]) { + wm8994_set_bits(wm8994, WM8994_GPIO_1 + i, + 0xffff, + pdata->gpio_defaults[i]); + } + } + } + + /* In some system designs where the regulators are not in use, + * we can achieve a small reduction in leakage currents by + * floating LDO outputs. This bit makes no difference if the + * LDOs are enabled, it only affects cases where the LDOs were + * in operation and are then disabled. + */ + for (i = 0; i < WM8994_NUM_LDO_REGS; i++) { + if (wm8994_ldo_in_use(pdata, i)) + wm8994_set_bits(wm8994, WM8994_LDO_1 + i, + WM8994_LDO1_DISCH, WM8994_LDO1_DISCH); + else + wm8994_set_bits(wm8994, WM8994_LDO_1 + i, + WM8994_LDO1_DISCH, 0); + } + + ret = mfd_add_devices(wm8994->dev, -1, + wm8994_devs, ARRAY_SIZE(wm8994_devs), + NULL, 0); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to add children: %d\n", ret); + goto err_enable; + } + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); +err_get: + regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); +err_supplies: + kfree(wm8994->supplies); +err: + mfd_remove_devices(wm8994->dev); + kfree(wm8994); + return ret; +} + +static void wm8994_device_exit(struct wm8994 *wm8994) +{ + mfd_remove_devices(wm8994->dev); + regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), + wm8994->supplies); + regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); + kfree(wm8994->supplies); + kfree(wm8994); +} + +static int wm8994_i2c_read_device(struct wm8994 *wm8994, unsigned short reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = wm8994->control_data; + int ret; + u16 r = cpu_to_be16(reg); + + ret = i2c_master_send(i2c, (unsigned char *)&r, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + if (ret != bytes) + return -EIO; + return 0; +} + +/* Currently we allocate the write buffer on the stack; this is OK for + * small writes - if we need to do large writes this will need to be + * revised. + */ +static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg, + int bytes, void *src) +{ + struct i2c_client *i2c = wm8994->control_data; + unsigned char msg[bytes + 2]; + int ret; + + reg = cpu_to_be16(reg); + memcpy(&msg[0], ®, 2); + memcpy(&msg[2], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 2); + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; +} + +static int wm8994_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8994 *wm8994; + + wm8994 = kzalloc(sizeof(struct wm8994), GFP_KERNEL); + if (wm8994 == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, wm8994); + wm8994->dev = &i2c->dev; + wm8994->control_data = i2c; + wm8994->read_dev = wm8994_i2c_read_device; + wm8994->write_dev = wm8994_i2c_write_device; + + return wm8994_device_init(wm8994, id->driver_data, i2c->irq); +} + +static int wm8994_i2c_remove(struct i2c_client *i2c) +{ + struct wm8994 *wm8994 = i2c_get_clientdata(i2c); + + wm8994_device_exit(wm8994); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8994_i2c_suspend(struct i2c_client *i2c, pm_message_t state) +{ + return wm8994_device_suspend(&i2c->dev); +} + +static int wm8994_i2c_resume(struct i2c_client *i2c) +{ + return wm8994_device_resume(&i2c->dev); +} +#else +#define wm8994_i2c_suspend NULL +#define wm8994_i2c_resume NULL +#endif + +static const struct i2c_device_id wm8994_i2c_id[] = { + { "wm8994", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8994_i2c_id); + +static struct i2c_driver wm8994_i2c_driver = { + .driver = { + .name = "wm8994", + .owner = THIS_MODULE, + }, + .probe = wm8994_i2c_probe, + .remove = wm8994_i2c_remove, + .suspend = wm8994_i2c_suspend, + .resume = wm8994_i2c_resume, + .id_table = wm8994_i2c_id, +}; + +static int __init wm8994_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&wm8994_i2c_driver); + if (ret != 0) + pr_err("Failed to register wm8994 I2C driver: %d\n", ret); + + return ret; +} +module_init(wm8994_i2c_init); + +static void __exit wm8994_i2c_exit(void) +{ + i2c_del_driver(&wm8994_i2c_driver); +} +module_exit(wm8994_i2c_exit); + +MODULE_DESCRIPTION("Core support for the WM8994 audio CODEC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown "); -- cgit v1.2.3 From 1ea933f4cdbb88197139b9e62778beba0120e229 Mon Sep 17 00:00:00 2001 From: Mike Turquette Date: Fri, 5 Feb 2010 09:51:37 +0100 Subject: mfd: Program twl4030 remap_sleep correctly Variable remap was incorrectly referencing remap_off for the remap_sleep case when configuring TWL4030 power scripts. Signed-off-by: Mike Turquette Signed-off-by: Samuel Ortiz --- drivers/mfd/twl4030-power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 0815292fdafc..5b045ff4a2c2 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -405,7 +405,7 @@ static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig) if (rconfig->remap_sleep != TWL4030_RESCONFIG_UNDEF) { remap &= ~SLEEP_STATE_MASK; - remap |= rconfig->remap_off << SLEEP_STATE_SHIFT; + remap |= rconfig->remap_sleep << SLEEP_STATE_SHIFT; } err = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, -- cgit v1.2.3 From b13c0df517bedbc40cff4ab5f797b08b1111918b Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 25 Jan 2010 06:26:34 -0500 Subject: mfd: Update i2c driver for max8925 Update I2C driver in order to fit all of three I2C components in max8925. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/max8925-i2c.c | 57 ++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/max8925-i2c.c b/drivers/mfd/max8925-i2c.c index 942068e730f9..c0b883c14f41 100644 --- a/drivers/mfd/max8925-i2c.c +++ b/drivers/mfd/max8925-i2c.c @@ -14,27 +14,23 @@ #include #include +#define RTC_I2C_ADDR 0x68 +#define ADC_I2C_ADDR 0x47 + static inline int max8925_read_device(struct i2c_client *i2c, int reg, int bytes, void *dest) { - unsigned char data; - unsigned char *buf; int ret; - buf = kzalloc(bytes + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - data = (unsigned char)reg; - ret = i2c_master_send(i2c, &data, 1); - if (ret < 0) - return ret; - - ret = i2c_master_recv(i2c, buf, bytes + 1); - if (ret < 0) - return ret; - memcpy(dest, buf, bytes); - return 0; + if (bytes > 1) + ret = i2c_smbus_read_i2c_block_data(i2c, reg, bytes, dest); + else { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret < 0) + return ret; + *(unsigned char *)dest = (unsigned char)ret; + } + return ret; } static inline int max8925_write_device(struct i2c_client *i2c, @@ -55,7 +51,7 @@ static inline int max8925_write_device(struct i2c_client *i2c, int max8925_reg_read(struct i2c_client *i2c, int reg) { struct max8925_chip *chip = i2c_get_clientdata(i2c); - unsigned char data; + unsigned char data = 0; int ret; mutex_lock(&chip->io_lock); @@ -134,7 +130,7 @@ EXPORT_SYMBOL(max8925_set_bits); static const struct i2c_device_id max8925_id_table[] = { { "max8925", 0 }, - {} + { }, }; MODULE_DEVICE_TABLE(i2c, max8925_id_table); @@ -142,27 +138,28 @@ static int __devinit max8925_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct max8925_platform_data *pdata = client->dev.platform_data; - struct max8925_chip *chip; + static struct max8925_chip *chip; if (!pdata) { pr_info("%s: platform data is missing\n", __func__); return -EINVAL; } - if ((pdata->chip_id <= MAX8925_INVALID) - || (pdata->chip_id >= MAX8925_MAX)) { - pr_info("#%s: wrong chip identification\n", __func__); - return -EINVAL; - } chip = kzalloc(sizeof(struct max8925_chip), GFP_KERNEL); if (chip == NULL) return -ENOMEM; chip->i2c = client; - chip->chip_id = pdata->chip_id; - i2c_set_clientdata(client, chip); chip->dev = &client->dev; - mutex_init(&chip->io_lock); + i2c_set_clientdata(client, chip); dev_set_drvdata(chip->dev, chip); + mutex_init(&chip->io_lock); + + chip->rtc = i2c_new_dummy(chip->i2c->adapter, RTC_I2C_ADDR); + i2c_set_clientdata(chip->rtc, chip); + + chip->adc = i2c_new_dummy(chip->i2c->adapter, ADC_I2C_ADDR); + i2c_set_clientdata(chip->adc, chip); + max8925_device_init(chip, pdata); return 0; @@ -173,7 +170,11 @@ static int __devexit max8925_remove(struct i2c_client *client) struct max8925_chip *chip = i2c_get_clientdata(client); max8925_device_exit(chip); - i2c_set_clientdata(client, NULL); + 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 1f1cf8f98cf6588365efeaab8e7e7758aaa77f6e Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 5 Feb 2010 16:07:54 +0100 Subject: mfd: Update irq handler in max8925 Update thread irq handler. Simply the interface of using thread irq. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 4 +- drivers/mfd/max8925-core.c | 678 +++++++++++++++++++++++++++++++-------------- 2 files changed, 467 insertions(+), 215 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 64fbe3334eb3..84a68d260772 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -204,8 +204,8 @@ config PMIC_ADP5520 under the corresponding menus. config MFD_MAX8925 - tristate "Maxim Semiconductor MAX8925 PMIC Support" - depends on I2C + bool "Maxim Semiconductor MAX8925 PMIC Support" + depends on I2C=y select MFD_CORE help Say yes here to support for Maxim Semiconductor MAX8925. This is diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index f36c494b80f1..85d63c04749b 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -1,7 +1,7 @@ /* * Base driver for Maxim MAX8925 * - * Copyright (C) 2009 Marvell International Ltd. + * Copyright (C) 2009-2010 Marvell International Ltd. * Haojian Zhuang * * This program is free software; you can redistribute it and/or modify @@ -12,14 +12,12 @@ #include #include #include +#include #include #include #include #include -#define IRQ_MODE_STATUS 0 -#define IRQ_MODE_MASK 1 - static struct resource backlight_resources[] = { { .name = "max8925-backlight", @@ -56,6 +54,42 @@ static struct mfd_cell touch_devs[] = { }, }; +static struct resource power_supply_resources[] = { + { + .name = "max8925-power", + .start = MAX8925_CHG_IRQ1, + .end = MAX8925_CHG_IRQ1_MASK, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell power_devs[] = { + { + .name = "max8925-power", + .num_resources = 1, + .resources = &power_supply_resources[0], + .id = -1, + }, +}; + +static struct resource rtc_resources[] = { + { + .name = "max8925-rtc", + .start = MAX8925_RTC_IRQ, + .end = MAX8925_RTC_IRQ_MASK, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell rtc_devs[] = { + { + .name = "max8925-rtc", + .num_resources = 1, + .resources = &rtc_resources[0], + .id = -1, + }, +}; + #define MAX8925_REG_RESOURCE(_start, _end) \ { \ .start = MAX8925_##_start, \ @@ -123,203 +157,450 @@ static struct mfd_cell regulator_devs[] = { MAX8925_REG_DEVS(LDO20), }; -static int __get_irq_offset(struct max8925_chip *chip, int irq, int mode, - int *offset, int *bit) +enum { + FLAGS_ADC = 1, /* register in ADC component */ + FLAGS_RTC, /* register in RTC component */ +}; + +struct max8925_irq_data { + int reg; + int mask_reg; + int enable; /* enable or not */ + int offs; /* bit offset in mask register */ + int flags; + int tsc_irq; +}; + +static struct max8925_irq_data max8925_irqs[] = { + [MAX8925_IRQ_VCHG_DC_OVP] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_VCHG_DC_F] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_VCHG_DC_R] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_VCHG_USB_OVP] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 3, + }, + [MAX8925_IRQ_VCHG_USB_F] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 4, + }, + [MAX8925_IRQ_VCHG_USB_R] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 5, + }, + [MAX8925_IRQ_VCHG_THM_OK_R] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_VCHG_THM_OK_F] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_VCHG_SYSLOW_F] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_VCHG_SYSLOW_R] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 3, + }, + [MAX8925_IRQ_VCHG_RST] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 4, + }, + [MAX8925_IRQ_VCHG_DONE] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 5, + }, + [MAX8925_IRQ_VCHG_TOPOFF] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 6, + }, + [MAX8925_IRQ_VCHG_TMR_FAULT] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 7, + }, + [MAX8925_IRQ_GPM_RSTIN] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_GPM_MPL] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_GPM_SW_3SEC] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_GPM_EXTON_F] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 3, + }, + [MAX8925_IRQ_GPM_EXTON_R] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 4, + }, + [MAX8925_IRQ_GPM_SW_1SEC] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 5, + }, + [MAX8925_IRQ_GPM_SW_F] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 6, + }, + [MAX8925_IRQ_GPM_SW_R] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 7, + }, + [MAX8925_IRQ_GPM_SYSCKEN_F] = { + .reg = MAX8925_ON_OFF_IRQ2, + .mask_reg = MAX8925_ON_OFF_IRQ2_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_GPM_SYSCKEN_R] = { + .reg = MAX8925_ON_OFF_IRQ2, + .mask_reg = MAX8925_ON_OFF_IRQ2_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_RTC_ALARM1] = { + .reg = MAX8925_RTC_IRQ, + .mask_reg = MAX8925_RTC_IRQ_MASK, + .offs = 1 << 2, + .flags = FLAGS_RTC, + }, + [MAX8925_IRQ_RTC_ALARM0] = { + .reg = MAX8925_RTC_IRQ, + .mask_reg = MAX8925_RTC_IRQ_MASK, + .offs = 1 << 3, + .flags = FLAGS_RTC, + }, + [MAX8925_IRQ_TSC_STICK] = { + .reg = MAX8925_TSC_IRQ, + .mask_reg = MAX8925_TSC_IRQ_MASK, + .offs = 1 << 0, + .flags = FLAGS_ADC, + .tsc_irq = 1, + }, + [MAX8925_IRQ_TSC_NSTICK] = { + .reg = MAX8925_TSC_IRQ, + .mask_reg = MAX8925_TSC_IRQ_MASK, + .offs = 1 << 1, + .flags = FLAGS_ADC, + .tsc_irq = 1, + }, +}; + +static inline struct max8925_irq_data *irq_to_max8925(struct max8925_chip *chip, + int irq) { - if (!offset || !bit) - return -EINVAL; + return &max8925_irqs[irq - chip->irq_base]; +} - switch (chip->chip_id) { - case MAX8925_GPM: - *bit = irq % BITS_PER_BYTE; - if (irq < (BITS_PER_BYTE << 1)) { /* irq = [0,15] */ - *offset = (mode) ? MAX8925_CHG_IRQ1_MASK - : MAX8925_CHG_IRQ1; - if (irq >= BITS_PER_BYTE) - (*offset)++; - } else { /* irq = [16,31] */ - *offset = (mode) ? MAX8925_ON_OFF_IRQ1_MASK - : MAX8925_ON_OFF_IRQ1; - if (irq >= (BITS_PER_BYTE * 3)) - (*offset)++; +static irqreturn_t max8925_irq(int irq, void *data) +{ + struct max8925_chip *chip = data; + struct max8925_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + /* TSC IRQ should be serviced in max8925_tsc_irq() */ + if (irq_data->tsc_irq) + continue; + if (irq_data->flags == FLAGS_RTC) + i2c = chip->rtc; + else if (irq_data->flags == FLAGS_ADC) + i2c = chip->adc; + else + i2c = chip->i2c; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = max8925_reg_read(i2c, irq_data->reg); } - break; - case MAX8925_ADC: - *bit = irq % BITS_PER_BYTE; - *offset = (mode) ? MAX8925_TSC_IRQ_MASK : MAX8925_TSC_IRQ; - break; - default: - goto out; + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); } - return 0; -out: - dev_err(chip->dev, "Wrong irq #%d is assigned\n", irq); - return -EINVAL; + return IRQ_HANDLED; } -static int __check_irq(int irq) +static irqreturn_t max8925_tsc_irq(int irq, void *data) { - if ((irq < 0) || (irq >= MAX8925_NUM_IRQ)) - return -EINVAL; - return 0; + struct max8925_chip *chip = data; + struct max8925_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + /* non TSC IRQ should be serviced in max8925_irq() */ + if (!irq_data->tsc_irq) + continue; + if (irq_data->flags == FLAGS_RTC) + i2c = chip->rtc; + else if (irq_data->flags == FLAGS_ADC) + i2c = chip->adc; + else + i2c = chip->i2c; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = max8925_reg_read(i2c, irq_data->reg); + } + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); + } + return IRQ_HANDLED; } -int max8925_mask_irq(struct max8925_chip *chip, int irq) +static void max8925_irq_lock(unsigned int irq) { - int offset, bit, ret; + struct max8925_chip *chip = get_irq_chip_data(irq); - ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); - if (ret < 0) - return ret; - ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 1 << bit); - return ret; + mutex_lock(&chip->irq_lock); } -int max8925_unmask_irq(struct max8925_chip *chip, int irq) +static void max8925_irq_sync_unlock(unsigned int irq) { - int offset, bit, ret; + struct max8925_chip *chip = get_irq_chip_data(irq); + struct max8925_irq_data *irq_data; + static unsigned char cache_chg[2] = {0xff, 0xff}; + static unsigned char cache_on[2] = {0xff, 0xff}; + static unsigned char cache_rtc = 0xff, cache_tsc = 0xff; + unsigned char irq_chg[2], irq_on[2]; + unsigned char irq_rtc, irq_tsc; + int i; + + /* Load cached value. In initial, all IRQs are masked */ + irq_chg[0] = cache_chg[0]; + irq_chg[1] = cache_chg[1]; + irq_on[0] = cache_on[0]; + irq_on[1] = cache_on[1]; + irq_rtc = cache_rtc; + irq_tsc = cache_tsc; + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + switch (irq_data->mask_reg) { + case MAX8925_CHG_IRQ1_MASK: + irq_chg[0] &= irq_data->enable; + break; + case MAX8925_CHG_IRQ2_MASK: + irq_chg[1] &= irq_data->enable; + break; + case MAX8925_ON_OFF_IRQ1_MASK: + irq_on[0] &= irq_data->enable; + break; + case MAX8925_ON_OFF_IRQ2_MASK: + irq_on[1] &= irq_data->enable; + break; + case MAX8925_RTC_IRQ_MASK: + irq_rtc &= irq_data->enable; + break; + case MAX8925_TSC_IRQ_MASK: + irq_tsc &= irq_data->enable; + break; + default: + dev_err(chip->dev, "wrong IRQ\n"); + break; + } + } + /* update mask into registers */ + if (cache_chg[0] != irq_chg[0]) { + cache_chg[0] = irq_chg[0]; + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, + irq_chg[0]); + } + if (cache_chg[1] != irq_chg[1]) { + cache_chg[1] = irq_chg[1]; + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK, + irq_chg[1]); + } + if (cache_on[0] != irq_on[0]) { + cache_on[0] = irq_on[0]; + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, + irq_on[0]); + } + if (cache_on[1] != irq_on[1]) { + cache_on[1] = irq_on[1]; + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, + irq_on[1]); + } + if (cache_rtc != irq_rtc) { + cache_rtc = irq_rtc; + max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, irq_rtc); + } + if (cache_tsc != irq_tsc) { + cache_tsc = irq_tsc; + max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, irq_tsc); + } - ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); - if (ret < 0) - return ret; - ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 0); - return ret; + mutex_unlock(&chip->irq_lock); } -#define INT_STATUS_NUM (MAX8925_NUM_IRQ / BITS_PER_BYTE) - -static irqreturn_t max8925_irq_thread(int irq, void *data) +static void max8925_irq_enable(unsigned int irq) { - struct max8925_chip *chip = data; - unsigned long irq_status[INT_STATUS_NUM]; - unsigned char status_buf[INT_STATUS_NUM << 1]; - int i, ret; - - memset(irq_status, 0, sizeof(unsigned long) * INT_STATUS_NUM); - - /* all these interrupt status registers are read-only */ - switch (chip->chip_id) { - case MAX8925_GPM: - ret = max8925_bulk_read(chip->i2c, MAX8925_CHG_IRQ1, - 4, status_buf); - if (ret < 0) - goto out; - ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ1, - 2, &status_buf[4]); - if (ret < 0) - goto out; - ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ2, - 2, &status_buf[6]); - if (ret < 0) - goto out; - /* clear masked interrupt status */ - status_buf[0] &= (~status_buf[2] & CHG_IRQ1_MASK); - irq_status[0] |= status_buf[0]; - status_buf[1] &= (~status_buf[3] & CHG_IRQ2_MASK); - irq_status[0] |= (status_buf[1] << BITS_PER_BYTE); - status_buf[4] &= (~status_buf[5] & ON_OFF_IRQ1_MASK); - irq_status[0] |= (status_buf[4] << (BITS_PER_BYTE * 2)); - status_buf[6] &= (~status_buf[7] & ON_OFF_IRQ2_MASK); - irq_status[0] |= (status_buf[6] << (BITS_PER_BYTE * 3)); - break; - case MAX8925_ADC: - ret = max8925_bulk_read(chip->i2c, MAX8925_TSC_IRQ, - 2, status_buf); - if (ret < 0) - goto out; - /* clear masked interrupt status */ - status_buf[0] &= (~status_buf[1] & TSC_IRQ_MASK); - irq_status[0] |= status_buf[0]; - break; - default: - goto out; - } - - for_each_bit(i, &irq_status[0], MAX8925_NUM_IRQ) { - clear_bit(i, irq_status); - dev_dbg(chip->dev, "Servicing IRQ #%d in %s\n", i, chip->name); - - mutex_lock(&chip->irq_lock); - if (chip->irq[i].handler) - chip->irq[i].handler(i, chip->irq[i].data); - else { - max8925_mask_irq(chip, i); - dev_err(chip->dev, "Noboday cares IRQ #%d in %s. " - "Now mask it.\n", i, chip->name); - } - mutex_unlock(&chip->irq_lock); - } -out: - return IRQ_HANDLED; + struct max8925_chip *chip = get_irq_chip_data(irq); + max8925_irqs[irq - chip->irq_base].enable + = max8925_irqs[irq - chip->irq_base].offs; } -int max8925_request_irq(struct max8925_chip *chip, int irq, - irq_handler_t handler, void *data) +static void max8925_irq_disable(unsigned int irq) { - if ((__check_irq(irq) < 0) || !handler) - return -EINVAL; - - mutex_lock(&chip->irq_lock); - chip->irq[irq].handler = handler; - chip->irq[irq].data = data; - mutex_unlock(&chip->irq_lock); - return 0; + struct max8925_chip *chip = get_irq_chip_data(irq); + max8925_irqs[irq - chip->irq_base].enable = 0; } -EXPORT_SYMBOL(max8925_request_irq); -int max8925_free_irq(struct max8925_chip *chip, int irq) +static struct irq_chip max8925_irq_chip = { + .name = "max8925", + .bus_lock = max8925_irq_lock, + .bus_sync_unlock = max8925_irq_sync_unlock, + .enable = max8925_irq_enable, + .disable = max8925_irq_disable, +}; + +static int max8925_irq_init(struct max8925_chip *chip, int irq, + struct max8925_platform_data *pdata) { - if (__check_irq(irq) < 0) + unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + struct irq_desc *desc; + int i, ret; + int __irq; + + if (!pdata || !pdata->irq_base) { + dev_warn(chip->dev, "No interrupt support on IRQ base\n"); return -EINVAL; + } + /* clear all interrupts */ + max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ1); + max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ2); + max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ1); + 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 */ + 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); + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK, 0xff); + 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; + chip->irq_base = pdata->irq_base; + desc = irq_to_desc(chip->core_irq); + + /* register with genirq */ + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + __irq = i + chip->irq_base; + set_irq_chip_data(__irq, chip); + set_irq_chip_and_handler(__irq, &max8925_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 + } + if (!irq) { + dev_warn(chip->dev, "No interrupt support on core IRQ\n"); + goto tsc_irq; + } - mutex_lock(&chip->irq_lock); - chip->irq[irq].handler = NULL; - chip->irq[irq].data = NULL; - mutex_unlock(&chip->irq_lock); + ret = request_threaded_irq(irq, NULL, max8925_irq, flags, + "max8925", chip); + if (ret) { + dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret); + chip->core_irq = 0; + } +tsc_irq: + if (!pdata->tsc_irq) { + dev_warn(chip->dev, "No interrupt support on TSC IRQ\n"); + return 0; + } + chip->tsc_irq = pdata->tsc_irq; + + ret = request_threaded_irq(chip->tsc_irq, NULL, max8925_tsc_irq, + flags, "max8925-tsc", chip); + if (ret) { + dev_err(chip->dev, "Failed to request TSC IRQ: %d\n", ret); + chip->tsc_irq = 0; + } return 0; } -EXPORT_SYMBOL(max8925_free_irq); -static int __devinit device_gpm_init(struct max8925_chip *chip, - struct i2c_client *i2c, - struct max8925_platform_data *pdata) +int __devinit max8925_device_init(struct max8925_chip *chip, + struct max8925_platform_data *pdata) { int ret; - /* mask all IRQs */ - ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ1_MASK, 0x7, 0x7); - if (ret < 0) - goto out; - ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ2_MASK, 0xff, 0xff); - if (ret < 0) - goto out; - ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff, 0xff); - if (ret < 0) - goto out; - ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ2_MASK, 0x3, 0x3); - if (ret < 0) - goto out; - - chip->name = "GPM"; - memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); - ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "max8925-gpm", chip); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); - goto out; + max8925_irq_init(chip, chip->i2c->irq, pdata); + + if (pdata && (pdata->power || pdata->touch)) { + /* enable ADC to control internal reference */ + max8925_set_bits(chip->i2c, MAX8925_RESET_CNFG, 1, 1); + /* enable internal reference for ADC */ + max8925_set_bits(chip->adc, MAX8925_TSC_CNFG1, 3, 2); + /* check for internal reference IRQ */ + do { + ret = max8925_reg_read(chip->adc, MAX8925_TSC_IRQ); + } while (ret & MAX8925_NREF_OK); + /* enaable ADC scheduler, interval is 1 second */ + max8925_set_bits(chip->adc, MAX8925_ADC_SCHED, 3, 2); } - chip->chip_irq = i2c->irq; - /* enable hard-reset for ONKEY power-off */ - max8925_set_bits(i2c, MAX8925_SYSENSEL, 0x80, 0x80); + /* enable Momentary Power Loss */ + max8925_set_bits(chip->rtc, MAX8925_MPL_CNTL, 1 << 4, 1 << 4); - ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], - ARRAY_SIZE(regulator_devs), - ®ulator_resources[0], 0); + ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0], + ARRAY_SIZE(rtc_devs), + &rtc_resources[0], 0); if (ret < 0) { - dev_err(chip->dev, "Failed to add regulator subdev\n"); - goto out_irq; + dev_err(chip->dev, "Failed to add rtc subdev\n"); + goto out; + } + if (pdata && pdata->regulator[0]) { + ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], + ARRAY_SIZE(regulator_devs), + ®ulator_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + goto out_dev; + } } if (pdata && pdata->backlight) { @@ -331,35 +612,17 @@ static int __devinit device_gpm_init(struct max8925_chip *chip, goto out_dev; } } - return 0; -out_dev: - mfd_remove_devices(chip->dev); -out_irq: - if (chip->chip_irq) - free_irq(chip->chip_irq, chip); -out: - return ret; -} - -static int __devinit device_adc_init(struct max8925_chip *chip, - struct i2c_client *i2c, - struct max8925_platform_data *pdata) -{ - int ret; - - /* mask all IRQs */ - ret = max8925_set_bits(i2c, MAX8925_TSC_IRQ_MASK, 3, 3); - chip->name = "ADC"; - memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); - ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "max8925-adc", chip); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); - goto out; + if (pdata && pdata->power) { + ret = mfd_add_devices(chip->dev, 0, &power_devs[0], + ARRAY_SIZE(power_devs), + &power_supply_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add power supply " + "subdev\n"); + goto out_dev; + } } - chip->chip_irq = i2c->irq; if (pdata && pdata->touch) { ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], @@ -367,38 +630,27 @@ static int __devinit device_adc_init(struct max8925_chip *chip, &touch_resources[0], 0); if (ret < 0) { dev_err(chip->dev, "Failed to add touch subdev\n"); - goto out_irq; + goto out_dev; } } + return 0; -out_irq: - if (chip->chip_irq) - free_irq(chip->chip_irq, chip); +out_dev: + mfd_remove_devices(chip->dev); out: return ret; } -int __devinit max8925_device_init(struct max8925_chip *chip, - struct max8925_platform_data *pdata) +void __devexit max8925_device_exit(struct max8925_chip *chip) { - switch (chip->chip_id) { - case MAX8925_GPM: - device_gpm_init(chip, chip->i2c, pdata); - break; - case MAX8925_ADC: - device_adc_init(chip, chip->i2c, pdata); - break; - } - return 0; -} - -void max8925_device_exit(struct max8925_chip *chip) -{ - if (chip->chip_irq >= 0) - free_irq(chip->chip_irq, chip); + if (chip->core_irq) + free_irq(chip->core_irq, chip); + if (chip->tsc_irq) + free_irq(chip->tsc_irq, chip); mfd_remove_devices(chip->dev); } + MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925"); MODULE_AUTHOR("Haojian Zhuang Date: Wed, 3 Feb 2010 15:38:12 -0500 Subject: mfd: Use i2c_dummy in 88pm860x Use i2c_dummy in 88pm860x driver to avoid using static in probe function. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-i2c.c | 72 +++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 52 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index 6d7dba2bce8a..c37e12bf3004 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -157,18 +157,24 @@ static int __devinit pm860x_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct pm860x_platform_data *pdata = client->dev.platform_data; - static struct pm860x_chip *chip; - struct i2c_board_info i2c_info = { - .type = "88PM860x", - .platform_data = client->dev.platform_data, - }; - int addr_c, found_companion = 0; - - if (pdata == NULL) { + struct pm860x_chip *chip; + + if (!pdata) { pr_info("No platform data in %s!\n", __func__); return -EINVAL; } + chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->id = verify_addr(client); + chip->client = client; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + /* * Both client and companion client shares same platform driver. * Driver distinguishes them by pdata->companion_addr. @@ -176,46 +182,14 @@ static int __devinit pm860x_probe(struct i2c_client *client, * At the same time, the companion_addr shouldn't equal to client * address. */ - addr_c = pdata->companion_addr; - if (addr_c && (addr_c != client->addr)) { - i2c_info.addr = addr_c; - found_companion = 1; - } - - if (found_companion || (addr_c == 0)) { - chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); - if (chip == NULL) - return -ENOMEM; - - chip->id = verify_addr(client); - chip->companion_addr = addr_c; - chip->client = client; - i2c_set_clientdata(client, chip); - chip->dev = &client->dev; - mutex_init(&chip->io_lock); - dev_set_drvdata(chip->dev, chip); - - if (found_companion) { - /* - * If this driver is built in, probe function is - * recursive. - * If this driver is built as module, the next probe - * function is called after the first one finished. - */ - chip->companion = i2c_new_device(client->adapter, - &i2c_info); - } - } - - /* - * If companion chip existes, it's called by companion probe. - * If there's no companion chip, it's called by client probe. - */ - if ((addr_c == 0) || (addr_c == client->addr)) { - chip->companion = client; + if (pdata->companion_addr && (pdata->companion_addr != client->addr)) { + chip->companion_addr = pdata->companion_addr; + chip->companion = i2c_new_dummy(chip->client->adapter, + chip->companion_addr); i2c_set_clientdata(chip->companion, chip); - pm860x_device_init(chip, pdata); } + + pm860x_device_init(chip, pdata); return 0; } @@ -223,12 +197,6 @@ static int __devexit pm860x_remove(struct i2c_client *client) { struct pm860x_chip *chip = i2c_get_clientdata(client); - /* - * If companion existes, companion client is removed first. - * Because companion client is registered last and removed first. - */ - if (chip->companion_addr == client->addr) - return 0; pm860x_device_exit(chip); i2c_unregister_device(chip->companion); i2c_set_clientdata(chip->companion, NULL); -- cgit v1.2.3 From 7731074ab21745cde00578148ce760df107eaf27 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Sun, 7 Feb 2010 10:16:14 +0900 Subject: mfd: Fix WM8994 error handling This patch fixes wrong goto statement for error handling on probe. Signed-off-by: Myungjoo Ham Signed-off-by: Joonyoung Shim Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 299c1af1334e..844e1c1b7d90 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -305,14 +305,14 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) wm8994->supplies); if (ret != 0) { dev_err(wm8994->dev, "Failed to get supplies: %d\n", ret); - goto err_get; + goto err_supplies; } ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); if (ret != 0) { dev_err(wm8994->dev, "Failed to enable supplies: %d\n", ret); - goto err_supplies; + goto err_get; } ret = wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET); -- cgit v1.2.3 From 2afa62ea76027b00e472ddb672191e6e15425b43 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 8 Feb 2010 05:02:00 -0500 Subject: mfd: Use genirq in 88pm860x Use genirq to simplify IRQ handling in 88pm860x. Remove the interface of mask/free IRQs on 88pm860x. All these work is taken by genirq. Update the touchscreen driver of 88pm860x since IRQ handling is changed. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 408 +++++++++++++++++++++++++++++++------------- 1 file changed, 294 insertions(+), 114 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 16f0dca707a7..6a14d2b1ccf0 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -12,11 +12,14 @@ #include #include #include +#include #include #include #include #include +#define INT_STATUS_NUM 3 + char pm860x_backlight_name[][MFD_NAME_SIZE] = { "backlight-0", "backlight-1", @@ -119,6 +122,42 @@ static struct mfd_cell touch_devs[] = { .flags = IORESOURCE_IO, \ } +static struct resource power_supply_resources[] = { + { + .name = "88pm860x-power", + .start = PM8607_IRQ_CHG, + .end = PM8607_IRQ_CHG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell power_devs[] = { + { + .name = "88pm860x-power", + .num_resources = 1, + .resources = &power_supply_resources[0], + .id = -1, + }, +}; + +static struct resource onkey_resources[] = { + { + .name = "88pm860x-onkey", + .start = PM8607_IRQ_ONKEY, + .end = PM8607_IRQ_ONKEY, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell onkey_devs[] = { + { + .name = "88pm860x-onkey", + .num_resources = 1, + .resources = &onkey_resources[0], + .id = -1, + }, +}; + static struct resource regulator_resources[] = { PM8607_REG_RESOURCE(BUCK1, BUCK1), PM8607_REG_RESOURCE(BUCK2, BUCK2), @@ -163,129 +202,224 @@ static struct mfd_cell regulator_devs[] = { PM8607_REG_DEVS(ldo14, LDO14), }; -#define CHECK_IRQ(irq) \ -do { \ - if ((irq < 0) || (irq >= PM860X_NUM_IRQ)) \ - return -EINVAL; \ -} while (0) - -/* IRQs only occur on 88PM8607 */ -int pm860x_mask_irq(struct pm860x_chip *chip, int irq) -{ - struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ - : chip->companion; - int offset, data, ret; - - CHECK_IRQ(irq); - - offset = (irq >> 3) + PM8607_INT_MASK_1; - data = 1 << (irq % 8); - ret = pm860x_set_bits(i2c, offset, data, 0); +struct pm860x_irq_data { + int reg; + int mask_reg; + int enable; /* enable or not */ + int offs; /* bit offset in mask register */ +}; - return ret; -} -EXPORT_SYMBOL(pm860x_mask_irq); +static struct pm860x_irq_data pm860x_irqs[] = { + [PM8607_IRQ_ONKEY] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 0, + }, + [PM8607_IRQ_EXTON] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 1, + }, + [PM8607_IRQ_CHG] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 2, + }, + [PM8607_IRQ_BAT] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 3, + }, + [PM8607_IRQ_RTC] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 4, + }, + [PM8607_IRQ_CC] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 5, + }, + [PM8607_IRQ_VBAT] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 0, + }, + [PM8607_IRQ_VCHG] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 1, + }, + [PM8607_IRQ_VSYS] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 2, + }, + [PM8607_IRQ_TINT] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 3, + }, + [PM8607_IRQ_GPADC0] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 4, + }, + [PM8607_IRQ_GPADC1] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 5, + }, + [PM8607_IRQ_GPADC2] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 6, + }, + [PM8607_IRQ_GPADC3] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 7, + }, + [PM8607_IRQ_AUDIO_SHORT] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 0, + }, + [PM8607_IRQ_PEN] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 1, + }, + [PM8607_IRQ_HEADSET] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 2, + }, + [PM8607_IRQ_HOOK] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 3, + }, + [PM8607_IRQ_MICIN] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 4, + }, + [PM8607_IRQ_CHG_FAIL] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 5, + }, + [PM8607_IRQ_CHG_DONE] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 6, + }, + [PM8607_IRQ_CHG_FAULT] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 7, + }, +}; -int pm860x_unmask_irq(struct pm860x_chip *chip, int irq) +static inline struct pm860x_irq_data *irq_to_pm860x(struct pm860x_chip *chip, + int irq) { - struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ - : chip->companion; - int offset, data, ret; - - CHECK_IRQ(irq); - - offset = (irq >> 3) + PM8607_INT_MASK_1; - data = 1 << (irq % 8); - ret = pm860x_set_bits(i2c, offset, data, data); - - return ret; + return &pm860x_irqs[irq - chip->irq_base]; } -EXPORT_SYMBOL(pm860x_unmask_irq); -#define INT_STATUS_NUM (3) - -static irqreturn_t pm8607_irq_thread(int irq, void *data) +static irqreturn_t pm860x_irq(int irq, void *data) { - DECLARE_BITMAP(irq_status, PM860X_NUM_IRQ); struct pm860x_chip *chip = data; - struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ - : chip->companion; - unsigned char status_buf[INT_STATUS_NUM << 1]; - unsigned long value; - int i, ret; - - irq_status[0] = 0; - - /* read out status register */ - ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, - INT_STATUS_NUM << 1, status_buf); - if (ret < 0) - goto out; - if (chip->irq_mode) { - /* 0, clear by read. 1, clear by write */ - ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, - INT_STATUS_NUM, status_buf); - if (ret < 0) - goto out; - } - - /* clear masked interrupt status */ - for (i = 0, value = 0; i < INT_STATUS_NUM; i++) { - status_buf[i] &= status_buf[i + INT_STATUS_NUM]; - irq_status[0] |= status_buf[i] << (i * 8); - } - - while (!bitmap_empty(irq_status, PM860X_NUM_IRQ)) { - irq = find_first_bit(irq_status, PM860X_NUM_IRQ); - clear_bit(irq, irq_status); - dev_dbg(chip->dev, "Servicing IRQ #%d\n", irq); - - mutex_lock(&chip->irq_lock); - if (chip->irq[irq].handler) - chip->irq[irq].handler(irq, chip->irq[irq].data); - else { - pm860x_mask_irq(chip, irq); - dev_err(chip->dev, "Nobody cares IRQ %d. " - "Now mask it.\n", irq); - for (i = 0; i < (INT_STATUS_NUM << 1); i++) { - dev_err(chip->dev, "status[%d]:%x\n", i, - status_buf[i]); - } + struct pm860x_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { + irq_data = &pm860x_irqs[i]; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = pm860x_reg_read(i2c, irq_data->reg); } - mutex_unlock(&chip->irq_lock); + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); } -out: return IRQ_HANDLED; } -int pm860x_request_irq(struct pm860x_chip *chip, int irq, - irq_handler_t handler, void *data) +static void pm860x_irq_lock(unsigned int irq) { - CHECK_IRQ(irq); - if (!handler) - return -EINVAL; + struct pm860x_chip *chip = get_irq_chip_data(irq); mutex_lock(&chip->irq_lock); - chip->irq[irq].handler = handler; - chip->irq[irq].data = data; - mutex_unlock(&chip->irq_lock); - - return 0; } -EXPORT_SYMBOL(pm860x_request_irq); -int pm860x_free_irq(struct pm860x_chip *chip, int irq) +static void pm860x_irq_sync_unlock(unsigned int irq) { - CHECK_IRQ(irq); + struct pm860x_chip *chip = get_irq_chip_data(irq); + struct pm860x_irq_data *irq_data; + struct i2c_client *i2c; + static unsigned char cached[3] = {0x0, 0x0, 0x0}; + unsigned char mask[3]; + int i; + + i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + /* Load cached value. In initial, all IRQs are masked */ + for (i = 0; i < 3; i++) + mask[i] = cached[i]; + for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { + irq_data = &pm860x_irqs[i]; + switch (irq_data->mask_reg) { + case PM8607_INT_MASK_1: + mask[0] &= ~irq_data->offs; + mask[0] |= irq_data->enable; + break; + case PM8607_INT_MASK_2: + mask[1] &= ~irq_data->offs; + mask[1] |= irq_data->enable; + break; + case PM8607_INT_MASK_3: + mask[2] &= ~irq_data->offs; + mask[2] |= irq_data->enable; + break; + default: + dev_err(chip->dev, "wrong IRQ\n"); + break; + } + } + /* update mask into registers */ + for (i = 0; i < 3; i++) { + if (mask[i] != cached[i]) { + cached[i] = mask[i]; + pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]); + } + } - mutex_lock(&chip->irq_lock); - chip->irq[irq].handler = NULL; - chip->irq[irq].data = NULL; mutex_unlock(&chip->irq_lock); +} - return 0; +static void pm860x_irq_enable(unsigned int irq) +{ + struct pm860x_chip *chip = get_irq_chip_data(irq); + pm860x_irqs[irq - chip->irq_base].enable + = pm860x_irqs[irq - chip->irq_base].offs; } -EXPORT_SYMBOL(pm860x_free_irq); + +static void pm860x_irq_disable(unsigned int irq) +{ + struct pm860x_chip *chip = get_irq_chip_data(irq); + pm860x_irqs[irq - chip->irq_base].enable = 0; +} + +static struct irq_chip pm860x_irq_chip = { + .name = "88pm860x", + .bus_lock = pm860x_irq_lock, + .bus_sync_unlock = pm860x_irq_sync_unlock, + .enable = pm860x_irq_enable, + .disable = pm860x_irq_disable, +}; static int __devinit device_gpadc_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) @@ -348,9 +482,15 @@ static int __devinit device_irq_init(struct pm860x_chip *chip, struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ : chip->companion; unsigned char status_buf[INT_STATUS_NUM]; - int data, mask, ret = -EINVAL; + unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + struct irq_desc *desc; + int i, data, mask, ret = -EINVAL; + int __irq; - mutex_init(&chip->irq_lock); + if (!pdata || !pdata->irq_base) { + dev_warn(chip->dev, "No interrupt support on IRQ base\n"); + return -EINVAL; + } mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR | PM8607_B0_MISC1_INT_MASK; @@ -389,25 +529,45 @@ static int __devinit device_irq_init(struct pm860x_chip *chip, if (ret < 0) goto out; - memset(chip->irq, 0, sizeof(struct pm860x_irq) * PM860X_NUM_IRQ); - - ret = request_threaded_irq(i2c->irq, NULL, pm8607_irq_thread, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "88PM8607", chip); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + mutex_init(&chip->irq_lock); + chip->irq_base = pdata->irq_base; + chip->core_irq = i2c->irq; + if (!chip->core_irq) goto out; + + desc = irq_to_desc(chip->core_irq); + + /* register IRQ by genirq */ + for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { + __irq = i + chip->irq_base; + set_irq_chip_data(__irq, chip); + set_irq_chip_and_handler(__irq, &pm860x_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 } - chip->chip_irq = i2c->irq; + + ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags, + "88pm860x", chip); + if (ret) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", ret); + chip->core_irq = 0; + } + return 0; out: + chip->core_irq = 0; return ret; } static void __devexit device_irq_exit(struct pm860x_chip *chip) { - if (chip->chip_irq >= 0) - free_irq(chip->chip_irq, chip); + if (chip->core_irq) + free_irq(chip->core_irq, chip); } static void __devinit device_8606_init(struct pm860x_chip *chip, @@ -513,6 +673,26 @@ static void __devinit device_8607_init(struct pm860x_chip *chip, goto out_dev; } } + + if (pdata && pdata->power) { + ret = mfd_add_devices(chip->dev, 0, &power_devs[0], + ARRAY_SIZE(power_devs), + &power_supply_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add power supply " + "subdev\n"); + goto out_dev; + } + } + + ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], + ARRAY_SIZE(onkey_devs), + &onkey_resources[0], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add onkey subdev\n"); + goto out_dev; + } + return; out_dev: mfd_remove_devices(chip->dev); @@ -524,7 +704,7 @@ out: int pm860x_device_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) { - chip->chip_irq = -EINVAL; + chip->core_irq = 0; switch (chip->id) { case CHIP_PM8606: -- cgit v1.2.3 From 14e5c82ca32870698b7cbe54196e00682d929ca5 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Thu, 11 Feb 2010 13:47:09 +0100 Subject: mfd: Fix twl_has_pwrbutton typo Change the typo CONFIG_INPUT_TWL4030_PWBUTTON_MODULE in the twl_has_pwrbutton definition to CONFIG_INPUT_TWL4030_PWRBUTTON_MODULE. Signed-off-by: Eduardo Valentin Signed-off-by: Christoph Egger Signed-off-by: Samuel Ortiz --- drivers/mfd/twl-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index d81003f4867e..942a1e837819 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -123,7 +123,7 @@ #define TWL_NUM_SLAVES 4 #if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \ - || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE) + || defined(CONFIG_INPUT_TWL4030_PWRBUTTON_MODULE) #define twl_has_pwrbutton() true #else #define twl_has_pwrbutton() false -- cgit v1.2.3 From 91fedede0338eb6203cdd618d8ece873fdb7c22c Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 19 Feb 2010 11:07:59 +0100 Subject: mfd: Check for ACPI conflicts For ACPI based systems, we should check for ACPI conflicts when adding the platform devices. The test will always succeed for non ACPI platforms. Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index ae15e495e20e..aa17f4bddc56 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -13,6 +13,7 @@ #include #include +#include #include static int mfd_add_device(struct device *parent, int id, @@ -62,6 +63,10 @@ static int mfd_add_device(struct device *parent, int id, res[r].start = cell->resources[r].start; res[r].end = cell->resources[r].end; } + + ret = acpi_check_resource_conflict(res); + if (ret) + goto fail_res; } platform_device_add_resources(pdev, res, cell->num_resources); -- cgit v1.2.3 From e82c60ae7d3a7f19c4b9b3e7bd6ea298fa5efe93 Mon Sep 17 00:00:00 2001 From: Denis Turischev Date: Fri, 19 Feb 2010 11:26:25 +0100 Subject: mfd: Introduce lpc_sch for Intel SCH LPC bridge Intel Poulsbo (SCH) chipset LPC bridge controller contains several functions. Creating and MFD driver for the LPC bridge controller allows simultaneous use of SMBus and GPIO interfaces on the SCH. Signed-off-by: Denis Turischev Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 ++++ drivers/mfd/Makefile | 1 + drivers/mfd/lpc_sch.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 drivers/mfd/lpc_sch.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 84a68d260772..9b25fe28233e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -389,6 +389,15 @@ config MFD_TIMBERDALE The timberdale FPGA can be found on the Intel Atom development board for in-vehicle infontainment, called Russellville. + +config LPC_SCH + tristate "Intel SCH LPC" + depends on PCI + select MFD_CORE + help + LPC bridge function of the Intel SCH provides support for + System Management Bus and General Purpose I/O. + endmenu menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 878cac691067..22715add99a7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -62,3 +62,4 @@ 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 diff --git a/drivers/mfd/lpc_sch.c b/drivers/mfd/lpc_sch.c new file mode 100644 index 000000000000..51b2f6065a0b --- /dev/null +++ b/drivers/mfd/lpc_sch.c @@ -0,0 +1,133 @@ +/* + * lpc_sch.c - LPC interface for Intel Poulsbo SCH + * + * LPC bridge function of the Intel SCH contains many other + * functional units, such as Interrupt controllers, Timers, + * Power Management, System Management, GPIO, RTC, and LPC + * Configuration Registers. + * + * Copyright (c) 2010 CompuLab Ltd + * Author: Denis Turischev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SMBASE 0x40 +#define SMBUS_IO_SIZE 64 + +#define GPIOBASE 0x44 +#define GPIO_IO_SIZE 64 + +static struct resource smbus_sch_resource = { + .flags = IORESOURCE_IO, +}; + + +static struct resource gpio_sch_resource = { + .flags = IORESOURCE_IO, +}; + +static struct mfd_cell lpc_sch_cells[] = { + { + .name = "isch_smbus", + .num_resources = 1, + .resources = &smbus_sch_resource, + }, + { + .name = "sch_gpio", + .num_resources = 1, + .resources = &gpio_sch_resource, + }, +}; + +static struct pci_device_id lpc_sch_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, lpc_sch_ids); + +static int __devinit lpc_sch_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int base_addr_cfg; + unsigned short base_addr; + + pci_read_config_dword(dev, SMBASE, &base_addr_cfg); + if (!(base_addr_cfg & (1 << 31))) { + dev_err(&dev->dev, "Decode of the SMBus I/O range disabled\n"); + return -ENODEV; + } + base_addr = (unsigned short)base_addr_cfg; + if (base_addr == 0) { + dev_err(&dev->dev, "I/O space for SMBus uninitialized\n"); + return -ENODEV; + } + + smbus_sch_resource.start = base_addr; + smbus_sch_resource.end = base_addr + SMBUS_IO_SIZE - 1; + + pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg); + if (!(base_addr_cfg & (1 << 31))) { + dev_err(&dev->dev, "Decode of the GPIO I/O range disabled\n"); + return -ENODEV; + } + base_addr = (unsigned short)base_addr_cfg; + if (base_addr == 0) { + dev_err(&dev->dev, "I/O space for GPIO uninitialized\n"); + return -ENODEV; + } + + gpio_sch_resource.start = base_addr; + gpio_sch_resource.end = base_addr + GPIO_IO_SIZE - 1; + + return mfd_add_devices(&dev->dev, -1, + lpc_sch_cells, ARRAY_SIZE(lpc_sch_cells), NULL, 0); +} + +static void __devexit lpc_sch_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); +} + +static struct pci_driver lpc_sch_driver = { + .name = "lpc_sch", + .id_table = lpc_sch_ids, + .probe = lpc_sch_probe, + .remove = __devexit_p(lpc_sch_remove), +}; + +static int __init lpc_sch_init(void) +{ + return pci_register_driver(&lpc_sch_driver); +} + +static void __exit lpc_sch_exit(void) +{ + pci_unregister_driver(&lpc_sch_driver); +} + +module_init(lpc_sch_init); +module_exit(lpc_sch_exit); + +MODULE_AUTHOR("Denis Turischev "); +MODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From a29aaf55cd6faa75e35abfe00bd3ffc537490485 Mon Sep 17 00:00:00 2001 From: Moiz Sonasath Date: Tue, 16 Feb 2010 18:57:21 -0600 Subject: mfd: Disable TWL4030/5030 I2C1/I2C4 internal pull-ups This patch disables TWL4030/5030 I2C1 adn I2C4(SR) internal pull-up, to use only the external HW resistor >=470 Ohm for the assured functionality in HS mode. While testing the I2C in High Speed mode, it was discovered that without a proper pull-up resistor, there is data corruption during multi-byte transfer. RTC(time_set) test case was used for testing. From the analysis done, it was concluded that ideally we need a pull-up of 1.6k Ohm(recomended) or atleast 470 Ohm or greater for assured performance in HS mode. Signed-off-by: Moiz Sonasath Signed-off-by: Allen Pais Signed-off-by: Samuel Ortiz --- drivers/mfd/twl-core.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 942a1e837819..7ccc39f3aa48 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -958,6 +958,7 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) int status; unsigned i; struct twl4030_platform_data *pdata = client->dev.platform_data; + u8 temp; if (!pdata) { dev_dbg(&client->dev, "no platform data?\n"); @@ -1025,6 +1026,18 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) goto fail; } + /* Disable TWL4030/TWL5030 I2C Pull-up on I2C1 and I2C4(SR) interface. + * Program I2C_SCL_CTRL_PU(bit 0)=0, I2C_SDA_CTRL_PU (bit 2)=0, + * SR_I2C_SCL_CTRL_PU(bit 4)=0 and SR_I2C_SDA_CTRL_PU(bit 6)=0. + */ + + if (twl_class_is_4030()) { + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &temp, REG_GPPUPDCTR1); + temp &= ~(SR_I2C_SDA_CTRL_PU | SR_I2C_SCL_CTRL_PU | \ + I2C_SDA_CTRL_PU | I2C_SCL_CTRL_PU); + twl_i2c_write_u8(TWL4030_MODULE_INTBR, temp, REG_GPPUPDCTR1); + } + status = add_children(pdata, id->driver_data); fail: if (status < 0) -- cgit v1.2.3 From fa0d976298b25d090fafc3460c63fee1c8eea854 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Fri, 19 Feb 2010 12:39:38 +0100 Subject: mfd: Add twl6030 base addr for ID0, ID1, ID2 Add base address for generic slave ID0, ID1, ID2 and introduced one more entry to align RTC module number between twl4030 and twl6030 Signed-off-by: Balaji T K Signed-off-by: Samuel Ortiz --- drivers/mfd/twl-core.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 7ccc39f3aa48..562cd4935e17 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -198,6 +198,7 @@ /* subchip/slave 3 0x4B - AUDIO */ #define TWL6030_BASEADD_AUDIO 0x0000 #define TWL6030_BASEADD_RSV 0x0000 +#define TWL6030_BASEADD_ZERO 0x0000 /* Few power values */ #define R_CFG_BOOT 0x05 @@ -313,9 +314,11 @@ static struct twl_mapping twl6030_map[] = { { SUB_CHIP_ID1, TWL6030_BASEADD_CHARGER }, { SUB_CHIP_ID1, TWL6030_BASEADD_GASGAUGE }, { SUB_CHIP_ID1, TWL6030_BASEADD_PWM }, - { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, - { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, + { SUB_CHIP_ID0, TWL6030_BASEADD_ZERO }, + { SUB_CHIP_ID1, TWL6030_BASEADD_ZERO }, + { SUB_CHIP_ID2, TWL6030_BASEADD_ZERO }, + { SUB_CHIP_ID2, TWL6030_BASEADD_ZERO }, { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, { SUB_CHIP_ID2, TWL6030_BASEADD_RSV }, -- cgit v1.2.3 From f92e8f8144243a3651b2e350b706ea2d04931f8c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Feb 2010 18:45:25 +0000 Subject: mfd: Add WM831x revision B support Revision B of the WM831x devices changes the sense of the tristate bit for GPIO configuration, inverting it to become an enable instead. Take account of this in the gpiolib driver. A current sink regulation status bit has also been added in revision B, add a flag indicating if it's present but don't use it yet. This revision also adds an interrupt on key up for the ON pin event which the existing code is able to take advantage of. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 4b2021af1d96..c428d9f918fc 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -1449,18 +1449,33 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) case WM8310: parent = WM8310; wm831x->num_gpio = 16; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + dev_info(wm831x->dev, "WM8310 revision %c\n", 'A' + rev); break; case WM8311: parent = WM8311; wm831x->num_gpio = 16; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + dev_info(wm831x->dev, "WM8311 revision %c\n", 'A' + rev); break; case WM8312: parent = WM8312; wm831x->num_gpio = 16; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + dev_info(wm831x->dev, "WM8312 revision %c\n", 'A' + rev); break; -- cgit v1.2.3 From 3c49e810507c9d0659b8aa61f7f91aecda428ee3 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 22 Feb 2010 13:37:09 +0900 Subject: mfd: sh_mobile_sdhi MMC_CAP_MMC_HIGHSPEED support Add MMC_CAP_MMC_HIGHSPEED support to the sh_mobile_sdhi driver. Also, remove type cast and FIXME comment. Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mfd/sh_mobile_sdhi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index 03efae8041ab..468fd366d4da 100644 --- a/drivers/mfd/sh_mobile_sdhi.c +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -21,7 +21,7 @@ #include #include #include - +#include #include #include #include @@ -95,9 +95,9 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) clk_enable(priv->clk); - /* FIXME: silly const unsigned int hclk */ - *(unsigned int *)&priv->mmc_data.hclk = clk_get_rate(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; memcpy(&priv->cell_mmc, &sh_mobile_sdhi_cell, sizeof(priv->cell_mmc)); priv->cell_mmc.driver_data = &priv->mmc_data; -- cgit v1.2.3 From 3d8ca8c0b9fe42d005360e255fbf7a6f8580ef87 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 22 Feb 2010 13:40:59 +0900 Subject: mfd/mmc: SDHI Kconfig update Enable the sh_mobile_sdhi mfd driver on SH-Mobile ARM processors. While at it, make CONFIG_TMIO_MMC depend on CONFIG_MFD_SH_MOBILE_SDHI. Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9b25fe28233e..951fa9b93fbe 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -47,7 +47,7 @@ config MFD_ASIC3 config MFD_SH_MOBILE_SDHI bool "Support for SuperH Mobile SDHI" - depends on SUPERH + depends on SUPERH || ARCH_SHMOBILE select MFD_CORE ---help--- This driver supports the SDHI hardware block found in many -- cgit v1.2.3 From 11a441ce82d6ffecfd39b324024de0cd630b36c1 Mon Sep 17 00:00:00 2001 From: Mike Turquette Date: Mon, 22 Feb 2010 11:16:30 -0600 Subject: mfd: Introduce remove_script function for twl4030 New function twl4030_remove_script(u8 flags) takes a script type as defined in twl.h and prevents any script already loaded in that position from running. This is accomplished by programming SEQ_ADD_* to 0x3f, the END_OF_SCRIPT value, where SEQ_ADD_* is determined by flags. (Future) users of this function include OMAP board files for machines facing a race condition between sleep and warm reset. Signed-off-by: Mike Turquette Signed-off-by: Samuel Ortiz --- drivers/mfd/twl4030-power.c | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 5b045ff4a2c2..7efa8789a3a2 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -461,6 +461,56 @@ out: return err; } +int twl4030_remove_script(u8 flags) +{ + int err = 0; + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1, + R_PROTECT_KEY); + if (err) { + pr_err("twl4030: unable to unlock PROTECT_KEY\n"); + return err; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2, + R_PROTECT_KEY); + if (err) { + pr_err("twl4030: unable to unlock PROTECT_KEY\n"); + return err; + } + + if (flags & TWL4030_WRST_SCRIPT) { + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_WARM); + if (err) + return err; + } + if (flags & TWL4030_WAKEUP12_SCRIPT) { + if (err) + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_S2A12); + return err; + } + if (flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_S2A3); + if (err) + return err; + } + if (flags & TWL4030_SLEEP_SCRIPT) { + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_A2S); + if (err) + return err; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY); + if (err) + pr_err("TWL4030 Unable to relock registers\n"); + + return err; +} + void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts) { int err = 0; -- cgit v1.2.3 From d19663ac61a6e36eec655d3c84a106686ebddd2c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 23 Feb 2010 11:08:05 +0000 Subject: mfd: Use completion interrupt for WM835x AUXADC Use the completion interrupt generated by the device rather than polling for conversions to complete. As a backup we still check the state of the AUXADC if we don't get a completion, mostly for systems that don't have the WM8350 interrupt infrastructure hooked up. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8350-core.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index 9a970bd68775..bd75807d5302 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -339,7 +339,6 @@ EXPORT_SYMBOL_GPL(wm8350_reg_unlock); int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref) { u16 reg, result = 0; - int tries = 5; if (channel < WM8350_AUXADC_AUX1 || channel > WM8350_AUXADC_TEMP) return -EINVAL; @@ -363,12 +362,13 @@ int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref) reg |= 1 << channel | WM8350_AUXADC_POLL; wm8350_reg_write(wm8350, WM8350_DIGITISER_CONTROL_1, reg); - do { - schedule_timeout_interruptible(1); - reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); - } while ((reg & WM8350_AUXADC_POLL) && --tries); + /* We ignore the result of the completion and just check for a + * conversion result, allowing us to soldier on if the IRQ + * infrastructure is not set up for the chip. */ + wait_for_completion_timeout(&wm8350->auxadc_done, msecs_to_jiffies(5)); - if (!tries) + reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); + if (reg & WM8350_AUXADC_POLL) dev_err(wm8350->dev, "adc chn %d read timeout\n", channel); else result = wm8350_reg_read(wm8350, @@ -385,6 +385,15 @@ int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref) } EXPORT_SYMBOL_GPL(wm8350_read_auxadc); +static irqreturn_t wm8350_auxadc_irq(int irq, void *irq_data) +{ + struct wm8350 *wm8350 = irq_data; + + complete(&wm8350->auxadc_done); + + return IRQ_HANDLED; +} + /* * Cache is always host endian. */ @@ -682,11 +691,22 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, } mutex_init(&wm8350->auxadc_mutex); + init_completion(&wm8350->auxadc_done); ret = wm8350_irq_init(wm8350, irq, pdata); if (ret < 0) goto err; + if (wm8350->irq_base) { + ret = request_threaded_irq(wm8350->irq_base + + WM8350_IRQ_AUXADC_DATARDY, + NULL, wm8350_auxadc_irq, 0, + "auxadc", wm8350); + if (ret < 0) + dev_warn(wm8350->dev, + "Failed to request AUXADC IRQ: %d\n", ret); + } + if (pdata && pdata->init) { ret = pdata->init(wm8350); if (ret != 0) { @@ -736,6 +756,9 @@ void wm8350_device_exit(struct wm8350 *wm8350) platform_device_unregister(wm8350->gpio.pdev); platform_device_unregister(wm8350->codec.pdev); + if (wm8350->irq_base) + free_irq(wm8350->irq_base + WM8350_IRQ_AUXADC_DATARDY, wm8350); + wm8350_irq_exit(wm8350); kfree(wm8350->reg_cache); -- cgit v1.2.3 From 473fe73650b9f92114edbedfbb616561c1a0026c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 23 Feb 2010 11:08:06 +0000 Subject: mfd: Use completion interrupt for WM831x AUXADC Use the completion interrupt generated by the device rather than polling for conversions to complete. As a backup we still check the status of the AUXADC if we don't get a completion, mostly for systems that don't have the WM831x interrupt infrastructure hooked up. Also reduce the timeout for completion of conversions to 5ms from the previous 10ms, the lower timeout should be sufficient. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index c428d9f918fc..07101e9e1cba 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -321,7 +321,6 @@ EXPORT_SYMBOL_GPL(wm831x_set_bits); */ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) { - int tries = 10; int ret, src; mutex_lock(&wm831x->auxadc_lock); @@ -349,13 +348,14 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) goto disable; } - do { - msleep(1); + /* 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) - ret = WM831X_AUX_CVT_ENA; - } while ((ret & WM831X_AUX_CVT_ENA) && --tries); + 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"); @@ -390,6 +390,15 @@ out: } EXPORT_SYMBOL_GPL(wm831x_auxadc_read); +static irqreturn_t wm831x_auxadc_irq(int irq, void *irq_data) +{ + struct wm831x *wm831x = irq_data; + + complete(&wm831x->auxadc_done); + + return IRQ_HANDLED; +} + /** * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC * @@ -1411,6 +1420,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) mutex_init(&wm831x->io_lock); mutex_init(&wm831x->key_lock); mutex_init(&wm831x->auxadc_lock); + init_completion(&wm831x->auxadc_done); dev_set_drvdata(wm831x->dev, wm831x); ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); @@ -1523,6 +1533,16 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) if (ret != 0) goto err; + if (wm831x->irq_base) { + ret = request_threaded_irq(wm831x->irq_base + + WM831X_IRQ_AUXADC_DATA, + NULL, wm831x_auxadc_irq, 0, + "auxadc", wm831x); + if (ret < 0) + dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n", + ret); + } + /* The core device is up, instantiate the subdevices. */ switch (parent) { case WM8310: @@ -1593,6 +1613,8 @@ static void wm831x_device_exit(struct wm831x *wm831x) { wm831x_otp_exit(wm831x); mfd_remove_devices(wm831x->dev); + if (wm831x->irq_base) + free_irq(wm831x->irq_base + WM831X_IRQ_AUXADC_DATA, wm831x); wm831x_irq_exit(wm831x); kfree(wm831x); } -- cgit v1.2.3 From 2c08583c6a6b4c5f5dea4cb0931eca82af7db6fe Mon Sep 17 00:00:00 2001 From: Peter Huewe Date: Sat, 6 Mar 2010 14:36:38 +0100 Subject: mfd: Fix ucb1x00 build failure for collie_defconfig This patch fixes a build failure[1], by adding the missing semaphore.h include References: [1] http://kisskb.ellerman.id.au/kisskb/buildresult/2234322/ Signed-off-by: Peter Huewe Signed-off-by: Samuel Ortiz --- drivers/mfd/ucb1x00-core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index 252b74188ec2..b281217334eb 100644 --- a/drivers/mfd/ucb1x00-core.c +++ b/drivers/mfd/ucb1x00-core.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From a5300dcbbd5d455a1a5978095b8f78af66c540a6 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Mon, 8 Mar 2010 20:07:48 +0100 Subject: mfd: Fix sm501 requested region size We should only request for the MFD used region, not the whole thing. Signed-off-by: Samuel Ortiz Tested-by: Paul Mundt Reviewed-by: H Hartley Sweeten --- drivers/mfd/sm501.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index dc9ea95c0561..497712fd13ed 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -1430,7 +1430,7 @@ static int __devinit sm501_plat_probe(struct platform_device *dev) } sm->regs_claim = request_mem_region(sm->io_res->start, - resource_size(sm->io_res), "sm501"); + 0x100, "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); @@ -1644,7 +1644,7 @@ static int __devinit sm501_pci_probe(struct pci_dev *dev, sm->mem_res = &dev->resource[0]; sm->regs_claim = request_mem_region(sm->io_res->start, - resource_size(sm->io_res), "sm501"); + 0x100, "sm501"); if (sm->regs_claim == NULL) { dev_err(&dev->dev, "cannot claim registers\n"); err= -EBUSY; -- cgit v1.2.3 From 8d002bdc9f1f9a1220c0d87a7321f0233ace97cd Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 15 Dec 2009 21:49:57 +0100 Subject: mfd: Several MFD drivers should depend on GENERIC_HARDIRQS commit 5fb4d38b19d95a5f980f0a10adba798f5b92128c ("mfd: Move WM831x to generic IRQ") didn't take into account that genirq support depends on GENERIC_HARDIRQS. Additionally, 2.6.34-rc1 added: - commit 2afa62ea76027b00e472ddb672191e6e15425b43 ("mfd: Use genirq in 88pm860x"). - commit 760e4518788df6762700e6bb9dd8692379f11168 ("mfd: Convert WM8350 to genirq"). - commit 1f1cf8f98cf6588365efeaab8e7e7758aaa77f6e ("mfd: Update irq handler in max8925") Make all of them depend on GENERIC_HARDIRQS to avoid compile errors on architectures that don't support genirq yet. Signed-off-by: Geert Uytterhoeven Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 951fa9b93fbe..2a5a0b78f84e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -11,7 +11,7 @@ config MFD_CORE config MFD_88PM860X bool "Support Marvell 88PM8606/88PM8607" - depends on I2C=y + depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE help This supports for Marvell 88PM8606/88PM8607 Power Management IC. @@ -205,7 +205,7 @@ config PMIC_ADP5520 config MFD_MAX8925 bool "Maxim Semiconductor MAX8925 PMIC Support" - depends on I2C=y + depends on I2C=y && GENERIC_HARDIRQS select MFD_CORE help Say yes here to support for Maxim Semiconductor MAX8925. This is @@ -226,7 +226,7 @@ config MFD_WM8400 config MFD_WM831X bool "Support Wolfson Microelectronics WM831x/2x PMICs" select MFD_CORE - depends on I2C=y + depends on I2C=y && GENERIC_HARDIRQS help Support for the Wolfson Microelecronics WM831x and WM832x PMICs. This driver provides common support for accessing the device, @@ -235,6 +235,7 @@ config MFD_WM831X config MFD_WM8350 bool + depends on GENERIC_HARDIRQS config MFD_WM8350_CONFIG_MODE_0 bool @@ -287,7 +288,7 @@ config MFD_WM8352_CONFIG_MODE_3 config MFD_WM8350_I2C bool "Support Wolfson Microelectronics WM8350 with I2C" select MFD_WM8350 - depends on I2C=y + depends on I2C=y && GENERIC_HARDIRQS help The WM8350 is an integrated audio and power management subsystem with watchdog and RTC functionality for embedded -- cgit v1.2.3