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') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 0cc5eeff5ee..dc9ea95c056 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') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index bcf4687d4af..e0bbddd7aac 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') diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index 4bc5a08a2b0..c59e5c5737d 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') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index e0bbddd7aac..26d9176fca9 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 +- drivers/power/wm8350_power.c | 24 ++++++++++++------------ drivers/regulator/wm8350-regulator.c | 2 +- drivers/rtc/rtc-wm8350.c | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c index 9025f29e270..655836bc69a 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; diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c index ad4f071e128..3839a5e1c4a 100644 --- a/drivers/power/wm8350_power.c +++ b/drivers/power/wm8350_power.c @@ -428,18 +428,18 @@ static void wm8350_init_charger(struct wm8350 *wm8350) static void free_charger_irq(struct wm8350 *wm8350) { - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); - wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB); - wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350); } static __devinit int wm8350_power_probe(struct platform_device *pdev) diff --git a/drivers/regulator/wm8350-regulator.c b/drivers/regulator/wm8350-regulator.c index 94227dd6ba7..723cd1fb486 100644 --- a/drivers/regulator/wm8350-regulator.c +++ b/drivers/regulator/wm8350-regulator.c @@ -1453,7 +1453,7 @@ static int wm8350_regulator_remove(struct platform_device *pdev) struct regulator_dev *rdev = platform_get_drvdata(pdev); struct wm8350 *wm8350 = rdev_get_drvdata(rdev); - wm8350_free_irq(wm8350, wm8350_reg[pdev->id].irq); + wm8350_free_irq(wm8350, wm8350_reg[pdev->id].irq, rdev); regulator_unregister(rdev); diff --git a/drivers/rtc/rtc-wm8350.c b/drivers/rtc/rtc-wm8350.c index f1e440521c5..a5512f51599 100644 --- a/drivers/rtc/rtc-wm8350.c +++ b/drivers/rtc/rtc-wm8350.c @@ -478,8 +478,8 @@ static int __devexit wm8350_rtc_remove(struct platform_device *pdev) struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct wm8350_rtc *wm_rtc = &wm8350->rtc; - wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC); - wm8350_free_irq(wm8350, WM8350_IRQ_RTC_ALM); + wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_RTC_ALM, wm8350); rtc_device_unregister(wm_rtc->rtc); -- cgit v1.2.3 From 29c71b138c83c8191f1f7e46fcc28b9d6bc8a5dd Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Jan 2010 13:59:08 +0000 Subject: rtc: Suppress duplicate enable/disable of WM8350 update interrupt Unlike the wm8350-custom code genirq nests enable and disable calls so we can't just unconditionally mask or unmask the interrupt, we need to remember the state we set and only mask or unmask when there is a real change. Signed-off-by: Mark Brown Acked-by: Alessandro Zummo Cc: rtc-linux@googlegroups.com Signed-off-by: Samuel Ortiz --- drivers/rtc/rtc-wm8350.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/rtc/rtc-wm8350.c b/drivers/rtc/rtc-wm8350.c index a5512f51599..3d0dc76b38a 100644 --- a/drivers/rtc/rtc-wm8350.c +++ b/drivers/rtc/rtc-wm8350.c @@ -307,11 +307,18 @@ static int wm8350_rtc_update_irq_enable(struct device *dev, { struct wm8350 *wm8350 = dev_get_drvdata(dev); + /* Suppress duplicate changes since genirq nests enable and + * disable calls. */ + if (enabled == wm8350->rtc.update_enabled) + return 0; + if (enabled) wm8350_unmask_irq(wm8350, WM8350_IRQ_RTC_SEC); else wm8350_mask_irq(wm8350, WM8350_IRQ_RTC_SEC); + wm8350->rtc.update_enabled = enabled; + return 0; } -- 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') diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c index 655836bc69a..f56c9adf949 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index b670d10d5c9..d957373d77e 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 59f25070df0325067d7916b467ad15725657fedc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 6 Jan 2010 19:24:25 +0000 Subject: mfd: Update WM8350 drivers for changed interrupt numbers The headphone detect and charger are using the IRQ numbers so need to take account of irq_base with the genirq conversion. I obviously picked the wrong system for initial testing. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/power/wm8350_power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c index 3839a5e1c4a..0693902d615 100644 --- a/drivers/power/wm8350_power.c +++ b/drivers/power/wm8350_power.c @@ -190,7 +190,7 @@ static irqreturn_t wm8350_charger_handler(int irq, void *data) struct wm8350_power *power = &wm8350->power; struct wm8350_charger_policy *policy = power->policy; - switch (irq) { + switch (irq - wm8350->irq_base) { case WM8350_IRQ_CHG_BAT_FAIL: dev_err(wm8350->dev, "battery failed\n"); break; -- cgit v1.2.3 From 38f6ce45f0bca04ac653c57cacd375c469995321 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 7 Jan 2010 16:10:08 +0000 Subject: gpiolib: Add support for WM8350 GPIO controller Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 1 + drivers/gpio/wm8350-gpiolib.c | 181 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 drivers/gpio/wm8350-gpiolib.c (limited to 'drivers') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a4cdbd51b1c..acac9f60db1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -185,6 +185,13 @@ config GPIO_WM831X Say yes here to access the GPIO signals of WM831x power management chips from Wolfson Microelectronics. +config GPIO_WM8350 + tristate "WM8350 GPIOs" + depends on MFD_WM8350 + help + Say yes here to access the GPIO signals of WM8350 power management + chips from Wolfson Microelectronics. + config GPIO_ADP5520 tristate "GPIO Support for ADP5520 PMIC" depends on PMIC_ADP5520 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 128abf8a98d..90b0880923d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o obj-$(CONFIG_GPIO_IT8761E) += it8761e_gpio.o obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o +obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o diff --git a/drivers/gpio/wm8350-gpiolib.c b/drivers/gpio/wm8350-gpiolib.c new file mode 100644 index 00000000000..511840d1c7b --- /dev/null +++ b/drivers/gpio/wm8350-gpiolib.c @@ -0,0 +1,181 @@ +/* + * wm835x-gpiolib.c -- gpiolib support for Wolfson WM835x PMICs + * + * 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 + +struct wm8350_gpio_data { + struct wm8350 *wm8350; + struct gpio_chip gpio_chip; +}; + +static inline struct wm8350_gpio_data *to_wm8350_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8350_gpio_data, gpio_chip); +} + +static int wm8350_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + return wm8350_set_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O, + 1 << offset); +} + +static int wm8350_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + int ret; + + ret = wm8350_reg_read(wm8350, WM8350_GPIO_LEVEL); + if (ret < 0) + return ret; + + if (ret & (1 << offset)) + return 1; + else + return 0; +} + +static void wm8350_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + if (value) + wm8350_set_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset); + else + wm8350_clear_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset); +} + +static int wm8350_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + int ret; + + ret = wm8350_clear_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O, + 1 << offset); + if (ret < 0) + return ret; + + /* Don't have an atomic direction/value setup */ + wm8350_gpio_set(chip, offset, value); + + return 0; +} + +static int wm8350_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip); + struct wm8350 *wm8350 = wm8350_gpio->wm8350; + + if (!wm8350->irq_base) + return -EINVAL; + + return wm8350->irq_base + WM8350_IRQ_GPIO(offset); +} + +static struct gpio_chip template_chip = { + .label = "wm8350", + .owner = THIS_MODULE, + .direction_input = wm8350_gpio_direction_in, + .get = wm8350_gpio_get, + .direction_output = wm8350_gpio_direction_out, + .set = wm8350_gpio_set, + .to_irq = wm8350_gpio_to_irq, + .can_sleep = 1, +}; + +static int __devinit wm8350_gpio_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = dev_get_drvdata(pdev->dev.parent); + struct wm8350_platform_data *pdata = wm8350->dev->platform_data; + struct wm8350_gpio_data *wm8350_gpio; + int ret; + + wm8350_gpio = kzalloc(sizeof(*wm8350_gpio), GFP_KERNEL); + if (wm8350_gpio == NULL) + return -ENOMEM; + + wm8350_gpio->wm8350 = wm8350; + wm8350_gpio->gpio_chip = template_chip; + wm8350_gpio->gpio_chip.ngpio = 13; + wm8350_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm8350_gpio->gpio_chip.base = pdata->gpio_base; + else + wm8350_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8350_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm8350_gpio); + + return ret; + +err: + kfree(wm8350_gpio); + return ret; +} + +static int __devexit wm8350_gpio_remove(struct platform_device *pdev) +{ + struct wm8350_gpio_data *wm8350_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm8350_gpio->gpio_chip); + if (ret == 0) + kfree(wm8350_gpio); + + return ret; +} + +static struct platform_driver wm8350_gpio_driver = { + .driver.name = "wm8350-gpio", + .driver.owner = THIS_MODULE, + .probe = wm8350_gpio_probe, + .remove = __devexit_p(wm8350_gpio_remove), +}; + +static int __init wm8350_gpio_init(void) +{ + return platform_driver_register(&wm8350_gpio_driver); +} +subsys_initcall(wm8350_gpio_init); + +static void __exit wm8350_gpio_exit(void) +{ + platform_driver_unregister(&wm8350_gpio_driver); +} +module_exit(wm8350_gpio_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("GPIO interface for WM8350 PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-gpio"); -- 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') diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index fd42a80e7bf..aa3824a1b4f 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') diff --git a/drivers/mfd/88pm8607.c b/drivers/mfd/88pm8607.c deleted file mode 100644 index 7e3f6590799..00000000000 --- 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 00000000000..d1464e54e65 --- /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 00000000000..dda23cbfe41 --- /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 78295d6a75f..88fa200188c 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 ++++++++++++++++++++++++++++++------------- drivers/regulator/88pm8607.c | 32 ++++---- 3 files changed, 175 insertions(+), 90 deletions(-) (limited to 'drivers') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index d1464e54e65..72b00304dc3 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 dda23cbfe41..6d7dba2bce8 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; } diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index 04719551381..97897a6bf4f 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -11,15 +11,17 @@ #include #include #include +#include #include #include #include -#include +#include struct pm8607_regulator_info { struct regulator_desc desc; - struct pm8607_chip *chip; + struct pm860x_chip *chip; struct regulator_dev *regulator; + struct i2c_client *i2c; int min_uV; int max_uV; @@ -46,7 +48,7 @@ static inline int check_range(struct pm8607_regulator_info *info, static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - uint8_t chip_id = info->chip->chip_id; + uint8_t chip_id = info->chip->chip_version; int ret = -EINVAL; switch (info->desc.id) { @@ -169,7 +171,7 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - uint8_t chip_id = info->chip->chip_id; + uint8_t chip_id = info->chip->chip_version; int val = -ENOENT; int ret; @@ -428,7 +430,6 @@ static int pm8607_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - struct pm8607_chip *chip = info->chip; uint8_t val, mask; int ret; @@ -443,13 +444,13 @@ static int pm8607_set_voltage(struct regulator_dev *rdev, val = (uint8_t)(ret << info->vol_shift); mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; - ret = pm8607_set_bits(chip, info->vol_reg, mask, val); + ret = pm860x_set_bits(info->i2c, info->vol_reg, mask, val); if (ret) return ret; switch (info->desc.id) { case PM8607_ID_BUCK1: case PM8607_ID_BUCK3: - ret = pm8607_set_bits(chip, info->update_reg, + ret = pm860x_set_bits(info->i2c, info->update_reg, 1 << info->update_bit, 1 << info->update_bit); break; @@ -460,11 +461,10 @@ static int pm8607_set_voltage(struct regulator_dev *rdev, static int pm8607_get_voltage(struct regulator_dev *rdev) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - struct pm8607_chip *chip = info->chip; uint8_t val, mask; int ret; - ret = pm8607_reg_read(chip, info->vol_reg); + ret = pm860x_reg_read(info->i2c, info->vol_reg); if (ret < 0) return ret; @@ -477,9 +477,8 @@ static int pm8607_get_voltage(struct regulator_dev *rdev) static int pm8607_enable(struct regulator_dev *rdev) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - struct pm8607_chip *chip = info->chip; - return pm8607_set_bits(chip, info->enable_reg, + return pm860x_set_bits(info->i2c, info->enable_reg, 1 << info->enable_bit, 1 << info->enable_bit); } @@ -487,19 +486,17 @@ static int pm8607_enable(struct regulator_dev *rdev) static int pm8607_disable(struct regulator_dev *rdev) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - struct pm8607_chip *chip = info->chip; - return pm8607_set_bits(chip, info->enable_reg, + return pm860x_set_bits(info->i2c, info->enable_reg, 1 << info->enable_bit, 0); } static int pm8607_is_enabled(struct regulator_dev *rdev) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - struct pm8607_chip *chip = info->chip; int ret; - ret = pm8607_reg_read(chip, info->enable_reg); + ret = pm860x_reg_read(info->i2c, info->enable_reg); if (ret < 0) return ret; @@ -589,8 +586,8 @@ static inline struct pm8607_regulator_info *find_regulator_info(int id) static int __devinit pm8607_regulator_probe(struct platform_device *pdev) { - struct pm8607_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm8607_platform_data *pdata = chip->dev->platform_data; + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_platform_data *pdata = chip->dev->platform_data; struct pm8607_regulator_info *info = NULL; info = find_regulator_info(pdev->id); @@ -599,6 +596,7 @@ static int __devinit pm8607_regulator_probe(struct platform_device *pdev) return -EINVAL; } + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; info->chip = chip; info->regulator = regulator_register(&info->desc, &pdev->dev, -- 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 ++- drivers/regulator/Kconfig | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d957373d77e..815907eb70a 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 88fa200188c..1e3ae062c1f 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 diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 834b4844182..dbfe31a4a51 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -191,7 +191,7 @@ config REGULATOR_TPS6507X config REGULATOR_88PM8607 bool "Marvell 88PM8607 Power regulators" - depends on MFD_88PM8607=y + depends on MFD_88PM860X=y help This driver supports 88PM8607 voltage regulator chips. -- 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') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 72b00304dc3..9185f0d945f 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') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 9185f0d945f..16f0dca707a 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 d07e8bf83fd71567aaf9c27c3b133ab3cf8f3c4c Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 9 Nov 2009 12:41:07 -0500 Subject: backlight: Enable backlight in 88pm860x At most, three backlight device can be supported in 88pm860x driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/video/backlight/88pm860x_bl.c | 304 ++++++++++++++++++++++++++++++++++ drivers/video/backlight/Kconfig | 6 + drivers/video/backlight/Makefile | 1 + 3 files changed, 311 insertions(+) create mode 100644 drivers/video/backlight/88pm860x_bl.c (limited to 'drivers') diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c new file mode 100644 index 00000000000..b8f705cca43 --- /dev/null +++ b/drivers/video/backlight/88pm860x_bl.c @@ -0,0 +1,304 @@ +/* + * Backlight driver for Marvell Semiconductor 88PM8606 + * + * 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 MAX_BRIGHTNESS (0xFF) +#define MIN_BRIGHTNESS (0) + +#define CURRENT_MASK (0x1F << 1) + +struct pm860x_backlight_data { + struct pm860x_chip *chip; + struct i2c_client *i2c; + int current_brightness; + int port; + int pwm; + int iset; +}; + +static inline int wled_a(int port) +{ + int ret; + + ret = ((port - PM8606_BACKLIGHT1) << 1) + 2; + return ret; +} + +static inline int wled_b(int port) +{ + int ret; + + ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; + return ret; +} + +/* WLED2 & WLED3 share the same IDC */ +static inline int wled_idc(int port) +{ + int ret; + + switch (port) { + case PM8606_BACKLIGHT1: + case PM8606_BACKLIGHT2: + ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; + break; + case PM8606_BACKLIGHT3: + default: + ret = ((port - PM8606_BACKLIGHT2) << 1) + 3; + break; + } + return ret; +} + +static int pm860x_backlight_set(struct backlight_device *bl, int brightness) +{ + struct pm860x_backlight_data *data = bl_get_data(bl); + struct pm860x_chip *chip = data->chip; + unsigned char value; + int ret; + + if (brightness > MAX_BRIGHTNESS) + value = MAX_BRIGHTNESS; + else + value = brightness; + + ret = pm860x_reg_write(data->i2c, wled_a(data->port), value); + if (ret < 0) + goto out; + + if ((data->current_brightness == 0) && brightness) { + if (data->iset) { + ret = pm860x_set_bits(data->i2c, wled_idc(data->port), + CURRENT_MASK, data->iset); + if (ret < 0) + goto out; + } + if (data->pwm) { + ret = pm860x_set_bits(data->i2c, PM8606_PWM, + PM8606_PWM_FREQ_MASK, data->pwm); + if (ret < 0) + goto out; + } + if (brightness == MAX_BRIGHTNESS) { + /* set WLED_ON bit as 100% */ + ret = pm860x_set_bits(data->i2c, wled_b(data->port), + PM8606_WLED_ON, PM8606_WLED_ON); + } + } else { + if (brightness == MAX_BRIGHTNESS) { + /* set WLED_ON bit as 100% */ + ret = pm860x_set_bits(data->i2c, wled_b(data->port), + PM8606_WLED_ON, PM8606_WLED_ON); + } else { + /* clear WLED_ON bit since it's not 100% */ + ret = pm860x_set_bits(data->i2c, wled_b(data->port), + PM8606_WLED_ON, 0); + } + } + if (ret < 0) + goto out; + + dev_dbg(chip->dev, "set brightness %d\n", value); + data->current_brightness = value; + return 0; +out: + dev_dbg(chip->dev, "set brightness %d failure with return " + "value:%d\n", value, ret); + return ret; +} + +static int pm860x_backlight_update_status(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK) + brightness = 0; + + if (bl->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + + if (bl->props.state & BL_CORE_SUSPENDED) + brightness = 0; + + return pm860x_backlight_set(bl, brightness); +} + +static int pm860x_backlight_get_brightness(struct backlight_device *bl) +{ + struct pm860x_backlight_data *data = bl_get_data(bl); + struct pm860x_chip *chip = data->chip; + int ret; + + ret = pm860x_reg_read(data->i2c, wled_a(data->port)); + if (ret < 0) + goto out; + data->current_brightness = ret; + dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); + return data->current_brightness; +out: + return -EINVAL; +} + +static struct backlight_ops pm860x_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = pm860x_backlight_update_status, + .get_brightness = pm860x_backlight_get_brightness, +}; + +static int __check_device(struct pm860x_backlight_pdata *pdata, char *name) +{ + struct pm860x_backlight_pdata *p = pdata; + int ret = -EINVAL; + + while (p && p->id) { + if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0)) + break; + + if (!strncmp(name, pm860x_backlight_name[p->flags], + MFD_NAME_SIZE)) { + ret = (int)p->flags; + break; + } + p++; + } + return ret; +} + +static int pm860x_backlight_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_platform_data *pm860x_pdata; + struct pm860x_backlight_pdata *pdata = NULL; + struct pm860x_backlight_data *data; + struct backlight_device *bl; + struct resource *res; + unsigned char value; + char name[MFD_NAME_SIZE]; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); + return -EINVAL; + } + + if (pdev->dev.parent->platform_data) { + pm860x_pdata = pdev->dev.parent->platform_data; + pdata = pm860x_pdata->backlight; + } + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "backlight\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + strncpy(name, res->name, MFD_NAME_SIZE); + data->chip = chip; + data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ + : chip->companion; + data->current_brightness = MAX_BRIGHTNESS; + data->pwm = pdata->pwm; + data->iset = pdata->iset; + data->port = __check_device(pdata, name); + if (data->port < 0) { + dev_err(&pdev->dev, "wrong platform data is assigned"); + return -EINVAL; + } + + bl = backlight_device_register(name, &pdev->dev, data, + &pm860x_backlight_ops); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + kfree(data); + return PTR_ERR(bl); + } + bl->props.max_brightness = MAX_BRIGHTNESS; + bl->props.brightness = MAX_BRIGHTNESS; + + platform_set_drvdata(pdev, bl); + + /* Enable reference VSYS */ + ret = pm860x_reg_read(data->i2c, PM8606_VSYS); + if (ret < 0) + goto out; + if ((ret & PM8606_VSYS_EN) == 0) { + value = ret | PM8606_VSYS_EN; + ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value); + if (ret < 0) + goto out; + } + /* Enable reference OSC */ + ret = pm860x_reg_read(data->i2c, PM8606_MISC); + if (ret < 0) + goto out; + if ((ret & PM8606_MISC_OSC_EN) == 0) { + value = ret | PM8606_MISC_OSC_EN; + ret = pm860x_reg_write(data->i2c, PM8606_MISC, value); + if (ret < 0) + goto out; + } + /* read current backlight */ + ret = pm860x_backlight_get_brightness(bl); + if (ret < 0) + goto out; + + backlight_update_status(bl); + return 0; +out: + kfree(data); + return ret; +} + +static int pm860x_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct pm860x_backlight_data *data = bl_get_data(bl); + + backlight_device_unregister(bl); + kfree(data); + return 0; +} + +static struct platform_driver pm860x_backlight_driver = { + .driver = { + .name = "88pm860x-backlight", + .owner = THIS_MODULE, + }, + .probe = pm860x_backlight_probe, + .remove = pm860x_backlight_remove, +}; + +static int __init pm860x_backlight_init(void) +{ + return platform_driver_register(&pm860x_backlight_driver); +} +module_init(pm860x_backlight_init); + +static void __exit pm860x_backlight_exit(void) +{ + platform_driver_unregister(&pm860x_backlight_driver); +} +module_exit(pm860x_backlight_exit); + +MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:88pm860x-backlight"); diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 09bfa9662e4..96a6b3060ac 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -262,3 +262,9 @@ config BACKLIGHT_ADP5520 To compile this driver as a module, choose M here: the module will be called adp5520_bl. +config BACKLIGHT_88PM860X + tristate "Backlight Driver for 88PM8606 using WLED" + depends on BACKLIGHT_CLASS_DEVICE && MFD_88PM860X + help + Say Y to enable the backlight driver for Marvell 88PM8606. + diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 9a405548874..802d467b08c 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -28,4 +28,5 @@ obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o obj-$(CONFIG_BACKLIGHT_ADX) += adx_bl.o obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o +obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o -- cgit v1.2.3 From 0a2f915b405ac1a8d19d9b7cf7873f26ba682a24 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 6 Jan 2010 17:10:09 -0500 Subject: led: Enable led in 88pm860x Enable led sub device in Marvell 88PM860x. Two LED arrays can be supported. Each LED array can be used for R,G,B leds. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-88pm860x.c | 325 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 drivers/leds/leds-88pm860x.c (limited to 'drivers') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 8a0e1ec95e4..e0b64312e66 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -17,6 +17,13 @@ config LEDS_CLASS comment "LED drivers" +config LEDS_88PM860X + tristate "LED Support for Marvell 88PM860x PMIC" + depends on LEDS_CLASS && MFD_88PM860X + help + This option enables support for on-chip LED drivers found on Marvell + Semiconductor 88PM8606 PMIC. + config LEDS_ATMEL_PWM tristate "LED Support using Atmel PWM outputs" depends on LEDS_CLASS && ATMEL_PWM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 9e63869d7c0..d76fb32b77c 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers +obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c new file mode 100644 index 00000000000..d196073a6ae --- /dev/null +++ b/drivers/leds/leds-88pm860x.c @@ -0,0 +1,325 @@ +/* + * LED driver for Marvell 88PM860x + * + * 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 LED_PWM_SHIFT (3) +#define LED_PWM_MASK (0x1F) +#define LED_CURRENT_MASK (0x07 << 5) + +#define LED_BLINK_ON_MASK (0x07) +#define LED_BLINK_PERIOD_MASK (0x0F << 3) +#define LED_BLINK_MASK (0x7F) + +#define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66) +#define LED_BLINK_PERIOD(x) ((x & 0xF) * 530 + 930) +#define LED_BLINK_ON_MIN LED_BLINK_ON(0) +#define LED_BLINK_ON_MAX LED_BLINK_ON(0x7) +#define LED_BLINK_PERIOD_MIN LED_BLINK_PERIOD(0) +#define LED_BLINK_PERIOD_MAX LED_BLINK_PERIOD(0xE) +#define LED_TO_ON(x) ((x - 66) / 66) +#define LED_TO_PERIOD(x) ((x - 930) / 530) + +#define LED1_BLINK_EN (1 << 1) +#define LED2_BLINK_EN (1 << 2) + +enum { + SET_BRIGHTNESS, + SET_BLINK, +}; + +struct pm860x_led { + struct led_classdev cdev; + struct i2c_client *i2c; + struct work_struct work; + struct pm860x_chip *chip; + struct mutex lock; + char name[MFD_NAME_SIZE]; + + int port; + int iset; + int command; + int offset; + unsigned char brightness; + unsigned char current_brightness; + + int blink_data; + int blink_time; + int blink_on; + int blink_off; +}; + +/* return offset of color register */ +static inline int __led_off(int port) +{ + int ret = -EINVAL; + + switch (port) { + case PM8606_LED1_RED: + case PM8606_LED1_GREEN: + case PM8606_LED1_BLUE: + ret = port - PM8606_LED1_RED + PM8606_RGB1B; + break; + case PM8606_LED2_RED: + case PM8606_LED2_GREEN: + case PM8606_LED2_BLUE: + ret = port - PM8606_LED2_RED + PM8606_RGB2B; + break; + } + return ret; +} + +/* return offset of blink register */ +static inline int __blink_off(int port) +{ + int ret = -EINVAL; + + switch (port) { + case PM8606_LED1_RED: + case PM8606_LED1_GREEN: + case PM8606_LED1_BLUE: + ret = PM8606_RGB1A; + case PM8606_LED2_RED: + case PM8606_LED2_GREEN: + case PM8606_LED2_BLUE: + ret = PM8606_RGB2A; + } + return ret; +} + +static inline int __blink_ctl_mask(int port) +{ + int ret = -EINVAL; + + switch (port) { + case PM8606_LED1_RED: + case PM8606_LED1_GREEN: + case PM8606_LED1_BLUE: + ret = LED1_BLINK_EN; + break; + case PM8606_LED2_RED: + case PM8606_LED2_GREEN: + case PM8606_LED2_BLUE: + ret = LED2_BLINK_EN; + break; + } + return ret; +} + +static int __led_set(struct pm860x_led *led, int command) +{ + struct pm860x_chip *chip = led->chip; + int mask, ret; + + mutex_lock(&led->lock); + switch (command) { + case SET_BRIGHTNESS: + if ((led->current_brightness == 0) && led->brightness) { + if (led->iset) { + ret = pm860x_set_bits(led->i2c, led->offset, + LED_CURRENT_MASK, led->iset); + if (ret < 0) + goto out; + } + } else if (led->brightness == 0) { + ret = pm860x_set_bits(led->i2c, led->offset, + LED_CURRENT_MASK, 0); + if (ret < 0) + goto out; + } + ret = pm860x_set_bits(led->i2c, led->offset, LED_PWM_MASK, + led->brightness); + if (ret < 0) + goto out; + led->current_brightness = led->brightness; + dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n", + led->offset, led->brightness); + break; + case SET_BLINK: + ret = pm860x_set_bits(led->i2c, led->offset, + LED_BLINK_MASK, led->blink_data); + if (ret < 0) + goto out; + + mask = __blink_ctl_mask(led->port); + ret = pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask); + if (ret < 0) + goto out; + dev_dbg(chip->dev, "LED blink delay on:%dms, delay off:%dms\n", + led->blink_on, led->blink_off); + break; + } +out: + mutex_unlock(&led->lock); + return 0; +} + +static void pm860x_led_work(struct work_struct *work) +{ + struct pm860x_led *led; + + led = container_of(work, struct pm860x_led, work); + __led_set(led, led->command); +} + +static void pm860x_led_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev); + + data->offset = __led_off(data->port); + data->brightness = value >> 3; + data->command = SET_BRIGHTNESS; + schedule_work(&data->work); +} + +static int pm860x_led_blink(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev); + int period, on; + + on = *delay_on; + if ((on < LED_BLINK_ON_MIN) || (on > LED_BLINK_ON_MAX)) + return -EINVAL; + + on = LED_TO_ON(on); + on = LED_BLINK_ON(on); + + period = on + *delay_off; + if ((period < LED_BLINK_PERIOD_MIN) || (period > LED_BLINK_PERIOD_MAX)) + return -EINVAL; + period = LED_TO_PERIOD(period); + period = LED_BLINK_PERIOD(period); + + data->offset = __blink_off(data->port); + data->blink_on = on; + data->blink_off = period - data->blink_on; + data->blink_data = (period << 3) | data->blink_on; + data->command = SET_BLINK; + schedule_work(&data->work); + + return 0; +} + +static int __check_device(struct pm860x_led_pdata *pdata, char *name) +{ + struct pm860x_led_pdata *p = pdata; + int ret = -EINVAL; + + while (p && p->id) { + if ((p->id != PM8606_ID_LED) || (p->flags < 0)) + break; + + if (!strncmp(name, pm860x_led_name[p->flags], + MFD_NAME_SIZE)) { + ret = (int)p->flags; + break; + } + p++; + } + return ret; +} + +static int pm860x_led_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_platform_data *pm860x_pdata; + struct pm860x_led_pdata *pdata; + struct pm860x_led *data; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); + return -EINVAL; + } + + if (pdev->dev.parent->platform_data) { + pm860x_pdata = pdev->dev.parent->platform_data; + pdata = pm860x_pdata->led; + } else + pdata = NULL; + + data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + strncpy(data->name, res->name, MFD_NAME_SIZE); + dev_set_drvdata(&pdev->dev, data); + data->chip = chip; + data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; + data->iset = pdata->iset; + data->port = __check_device(pdata, data->name); + if (data->port < 0) + return -EINVAL; + + data->current_brightness = 0; + data->cdev.name = data->name; + data->cdev.brightness_set = pm860x_led_set; + data->cdev.blink_set = pm860x_led_blink; + mutex_init(&data->lock); + INIT_WORK(&data->work, pm860x_led_work); + + ret = led_classdev_register(chip->dev, &data->cdev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); + goto out; + } + return 0; +out: + kfree(data); + return ret; +} + +static int pm860x_led_remove(struct platform_device *pdev) +{ + struct pm860x_led *data = platform_get_drvdata(pdev); + + led_classdev_unregister(&data->cdev); + kfree(data); + + return 0; +} + +static struct platform_driver pm860x_led_driver = { + .driver = { + .name = "88pm860x-led", + .owner = THIS_MODULE, + }, + .probe = pm860x_led_probe, + .remove = pm860x_led_remove, +}; + +static int __devinit pm860x_led_init(void) +{ + return platform_driver_register(&pm860x_led_driver); +} +module_init(pm860x_led_init); + +static void __devexit pm860x_led_exit(void) +{ + platform_driver_unregister(&pm860x_led_driver); +} +module_exit(pm860x_led_exit); + +MODULE_DESCRIPTION("LED driver for Marvell PM860x"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:88pm860x-led"); -- cgit v1.2.3 From 866a98ae6e1a9768cd25fe1185481569c7e4b4a9 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Tue, 15 Dec 2009 16:06:17 -0500 Subject: input: Enable touch on 88pm860x Enable touchscreen driver for the 88pm860x multi function core. Signed-off-by: Haojian Zhuang Acked-by: Dmitry Torokhov Signed-off-by: Samuel Ortiz --- drivers/input/touchscreen/88pm860x-ts.c | 241 ++++++++++++++++++++++++++++++++ drivers/input/touchscreen/Kconfig | 12 ++ drivers/input/touchscreen/Makefile | 1 + 3 files changed, 254 insertions(+) create mode 100644 drivers/input/touchscreen/88pm860x-ts.c (limited to 'drivers') diff --git a/drivers/input/touchscreen/88pm860x-ts.c b/drivers/input/touchscreen/88pm860x-ts.c new file mode 100644 index 00000000000..56254d2a1f6 --- /dev/null +++ b/drivers/input/touchscreen/88pm860x-ts.c @@ -0,0 +1,241 @@ +/* + * Touchscreen driver for Marvell 88PM860x + * + * 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 MEAS_LEN (8) +#define ACCURATE_BIT (12) + +/* touch register */ +#define MEAS_EN3 (0x52) + +#define MEAS_TSIX_1 (0x8D) +#define MEAS_TSIX_2 (0x8E) +#define MEAS_TSIY_1 (0x8F) +#define MEAS_TSIY_2 (0x90) +#define MEAS_TSIZ1_1 (0x91) +#define MEAS_TSIZ1_2 (0x92) +#define MEAS_TSIZ2_1 (0x93) +#define MEAS_TSIZ2_2 (0x94) + +/* bit definitions of touch */ +#define MEAS_PD_EN (1 << 3) +#define MEAS_TSIX_EN (1 << 4) +#define MEAS_TSIY_EN (1 << 5) +#define MEAS_TSIZ1_EN (1 << 6) +#define MEAS_TSIZ2_EN (1 << 7) + +struct pm860x_touch { + struct input_dev *idev; + struct i2c_client *i2c; + struct pm860x_chip *chip; + int irq; + int res_x; /* resistor of Xplate */ +}; + +static irqreturn_t pm860x_touch_handler(int irq, void *data) +{ + struct pm860x_touch *touch = data; + struct pm860x_chip *chip = touch->chip; + unsigned char buf[MEAS_LEN]; + int x, y, pen_down; + int z1, z2, rt = 0; + int ret; + + pm860x_mask_irq(chip, irq); + ret = pm860x_bulk_read(touch->i2c, MEAS_TSIX_1, MEAS_LEN, buf); + if (ret < 0) + goto out; + + pen_down = buf[1] & (1 << 6); + x = ((buf[0] & 0xFF) << 4) | (buf[1] & 0x0F); + y = ((buf[2] & 0xFF) << 4) | (buf[3] & 0x0F); + z1 = ((buf[4] & 0xFF) << 4) | (buf[5] & 0x0F); + z2 = ((buf[6] & 0xFF) << 4) | (buf[7] & 0x0F); + + if (pen_down) { + if ((x != 0) && (z1 != 0) && (touch->res_x != 0)) { + rt = z2 / z1 - 1; + rt = (rt * touch->res_x * x) >> ACCURATE_BIT; + dev_dbg(chip->dev, "z1:%d, z2:%d, rt:%d\n", + z1, z2, rt); + } + input_report_abs(touch->idev, ABS_X, x); + input_report_abs(touch->idev, ABS_Y, y); + input_report_abs(touch->idev, ABS_PRESSURE, rt); + input_report_key(touch->idev, BTN_TOUCH, 1); + dev_dbg(chip->dev, "pen down at [%d, %d].\n", x, y); + } else { + input_report_abs(touch->idev, ABS_PRESSURE, 0); + input_report_key(touch->idev, BTN_TOUCH, 0); + dev_dbg(chip->dev, "pen release\n"); + } + input_sync(touch->idev); + pm860x_unmask_irq(chip, irq); + +out: + return IRQ_HANDLED; +} + +static int pm860x_touch_open(struct input_dev *dev) +{ + struct pm860x_touch *touch = input_get_drvdata(dev); + struct pm860x_chip *chip = touch->chip; + int data, ret; + + data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN + | MEAS_TSIZ1_EN | MEAS_TSIZ2_EN; + ret = pm860x_set_bits(touch->i2c, MEAS_EN3, data, data); + if (ret < 0) + goto out; + pm860x_unmask_irq(chip, touch->irq); + return 0; +out: + return ret; +} + +static void pm860x_touch_close(struct input_dev *dev) +{ + struct pm860x_touch *touch = input_get_drvdata(dev); + struct pm860x_chip *chip = touch->chip; + int data; + + data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN + | MEAS_TSIZ1_EN | MEAS_TSIZ2_EN; + pm860x_set_bits(touch->i2c, MEAS_EN3, data, 0); + pm860x_mask_irq(chip, touch->irq); +} + +static int __devinit pm860x_touch_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_platform_data *pm860x_pdata = \ + pdev->dev.parent->platform_data; + struct pm860x_touch_pdata *pdata = NULL; + struct pm860x_touch *touch; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + if (!pm860x_pdata) { + dev_err(&pdev->dev, "platform data is missing\n"); + return -EINVAL; + } + + pdata = pm860x_pdata->touch; + if (!pdata) { + dev_err(&pdev->dev, "touchscreen data is missing\n"); + return -EINVAL; + } + + touch = kzalloc(sizeof(struct pm860x_touch), GFP_KERNEL); + if (touch == NULL) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, touch); + + touch->idev = input_allocate_device(); + if (touch->idev == NULL) { + dev_err(&pdev->dev, "Failed to allocate input device!\n"); + ret = -ENOMEM; + goto out; + } + + touch->idev->name = "88pm860x-touch"; + touch->idev->phys = "88pm860x/input0"; + touch->idev->id.bustype = BUS_I2C; + touch->idev->dev.parent = &pdev->dev; + touch->idev->open = pm860x_touch_open; + touch->idev->close = pm860x_touch_close; + touch->chip = chip; + touch->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + touch->irq = irq; + touch->res_x = pdata->res_x; + input_set_drvdata(touch->idev, touch); + + ret = pm860x_request_irq(chip, irq, pm860x_touch_handler, touch); + if (ret < 0) + goto out_irq; + + __set_bit(EV_ABS, touch->idev->evbit); + __set_bit(ABS_X, touch->idev->absbit); + __set_bit(ABS_Y, touch->idev->absbit); + __set_bit(ABS_PRESSURE, touch->idev->absbit); + __set_bit(EV_SYN, touch->idev->evbit); + __set_bit(EV_KEY, touch->idev->evbit); + __set_bit(BTN_TOUCH, touch->idev->keybit); + + input_set_abs_params(touch->idev, ABS_X, 0, 1 << ACCURATE_BIT, 0, 0); + input_set_abs_params(touch->idev, ABS_Y, 0, 1 << ACCURATE_BIT, 0, 0); + input_set_abs_params(touch->idev, ABS_PRESSURE, 0, 1 << ACCURATE_BIT, + 0, 0); + + ret = input_register_device(touch->idev); + if (ret < 0) { + dev_err(chip->dev, "Failed to register touch!\n"); + goto out_rg; + } + + platform_set_drvdata(pdev, touch); + return 0; +out_rg: + pm860x_free_irq(chip, irq); +out_irq: + input_free_device(touch->idev); +out: + kfree(touch); + return ret; +} + +static int __devexit pm860x_touch_remove(struct platform_device *pdev) +{ + struct pm860x_touch *touch = platform_get_drvdata(pdev); + + input_unregister_device(touch->idev); + pm860x_free_irq(touch->chip, touch->irq); + platform_set_drvdata(pdev, NULL); + kfree(touch); + return 0; +} + +static struct platform_driver pm860x_touch_driver = { + .driver = { + .name = "88pm860x-touch", + .owner = THIS_MODULE, + }, + .probe = pm860x_touch_probe, + .remove = __devexit_p(pm860x_touch_remove), +}; + +static int __init pm860x_touch_init(void) +{ + return platform_driver_register(&pm860x_touch_driver); +} +module_init(pm860x_touch_init); + +static void __exit pm860x_touch_exit(void) +{ + platform_driver_unregister(&pm860x_touch_driver); +} +module_exit(pm860x_touch_exit); + +MODULE_DESCRIPTION("Touchscreen driver for Marvell Semiconductor 88PM860x"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:88pm860x-touch"); + diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 6457e060ae4..7208654a94a 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,18 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +config TOUCHSCREEN_88PM860X + tristate "Marvell 88PM860x touchscreen" + depends on MFD_88PM860X + help + Say Y here if you have a 88PM860x PMIC and want to enable + support for the built-in touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called 88pm860x-ts. + config TOUCHSCREEN_ADS7846 tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" depends on SPI_MASTER diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index d61a3b4def9..7fef7d5cca2 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -6,6 +6,7 @@ wm97xx-ts-y := wm97xx-core.o +obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o -- cgit v1.2.3 From 34a4b2391e9fbd12de9817de4ae409528bd7d7b6 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 10 Dec 2009 06:06:17 -0500 Subject: regulator: Unsupport 88pm8607 A0 and A1 Remove the support 88PM8607 A0/A1 stepping. There's some register definition changes in B0 stepping. It can make software more efficient. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/regulator/88pm8607.c | 290 ++++++++++++------------------------------- 1 file changed, 77 insertions(+), 213 deletions(-) (limited to 'drivers') diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index 97897a6bf4f..5fb83e2ced2 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -48,7 +48,6 @@ static inline int check_range(struct pm8607_regulator_info *info, static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - uint8_t chip_id = info->chip->chip_version; int ret = -EINVAL; switch (info->desc.id) { @@ -90,79 +89,29 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) case PM8607_ID_LDO2: case PM8607_ID_LDO3: case PM8607_ID_LDO9: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2550000) : - -EINVAL); - break; - case PM8607_CHIP_B0: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2550000) : - 3300000); - break; - } + ret = (index < 3) ? (index * 50000 + 1800000) : + ((index < 7) ? (index * 50000 + 2550000) : + 3300000); break; case PM8607_ID_LDO4: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2550000) : - -EINVAL); - break; - case PM8607_CHIP_B0: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 6) ? (index * 50000 + 2550000) : - ((index == 6) ? 2900000 : 3300000)); - break; - } + ret = (index < 3) ? (index * 50000 + 1800000) : + ((index < 6) ? (index * 50000 + 2550000) : + ((index == 6) ? 2900000 : 3300000)); break; case PM8607_ID_LDO6: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2450000) : - -EINVAL); - break; - case PM8607_CHIP_B0: - ret = (index < 2) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2500000) : - 3300000); - break; - } + ret = (index < 2) ? (index * 50000 + 1800000) : + ((index < 7) ? (index * 50000 + 2500000) : + 3300000); break; case PM8607_ID_LDO10: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2550000) : - 1200000); - break; - case PM8607_CHIP_B0: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2550000) : - ((index == 7) ? 3300000 : 1200000)); - break; - } + ret = (index < 3) ? (index * 50000 + 1800000) : + ((index < 7) ? (index * 50000 + 2550000) : + ((index == 7) ? 3300000 : 1200000)); break; case PM8607_ID_LDO14: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - ret = (index < 3) ? (index * 50000 + 1800000) : - ((index < 8) ? (index * 50000 + 2550000) : - -EINVAL); - break; - case PM8607_CHIP_B0: - ret = (index < 2) ? (index * 50000 + 1800000) : - ((index < 7) ? (index * 50000 + 2600000) : - 3300000); - break; - } + ret = (index < 2) ? (index * 50000 + 1800000) : + ((index < 7) ? (index * 50000 + 2600000) : + 3300000); break; } return ret; @@ -171,7 +120,6 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) { struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); - uint8_t chip_id = info->chip->chip_version; int val = -ENOENT; int ret; @@ -256,161 +204,77 @@ static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV) case PM8607_ID_LDO2: case PM8607_ID_LDO3: case PM8607_ID_LDO9: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - if (min_uV < 2700000) /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else - val = -EINVAL; - } - break; - case PM8607_CHIP_B0: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2850mV / 50mV */ - if (min_uV <= 2850000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; + if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ + if (min_uV <= 1800000) + val = 0; + else if (min_uV <= 1900000) + val = (min_uV - 1750001) / 50000; + else + val = 3; /* 2700mV */ + } else { /* 2700mV ~ 2850mV / 50mV */ + if (min_uV <= 2850000) { + val = (min_uV - 2650001) / 50000; + val += 3; + } else if (min_uV <= 3300000) + val = 7; + else + val = -EINVAL; } break; case PM8607_ID_LDO4: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - if (min_uV < 2700000) /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else - val = -EINVAL; - } - break; - case PM8607_CHIP_B0: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2800mV / 50mV */ - if (min_uV <= 2850000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else if (min_uV <= 2900000) - val = 6; - else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; + if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ + if (min_uV <= 1800000) + val = 0; + else if (min_uV <= 1900000) + val = (min_uV - 1750001) / 50000; + else + val = 3; /* 2700mV */ + } else { /* 2700mV ~ 2800mV / 50mV */ + if (min_uV <= 2850000) { + val = (min_uV - 2650001) / 50000; + val += 3; + } else if (min_uV <= 2900000) + val = 6; + else if (min_uV <= 3300000) + val = 7; + else + val = -EINVAL; } break; case PM8607_ID_LDO6: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - if (min_uV < 2600000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2600mV */ - } else { /* 2600mV ~ 2800mV / 50mV */ - if (min_uV <= 2800000) { - val = (min_uV - 2550001) / 50000; - val += 3; - } else - val = -EINVAL; - } - break; - case PM8607_CHIP_B0: - if (min_uV < 2600000) { /* 1800mV ~ 1850mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1850000) - val = (min_uV - 1750001) / 50000; - else - val = 2; /* 2600mV */ - } else { /* 2600mV ~ 2800mV / 50mV */ - if (min_uV <= 2800000) { - val = (min_uV - 2550001) / 50000; - val += 2; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; + if (min_uV < 2600000) { /* 1800mV ~ 1850mV / 50mV */ + if (min_uV <= 1800000) + val = 0; + else if (min_uV <= 1850000) + val = (min_uV - 1750001) / 50000; + else + val = 2; /* 2600mV */ + } else { /* 2600mV ~ 2800mV / 50mV */ + if (min_uV <= 2800000) { + val = (min_uV - 2550001) / 50000; + val += 2; + } else if (min_uV <= 3300000) + val = 7; + else + val = -EINVAL; } break; case PM8607_ID_LDO14: - switch (chip_id) { - case PM8607_CHIP_A0: - case PM8607_CHIP_A1: - if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1900000) - val = (min_uV - 1750001) / 50000; - else - val = 3; /* 2700mV */ - } else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 3; - } else - val = -EINVAL; - } - break; - case PM8607_CHIP_B0: - if (min_uV < 2700000) { /* 1800mV ~ 1850mV / 50mV */ - if (min_uV <= 1800000) - val = 0; - else if (min_uV <= 1850000) - val = (min_uV - 1750001) / 50000; - else - val = 2; /* 2700mV */ - } else { /* 2700mV ~ 2900mV / 50mV */ - if (min_uV <= 2900000) { - val = (min_uV - 2650001) / 50000; - val += 2; - } else if (min_uV <= 3300000) - val = 7; - else - val = -EINVAL; - } - break; + if (min_uV < 2700000) { /* 1800mV ~ 1850mV / 50mV */ + if (min_uV <= 1800000) + val = 0; + else if (min_uV <= 1850000) + val = (min_uV - 1750001) / 50000; + else + val = 2; /* 2700mV */ + } else { /* 2700mV ~ 2900mV / 50mV */ + if (min_uV <= 2900000) { + val = (min_uV - 2650001) / 50000; + val += 2; + } else if (min_uV <= 3300000) + val = 7; + else + val = -EINVAL; } break; } -- 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 815907eb70a..ee416eefb8e 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 1e3ae062c1f..26163570024 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 00000000000..3e26267960b --- /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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ee416eefb8e..d63ab2eec66 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 3e26267960b..f36c494b80f 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 4f811ef0d40048ae80b6ea77080b814725bffc58 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 18 Dec 2009 10:01:45 -0500 Subject: backlight: Enable max8925 backlight Enable max8925 backlight sub device. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/video/backlight/Kconfig | 7 ++ drivers/video/backlight/Makefile | 1 + drivers/video/backlight/max8925_bl.c | 200 +++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 drivers/video/backlight/max8925_bl.c (limited to 'drivers') diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 96a6b3060ac..0c77fc61021 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -212,6 +212,13 @@ config BACKLIGHT_DA903X If you have a LCD backlight connected to the WLED output of DA9030 or DA9034 WLED output, say Y here to enable this driver. +config BACKLIGHT_MAX8925 + tristate "Backlight driver for MAX8925" + depends on BACKLIGHT_CLASS_DEVICE && MFD_MAX8925 + help + If you have a LCD backlight connected to the WLED output of MAX8925 + WLED output, say Y here to enable this driver. + config BACKLIGHT_MBP_NVIDIA tristate "MacBook Pro Nvidia Backlight Driver" depends on BACKLIGHT_CLASS_DEVICE && X86 diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 802d467b08c..6c704d41462 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o +obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o obj-$(CONFIG_BACKLIGHT_MBP_NVIDIA) += mbp_nvidia_bl.o obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o diff --git a/drivers/video/backlight/max8925_bl.c b/drivers/video/backlight/max8925_bl.c new file mode 100644 index 00000000000..c267069a52a --- /dev/null +++ b/drivers/video/backlight/max8925_bl.c @@ -0,0 +1,200 @@ +/* + * Backlight 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 MAX_BRIGHTNESS (0xff) +#define MIN_BRIGHTNESS (0) + +#define LWX_FREQ(x) (((x - 601) / 100) & 0x7) + +struct max8925_backlight_data { + struct max8925_chip *chip; + + int current_brightness; +}; + +static int max8925_backlight_set(struct backlight_device *bl, int brightness) +{ + struct max8925_backlight_data *data = bl_get_data(bl); + struct max8925_chip *chip = data->chip; + unsigned char value; + int ret; + + if (brightness > MAX_BRIGHTNESS) + value = MAX_BRIGHTNESS; + else + value = brightness; + + ret = max8925_reg_write(chip->i2c, MAX8925_WLED_CNTL, value); + if (ret < 0) + goto out; + + if (!data->current_brightness && brightness) + /* enable WLED output */ + ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 1, 1); + else if (!brightness) + /* disable WLED output */ + ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 1, 0); + if (ret < 0) + goto out; + dev_dbg(chip->dev, "set brightness %d\n", value); + data->current_brightness = value; + return 0; +out: + dev_dbg(chip->dev, "set brightness %d failure with return value:%d\n", + value, ret); + return ret; +} + +static int max8925_backlight_update_status(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK) + brightness = 0; + + if (bl->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + + if (bl->props.state & BL_CORE_SUSPENDED) + brightness = 0; + + return max8925_backlight_set(bl, brightness); +} + +static int max8925_backlight_get_brightness(struct backlight_device *bl) +{ + struct max8925_backlight_data *data = bl_get_data(bl); + struct max8925_chip *chip = data->chip; + int ret; + + ret = max8925_reg_read(chip->i2c, MAX8925_WLED_CNTL); + if (ret < 0) + return -EINVAL; + data->current_brightness = ret; + dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); + return ret; +} + +static struct backlight_ops max8925_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = max8925_backlight_update_status, + .get_brightness = max8925_backlight_get_brightness, +}; + +static int __devinit max8925_backlight_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_platform_data *max8925_pdata; + struct max8925_backlight_pdata *pdata = NULL; + struct max8925_backlight_data *data; + struct backlight_device *bl; + struct resource *res; + char name[MAX8925_NAME_SIZE]; + unsigned char value; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); + return -EINVAL; + } + + if (pdev->dev.parent->platform_data) { + max8925_pdata = pdev->dev.parent->platform_data; + pdata = max8925_pdata->backlight; + } + + if (!pdata) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "backlight\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(struct max8925_backlight_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + strncpy(name, res->name, MAX8925_NAME_SIZE); + data->chip = chip; + data->current_brightness = 0; + + bl = backlight_device_register(name, &pdev->dev, data, + &max8925_backlight_ops); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + kfree(data); + return PTR_ERR(bl); + } + bl->props.max_brightness = MAX_BRIGHTNESS; + bl->props.brightness = MAX_BRIGHTNESS; + + platform_set_drvdata(pdev, bl); + + value = 0; + if (pdata->lxw_scl) + value |= (1 << 7); + if (pdata->lxw_freq) + value |= (LWX_FREQ(pdata->lxw_freq) << 4); + if (pdata->dual_string) + value |= (1 << 1); + ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 0xfe, value); + if (ret < 0) + goto out; + + backlight_update_status(bl); + return 0; +out: + kfree(data); + return ret; +} + +static int __devexit max8925_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct max8925_backlight_data *data = bl_get_data(bl); + + backlight_device_unregister(bl); + kfree(data); + return 0; +} + +static struct platform_driver max8925_backlight_driver = { + .driver = { + .name = "max8925-backlight", + .owner = THIS_MODULE, + }, + .probe = max8925_backlight_probe, + .remove = __devexit_p(max8925_backlight_remove), +}; + +static int __init max8925_backlight_init(void) +{ + return platform_driver_register(&max8925_backlight_driver); +} +module_init(max8925_backlight_init); + +static void __exit max8925_backlight_exit(void) +{ + platform_driver_unregister(&max8925_backlight_driver); +}; +module_exit(max8925_backlight_exit); + +MODULE_DESCRIPTION("Backlight Driver for Maxim MAX8925"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:max8925-backlight"); -- cgit v1.2.3 From 08ff6f2a9908c87b0adffe6453291be22db3a62f Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 11 Jan 2010 13:10:08 +0100 Subject: regulator: Add max8925 support MAX8925 contains 3 Buck and 20 LDO regulator. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 6 + drivers/regulator/Makefile | 1 + drivers/regulator/max8925-regulator.c | 306 ++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 drivers/regulator/max8925-regulator.c (limited to 'drivers') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index dbfe31a4a51..04f2e085116 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -94,6 +94,12 @@ config REGULATOR_MAX8660 This driver controls a Maxim 8660/8661 voltage output regulator via I2C bus. +config REGULATOR_MAX8925 + tristate "Maxim MAX8925 Power Management IC" + depends on MFD_MAX8925 + help + Say y here to support the voltage regulaltor of Maxim MAX8925 PMIC. + config REGULATOR_TWL4030 bool "TI TWL4030/TWL5030/TWL6030/TPS695x0 PMIC" depends on TWL4030_CORE diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e845b66ad59..4e7feece22d 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o +obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o diff --git a/drivers/regulator/max8925-regulator.c b/drivers/regulator/max8925-regulator.c new file mode 100644 index 00000000000..67873f08ed4 --- /dev/null +++ b/drivers/regulator/max8925-regulator.c @@ -0,0 +1,306 @@ +/* + * Regulators 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 +#include + +#define SD1_DVM_VMIN 850000 +#define SD1_DVM_VMAX 1000000 +#define SD1_DVM_STEP 50000 +#define SD1_DVM_SHIFT 5 /* SDCTL1 bit5 */ +#define SD1_DVM_EN 6 /* SDV1 bit 6 */ + +struct max8925_regulator_info { + struct regulator_desc desc; + struct regulator_dev *regulator; + struct i2c_client *i2c; + struct max8925_chip *chip; + + int min_uV; + int max_uV; + int step_uV; + int vol_reg; + int vol_shift; + int vol_nbits; + int enable_bit; + int enable_reg; +}; + +static inline int check_range(struct max8925_regulator_info *info, + int min_uV, int max_uV) +{ + if (min_uV < info->min_uV || min_uV > info->max_uV) + return -EINVAL; + + return 0; +} + +static int max8925_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + return info->min_uV + index * info->step_uV; +} + +static int max8925_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char data, mask; + + if (check_range(info, min_uV, max_uV)) { + dev_err(info->chip->dev, "invalid voltage range (%d, %d) uV\n", + min_uV, max_uV); + return -EINVAL; + } + data = (min_uV - info->min_uV + info->step_uV - 1) / info->step_uV; + data <<= info->vol_shift; + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + + return max8925_set_bits(info->i2c, info->vol_reg, mask, data); +} + +static int max8925_get_voltage(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char data, mask; + int ret; + + ret = max8925_reg_read(info->i2c, info->vol_reg); + if (ret < 0) + return ret; + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + data = (ret & mask) >> info->vol_shift; + + return max8925_list_voltage(rdev, data); +} + +static int max8925_enable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->enable_reg, + 1 << info->enable_bit, + 1 << info->enable_bit); +} + +static int max8925_disable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->enable_reg, + 1 << info->enable_bit, 0); +} + +static int max8925_is_enabled(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + ret = max8925_reg_read(info->i2c, info->vol_reg); + if (ret < 0) + return ret; + + return ret & (1 << info->enable_bit); +} + +static int max8925_set_dvm_voltage(struct regulator_dev *rdev, int uV) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char data, mask; + + if (uV < SD1_DVM_VMIN || uV > SD1_DVM_VMAX) + return -EINVAL; + + data = (uV - SD1_DVM_VMIN + SD1_DVM_STEP - 1) / SD1_DVM_STEP; + data <<= SD1_DVM_SHIFT; + mask = 3 << SD1_DVM_SHIFT; + + return max8925_set_bits(info->i2c, info->enable_reg, mask, data); +} + +static int max8925_set_dvm_enable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN, + 1 << SD1_DVM_EN); +} + +static int max8925_set_dvm_disable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN, 0); +} + +static struct regulator_ops max8925_regulator_sdv_ops = { + .set_voltage = max8925_set_voltage, + .get_voltage = max8925_get_voltage, + .enable = max8925_enable, + .disable = max8925_disable, + .is_enabled = max8925_is_enabled, + .set_suspend_voltage = max8925_set_dvm_voltage, + .set_suspend_enable = max8925_set_dvm_enable, + .set_suspend_disable = max8925_set_dvm_disable, +}; + +static struct regulator_ops max8925_regulator_ldo_ops = { + .set_voltage = max8925_set_voltage, + .get_voltage = max8925_get_voltage, + .enable = max8925_enable, + .disable = max8925_disable, + .is_enabled = max8925_is_enabled, +}; + +#define MAX8925_SDV(_id, min, max, step) \ +{ \ + .desc = { \ + .name = "SDV" #_id, \ + .ops = &max8925_regulator_sdv_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MAX8925_ID_SD##_id, \ + .owner = THIS_MODULE, \ + }, \ + .min_uV = min * 1000, \ + .max_uV = max * 1000, \ + .step_uV = step * 1000, \ + .vol_reg = MAX8925_SDV##_id, \ + .vol_shift = 0, \ + .vol_nbits = 6, \ + .enable_reg = MAX8925_SDCTL##_id, \ + .enable_bit = 0, \ +} + +#define MAX8925_LDO(_id, min, max, step) \ +{ \ + .desc = { \ + .name = "LDO" #_id, \ + .ops = &max8925_regulator_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MAX8925_ID_LDO##_id, \ + .owner = THIS_MODULE, \ + }, \ + .min_uV = min * 1000, \ + .max_uV = max * 1000, \ + .step_uV = step * 1000, \ + .vol_reg = MAX8925_LDOVOUT##_id, \ + .vol_shift = 0, \ + .vol_nbits = 6, \ + .enable_reg = MAX8925_LDOCTL##_id, \ + .enable_bit = 0, \ +} + +static struct max8925_regulator_info max8925_regulator_info[] = { + MAX8925_SDV(1, 637.5, 1425, 12.5), + MAX8925_SDV(2, 650, 2225, 25), + MAX8925_SDV(3, 750, 3900, 50), + + MAX8925_LDO(1, 750, 3900, 50), + MAX8925_LDO(2, 650, 2250, 25), + MAX8925_LDO(3, 650, 2250, 25), + MAX8925_LDO(4, 750, 3900, 50), + MAX8925_LDO(5, 750, 3900, 50), + MAX8925_LDO(6, 750, 3900, 50), + MAX8925_LDO(7, 750, 3900, 50), + MAX8925_LDO(8, 750, 3900, 50), + MAX8925_LDO(9, 750, 3900, 50), + MAX8925_LDO(10, 750, 3900, 50), + MAX8925_LDO(11, 750, 3900, 50), + MAX8925_LDO(12, 750, 3900, 50), + MAX8925_LDO(13, 750, 3900, 50), + MAX8925_LDO(14, 750, 3900, 50), + MAX8925_LDO(15, 750, 3900, 50), + MAX8925_LDO(16, 750, 3900, 50), + MAX8925_LDO(17, 650, 2250, 25), + MAX8925_LDO(18, 650, 2250, 25), + MAX8925_LDO(19, 750, 3900, 50), + MAX8925_LDO(20, 750, 3900, 50), +}; + +static inline struct max8925_regulator_info *find_regulator_info(int id) +{ + struct max8925_regulator_info *ri; + int i; + + for (i = 0; i < ARRAY_SIZE(max8925_regulator_info); i++) { + ri = &max8925_regulator_info[i]; + if (ri->desc.id == id) + return ri; + } + return NULL; +} + +static int __devinit max8925_regulator_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_platform_data *pdata = chip->dev->platform_data; + struct max8925_regulator_info *ri = NULL; + struct regulator_dev *rdev; + + ri = find_regulator_info(pdev->id); + if (ri == NULL) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + ri->i2c = chip->i2c; + ri->chip = chip; + + rdev = regulator_register(&ri->desc, &pdev->dev, + pdata->regulator[pdev->id], ri); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + return 0; +} + +static int __devexit max8925_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + return 0; +} + +static struct platform_driver max8925_regulator_driver = { + .driver = { + .name = "max8925-regulator", + .owner = THIS_MODULE, + }, + .probe = max8925_regulator_probe, + .remove = __devexit_p(max8925_regulator_remove), +}; + +static int __init max8925_regulator_init(void) +{ + return platform_driver_register(&max8925_regulator_driver); +} +subsys_initcall(max8925_regulator_init); + +static void __exit max8925_regulator_exit(void) +{ + platform_driver_unregister(&max8925_regulator_driver); +} +module_exit(max8925_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_DESCRIPTION("Regulator Driver for Maxim 8925 PMIC"); +MODULE_ALIAS("platform:max8925-regulator"); + -- 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d63ab2eec66..9bcd447e71e 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 26163570024..142d31202b1 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 00000000000..37b9fdab4f3 --- /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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9bcd447e71e..ea2557bb0bd 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 aa3824a1b4f..a2ce3b6af4a 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') diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 0499b2031a2..b603469dff6 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') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 19a930d0624..d81003f4867 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ea2557bb0bd..a760dbefd27 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a760dbefd27..64fbe3334eb 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 142d31202b1..878cac69106 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 00000000000..299c1af1334 --- /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 2955c309921e23d592ef585cf8e7ded1e11565b7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 29 Jan 2010 18:20:30 +0000 Subject: gpiolib: Add WM8994 GPIO support The WM8994 has 11 GPIO lines. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 1 + drivers/gpio/wm8994-gpio.c | 204 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 drivers/gpio/wm8994-gpio.c (limited to 'drivers') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index acac9f60db1..f42cb317a77 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -192,6 +192,13 @@ config GPIO_WM8350 Say yes here to access the GPIO signals of WM8350 power management chips from Wolfson Microelectronics. +config GPIO_WM8994 + tristate "WM8994 GPIOs" + depends on MFD_WM8994 + help + Say yes here to access the GPIO signals of WM8994 audio hub + CODECs from Wolfson Microelectronics. + config GPIO_ADP5520 tristate "GPIO Support for ADP5520 PMIC" depends on PMIC_ADP5520 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 90b0880923d..594bb70f65a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_GPIO_IT8761E) += it8761e_gpio.o obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o +obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o diff --git a/drivers/gpio/wm8994-gpio.c b/drivers/gpio/wm8994-gpio.c new file mode 100644 index 00000000000..de28b4a470e --- /dev/null +++ b/drivers/gpio/wm8994-gpio.c @@ -0,0 +1,204 @@ +/* + * wm8994-gpio.c -- gpiolib support 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 + +struct wm8994_gpio { + struct wm8994 *wm8994; + struct gpio_chip gpio_chip; +}; + +static inline struct wm8994_gpio *to_wm8994_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8994_gpio, gpio_chip); +} + +static int wm8994_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, + WM8994_GPN_DIR, WM8994_GPN_DIR); +} + +static int wm8994_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + int ret; + + ret = wm8994_reg_read(wm8994, WM8994_GPIO_1 + offset); + if (ret < 0) + return ret; + + if (ret & WM8994_GPN_LVL) + return 1; + else + return 0; +} + +static int wm8994_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, + WM8994_GPN_DIR, 0); +} + +static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (value) + value = WM8994_GPN_LVL; + + wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value); +} + +#ifdef CONFIG_DEBUG_FS +static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + int i; + + for (i = 0; i < chip->ngpio; i++) { + int gpio = i + chip->base; + int reg; + const char *label; + + /* We report the GPIO even if it's not requested since + * we're also reporting things like alternate + * functions which apply even when the GPIO is not in + * use as a GPIO. + */ + label = gpiochip_is_requested(chip, i); + if (!label) + label = "Unrequested"; + + seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + + reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i); + if (reg < 0) { + dev_err(wm8994->dev, + "GPIO control %d read failed: %d\n", + gpio, reg); + seq_printf(s, "\n"); + continue; + } + + /* No decode yet; note that GPIO2 is special */ + seq_printf(s, "(%x)\n", reg); + } +} +#else +#define wm8994_gpio_dbg_show NULL +#endif + +static struct gpio_chip template_chip = { + .label = "wm8994", + .owner = THIS_MODULE, + .direction_input = wm8994_gpio_direction_in, + .get = wm8994_gpio_get, + .direction_output = wm8994_gpio_direction_out, + .set = wm8994_gpio_set, + .dbg_show = wm8994_gpio_dbg_show, + .can_sleep = 1, +}; + +static int __devinit wm8994_gpio_probe(struct platform_device *pdev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent); + struct wm8994_pdata *pdata = wm8994->dev->platform_data; + struct wm8994_gpio *wm8994_gpio; + int ret; + + wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL); + if (wm8994_gpio == NULL) + return -ENOMEM; + + wm8994_gpio->wm8994 = wm8994; + wm8994_gpio->gpio_chip = template_chip; + wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX; + wm8994_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm8994_gpio->gpio_chip.base = pdata->gpio_base; + else + wm8994_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8994_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm8994_gpio); + + return ret; + +err: + kfree(wm8994_gpio); + return ret; +} + +static int __devexit wm8994_gpio_remove(struct platform_device *pdev) +{ + struct wm8994_gpio *wm8994_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm8994_gpio->gpio_chip); + if (ret == 0) + kfree(wm8994_gpio); + + return ret; +} + +static struct platform_driver wm8994_gpio_driver = { + .driver.name = "wm8994-gpio", + .driver.owner = THIS_MODULE, + .probe = wm8994_gpio_probe, + .remove = __devexit_p(wm8994_gpio_remove), +}; + +static int __init wm8994_gpio_init(void) +{ + return platform_driver_register(&wm8994_gpio_driver); +} +subsys_initcall(wm8994_gpio_init); + +static void __exit wm8994_gpio_exit(void) +{ + platform_driver_unregister(&wm8994_gpio_driver); +} +module_exit(wm8994_gpio_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("GPIO interface for WM8994"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8994-gpio"); -- 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') diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 0815292fdaf..5b045ff4a2c 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') diff --git a/drivers/mfd/max8925-i2c.c b/drivers/mfd/max8925-i2c.c index 942068e730f..c0b883c14f4 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 64fbe3334eb..84a68d26077 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 f36c494b80f..85d63c04749 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: Mon, 25 Jan 2010 06:30:29 -0500 Subject: rtc: Enable rtc in max8925 MAX8925 is a PMIC that contains RTC component. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-max8925.c | 314 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 drivers/rtc/rtc-max8925.c (limited to 'drivers') diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2bb8a8b7ffa..6a130375943 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -175,6 +175,16 @@ config RTC_DRV_MAX6900 This driver can also be built as a module. If so, the module will be called rtc-max6900. +config RTC_DRV_MAX8925 + tristate "Maxim MAX8925" + depends on MFD_MAX8925 + help + If you say yes here you will get support for the + RTC of Maxim MAX8925 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max8925. + config RTC_DRV_RS5C372 tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index b7148afb8f5..44ef194a957 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o obj-$(CONFIG_RTC_MXC) += rtc-mxc.o obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o +obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o obj-$(CONFIG_RTC_DRV_MC13783) += rtc-mc13783.o obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o diff --git a/drivers/rtc/rtc-max8925.c b/drivers/rtc/rtc-max8925.c new file mode 100644 index 00000000000..acdbb176018 --- /dev/null +++ b/drivers/rtc/rtc-max8925.c @@ -0,0 +1,314 @@ +/* + * RTC driver for Maxim MAX8925 + * + * Copyright (C) 2009-2010 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 + +enum { + RTC_SEC = 0, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_DATE, + RTC_MONTH, + RTC_YEAR1, + RTC_YEAR2, +}; + +#define MAX8925_RTC_SEC 0x00 +#define MAX8925_RTC_MIN 0x01 +#define MAX8925_RTC_HOUR 0x02 +#define MAX8925_RTC_WEEKDAY 0x03 +#define MAX8925_RTC_DATE 0x04 +#define MAX8925_RTC_MONTH 0x05 +#define MAX8925_RTC_YEAR1 0x06 +#define MAX8925_RTC_YEAR2 0x07 +#define MAX8925_ALARM0_SEC 0x08 +#define MAX8925_ALARM0_MIN 0x09 +#define MAX8925_ALARM0_HOUR 0x0a +#define MAX8925_ALARM0_WEEKDAY 0x0b +#define MAX8925_ALARM0_DATE 0x0c +#define MAX8925_ALARM0_MON 0x0d +#define MAX8925_ALARM0_YEAR1 0x0e +#define MAX8925_ALARM0_YEAR2 0x0f +#define MAX8925_ALARM1_SEC 0x10 +#define MAX8925_ALARM1_MIN 0x11 +#define MAX8925_ALARM1_HOUR 0x12 +#define MAX8925_ALARM1_WEEKDAY 0x13 +#define MAX8925_ALARM1_DATE 0x14 +#define MAX8925_ALARM1_MON 0x15 +#define MAX8925_ALARM1_YEAR1 0x16 +#define MAX8925_ALARM1_YEAR2 0x17 +#define MAX8925_RTC_CNTL 0x1b +#define MAX8925_RTC_STATUS 0x20 + +#define TIME_NUM 8 +#define ALARM_1SEC (1 << 7) +#define HOUR_12 (1 << 7) +#define HOUR_AM_PM (1 << 5) +#define ALARM0_IRQ (1 << 3) +#define ALARM1_IRQ (1 << 2) +#define ALARM0_STATUS (1 << 2) +#define ALARM1_STATUS (1 << 1) + + +struct max8925_rtc_info { + struct rtc_device *rtc_dev; + struct max8925_chip *chip; + struct i2c_client *rtc; + struct device *dev; +}; + +static irqreturn_t rtc_update_handler(int irq, void *data) +{ + struct max8925_rtc_info *info = (struct max8925_rtc_info *)data; + + /* disable ALARM0 except for 1SEC alarm */ + max8925_set_bits(info->rtc, MAX8925_ALARM0_CNTL, 0x7f, 0); + rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int tm_calc(struct rtc_time *tm, unsigned char *buf, int len) +{ + if (len < TIME_NUM) + return -EINVAL; + tm->tm_year = (buf[RTC_YEAR2] >> 4) * 1000 + + (buf[RTC_YEAR2] & 0xf) * 100 + + (buf[RTC_YEAR1] >> 4) * 10 + + (buf[RTC_YEAR1] & 0xf); + tm->tm_year -= 1900; + tm->tm_mon = ((buf[RTC_MONTH] >> 4) & 0x01) * 10 + + (buf[RTC_MONTH] & 0x0f); + tm->tm_mday = ((buf[RTC_DATE] >> 4) & 0x03) * 10 + + (buf[RTC_DATE] & 0x0f); + tm->tm_wday = buf[RTC_WEEKDAY] & 0x07; + if (buf[RTC_HOUR] & HOUR_12) { + tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x1) * 10 + + (buf[RTC_HOUR] & 0x0f); + if (buf[RTC_HOUR] & HOUR_AM_PM) + tm->tm_hour += 12; + } else + tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x03) * 10 + + (buf[RTC_HOUR] & 0x0f); + tm->tm_min = ((buf[RTC_MIN] >> 4) & 0x7) * 10 + + (buf[RTC_MIN] & 0x0f); + tm->tm_sec = ((buf[RTC_SEC] >> 4) & 0x7) * 10 + + (buf[RTC_SEC] & 0x0f); + return 0; +} + +static int data_calc(unsigned char *buf, struct rtc_time *tm, int len) +{ + unsigned char high, low; + + if (len < TIME_NUM) + return -EINVAL; + + high = (tm->tm_year + 1900) / 1000; + low = (tm->tm_year + 1900) / 100; + low = low - high * 10; + buf[RTC_YEAR2] = (high << 4) + low; + high = (tm->tm_year + 1900) / 10; + low = tm->tm_year + 1900; + low = low - high * 10; + high = high - (high / 10) * 10; + buf[RTC_YEAR1] = (high << 4) + low; + high = tm->tm_mon / 10; + low = tm->tm_mon; + low = low - high * 10; + buf[RTC_MONTH] = (high << 4) + low; + high = tm->tm_mday / 10; + low = tm->tm_mday; + low = low - high * 10; + buf[RTC_DATE] = (high << 4) + low; + buf[RTC_WEEKDAY] = tm->tm_wday; + high = tm->tm_hour / 10; + low = tm->tm_hour; + low = low - high * 10; + buf[RTC_HOUR] = (high << 4) + low; + high = tm->tm_min / 10; + low = tm->tm_min; + low = low - high * 10; + buf[RTC_MIN] = (high << 4) + low; + high = tm->tm_sec / 10; + low = tm->tm_sec; + low = low - high * 10; + buf[RTC_SEC] = (high << 4) + low; + return 0; +} + +static int max8925_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max8925_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = max8925_bulk_read(info->rtc, MAX8925_RTC_SEC, TIME_NUM, buf); + if (ret < 0) + goto out; + ret = tm_calc(tm, buf, TIME_NUM); +out: + return ret; +} + +static int max8925_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max8925_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = data_calc(buf, tm, TIME_NUM); + if (ret < 0) + goto out; + ret = max8925_bulk_write(info->rtc, MAX8925_RTC_SEC, TIME_NUM, buf); +out: + return ret; +} + +static int max8925_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8925_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = max8925_bulk_read(info->rtc, MAX8925_ALARM0_SEC, TIME_NUM, buf); + if (ret < 0) + goto out; + ret = tm_calc(&alrm->time, buf, TIME_NUM); + if (ret < 0) + goto out; + ret = max8925_reg_read(info->rtc, MAX8925_RTC_IRQ_MASK); + if (ret < 0) + goto out; + if ((ret & ALARM0_IRQ) == 0) + alrm->enabled = 1; + else + alrm->enabled = 0; + ret = max8925_reg_read(info->rtc, MAX8925_RTC_STATUS); + if (ret < 0) + goto out; + if (ret & ALARM0_STATUS) + alrm->pending = 1; + else + alrm->pending = 0; +out: + return ret; +} + +static int max8925_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8925_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = data_calc(buf, &alrm->time, TIME_NUM); + if (ret < 0) + goto out; + ret = max8925_bulk_write(info->rtc, MAX8925_ALARM0_SEC, TIME_NUM, buf); + if (ret < 0) + goto out; + /* only enable alarm on year/month/day/hour/min/sec */ + ret = max8925_reg_write(info->rtc, MAX8925_ALARM0_CNTL, 0x77); + if (ret < 0) + goto out; +out: + return ret; +} + +static const struct rtc_class_ops max8925_rtc_ops = { + .read_time = max8925_rtc_read_time, + .set_time = max8925_rtc_set_time, + .read_alarm = max8925_rtc_read_alarm, + .set_alarm = max8925_rtc_set_alarm, +}; + +static int __devinit max8925_rtc_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_rtc_info *info; + int irq, ret; + + info = kzalloc(sizeof(struct max8925_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->rtc = chip->rtc; + info->dev = &pdev->dev; + irq = chip->irq_base + MAX8925_IRQ_RTC_ALARM0; + + ret = request_threaded_irq(irq, NULL, rtc_update_handler, + IRQF_ONESHOT, "rtc-alarm0", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + irq, ret); + goto out_irq; + } + + info->rtc_dev = rtc_device_register("max8925-rtc", &pdev->dev, + &max8925_rtc_ops, THIS_MODULE); + ret = PTR_ERR(info->rtc_dev); + if (IS_ERR(info->rtc_dev)) { + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + goto out_rtc; + } + + dev_set_drvdata(&pdev->dev, info); + platform_set_drvdata(pdev, info); + + return 0; +out_rtc: + free_irq(chip->irq_base + MAX8925_IRQ_RTC_ALARM0, info); +out_irq: + kfree(info); + return ret; +} + +static int __devexit max8925_rtc_remove(struct platform_device *pdev) +{ + struct max8925_rtc_info *info = platform_get_drvdata(pdev); + + if (info) { + free_irq(info->chip->irq_base + MAX8925_IRQ_RTC_ALARM0, info); + rtc_device_unregister(info->rtc_dev); + kfree(info); + } + return 0; +} + +static struct platform_driver max8925_rtc_driver = { + .driver = { + .name = "max8925-rtc", + .owner = THIS_MODULE, + }, + .probe = max8925_rtc_probe, + .remove = __devexit_p(max8925_rtc_remove), +}; + +static int __init max8925_rtc_init(void) +{ + return platform_driver_register(&max8925_rtc_driver); +} +module_init(max8925_rtc_init); + +static void __exit max8925_rtc_exit(void) +{ + platform_driver_unregister(&max8925_rtc_driver); +} +module_exit(max8925_rtc_exit); + +MODULE_DESCRIPTION("Maxim MAX8925 RTC driver"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 28db38888b2a53b6bdd70104c2d2a199632ca507 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 25 Jan 2010 10:38:35 -0500 Subject: power_supply: Enable power supply of max8925 MAX8925 is a PMIC that contains charger component Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/max8925_power.c | 534 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 542 insertions(+) create mode 100644 drivers/power/max8925_power.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index bf146721395..faaa9b4d0d0 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -29,6 +29,13 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config MAX8925_POWER + tristate "MAX8925 battery charger support" + depends on MFD_MAX8925 + help + Say Y here to enable support for the battery charger in the Maxim + MAX8925 PMIC. + config WM831X_BACKUP tristate "WM831X backup battery charger support" depends on MFD_WM831X diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 573597c683b..a2ba7c85c97 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o obj-$(CONFIG_WM831X_POWER) += wm831x_power.o obj-$(CONFIG_WM8350_POWER) += wm8350_power.o diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c new file mode 100644 index 00000000000..a1b4410544d --- /dev/null +++ b/drivers/power/max8925_power.c @@ -0,0 +1,534 @@ +/* + * Battery driver for Maxim MAX8925 + * + * Copyright (c) 2009-2010 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 + +/* registers in GPM */ +#define MAX8925_OUT5VEN 0x54 +#define MAX8925_OUT3VEN 0x58 +#define MAX8925_CHG_CNTL1 0x7c + +/* bits definition */ +#define MAX8925_CHG_STAT_VSYSLOW (1 << 0) +#define MAX8925_CHG_STAT_MODE_MASK (3 << 2) +#define MAX8925_CHG_STAT_EN_MASK (1 << 4) +#define MAX8925_CHG_MBDET (1 << 1) +#define MAX8925_CHG_AC_RANGE_MASK (3 << 6) + +/* registers in ADC */ +#define MAX8925_ADC_RES_CNFG1 0x06 +#define MAX8925_ADC_AVG_CNFG1 0x07 +#define MAX8925_ADC_ACQ_CNFG1 0x08 +#define MAX8925_ADC_ACQ_CNFG2 0x09 +/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */ +#define MAX8925_ADC_AUX2 0x62 +#define MAX8925_ADC_VCHG 0x64 +#define MAX8925_ADC_VBBATT 0x66 +#define MAX8925_ADC_VMBATT 0x68 +#define MAX8925_ADC_ISNS 0x6a +#define MAX8925_ADC_THM 0x6c +#define MAX8925_ADC_TDIE 0x6e +#define MAX8925_CMD_AUX2 0xc8 +#define MAX8925_CMD_VCHG 0xd0 +#define MAX8925_CMD_VBBATT 0xd8 +#define MAX8925_CMD_VMBATT 0xe0 +#define MAX8925_CMD_ISNS 0xe8 +#define MAX8925_CMD_THM 0xf0 +#define MAX8925_CMD_TDIE 0xf8 + +enum { + MEASURE_AUX2, + MEASURE_VCHG, + MEASURE_VBBATT, + MEASURE_VMBATT, + MEASURE_ISNS, + MEASURE_THM, + MEASURE_TDIE, + MEASURE_MAX, +}; + +struct max8925_power_info { + struct max8925_chip *chip; + struct i2c_client *gpm; + struct i2c_client *adc; + + struct power_supply ac; + struct power_supply usb; + struct power_supply battery; + int irq_base; + unsigned ac_online:1; + unsigned usb_online:1; + unsigned bat_online:1; + unsigned chg_mode:2; + unsigned batt_detect:1; /* detecing MB by ID pin */ + unsigned topoff_threshold:2; + unsigned fast_charge:3; + + int (*set_charger) (int); +}; + +static int __set_charger(struct max8925_power_info *info, int enable) +{ + struct max8925_chip *chip = info->chip; + if (enable) { + /* enable charger in platform */ + if (info->set_charger) + info->set_charger(1); + /* enable charger */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0); + } else { + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + if (info->set_charger) + info->set_charger(0); + } + dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger" + : "Disable charger"); + return 0; +} + +static irqreturn_t max8925_charger_handler(int irq, void *data) +{ + struct max8925_power_info *info = (struct max8925_power_info *)data; + struct max8925_chip *chip = info->chip; + + switch (irq - chip->irq_base) { + case MAX8925_IRQ_VCHG_DC_R: + info->ac_online = 1; + __set_charger(info, 1); + dev_dbg(chip->dev, "Adapter inserted\n"); + break; + case MAX8925_IRQ_VCHG_DC_F: + info->ac_online = 0; + __set_charger(info, 0); + dev_dbg(chip->dev, "Adapter is removal\n"); + break; + case MAX8925_IRQ_VCHG_USB_R: + info->usb_online = 1; + __set_charger(info, 1); + dev_dbg(chip->dev, "USB inserted\n"); + break; + case MAX8925_IRQ_VCHG_USB_F: + info->usb_online = 0; + __set_charger(info, 0); + dev_dbg(chip->dev, "USB is removal\n"); + break; + case MAX8925_IRQ_VCHG_THM_OK_F: + /* Battery is not ready yet */ + dev_dbg(chip->dev, "Battery temperature is out of range\n"); + case MAX8925_IRQ_VCHG_DC_OVP: + dev_dbg(chip->dev, "Error detection\n"); + __set_charger(info, 0); + break; + case MAX8925_IRQ_VCHG_THM_OK_R: + /* Battery is ready now */ + dev_dbg(chip->dev, "Battery temperature is in range\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_R: + /* VSYS is low */ + dev_info(chip->dev, "Sys power is too low\n"); + break; + case MAX8925_IRQ_VCHG_SYSLOW_F: + dev_dbg(chip->dev, "Sys power is above low threshold\n"); + break; + case MAX8925_IRQ_VCHG_DONE: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charging is done\n"); + break; + case MAX8925_IRQ_VCHG_TOPOFF: + dev_dbg(chip->dev, "Charging in top-off mode\n"); + break; + case MAX8925_IRQ_VCHG_TMR_FAULT: + __set_charger(info, 0); + dev_dbg(chip->dev, "Safe timer is expired\n"); + break; + case MAX8925_IRQ_VCHG_RST: + __set_charger(info, 0); + dev_dbg(chip->dev, "Charger is reset\n"); + break; + } + return IRQ_HANDLED; +} + +static int start_measure(struct max8925_power_info *info, int type) +{ + unsigned char buf[2] = {0, 0}; + int meas_reg = 0, ret; + + switch (type) { + case MEASURE_VCHG: + meas_reg = MAX8925_ADC_VCHG; + break; + case MEASURE_VBBATT: + meas_reg = MAX8925_ADC_VBBATT; + break; + case MEASURE_VMBATT: + meas_reg = MAX8925_ADC_VMBATT; + break; + case MEASURE_ISNS: + meas_reg = MAX8925_ADC_ISNS; + break; + default: + return -EINVAL; + } + + max8925_bulk_read(info->adc, meas_reg, 2, buf); + ret = (buf[0] << 4) | (buf[1] >> 4); + + return ret; +} + +static int max8925_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->ac_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->ac_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->usb_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->usb_online) { + ret = start_measure(info, MEASURE_VCHG); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + goto out; + } + } + ret = -ENODATA; + break; + default: + ret = -ENODEV; + break; + } +out: + return ret; +} + +static enum power_supply_property max8925_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int max8925_bat_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent); + long long int tmp = 0; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->bat_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_VMBATT); + if (ret >= 0) { + val->intval = ret << 1; /* unit is mV */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (info->bat_online) { + ret = start_measure(info, MEASURE_ISNS); + if (ret >= 0) { + tmp = (long long int)ret * 6250 / 4096 - 3125; + ret = (int)tmp; + val->intval = 0; + if (ret > 0) + val->intval = ret; /* unit is mA */ + ret = 0; + break; + } + } + ret = -ENODATA; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2; + switch (ret) { + case 1: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case 0: + case 2: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 3: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + ret = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!info->bat_online) { + ret = -ENODATA; + break; + } + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (info->usb_online || info->ac_online) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + if (ret & MAX8925_CHG_STAT_EN_MASK) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + ret = 0; + break; + default: + ret = -ENODEV; + break; + } + return ret; +} + +static enum power_supply_property max8925_battery_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_STATUS, +}; + +#define REQUEST_IRQ(_irq, _name) \ +do { \ + ret = request_threaded_irq(chip->irq_base + _irq, NULL, \ + max8925_charger_handler, \ + IRQF_ONESHOT, _name, info); \ + if (ret) \ + dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \ + _irq, ret); \ +} while (0) + +static __devinit int max8925_init_charger(struct max8925_chip *chip, + struct max8925_power_info *info) +{ + int ret; + + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire"); + + info->ac_online = 0; + info->usb_online = 0; + info->bat_online = 0; + ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); + if (ret >= 0) { + /* + * If battery detection is enabled, ID pin of battery is + * connected to MBDET pin of MAX8925. It could be used to + * detect battery presence. + * Otherwise, we have to assume that battery is always on. + */ + if (info->batt_detect) + info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1; + else + info->bat_online = 1; + if (ret & MAX8925_CHG_AC_RANGE_MASK) + info->ac_online = 1; + else + info->ac_online = 0; + } + /* disable charge */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); + /* set charging current in charge topoff mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5, + info->topoff_threshold << 5); + /* set charing current in fast charge mode */ + max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge); + + return 0; +} + +static __devexit int max8925_deinit_charger(struct max8925_power_info *info) +{ + struct max8925_chip *chip = info->chip; + int irq; + + irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP; + for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++) + free_irq(irq, info); + + return 0; +} + +static __devinit int max8925_power_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_platform_data *max8925_pdata; + struct max8925_power_pdata *pdata = NULL; + struct max8925_power_info *info; + int ret; + + if (pdev->dev.parent->platform_data) { + max8925_pdata = pdev->dev.parent->platform_data; + pdata = max8925_pdata->power; + } + + if (!pdata) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "power supply\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->gpm = chip->i2c; + info->adc = chip->adc; + + info->ac.name = "max8925-ac"; + info->ac.type = POWER_SUPPLY_TYPE_MAINS; + info->ac.properties = max8925_ac_props; + info->ac.num_properties = ARRAY_SIZE(max8925_ac_props); + info->ac.get_property = max8925_ac_get_prop; + ret = power_supply_register(&pdev->dev, &info->ac); + if (ret) + goto out; + info->ac.dev->parent = &pdev->dev; + + info->usb.name = "max8925-usb"; + info->usb.type = POWER_SUPPLY_TYPE_USB; + info->usb.properties = max8925_usb_props; + info->usb.num_properties = ARRAY_SIZE(max8925_usb_props); + info->usb.get_property = max8925_usb_get_prop; + ret = power_supply_register(&pdev->dev, &info->usb); + if (ret) + goto out_usb; + info->usb.dev->parent = &pdev->dev; + + info->battery.name = "max8925-battery"; + info->battery.type = POWER_SUPPLY_TYPE_BATTERY; + info->battery.properties = max8925_battery_props; + info->battery.num_properties = ARRAY_SIZE(max8925_battery_props); + info->battery.get_property = max8925_bat_get_prop; + ret = power_supply_register(&pdev->dev, &info->battery); + if (ret) + goto out_battery; + info->battery.dev->parent = &pdev->dev; + + info->batt_detect = pdata->batt_detect; + info->topoff_threshold = pdata->topoff_threshold; + info->fast_charge = pdata->fast_charge; + info->set_charger = pdata->set_charger; + dev_set_drvdata(&pdev->dev, info); + platform_set_drvdata(pdev, info); + + max8925_init_charger(chip, info); + return 0; +out_battery: + power_supply_unregister(&info->battery); +out_usb: + power_supply_unregister(&info->ac); +out: + kfree(info); + return ret; +} + +static __devexit int max8925_power_remove(struct platform_device *pdev) +{ + struct max8925_power_info *info = platform_get_drvdata(pdev); + + if (info) { + power_supply_unregister(&info->ac); + power_supply_unregister(&info->usb); + power_supply_unregister(&info->battery); + max8925_deinit_charger(info); + kfree(info); + } + return 0; +} + +static struct platform_driver max8925_power_driver = { + .probe = max8925_power_probe, + .remove = __devexit_p(max8925_power_remove), + .driver = { + .name = "max8925-power", + }, +}; + +static int __init max8925_power_init(void) +{ + return platform_driver_register(&max8925_power_driver); +} +module_init(max8925_power_init); + +static void __exit max8925_power_exit(void) +{ + platform_driver_unregister(&max8925_power_driver); +} +module_exit(max8925_power_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for MAX8925"); +MODULE_ALIAS("platform:max8925-power"); -- cgit v1.2.3 From e8343ddaccb322b1a2a989ac362cabd1e94597ad Mon Sep 17 00:00:00 2001 From: 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') diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index 6d7dba2bce8..c37e12bf300 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') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 299c1af1334..844e1c1b7d9 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/input/touchscreen/88pm860x-ts.c | 15 +- drivers/mfd/88pm860x-core.c | 408 +++++++++++++++++++++++--------- 2 files changed, 299 insertions(+), 124 deletions(-) (limited to 'drivers') diff --git a/drivers/input/touchscreen/88pm860x-ts.c b/drivers/input/touchscreen/88pm860x-ts.c index 56254d2a1f6..286bb490a9f 100644 --- a/drivers/input/touchscreen/88pm860x-ts.c +++ b/drivers/input/touchscreen/88pm860x-ts.c @@ -54,7 +54,6 @@ static irqreturn_t pm860x_touch_handler(int irq, void *data) int z1, z2, rt = 0; int ret; - pm860x_mask_irq(chip, irq); ret = pm860x_bulk_read(touch->i2c, MEAS_TSIX_1, MEAS_LEN, buf); if (ret < 0) goto out; @@ -83,7 +82,6 @@ static irqreturn_t pm860x_touch_handler(int irq, void *data) dev_dbg(chip->dev, "pen release\n"); } input_sync(touch->idev); - pm860x_unmask_irq(chip, irq); out: return IRQ_HANDLED; @@ -92,7 +90,6 @@ out: static int pm860x_touch_open(struct input_dev *dev) { struct pm860x_touch *touch = input_get_drvdata(dev); - struct pm860x_chip *chip = touch->chip; int data, ret; data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN @@ -100,7 +97,6 @@ static int pm860x_touch_open(struct input_dev *dev) ret = pm860x_set_bits(touch->i2c, MEAS_EN3, data, data); if (ret < 0) goto out; - pm860x_unmask_irq(chip, touch->irq); return 0; out: return ret; @@ -109,13 +105,11 @@ out: static void pm860x_touch_close(struct input_dev *dev) { struct pm860x_touch *touch = input_get_drvdata(dev); - struct pm860x_chip *chip = touch->chip; int data; data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN | MEAS_TSIZ1_EN | MEAS_TSIZ2_EN; pm860x_set_bits(touch->i2c, MEAS_EN3, data, 0); - pm860x_mask_irq(chip, touch->irq); } static int __devinit pm860x_touch_probe(struct platform_device *pdev) @@ -164,11 +158,12 @@ static int __devinit pm860x_touch_probe(struct platform_device *pdev) touch->idev->close = pm860x_touch_close; touch->chip = chip; touch->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; - touch->irq = irq; + touch->irq = irq + chip->irq_base; touch->res_x = pdata->res_x; input_set_drvdata(touch->idev, touch); - ret = pm860x_request_irq(chip, irq, pm860x_touch_handler, touch); + ret = request_threaded_irq(touch->irq, NULL, pm860x_touch_handler, + IRQF_ONESHOT, "touch", touch); if (ret < 0) goto out_irq; @@ -194,7 +189,7 @@ static int __devinit pm860x_touch_probe(struct platform_device *pdev) platform_set_drvdata(pdev, touch); return 0; out_rg: - pm860x_free_irq(chip, irq); + free_irq(touch->irq, touch); out_irq: input_free_device(touch->idev); out: @@ -207,7 +202,7 @@ static int __devexit pm860x_touch_remove(struct platform_device *pdev) struct pm860x_touch *touch = platform_get_drvdata(pdev); input_unregister_device(touch->idev); - pm860x_free_irq(touch->chip, touch->irq); + free_irq(touch->irq, touch); platform_set_drvdata(pdev, NULL); kfree(touch); return 0; diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 16f0dca707a..6a14d2b1ccf 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 6985403138b74ad28c8ddac8baf88869556bec9a Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 3 Feb 2010 15:40:59 -0500 Subject: input: Support onkey in 88pm860x Signed-off-by: Haojian Zhuang Acked-by: Dmitry Torokhov Signed-off-by: Samuel Ortiz --- drivers/input/misc/88pm860x_onkey.c | 155 ++++++++++++++++++++++++++++++++++++ drivers/input/misc/Kconfig | 10 +++ drivers/input/misc/Makefile | 1 + 3 files changed, 166 insertions(+) create mode 100644 drivers/input/misc/88pm860x_onkey.c (limited to 'drivers') diff --git a/drivers/input/misc/88pm860x_onkey.c b/drivers/input/misc/88pm860x_onkey.c new file mode 100644 index 00000000000..69a48e8701b --- /dev/null +++ b/drivers/input/misc/88pm860x_onkey.c @@ -0,0 +1,155 @@ +/* + * 88pm860x_onkey.c - Marvell 88PM860x ONKEY driver + * + * Copyright (C) 2009-2010 Marvell International Ltd. + * Haojian Zhuang + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#define PM8607_WAKEUP 0x0b + +#define LONG_ONKEY_EN (1 << 1) +#define ONKEY_STATUS (1 << 0) + +struct pm860x_onkey_info { + struct input_dev *idev; + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct device *dev; + int irq; +}; + +/* 88PM860x gives us an interrupt when ONKEY is held */ +static irqreturn_t pm860x_onkey_handler(int irq, void *data) +{ + struct pm860x_onkey_info *info = data; + int ret; + + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + ret &= ONKEY_STATUS; + input_report_key(info->idev, KEY_POWER, ret); + input_sync(info->idev); + + /* Enable 8-second long onkey detection */ + pm860x_set_bits(info->i2c, PM8607_WAKEUP, 3, LONG_ONKEY_EN); + return IRQ_HANDLED; +} + +static int __devinit pm860x_onkey_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_onkey_info *info; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct pm860x_onkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->dev = &pdev->dev; + info->irq = irq + chip->irq_base; + + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(chip->dev, "Failed to allocate input dev\n"); + ret = -ENOMEM; + goto out; + } + + info->idev->name = "88pm860x_on"; + info->idev->phys = "88pm860x_on/input0"; + info->idev->id.bustype = BUS_I2C; + info->idev->dev.parent = &pdev->dev; + info->irq = irq; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + + ret = input_register_device(info->idev); + if (ret) { + dev_err(chip->dev, "Can't register input device: %d\n", ret); + goto out_reg; + } + + ret = request_threaded_irq(info->irq, NULL, pm860x_onkey_handler, + IRQF_ONESHOT, "onkey", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + info->irq, ret); + goto out_irq; + } + + platform_set_drvdata(pdev, info); + return 0; + +out_irq: + input_unregister_device(info->idev); + kfree(info); + return ret; + +out_reg: + input_free_device(info->idev); +out: + kfree(info); + return ret; +} + +static int __devexit pm860x_onkey_remove(struct platform_device *pdev) +{ + struct pm860x_onkey_info *info = platform_get_drvdata(pdev); + + free_irq(info->irq, info); + input_unregister_device(info->idev); + kfree(info); + return 0; +} + +static struct platform_driver pm860x_onkey_driver = { + .driver = { + .name = "88pm860x-onkey", + .owner = THIS_MODULE, + }, + .probe = pm860x_onkey_probe, + .remove = __devexit_p(pm860x_onkey_remove), +}; + +static int __init pm860x_onkey_init(void) +{ + return platform_driver_register(&pm860x_onkey_driver); +} +module_init(pm860x_onkey_init); + +static void __exit pm860x_onkey_exit(void) +{ + platform_driver_unregister(&pm860x_onkey_driver); +} +module_exit(pm860x_onkey_exit); + +MODULE_DESCRIPTION("Marvell 88PM860x ONKEY driver"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 16ec5233441..7097bfe581d 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -12,6 +12,16 @@ menuconfig INPUT_MISC if INPUT_MISC +config INPUT_88PM860X_ONKEY + tristate "88PM860x ONKEY support" + depends on MFD_88PM860X + help + Support the ONKEY of Marvell 88PM860x PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called 88pm860x_onkey. + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a8b84854fb7..b611615e24a 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -4,6 +4,7 @@ # Each configuration option enables a list of files. +obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_INPUT_ATI_REMOTE) += ati_remote.o obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o -- 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') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index d81003f4867..942a1e83781 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') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index ae15e495e20..aa17f4bddc5 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') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 84a68d26077..9b25fe28233 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 878cac69106..22715add99a 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 00000000000..51b2f6065a0 --- /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') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 942a1e83781..7ccc39f3aa4 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') diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 7ccc39f3aa4..562cd4935e1 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 a8c39d8deb8341d15fc0eabaa9750f7a1db9d0df Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 17 Feb 2010 16:37:45 +0900 Subject: tmio_mmc: Keep card-detect interrupts enabled On SuperH platforms the SDHI controller does not produce any command IRQs after a completed IO. This leads to card-detect interrupts staying disabled. Do not disable card-detect interrupts on DATA IRQs. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mmc/host/tmio_mmc.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 692dc23363b..dafecfbcd91 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -55,10 +55,8 @@ /* Define some IRQ masks */ /* This is the mask used at reset by the chip */ #define TMIO_MASK_ALL 0x837f031d -#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND | \ - TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) -#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND | \ - TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) +#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND) +#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND) #define TMIO_MASK_CMD (TMIO_STAT_CMDRESPEND | TMIO_STAT_CMDTIMEOUT | \ TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) #define TMIO_MASK_IRQ (TMIO_MASK_READOP | TMIO_MASK_WRITEOP | TMIO_MASK_CMD) -- cgit v1.2.3 From b741d440a97c376af309e902eeb2f3c5673d2c92 Mon Sep 17 00:00:00 2001 From: Yusuke Goda Date: Wed, 17 Feb 2010 16:37:55 +0900 Subject: tmio_mmc: Add MMC_CAP_MMC_HIGHSPEED support V2 Enable MMC_CAP_XX support in the tmio_mmc driver if pdata->capabilities is set. Signed-off-by: Yusuke Goda Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mmc/host/tmio_mmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index e22c3fa3516..e2c0cc9a0ca 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -550,6 +550,7 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev) mmc->ops = &tmio_mmc_ops; mmc->caps = MMC_CAP_4_BIT_DATA; + mmc->caps |= pdata->capabilities; mmc->f_max = pdata->hclk; mmc->f_min = mmc->f_max / 512; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; -- cgit v1.2.3 From 6d9af5af61a6c30676ef6f56a791d4036c92d249 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 17 Feb 2010 16:38:04 +0900 Subject: tmio_mmc: Use 100ms mmc_detect_change() delay This patch changes the tmio_mmc driver to wait 100ms before checking the card detect status. This type of delay is quite common among mmc drivers, it seems that most hardware platforms need to give the hardware some time to settle before checking card availabilty. Hotplug is half-broken without this patch on the sh7724 Ecovec board. Hot insertion seems ok but eject is never detected without this patch. Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mmc/host/tmio_mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index e2c0cc9a0ca..7b2376d4050 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -323,7 +323,7 @@ static irqreturn_t tmio_mmc_irq(int irq, void *devid) if (ireg & (TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE)) { ack_mmc_irqs(host, TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE); - mmc_detect_change(host->mmc, 0); + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); } /* CRC and other errors */ -- cgit v1.2.3 From 7ee422dc6e947fcdc153246d47f26ae0b7cf083d Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 17 Feb 2010 16:38:23 +0900 Subject: tmio_mmc: Balance cell enable()/disable() calls This patch adds cell->disable() calls to the tmio-mmc probe() error handling and the remove() function. Signed-off-by: Magnus Damm Signed-off-by: Samuel Ortiz --- drivers/mmc/host/tmio_mmc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index 7b2376d4050..b2b577f6afd 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -569,14 +569,14 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev) if (ret >= 0) host->irq = ret; else - goto unmap_ctl; + goto cell_disable; disable_mmc_irqs(host, TMIO_MASK_ALL); ret = request_irq(host->irq, tmio_mmc_irq, IRQF_DISABLED | IRQF_TRIGGER_FALLING, dev_name(&dev->dev), host); if (ret) - goto unmap_ctl; + goto cell_disable; mmc_add_host(mmc); @@ -588,6 +588,9 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev) return 0; +cell_disable: + if (cell->disable) + cell->disable(dev); unmap_ctl: iounmap(host->ctl); host_free: @@ -598,6 +601,7 @@ out: static int __devexit tmio_mmc_remove(struct platform_device *dev) { + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; struct mmc_host *mmc = platform_get_drvdata(dev); platform_set_drvdata(dev, NULL); @@ -606,6 +610,8 @@ static int __devexit tmio_mmc_remove(struct platform_device *dev) struct tmio_mmc_host *host = mmc_priv(mmc); mmc_remove_host(mmc); free_irq(host->irq, host); + if (cell->disable) + cell->disable(dev); iounmap(host->ctl); mmc_free_host(mmc); } -- cgit v1.2.3 From 3383d23d86791503559cb87837491af37469d9e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Feb 2010 18:04:35 +0000 Subject: gpiolib: Actually set output state in wm831x_gpio_direction_output() wm831x_gpio_direction_output() ignored the state passed into it. Signed-off-by: Mark Brown Cc: stable@kernel.org Signed-off-by: Samuel Ortiz --- drivers/gpio/wm831x-gpio.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c index b4468b61689..c5a00f7a7bf 100644 --- a/drivers/gpio/wm831x-gpio.c +++ b/drivers/gpio/wm831x-gpio.c @@ -60,23 +60,31 @@ static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) return 0; } -static int wm831x_gpio_direction_out(struct gpio_chip *chip, - unsigned offset, int value) +static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); struct wm831x *wm831x = wm831x_gpio->wm831x; - return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, - WM831X_GPN_DIR | WM831X_GPN_TRI, 0); + wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset, + value << offset); } -static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int wm831x_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) { struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); struct wm831x *wm831x = wm831x_gpio->wm831x; + int ret; - wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset, - value << offset); + ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, + WM831X_GPN_DIR | WM831X_GPN_TRI, 0); + if (ret < 0) + return ret; + + /* Can only set GPIO state once it's in output mode */ + wm831x_gpio_set(chip, offset, value); + + return 0; } static int wm831x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) -- cgit v1.2.3 From 6b8274fafeec9b112cee5b6ced5f9189957c889f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Feb 2010 18:45:24 +0000 Subject: gpiolib: Correct debugfs display of WM831x GPIO inversion Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/gpio/wm831x-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c index c5a00f7a7bf..2554180534a 100644 --- a/drivers/gpio/wm831x-gpio.c +++ b/drivers/gpio/wm831x-gpio.c @@ -176,7 +176,7 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) wm831x_gpio_get(chip, i) ? "high" : "low", pull, powerdomain, - reg & WM831X_GPN_POL ? " inverted" : "", + reg & WM831X_GPN_POL ? "" : " inverted", reg & WM831X_GPN_OD ? "open-drain" : "CMOS", reg & WM831X_GPN_TRI ? " tristated" : "", reg); -- 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/gpio/wm831x-gpio.c | 21 ++++++++++++++++----- drivers/mfd/wm831x-core.c | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c index 2554180534a..5b8dc098d80 100644 --- a/drivers/gpio/wm831x-gpio.c +++ b/drivers/gpio/wm831x-gpio.c @@ -38,10 +38,13 @@ static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) { struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); struct wm831x *wm831x = wm831x_gpio->wm831x; + int val = WM831X_GPN_DIR; + + if (wm831x->has_gpio_ena) + val |= WM831X_GPN_TRI; return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, - WM831X_GPN_DIR | WM831X_GPN_TRI, - WM831X_GPN_DIR); + WM831X_GPN_DIR | WM831X_GPN_TRI, val); } static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) @@ -74,10 +77,14 @@ static int wm831x_gpio_direction_out(struct gpio_chip *chip, { struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); struct wm831x *wm831x = wm831x_gpio->wm831x; + int val = 0; int ret; + if (wm831x->has_gpio_ena) + val |= WM831X_GPN_TRI; + ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, - WM831X_GPN_DIR | WM831X_GPN_TRI, 0); + WM831X_GPN_DIR | WM831X_GPN_TRI, val); if (ret < 0) return ret; @@ -103,7 +110,7 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); struct wm831x *wm831x = wm831x_gpio->wm831x; - int i; + int i, tristated; for (i = 0; i < chip->ngpio; i++) { int gpio = i + chip->base; @@ -170,6 +177,10 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) break; } + tristated = reg & WM831X_GPN_TRI; + if (wm831x->has_gpio_ena) + tristated = !tristated; + seq_printf(s, " %s %s %s %s%s\n" " %s%s (0x%4x)\n", reg & WM831X_GPN_DIR ? "in" : "out", @@ -178,7 +189,7 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) powerdomain, reg & WM831X_GPN_POL ? "" : " inverted", reg & WM831X_GPN_OD ? "open-drain" : "CMOS", - reg & WM831X_GPN_TRI ? " tristated" : "", + tristated ? " tristated" : "", reg); } } diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 4b2021af1d9..c428d9f918f 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 1bca748cccec1c7f30b4424882de965100744432 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Feb 2010 18:45:26 +0000 Subject: gpiolib: Force wm831x GPIOs into GPIO mode when requested This is the chip default but it's possible the bootloader or OTP will have been configured to a different mode (eg, to provide feedback during startup). Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/gpio/wm831x-gpio.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c index 5b8dc098d80..d09021f4a7d 100644 --- a/drivers/gpio/wm831x-gpio.c +++ b/drivers/gpio/wm831x-gpio.c @@ -44,7 +44,8 @@ static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) val |= WM831X_GPN_TRI; return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, - WM831X_GPN_DIR | WM831X_GPN_TRI, val); + WM831X_GPN_DIR | WM831X_GPN_TRI | + WM831X_GPN_FN_MASK, val); } static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) @@ -84,7 +85,8 @@ static int wm831x_gpio_direction_out(struct gpio_chip *chip, val |= WM831X_GPN_TRI; ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, - WM831X_GPN_DIR | WM831X_GPN_TRI, val); + WM831X_GPN_DIR | WM831X_GPN_TRI | + WM831X_GPN_FN_MASK, val); if (ret < 0) return ret; -- 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') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index 03efae8041a..468fd366d4d 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 +- drivers/mmc/host/Kconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9b25fe28233..951fa9b93fb 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 diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 7b431bbab7f..2e13b94769f 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -365,7 +365,7 @@ config MMC_SDRICOH_CS config MMC_TMIO tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" - depends on MFD_TMIO || MFD_ASIC3 || SUPERH + depends on MFD_TMIO || MFD_ASIC3 || MFD_SH_MOBILE_SDHI help This provides support for the SD/MMC cell found in TC6393XB, T7L66XB and also HTC ASIC3 -- 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') diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 5b045ff4a2c..7efa8789a3a 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') diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index 9a970bd6877..bd75807d530 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') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index c428d9f918f..07101e9e1cb 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 fd46a0064af171c90664e5b7165c0c9f94478a87 Mon Sep 17 00:00:00 2001 From: Denis Turischev Date: Mon, 1 Mar 2010 18:59:55 +0200 Subject: i2c: convert i2c-isch to platform_device Convert i2c-isch to platform_device for the lpc mfd core to add it at probe time. Signed-off-by: Denis Turischev Signed-off-by: Samuel Ortiz --- drivers/i2c/busses/Kconfig | 2 +- drivers/i2c/busses/i2c-isch.c | 68 +++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 42 deletions(-) (limited to 'drivers') diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 4cc3807bd31..f22eff1da87 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -105,7 +105,7 @@ config I2C_I801 config I2C_ISCH tristate "Intel SCH SMBus 1.0" - depends on PCI + select LPC_SCH help Say Y here if you want to use SMBus controller on the Intel SCH based systems. diff --git a/drivers/i2c/busses/i2c-isch.c b/drivers/i2c/busses/i2c-isch.c index 69c22f79f23..ddc258edb34 100644 --- a/drivers/i2c/busses/i2c-isch.c +++ b/drivers/i2c/busses/i2c-isch.c @@ -27,7 +27,7 @@ */ #include -#include +#include #include #include #include @@ -46,12 +46,6 @@ #define SMBHSTDAT1 (7 + sch_smba) #define SMBBLKDAT (0x20 + sch_smba) -/* count for request_region */ -#define SMBIOSIZE 64 - -/* PCI Address Constants */ -#define SMBBA_SCH 0x40 - /* Other settings */ #define MAX_TIMEOUT 500 @@ -63,7 +57,6 @@ #define SCH_BLOCK_DATA 0x05 static unsigned short sch_smba; -static struct pci_driver sch_driver; static struct i2c_adapter sch_adapter; /* @@ -256,37 +249,23 @@ static struct i2c_adapter sch_adapter = { .algo = &smbus_algorithm, }; -static const struct pci_device_id sch_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) }, - { 0, } -}; - -MODULE_DEVICE_TABLE(pci, sch_ids); - -static int __devinit sch_probe(struct pci_dev *dev, - const struct pci_device_id *id) +static int __devinit smbus_sch_probe(struct platform_device *dev) { + struct resource *res; int retval; - unsigned int smba; - pci_read_config_dword(dev, SMBBA_SCH, &smba); - if (!(smba & (1 << 31))) { - dev_err(&dev->dev, "SMBus I/O space disabled!\n"); - return -ENODEV; - } + res = platform_get_resource(dev, IORESOURCE_IO, 0); + if (!res) + return -EBUSY; - sch_smba = (unsigned short)smba; - if (sch_smba == 0) { - dev_err(&dev->dev, "SMBus base address uninitialized!\n"); - return -ENODEV; - } - if (acpi_check_region(sch_smba, SMBIOSIZE, sch_driver.name)) - return -ENODEV; - if (!request_region(sch_smba, SMBIOSIZE, sch_driver.name)) { + if (!request_region(res->start, resource_size(res), dev->name)) { dev_err(&dev->dev, "SMBus region 0x%x already in use!\n", sch_smba); return -EBUSY; } + + sch_smba = res->start; + dev_dbg(&dev->dev, "SMBA = 0x%X\n", sch_smba); /* set up the sysfs linkage to our parent device */ @@ -298,37 +277,43 @@ static int __devinit sch_probe(struct pci_dev *dev, retval = i2c_add_adapter(&sch_adapter); if (retval) { dev_err(&dev->dev, "Couldn't register adapter!\n"); - release_region(sch_smba, SMBIOSIZE); + release_region(res->start, resource_size(res)); sch_smba = 0; } return retval; } -static void __devexit sch_remove(struct pci_dev *dev) +static int __devexit smbus_sch_remove(struct platform_device *pdev) { + struct resource *res; if (sch_smba) { i2c_del_adapter(&sch_adapter); - release_region(sch_smba, SMBIOSIZE); + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + release_region(res->start, resource_size(res)); sch_smba = 0; } + + return 0; } -static struct pci_driver sch_driver = { - .name = "isch_smbus", - .id_table = sch_ids, - .probe = sch_probe, - .remove = __devexit_p(sch_remove), +static struct platform_driver smbus_sch_driver = { + .driver = { + .name = "isch_smbus", + .owner = THIS_MODULE, + }, + .probe = smbus_sch_probe, + .remove = __devexit_p(smbus_sch_remove), }; static int __init i2c_sch_init(void) { - return pci_register_driver(&sch_driver); + return platform_driver_register(&smbus_sch_driver); } static void __exit i2c_sch_exit(void) { - pci_unregister_driver(&sch_driver); + platform_driver_unregister(&smbus_sch_driver); } MODULE_AUTHOR("Jacob Pan "); @@ -337,3 +322,4 @@ MODULE_LICENSE("GPL"); module_init(i2c_sch_init); module_exit(i2c_sch_exit); +MODULE_ALIAS("platform:isch_smbus"); -- cgit v1.2.3 From be9b06b2d80fe661491138c6993f944babb26260 Mon Sep 17 00:00:00 2001 From: Denis Turischev Date: Tue, 2 Mar 2010 10:48:55 +0100 Subject: gpio: add Intel SCH GPIO controller driver Signed-off-by: Denis Turischev Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 16 +++ drivers/gpio/Makefile | 1 + drivers/gpio/sch_gpio.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 drivers/gpio/sch_gpio.c (limited to 'drivers') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f42cb317a77..60cb3632c8f 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -94,6 +94,22 @@ config GPIO_VR41XX help Say yes here to support the NEC VR4100 series General-purpose I/O Uint +config GPIO_SCH + tristate "Intel SCH GPIO" + depends on GPIOLIB + select LPC_SCH + help + Say yes here to support GPIO interface on Intel Poulsbo SCH. + The Intel SCH contains a total of 14 GPIO pins. Ten GPIOs are + powered by the core power rail and are turned off during sleep + modes (S3 and higher). The remaining four GPIOs are powered by + the Intel SCH suspend power supply. These GPIOs remain + active during S3. The suspend powered GPIOs can be used to wake the + system from the Suspend-to-RAM state. + + This driver can also be built as a module. If so, the module + will be called sch-gpio. + comment "I2C GPIO expanders:" config GPIO_MAX7300 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 594bb70f65a..10f3f8d958b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o +obj-$(CONFIG_GPIO_SCH) += sch_gpio.o \ No newline at end of file diff --git a/drivers/gpio/sch_gpio.c b/drivers/gpio/sch_gpio.c new file mode 100644 index 00000000000..761071ae359 --- /dev/null +++ b/drivers/gpio/sch_gpio.c @@ -0,0 +1,282 @@ +/* + * sch_gpio.c - GPIO interface for Intel Poulsbo SCH + * + * 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 + +#include + +static DEFINE_SPINLOCK(gpio_lock); + +#define CGEN (0x00) +#define CGIO (0x04) +#define CGLV (0x08) + +#define RGEN (0x20) +#define RGIO (0x24) +#define RGLV (0x28) + +static unsigned short gpio_ba; + +static int sch_gpio_core_direction_in(struct gpio_chip *gc, unsigned gpio_num) +{ + u8 curr_dirs; + unsigned short offset, bit; + + spin_lock(&gpio_lock); + + offset = CGIO + gpio_num / 8; + bit = gpio_num % 8; + + curr_dirs = inb(gpio_ba + offset); + + if (!(curr_dirs & (1 << bit))) + outb(curr_dirs | (1 << bit), gpio_ba + offset); + + spin_unlock(&gpio_lock); + return 0; +} + +static int sch_gpio_core_get(struct gpio_chip *gc, unsigned gpio_num) +{ + int res; + unsigned short offset, bit; + + offset = CGLV + gpio_num / 8; + bit = gpio_num % 8; + + res = !!(inb(gpio_ba + offset) & (1 << bit)); + return res; +} + +static void sch_gpio_core_set(struct gpio_chip *gc, unsigned gpio_num, int val) +{ + u8 curr_vals; + unsigned short offset, bit; + + spin_lock(&gpio_lock); + + offset = CGLV + gpio_num / 8; + bit = gpio_num % 8; + + curr_vals = inb(gpio_ba + offset); + + if (val) + outb(curr_vals | (1 << bit), gpio_ba + offset); + else + outb((curr_vals & ~(1 << bit)), gpio_ba + offset); + spin_unlock(&gpio_lock); +} + +static int sch_gpio_core_direction_out(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_dirs; + unsigned short offset, bit; + + sch_gpio_core_set(gc, gpio_num, val); + + spin_lock(&gpio_lock); + + offset = CGIO + gpio_num / 8; + bit = gpio_num % 8; + + curr_dirs = inb(gpio_ba + offset); + if (curr_dirs & (1 << bit)) + outb(curr_dirs & ~(1 << bit), gpio_ba + offset); + + spin_unlock(&gpio_lock); + return 0; +} + +static struct gpio_chip sch_gpio_core = { + .label = "sch_gpio_core", + .owner = THIS_MODULE, + .direction_input = sch_gpio_core_direction_in, + .get = sch_gpio_core_get, + .direction_output = sch_gpio_core_direction_out, + .set = sch_gpio_core_set, +}; + +static int sch_gpio_resume_direction_in(struct gpio_chip *gc, + unsigned gpio_num) +{ + u8 curr_dirs; + + spin_lock(&gpio_lock); + + curr_dirs = inb(gpio_ba + RGIO); + + if (!(curr_dirs & (1 << gpio_num))) + outb(curr_dirs | (1 << gpio_num) , gpio_ba + RGIO); + + spin_unlock(&gpio_lock); + return 0; +} + +static int sch_gpio_resume_get(struct gpio_chip *gc, unsigned gpio_num) +{ + return !!(inb(gpio_ba + RGLV) & (1 << gpio_num)); +} + +static void sch_gpio_resume_set(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_vals; + + spin_lock(&gpio_lock); + + curr_vals = inb(gpio_ba + RGLV); + + if (val) + outb(curr_vals | (1 << gpio_num), gpio_ba + RGLV); + else + outb((curr_vals & ~(1 << gpio_num)), gpio_ba + RGLV); + + spin_unlock(&gpio_lock); +} + +static int sch_gpio_resume_direction_out(struct gpio_chip *gc, + unsigned gpio_num, int val) +{ + u8 curr_dirs; + + sch_gpio_resume_set(gc, gpio_num, val); + + spin_lock(&gpio_lock); + + curr_dirs = inb(gpio_ba + RGIO); + if (curr_dirs & (1 << gpio_num)) + outb(curr_dirs & ~(1 << gpio_num), gpio_ba + RGIO); + + spin_unlock(&gpio_lock); + return 0; +} + +static struct gpio_chip sch_gpio_resume = { + .label = "sch_gpio_resume", + .owner = THIS_MODULE, + .direction_input = sch_gpio_resume_direction_in, + .get = sch_gpio_resume_get, + .direction_output = sch_gpio_resume_direction_out, + .set = sch_gpio_resume_set, +}; + +static int __devinit sch_gpio_probe(struct platform_device *pdev) +{ + struct resource *res; + int err; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -EBUSY; + + if (!request_region(res->start, resource_size(res), pdev->name)) + return -EBUSY; + + gpio_ba = res->start; + + sch_gpio_core.base = 0; + sch_gpio_core.ngpio = 10; + sch_gpio_core.dev = &pdev->dev; + + sch_gpio_resume.base = 10; + sch_gpio_resume.ngpio = 4; + sch_gpio_resume.dev = &pdev->dev; + + err = gpiochip_add(&sch_gpio_core); + if (err < 0) + goto err_sch_gpio_core; + + err = gpiochip_add(&sch_gpio_resume); + if (err < 0) + goto err_sch_gpio_resume; + + /* + * GPIO[6:0] enabled by default + * GPIO7 is configured by the CMC as SLPIOVR + * Enable GPIO[9:8] core powered gpios explicitly + */ + outb(0x3, gpio_ba + CGEN + 1); + /* + * SUS_GPIO[2:0] enabled by default + * Enable SUS_GPIO3 resume powered gpio explicitly + */ + outb(0x8, gpio_ba + RGEN); + + return 0; + +err_sch_gpio_resume: + gpiochip_remove(&sch_gpio_core); + +err_sch_gpio_core: + release_region(res->start, resource_size(res)); + gpio_ba = 0; + + return err; +} + +static int __devexit sch_gpio_remove(struct platform_device *pdev) +{ + struct resource *res; + if (gpio_ba) { + gpiochip_remove(&sch_gpio_core); + gpiochip_remove(&sch_gpio_resume); + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + + release_region(res->start, resource_size(res)); + gpio_ba = 0; + } + + return 0; +} + +static struct platform_driver sch_gpio_driver = { + .driver = { + .name = "sch_gpio", + .owner = THIS_MODULE, + }, + .probe = sch_gpio_probe, + .remove = __devexit_p(sch_gpio_remove), +}; + +static int __init sch_gpio_init(void) +{ + return platform_driver_register(&sch_gpio_driver); +} + +static void __exit sch_gpio_exit(void) +{ + platform_driver_unregister(&sch_gpio_driver); +} + +module_init(sch_gpio_init); +module_exit(sch_gpio_exit); + +MODULE_AUTHOR("Denis Turischev "); +MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sch_gpio"); -- cgit v1.2.3 From 8e7aafe41be2a7cd5c181ed68876e9ec55ccdba7 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 5 Mar 2010 17:14:01 +0100 Subject: gpio: Fix sch_gpio warning We need to check for gpiochip_remove() errors. Signed-off-by: Samuel Ortiz --- drivers/gpio/sch_gpio.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/sch_gpio.c b/drivers/gpio/sch_gpio.c index 761071ae359..583521352c1 100644 --- a/drivers/gpio/sch_gpio.c +++ b/drivers/gpio/sch_gpio.c @@ -229,7 +229,10 @@ static int __devinit sch_gpio_probe(struct platform_device *pdev) return 0; err_sch_gpio_resume: - gpiochip_remove(&sch_gpio_core); + err = gpiochip_remove(&sch_gpio_core); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); err_sch_gpio_core: release_region(res->start, resource_size(res)); @@ -242,13 +245,23 @@ static int __devexit sch_gpio_remove(struct platform_device *pdev) { struct resource *res; if (gpio_ba) { - gpiochip_remove(&sch_gpio_core); - gpiochip_remove(&sch_gpio_resume); + int err; + + err = gpiochip_remove(&sch_gpio_core); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); + err = gpiochip_remove(&sch_gpio_resume); + if (err) + dev_err(&pdev->dev, "%s failed, %d\n", + "gpiochip_remove()", err); res = platform_get_resource(pdev, IORESOURCE_IO, 0); release_region(res->start, resource_size(res)); gpio_ba = 0; + + return err; } return 0; -- cgit v1.2.3 From 860fb8c134ebca9be8bc2ddd2b13e19ac10c192e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 5 Mar 2010 09:43:06 -0800 Subject: mfd: Fix lpc_sch related depends/selects, fix build error LPC_SCH is selected by GPI_SCH and I2C_ISCH, even when PCI is not enabled, but LPC_SCH depends on PCI, so make GPI_SCH and I2C_ISCH also depend on PCI. Those 2 selects also need to select what LPC_SCH selects, since kconfig does not follow selects. Signed-off-by: Randy Dunlap Cc: Denis Turischev Signed-off-by: Samuel Ortiz --- drivers/gpio/Kconfig | 3 ++- drivers/i2c/busses/Kconfig | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 60cb3632c8f..fee678f74a1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -96,7 +96,8 @@ config GPIO_VR41XX config GPIO_SCH tristate "Intel SCH GPIO" - depends on GPIOLIB + depends on GPIOLIB && PCI + select MFD_CORE select LPC_SCH help Say yes here to support GPIO interface on Intel Poulsbo SCH. diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index f22eff1da87..4cd1ff77ced 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -105,6 +105,8 @@ config I2C_I801 config I2C_ISCH tristate "Intel SCH SMBus 1.0" + depends on PCI + select MFD_CORE select LPC_SCH help Say Y here if you want to use SMBus controller on the Intel SCH -- 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') diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index 252b74188ec..b281217334e 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