From d0f7a6d6d193c22793b128728666726fbd3667eb Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Fri, 18 Jun 2010 19:59:53 +0200 Subject: mfd: Enable onkey on max8925 Enable onkey feature in max8925 driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/max8925-core.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index f621bcea3d02..04028a9ee082 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -90,6 +90,24 @@ static struct mfd_cell rtc_devs[] = { }, }; +static struct resource onkey_resources[] = { + { + .name = "max8925-onkey", + .start = MAX8925_IRQ_GPM_SW_3SEC, + .end = MAX8925_IRQ_GPM_SW_3SEC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell onkey_devs[] = { + { + .name = "max8925-onkey", + .num_resources = 1, + .resources = &onkey_resources[0], + .id = -1, + }, +}; + #define MAX8925_REG_RESOURCE(_start, _end) \ { \ .start = MAX8925_##_start, \ @@ -596,6 +614,15 @@ int __devinit max8925_device_init(struct max8925_chip *chip, dev_err(chip->dev, "Failed to add rtc subdev\n"); goto out; } + + 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; + } + if (pdata && pdata->regulator[0]) { ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], ARRAY_SIZE(regulator_devs), -- cgit v1.2.3 From 21f1fc38606b35bb12a4772367ef68128cd12c30 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Wed, 19 May 2010 13:06:59 +0800 Subject: mfd: Fix led resource in 88pm860x Fix typo error in LED resource of 88pm860x. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 2c65a2c57294..1580f1f2ec62 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -74,12 +74,12 @@ static struct mfd_cell backlight_devs[] = { } 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), + PM8606_LED_RESOURCE(PM8606_LED1_RED, RGB1B), + PM8606_LED_RESOURCE(PM8606_LED1_GREEN, RGB1C), + PM8606_LED_RESOURCE(PM8606_LED1_BLUE, RGB1D), + PM8606_LED_RESOURCE(PM8606_LED2_RED, RGB2B), + PM8606_LED_RESOURCE(PM8606_LED2_GREEN, RGB2C), + PM8606_LED_RESOURCE(PM8606_LED2_BLUE, RGB2D), }; #define PM8606_LED_DEVS(_i) \ -- cgit v1.2.3 From d281b80c46da8bae806c4ef5682187f07e35389d Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 25 May 2010 14:49:51 +0800 Subject: mfd: Fix memory leak in ab3100_otp_probe In current implementation, there is a memory leak if ab3100_otp_read fail. And in the case of ab3100_otp_init_debugfs fail, it does not properly remove sysfs entries. This patch properly handle above failure cases. Signed-off-by: Axel Lin Acked-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3100-otp.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c index 63d2b727ddbb..8440010eb2b8 100644 --- a/drivers/mfd/ab3100-otp.c +++ b/drivers/mfd/ab3100-otp.c @@ -199,7 +199,7 @@ static int __init ab3100_otp_probe(struct platform_device *pdev) err = ab3100_otp_read(otp); if (err) - return err; + goto err_otp_read; dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); @@ -208,21 +208,21 @@ static int __init ab3100_otp_probe(struct platform_device *pdev) err = device_create_file(&pdev->dev, &ab3100_otp_attrs[i]); if (err) - goto out_no_sysfs; + goto err_create_file; } /* debugfs entries */ err = ab3100_otp_init_debugfs(&pdev->dev, otp); if (err) - goto out_no_debugfs; + goto err_init_debugfs; return 0; -out_no_sysfs: - for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) - device_remove_file(&pdev->dev, - &ab3100_otp_attrs[i]); -out_no_debugfs: +err_init_debugfs: +err_create_file: + while (--i >= 0) + device_remove_file(&pdev->dev, &ab3100_otp_attrs[i]); +err_otp_read: kfree(otp); return err; } -- cgit v1.2.3 From eb6e8ddf06ff0d6b6548ca0b95a4edfeb2aaadd0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 27 May 2010 00:54:09 +0200 Subject: mfd: Fix 88pm860x uninitialized variable and clean up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original code had a compile warning: drivers/mfd/88pm860x-core.c:431: warning: ‘ret’ may be used uninitialized in this function It seems like the warning is valid if either pdata or pdata->touch is NULL. This patch checks pdata and pdata->touch at the beginning of the function. That means everything can be pulled in one indent level. Now all the statements fit within the 80 character limit. Also at that point the "use_gpadc" variable isn't needed and removing it simplifies the logic. Signed-off-by: Dan Carpenter Acked-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 72 ++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 40 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 1580f1f2ec62..07933f3f7e4c 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -428,52 +428,44 @@ static int __devinit device_gpadc_init(struct pm860x_chip *chip, { struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ : chip->companion; - int use_gpadc = 0, data, ret; + int data; + int 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; - } + if (!pdata || !pdata->touch) + return -EINVAL; - use_gpadc = 1; + /* 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; } - - /* turn on GPADC */ - if (use_gpadc) { - ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, - PM8607_GPADC_EN, PM8607_GPADC_EN); + /* 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; + } + + ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, + PM8607_GPADC_EN, PM8607_GPADC_EN); out: return ret; } -- cgit v1.2.3 From c0d4010e11e23d6f55f9bdda63b1974f35af71bf Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 28 May 2010 02:59:44 +0200 Subject: mfd: AB8500 mask off irrelevant bits from the SPI message The registers on the AB8500 are only 8 bits wide, so the content of the remaining bits is undefined. Let's mask off the undefined stuff when returning a register in an SPI read. Acked-by: Rabin Vincent Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab8500-spi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ab8500-spi.c b/drivers/mfd/ab8500-spi.c index b81d4f768ef6..e1c8b62b086d 100644 --- a/drivers/mfd/ab8500-spi.c +++ b/drivers/mfd/ab8500-spi.c @@ -68,7 +68,12 @@ static int ab8500_spi_read(struct ab8500 *ab8500, u16 addr) ret = spi_sync(spi, &msg); if (!ret) - ret = ab8500->rx_buf[0]; + /* + * Only the 8 lowermost bytes are + * defined with value, the rest may + * vary depending on chip/board noise. + */ + ret = ab8500->rx_buf[0] & 0xFFU; return ret; } -- cgit v1.2.3 From 25fe24f884015ba2e1e39376deb055bbbdc5ba83 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 28 May 2010 22:58:46 +0200 Subject: mfd: kzalloc doesn't return ERR_PTR Use !x rather than IS_ERR(x) to test the result of kzalloc. The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @@ expression x,E; @@ x = \(kmalloc\|kzalloc\|kcalloc\)(...) ... when != x = E - IS_ERR(x) + !x // Signed-off-by: Julia Lawall Signed-off-by: Samuel Ortiz --- drivers/mfd/abx500-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/abx500-core.c b/drivers/mfd/abx500-core.c index 3b3b97ec32a7..f12720dbe126 100644 --- a/drivers/mfd/abx500-core.c +++ b/drivers/mfd/abx500-core.c @@ -36,7 +36,7 @@ int abx500_register_ops(struct device *dev, struct abx500_ops *ops) struct abx500_device_entry *dev_entry; dev_entry = kzalloc(sizeof(struct abx500_device_entry), GFP_KERNEL); - if (IS_ERR(dev_entry)) { + if (!dev_entry) { dev_err(dev, "register_ops kzalloc failed"); return -ENOMEM; } -- cgit v1.2.3 From 8af5fe3bc59d73479ff701340e1a9bc7c6b5f0ff Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 31 May 2010 17:30:55 +0800 Subject: mfd: properly handle platform_device_add_resources fail in mfd_add_device platform_device_add_resources may fail, thus add error checking for it. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 7dd76bceaae8..1823a57b7d8f 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -70,7 +70,9 @@ static int mfd_add_device(struct device *parent, int id, goto fail_res; } - platform_device_add_resources(pdev, res, cell->num_resources); + ret = platform_device_add_resources(pdev, res, cell->num_resources); + if (ret) + goto fail_res; ret = platform_device_add(pdev); if (ret) -- cgit v1.2.3 From e2bde787131a01d0f2ca9baccaf3d107bf6c0c92 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Tue, 1 Jun 2010 16:34:38 +0200 Subject: mfd: Fix davinci memory leak Error handling code following a kmalloc should free the allocated data. The semantic match that finds the problem is as follows: (http://www.emn.fr/x-info/coccinelle/) // @r exists@ local idexpression x; statement S; expression E; identifier f,f1,l; position p1,p2; expression *ptr != NULL; @@ x@p1 = \(kmalloc\|kzalloc\|kcalloc\)(...); ... if (x == NULL) S <... when != x when != if (...) { <+...x...+> } ( x->f1 = E | (x->f1 == NULL || ...) | f(...,x->f1,...) ) ...> ( return \(0\|<+...x...+>\|ptr\); | return@p2 ...; ) @script:python@ p1 << r.p1; p2 << r.p2; @@ print "* file: %s kmalloc %s return %s" % (p1[0].file,p1[0].line,p2[0].line) // Signed-off-by: Julia Lawall Signed-off-by: Samuel Ortiz --- drivers/mfd/davinci_voicecodec.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c index 3e75f02e4778..33c923d215c7 100644 --- a/drivers/mfd/davinci_voicecodec.c +++ b/drivers/mfd/davinci_voicecodec.c @@ -94,7 +94,8 @@ static int __init davinci_vc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); - return -ENXIO; + ret = -ENXIO; + goto fail4; } davinci_vc->davinci_vcif.dma_tx_channel = res->start; @@ -104,7 +105,8 @@ static int __init davinci_vc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); - return -ENXIO; + ret = -ENXIO; + goto fail4; } davinci_vc->davinci_vcif.dma_rx_channel = res->start; -- cgit v1.2.3 From 3cb46ac058f8ce3ed339fc5dcf6faae4329153c9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 9 Jun 2010 14:54:54 +0100 Subject: mfd: Staticise ab3550 register access functions These are now exported via an ops table rather than referenced directly and so should be staticised. Signed-off-by: Mark Brown Acked-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3550-core.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c index f54ab62e7bc6..8a98739e6d9c 100644 --- a/drivers/mfd/ab3550-core.c +++ b/drivers/mfd/ab3550-core.c @@ -589,16 +589,16 @@ static bool reg_read_allowed(const struct ab3550_reg_ranges *ranges, u8 reg) } /* - * The exported register access functionality. + * The register access functionality. */ -int ab3550_get_chip_id(struct device *dev) +static int ab3550_get_chip_id(struct device *dev) { struct ab3550 *ab = dev_get_drvdata(dev->parent); return (int)ab->chip_id; } -int ab3550_mask_and_set_register_interruptible(struct device *dev, u8 bank, - u8 reg, u8 bitmask, u8 bitvalues) +static int ab3550_mask_and_set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 bitmask, u8 bitvalues) { struct ab3550 *ab; struct platform_device *pdev = to_platform_device(dev); @@ -612,15 +612,15 @@ int ab3550_mask_and_set_register_interruptible(struct device *dev, u8 bank, bitmask, bitvalues); } -int ab3550_set_register_interruptible(struct device *dev, u8 bank, u8 reg, - u8 value) +static int ab3550_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 value) { return ab3550_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, value); } -int ab3550_get_register_interruptible(struct device *dev, u8 bank, u8 reg, - u8 *value) +static int ab3550_get_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 *value) { struct ab3550 *ab; struct platform_device *pdev = to_platform_device(dev); @@ -633,7 +633,7 @@ int ab3550_get_register_interruptible(struct device *dev, u8 bank, u8 reg, return get_register_interruptible(ab, bank, reg, value); } -int ab3550_get_register_page_interruptible(struct device *dev, u8 bank, +static int ab3550_get_register_page_interruptible(struct device *dev, u8 bank, u8 first_reg, u8 *regvals, u8 numregs) { struct ab3550 *ab; @@ -649,7 +649,8 @@ int ab3550_get_register_page_interruptible(struct device *dev, u8 bank, numregs); } -int ab3550_event_registers_startup_state_get(struct device *dev, u8 *event) +static int ab3550_event_registers_startup_state_get(struct device *dev, + u8 *event) { struct ab3550 *ab; @@ -661,7 +662,7 @@ int ab3550_event_registers_startup_state_get(struct device *dev, u8 *event) return 0; } -int ab3550_startup_irq_enabled(struct device *dev, unsigned int irq) +static int ab3550_startup_irq_enabled(struct device *dev, unsigned int irq) { struct ab3550 *ab; struct ab3550_platform_data *plf_data; -- cgit v1.2.3 From 15742c8a42c89aa5e63542b0de00b0dc4c5038cf Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Sat, 19 Jun 2010 02:29:24 +0200 Subject: mfd: More verbose MFD Kconfig entry For people to be able to intellingibly decide if they want to enable MFD drivers or not, we have to give them a much better description of what they are. --- drivers/mfd/Kconfig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9da0e504bbe9..29781ca1eb6f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -7,7 +7,16 @@ menuconfig MFD_SUPPORT depends on HAS_IOMEM default y help - Configure MFD device drivers. + Multifunction devices embed several functions (e.g. GPIOs, + touchscreens, keyboards, current regulators, power management chips, + etc...) in one single integrated circuit. They usually talk to the + main CPU through one or more IRQ lines and low speed data busses (SPI, + I2C, etc..). They appear as one single device to the main system + through the data bus and the MFD framework allows for sub devices + (a.k.a. functions) to appear as discrete platform devices. + MFDs are typically found on embedded platforms. + + This option alone does not add any kernel code. if MFD_SUPPORT -- cgit v1.2.3 From d2d272a965baeb3d78f843374bc48f0cbce8ac3d Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Thu, 1 Jul 2010 17:43:50 +0800 Subject: mfd: Properly free t7l66xb clk32k clock source This patch includes below fixes to properly free clk32k clock source: 1. remove a redundant clk_put in t7l66xb_probe error path 2. add missing clk_disable(t7l66xb->clk32k) and clk_put(t7l66xb->clk32k) to properly free the clock source. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/t7l66xb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index 5041d33adf0b..006c121f3f0d 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -350,7 +350,6 @@ static int t7l66xb_probe(struct platform_device *dev) t7l66xb->clk48m = clk_get(&dev->dev, "CLK_CK48M"); if (IS_ERR(t7l66xb->clk48m)) { ret = PTR_ERR(t7l66xb->clk48m); - clk_put(t7l66xb->clk32k); goto err_clk48m_get; } @@ -425,6 +424,8 @@ static int t7l66xb_remove(struct platform_device *dev) ret = pdata->disable(dev); clk_disable(t7l66xb->clk48m); clk_put(t7l66xb->clk48m); + clk_disable(t7l66xb->clk32k); + clk_put(t7l66xb->clk32k); t7l66xb_detach_irq(dev); iounmap(t7l66xb->scr); release_resource(&t7l66xb->rscr); -- cgit v1.2.3 From 27e34995e1a863c1e9beba30e51dfe2a083f918d Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 2 Jul 2010 16:52:08 +0530 Subject: mfd: Add STMPE I/O Expander support Add support for the STMPE family of I/O Expanders from STMicroelectronics. These devices include upto 24 gpios and a varying selection of blocks, including PWM, keypad, and touchscreen controllers. This patch adds the MFD core. [l.fu@pengutronix.de: fix stmpe811 enable hook] [l.fu@pengutronix.de: add touchscreen platform data] Acked-by: Luotao Fu Acked-by: Linus Walleij Signed-off-by: Rabin Vincent Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 23 ++ drivers/mfd/Makefile | 1 + drivers/mfd/stmpe.c | 915 +++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/stmpe.h | 176 ++++++++++ 4 files changed, 1115 insertions(+) create mode 100644 drivers/mfd/stmpe.c create mode 100644 drivers/mfd/stmpe.h (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 29781ca1eb6f..8f5145b4b56f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -186,6 +186,29 @@ config TWL4030_CODEC select MFD_CORE default n +config MFD_STMPE + bool "Support STMicroelectronics STMPE" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the STMPE family of I/O Expanders from + STMicroelectronics. + + Currently supported devices are: + + STMPE811: GPIO, Touchscreen + STMPE1601: GPIO, Keypad + STMPE2401: GPIO, Keypad + STMPE2403: GPIO, Keypad + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. Currently available sub drivers are: + + GPIO: stmpe-gpio + Keypad: stmpe-keypad + Touchscreen: stmpe-ts + config MFD_TC35892 bool "Support Toshiba TC35892" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index fb503e77dc60..4410747f7b5b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o +obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_MFD_TC35892) += tc35892.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c new file mode 100644 index 000000000000..a7f3099fdcfd --- /dev/null +++ b/drivers/mfd/stmpe.c @@ -0,0 +1,915 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent for ST-Ericsson + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "stmpe.h" + +static int __stmpe_enable(struct stmpe *stmpe, unsigned int blocks) +{ + return stmpe->variant->enable(stmpe, blocks, true); +} + +static int __stmpe_disable(struct stmpe *stmpe, unsigned int blocks) +{ + return stmpe->variant->enable(stmpe, blocks, false); +} + +static int __stmpe_reg_read(struct stmpe *stmpe, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(stmpe->i2c, reg); + if (ret < 0) + dev_err(stmpe->dev, "failed to read reg %#x: %d\n", + reg, ret); + + dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret); + + return ret; +} + +static int __stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + int ret; + + dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val); + + ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val); + if (ret < 0) + dev_err(stmpe->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} + +static int __stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val) +{ + int ret; + + ret = __stmpe_reg_read(stmpe, reg); + if (ret < 0) + return ret; + + ret &= ~mask; + ret |= val; + + return __stmpe_reg_write(stmpe, reg, ret); +} + +static int __stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, + u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values); + if (ret < 0) + dev_err(stmpe->dev, "failed to read regs %#x: %d\n", + reg, ret); + + dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret); + stmpe_dump_bytes("stmpe rd: ", values, length); + + return ret; +} + +static int __stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length); + stmpe_dump_bytes("stmpe wr: ", values, length); + + ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length, + values); + if (ret < 0) + dev_err(stmpe->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} + +/** + * stmpe_enable - enable blocks on an STMPE device + * @stmpe: Device to work on + * @blocks: Mask of blocks (enum stmpe_block values) to enable + */ +int stmpe_enable(struct stmpe *stmpe, unsigned int blocks) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_enable(stmpe, blocks); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_enable); + +/** + * stmpe_disable - disable blocks on an STMPE device + * @stmpe: Device to work on + * @blocks: Mask of blocks (enum stmpe_block values) to enable + */ +int stmpe_disable(struct stmpe *stmpe, unsigned int blocks) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_disable(stmpe, blocks); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_disable); + +/** + * stmpe_reg_read() - read a single STMPE register + * @stmpe: Device to read from + * @reg: Register to read + */ +int stmpe_reg_read(struct stmpe *stmpe, u8 reg) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_reg_read(stmpe, reg); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_reg_read); + +/** + * stmpe_reg_write() - write a single STMPE register + * @stmpe: Device to write to + * @reg: Register to write + * @val: Value to write + */ +int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_reg_write(stmpe, reg, val); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_reg_write); + +/** + * stmpe_set_bits() - set the value of a bitfield in a STMPE register + * @stmpe: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @val: Value to set + */ +int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_set_bits(stmpe, reg, mask, val); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_set_bits); + +/** + * stmpe_block_read() - read multiple STMPE registers + * @stmpe: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_block_read(stmpe, reg, length, values); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_block_read); + +/** + * stmpe_block_write() - write multiple STMPE registers + * @stmpe: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_block_write(stmpe, reg, length, values); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_block_write); + +/** + * stmpe_set_altfunc: set the alternate function for STMPE pins + * @stmpe: Device to configure + * @pins: Bitmask of pins to affect + * @block: block to enable alternate functions for + * + * @pins is assumed to have a bit set for each of the bits whose alternate + * function is to be changed, numbered according to the GPIOXY numbers. + * + * If the GPIO module is not enabled, this function automatically enables it in + * order to perform the change. + */ +int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block) +{ + struct stmpe_variant_info *variant = stmpe->variant; + u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB]; + int af_bits = variant->af_bits; + int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8); + int afperreg = 8 / af_bits; + int mask = (1 << af_bits) - 1; + u8 regs[numregs]; + int af; + int ret; + + mutex_lock(&stmpe->lock); + + ret = __stmpe_enable(stmpe, STMPE_BLOCK_GPIO); + if (ret < 0) + goto out; + + ret = __stmpe_block_read(stmpe, regaddr, numregs, regs); + if (ret < 0) + goto out; + + af = variant->get_altfunc(stmpe, block); + + while (pins) { + int pin = __ffs(pins); + int regoffset = numregs - (pin / afperreg) - 1; + int pos = (pin % afperreg) * (8 / afperreg); + + regs[regoffset] &= ~(mask << pos); + regs[regoffset] |= af << pos; + + pins &= ~(1 << pin); + } + + ret = __stmpe_block_write(stmpe, regaddr, numregs, regs); + +out: + mutex_unlock(&stmpe->lock); + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_set_altfunc); + +/* + * GPIO (all variants) + */ + +static struct resource stmpe_gpio_resources[] = { + /* Start and end filled dynamically */ + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell stmpe_gpio_cell = { + .name = "stmpe-gpio", + .resources = stmpe_gpio_resources, + .num_resources = ARRAY_SIZE(stmpe_gpio_resources), +}; + +/* + * Keypad (1601, 2401, 2403) + */ + +static struct resource stmpe_keypad_resources[] = { + { + .name = "KEYPAD", + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "KEYPAD_OVER", + .start = 1, + .end = 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell stmpe_keypad_cell = { + .name = "stmpe-keypad", + .resources = stmpe_keypad_resources, + .num_resources = ARRAY_SIZE(stmpe_keypad_resources), +}; + +/* + * Touchscreen (STMPE811) + */ + +static struct resource stmpe_ts_resources[] = { + { + .name = "TOUCH_DET", + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "FIFO_TH", + .start = 1, + .end = 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell stmpe_ts_cell = { + .name = "stmpe-ts", + .resources = stmpe_ts_resources, + .num_resources = ARRAY_SIZE(stmpe_ts_resources), +}; + +/* + * STMPE811 + */ + +static const u8 stmpe811_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE811_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE811_REG_INT_CTRL, + [STMPE_IDX_IER_LSB] = STMPE811_REG_INT_EN, + [STMPE_IDX_ISR_MSB] = STMPE811_REG_INT_STA, + [STMPE_IDX_GPMR_LSB] = STMPE811_REG_GPIO_MP_STA, + [STMPE_IDX_GPSR_LSB] = STMPE811_REG_GPIO_SET_PIN, + [STMPE_IDX_GPCR_LSB] = STMPE811_REG_GPIO_CLR_PIN, + [STMPE_IDX_GPDR_LSB] = STMPE811_REG_GPIO_DIR, + [STMPE_IDX_GPRER_LSB] = STMPE811_REG_GPIO_RE, + [STMPE_IDX_GPFER_LSB] = STMPE811_REG_GPIO_FE, + [STMPE_IDX_GPAFR_U_MSB] = STMPE811_REG_GPIO_AF, + [STMPE_IDX_IEGPIOR_LSB] = STMPE811_REG_GPIO_INT_EN, + [STMPE_IDX_ISGPIOR_MSB] = STMPE811_REG_GPIO_INT_STA, + [STMPE_IDX_GPEDR_MSB] = STMPE811_REG_GPIO_ED, +}; + +static struct stmpe_variant_block stmpe811_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE811_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_ts_cell, + .irq = STMPE811_IRQ_TOUCH_DET, + .block = STMPE_BLOCK_TOUCHSCREEN, + }, +}; + +static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE811_SYS_CTRL2_GPIO_OFF; + + if (blocks & STMPE_BLOCK_ADC) + mask |= STMPE811_SYS_CTRL2_ADC_OFF; + + if (blocks & STMPE_BLOCK_TOUCHSCREEN) + mask |= STMPE811_SYS_CTRL2_TSC_OFF; + + return __stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask, + enable ? 0 : mask); +} + +static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + /* 0 for touchscreen, 1 for GPIO */ + return block != STMPE_BLOCK_TOUCHSCREEN; +} + +static struct stmpe_variant_info stmpe811 = { + .name = "stmpe811", + .id_val = 0x0811, + .id_mask = 0xffff, + .num_gpios = 8, + .af_bits = 1, + .regs = stmpe811_regs, + .blocks = stmpe811_blocks, + .num_blocks = ARRAY_SIZE(stmpe811_blocks), + .num_irqs = STMPE811_NR_INTERNAL_IRQS, + .enable = stmpe811_enable, + .get_altfunc = stmpe811_get_altfunc, +}; + +/* + * STMPE1601 + */ + +static const u8 stmpe1601_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE1601_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE1601_REG_ICR_LSB, + [STMPE_IDX_IER_LSB] = STMPE1601_REG_IER_LSB, + [STMPE_IDX_ISR_MSB] = STMPE1601_REG_ISR_MSB, + [STMPE_IDX_GPMR_LSB] = STMPE1601_REG_GPIO_MP_LSB, + [STMPE_IDX_GPSR_LSB] = STMPE1601_REG_GPIO_SET_LSB, + [STMPE_IDX_GPCR_LSB] = STMPE1601_REG_GPIO_CLR_LSB, + [STMPE_IDX_GPDR_LSB] = STMPE1601_REG_GPIO_SET_DIR_LSB, + [STMPE_IDX_GPRER_LSB] = STMPE1601_REG_GPIO_RE_LSB, + [STMPE_IDX_GPFER_LSB] = STMPE1601_REG_GPIO_FE_LSB, + [STMPE_IDX_GPAFR_U_MSB] = STMPE1601_REG_GPIO_AF_U_MSB, + [STMPE_IDX_IEGPIOR_LSB] = STMPE1601_REG_INT_EN_GPIO_MASK_LSB, + [STMPE_IDX_ISGPIOR_MSB] = STMPE1601_REG_INT_STA_GPIO_MSB, + [STMPE_IDX_GPEDR_MSB] = STMPE1601_REG_GPIO_ED_MSB, +}; + +static struct stmpe_variant_block stmpe1601_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE24XX_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE24XX_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE1601_SYS_CTRL_ENABLE_KPC; + + return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask, + enable ? mask : 0); +} + +static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + switch (block) { + case STMPE_BLOCK_PWM: + return 2; + + case STMPE_BLOCK_KEYPAD: + return 1; + + case STMPE_BLOCK_GPIO: + default: + return 0; + } +} + +static struct stmpe_variant_info stmpe1601 = { + .name = "stmpe1601", + .id_val = 0x0210, + .id_mask = 0xfff0, /* at least 0x0210 and 0x0212 */ + .num_gpios = 16, + .af_bits = 2, + .regs = stmpe1601_regs, + .blocks = stmpe1601_blocks, + .num_blocks = ARRAY_SIZE(stmpe1601_blocks), + .num_irqs = STMPE1601_NR_INTERNAL_IRQS, + .enable = stmpe1601_enable, + .get_altfunc = stmpe1601_get_altfunc, +}; + +/* + * STMPE24XX + */ + +static const u8 stmpe24xx_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE24XX_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE24XX_REG_ICR_LSB, + [STMPE_IDX_IER_LSB] = STMPE24XX_REG_IER_LSB, + [STMPE_IDX_ISR_MSB] = STMPE24XX_REG_ISR_MSB, + [STMPE_IDX_GPMR_LSB] = STMPE24XX_REG_GPMR_LSB, + [STMPE_IDX_GPSR_LSB] = STMPE24XX_REG_GPSR_LSB, + [STMPE_IDX_GPCR_LSB] = STMPE24XX_REG_GPCR_LSB, + [STMPE_IDX_GPDR_LSB] = STMPE24XX_REG_GPDR_LSB, + [STMPE_IDX_GPRER_LSB] = STMPE24XX_REG_GPRER_LSB, + [STMPE_IDX_GPFER_LSB] = STMPE24XX_REG_GPFER_LSB, + [STMPE_IDX_GPAFR_U_MSB] = STMPE24XX_REG_GPAFR_U_MSB, + [STMPE_IDX_IEGPIOR_LSB] = STMPE24XX_REG_IEGPIOR_LSB, + [STMPE_IDX_ISGPIOR_MSB] = STMPE24XX_REG_ISGPIOR_MSB, + [STMPE_IDX_GPEDR_MSB] = STMPE24XX_REG_GPEDR_MSB, +}; + +static struct stmpe_variant_block stmpe24xx_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE24XX_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE24XX_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC; + + return __stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask, + enable ? mask : 0); +} + +static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + switch (block) { + case STMPE_BLOCK_ROTATOR: + return 2; + + case STMPE_BLOCK_KEYPAD: + return 1; + + case STMPE_BLOCK_GPIO: + default: + return 0; + } +} + +static struct stmpe_variant_info stmpe2401 = { + .name = "stmpe2401", + .id_val = 0x0101, + .id_mask = 0xffff, + .num_gpios = 24, + .af_bits = 2, + .regs = stmpe24xx_regs, + .blocks = stmpe24xx_blocks, + .num_blocks = ARRAY_SIZE(stmpe24xx_blocks), + .num_irqs = STMPE24XX_NR_INTERNAL_IRQS, + .enable = stmpe24xx_enable, + .get_altfunc = stmpe24xx_get_altfunc, +}; + +static struct stmpe_variant_info stmpe2403 = { + .name = "stmpe2403", + .id_val = 0x0120, + .id_mask = 0xffff, + .num_gpios = 24, + .af_bits = 2, + .regs = stmpe24xx_regs, + .blocks = stmpe24xx_blocks, + .num_blocks = ARRAY_SIZE(stmpe24xx_blocks), + .num_irqs = STMPE24XX_NR_INTERNAL_IRQS, + .enable = stmpe24xx_enable, + .get_altfunc = stmpe24xx_get_altfunc, +}; + +static struct stmpe_variant_info *stmpe_variant_info[] = { + [STMPE811] = &stmpe811, + [STMPE1601] = &stmpe1601, + [STMPE2401] = &stmpe2401, + [STMPE2403] = &stmpe2403, +}; + +static irqreturn_t stmpe_irq(int irq, void *data) +{ + struct stmpe *stmpe = data; + struct stmpe_variant_info *variant = stmpe->variant; + int num = DIV_ROUND_UP(variant->num_irqs, 8); + u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB]; + u8 isr[num]; + int ret; + int i; + + ret = stmpe_block_read(stmpe, israddr, num, isr); + if (ret < 0) + return IRQ_NONE; + + for (i = 0; i < num; i++) { + int bank = num - i - 1; + u8 status = isr[i]; + u8 clear; + + status &= stmpe->ier[bank]; + if (!status) + continue; + + clear = status; + while (status) { + int bit = __ffs(status); + int line = bank * 8 + bit; + + handle_nested_irq(stmpe->irq_base + line); + status &= ~(1 << bit); + } + + stmpe_reg_write(stmpe, israddr + i, clear); + } + + return IRQ_HANDLED; +} + +static void stmpe_irq_lock(unsigned int irq) +{ + struct stmpe *stmpe = get_irq_chip_data(irq); + + mutex_lock(&stmpe->irq_lock); +} + +static void stmpe_irq_sync_unlock(unsigned int irq) +{ + struct stmpe *stmpe = get_irq_chip_data(irq); + struct stmpe_variant_info *variant = stmpe->variant; + int num = DIV_ROUND_UP(variant->num_irqs, 8); + int i; + + for (i = 0; i < num; i++) { + u8 new = stmpe->ier[i]; + u8 old = stmpe->oldier[i]; + + if (new == old) + continue; + + stmpe->oldier[i] = new; + stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new); + } + + mutex_unlock(&stmpe->irq_lock); +} + +static void stmpe_irq_mask(unsigned int irq) +{ + struct stmpe *stmpe = get_irq_chip_data(irq); + int offset = irq - stmpe->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe->ier[regoffset] &= ~mask; +} + +static void stmpe_irq_unmask(unsigned int irq) +{ + struct stmpe *stmpe = get_irq_chip_data(irq); + int offset = irq - stmpe->irq_base; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe->ier[regoffset] |= mask; +} + +static struct irq_chip stmpe_irq_chip = { + .name = "stmpe", + .bus_lock = stmpe_irq_lock, + .bus_sync_unlock = stmpe_irq_sync_unlock, + .mask = stmpe_irq_mask, + .unmask = stmpe_irq_unmask, +}; + +static int __devinit stmpe_irq_init(struct stmpe *stmpe) +{ + int num_irqs = stmpe->variant->num_irqs; + int base = stmpe->irq_base; + int irq; + + for (irq = base; irq < base + num_irqs; irq++) { + set_irq_chip_data(irq, stmpe); + set_irq_chip_and_handler(irq, &stmpe_irq_chip, + handle_edge_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void stmpe_irq_remove(struct stmpe *stmpe) +{ + int num_irqs = stmpe->variant->num_irqs; + int base = stmpe->irq_base; + int irq; + + for (irq = base; irq < base + num_irqs; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + +static int __devinit stmpe_chip_init(struct stmpe *stmpe) +{ + unsigned int irq_trigger = stmpe->pdata->irq_trigger; + struct stmpe_variant_info *variant = stmpe->variant; + u8 icr = STMPE_ICR_LSB_GIM; + unsigned int id; + u8 data[2]; + int ret; + + ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID], + ARRAY_SIZE(data), data); + if (ret < 0) + return ret; + + id = (data[0] << 8) | data[1]; + if ((id & variant->id_mask) != variant->id_val) { + dev_err(stmpe->dev, "unknown chip id: %#x\n", id); + return -EINVAL; + } + + dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id); + + /* Disable all modules -- subdrivers should enable what they need. */ + ret = stmpe_disable(stmpe, ~0); + if (ret) + return ret; + + if (irq_trigger == IRQF_TRIGGER_FALLING || + irq_trigger == IRQF_TRIGGER_RISING) + icr |= STMPE_ICR_LSB_EDGE; + + if (irq_trigger == IRQF_TRIGGER_RISING || + irq_trigger == IRQF_TRIGGER_HIGH) + icr |= STMPE_ICR_LSB_HIGH; + + if (stmpe->pdata->irq_invert_polarity) + icr ^= STMPE_ICR_LSB_HIGH; + + return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr); +} + +static int __devinit stmpe_add_device(struct stmpe *stmpe, + struct mfd_cell *cell, int irq) +{ + return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1, + NULL, stmpe->irq_base + irq); +} + +static int __devinit stmpe_devices_init(struct stmpe *stmpe) +{ + struct stmpe_variant_info *variant = stmpe->variant; + unsigned int platform_blocks = stmpe->pdata->blocks; + int ret = -EINVAL; + int i; + + for (i = 0; i < variant->num_blocks; i++) { + struct stmpe_variant_block *block = &variant->blocks[i]; + + if (!(platform_blocks & block->block)) + continue; + + platform_blocks &= ~block->block; + ret = stmpe_add_device(stmpe, block->cell, block->irq); + if (ret) + return ret; + } + + if (platform_blocks) + dev_warn(stmpe->dev, + "platform wants blocks (%#x) not present on variant", + platform_blocks); + + return ret; +} + +static int __devinit stmpe_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct stmpe_platform_data *pdata = i2c->dev.platform_data; + struct stmpe *stmpe; + int ret; + + if (!pdata) + return -EINVAL; + + stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL); + if (!stmpe) + return -ENOMEM; + + mutex_init(&stmpe->irq_lock); + mutex_init(&stmpe->lock); + + stmpe->dev = &i2c->dev; + stmpe->i2c = i2c; + + stmpe->pdata = pdata; + stmpe->irq_base = pdata->irq_base; + + stmpe->partnum = id->driver_data; + stmpe->variant = stmpe_variant_info[stmpe->partnum]; + stmpe->regs = stmpe->variant->regs; + stmpe->num_gpios = stmpe->variant->num_gpios; + + i2c_set_clientdata(i2c, stmpe); + + ret = stmpe_chip_init(stmpe); + if (ret) + goto out_free; + + ret = stmpe_irq_init(stmpe); + if (ret) + goto out_free; + + ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq, + pdata->irq_trigger | IRQF_ONESHOT, + "stmpe", stmpe); + if (ret) { + dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret); + goto out_removeirq; + } + + ret = stmpe_devices_init(stmpe); + if (ret) { + dev_err(stmpe->dev, "failed to add children\n"); + goto out_removedevs; + } + + return 0; + +out_removedevs: + mfd_remove_devices(stmpe->dev); + free_irq(stmpe->i2c->irq, stmpe); +out_removeirq: + stmpe_irq_remove(stmpe); +out_free: + kfree(stmpe); + return ret; +} + +static int __devexit stmpe_remove(struct i2c_client *client) +{ + struct stmpe *stmpe = i2c_get_clientdata(client); + + mfd_remove_devices(stmpe->dev); + + free_irq(stmpe->i2c->irq, stmpe); + stmpe_irq_remove(stmpe); + + kfree(stmpe); + + return 0; +} + +static const struct i2c_device_id stmpe_id[] = { + { "stmpe811", STMPE811 }, + { "stmpe1601", STMPE1601 }, + { "stmpe2401", STMPE2401 }, + { "stmpe2403", STMPE2403 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stmpe_id); + +static struct i2c_driver stmpe_driver = { + .driver.name = "stmpe", + .driver.owner = THIS_MODULE, + .probe = stmpe_probe, + .remove = __devexit_p(stmpe_remove), + .id_table = stmpe_id, +}; + +static int __init stmpe_init(void) +{ + return i2c_add_driver(&stmpe_driver); +} +subsys_initcall(stmpe_init); + +static void __exit stmpe_exit(void) +{ + i2c_del_driver(&stmpe_driver); +} +module_exit(stmpe_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMPE MFD core driver"); +MODULE_AUTHOR("Rabin Vincent "); diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h new file mode 100644 index 000000000000..991f0ecbeb34 --- /dev/null +++ b/drivers/mfd/stmpe.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent for ST-Ericsson + */ + +#ifndef __STMPE_H +#define __STMPE_H + +#ifdef STMPE_DUMP_BYTES +static inline void stmpe_dump_bytes(const char *str, const void *buf, + size_t len) +{ + print_hex_dump_bytes(str, DUMP_PREFIX_OFFSET, buf, len); +} +#else +static inline void stmpe_dump_bytes(const char *str, const void *buf, + size_t len) +{ +} +#endif + +/** + * struct stmpe_variant_block - information about block + * @cell: base mfd cell + * @irq: interrupt number to be added to each IORESOURCE_IRQ + * in the cell + * @block: block id; used for identification with platform data and for + * enable and altfunc callbacks + */ +struct stmpe_variant_block { + struct mfd_cell *cell; + int irq; + enum stmpe_block block; +}; + +/** + * struct stmpe_variant_info - variant-specific information + * @name: part name + * @id_val: content of CHIPID register + * @id_mask: bits valid in CHIPID register for comparison with id_val + * @num_gpios: number of GPIOS + * @af_bits: number of bits used to specify the alternate function + * @blocks: list of blocks present on this device + * @num_blocks: number of blocks present on this device + * @num_irqs: number of internal IRQs available on this device + * @enable: callback to enable the specified blocks. + * Called with the I/O lock held. + * @get_altfunc: callback to get the alternate function number for the + * specific block + */ +struct stmpe_variant_info { + const char *name; + u16 id_val; + u16 id_mask; + int num_gpios; + int af_bits; + const u8 *regs; + struct stmpe_variant_block *blocks; + int num_blocks; + int num_irqs; + int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable); + int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block); +}; + +#define STMPE_ICR_LSB_HIGH (1 << 2) +#define STMPE_ICR_LSB_EDGE (1 << 1) +#define STMPE_ICR_LSB_GIM (1 << 0) + +/* + * STMPE811 + */ + +#define STMPE811_IRQ_TOUCH_DET 0 +#define STMPE811_IRQ_FIFO_TH 1 +#define STMPE811_IRQ_FIFO_OFLOW 2 +#define STMPE811_IRQ_FIFO_FULL 3 +#define STMPE811_IRQ_FIFO_EMPTY 4 +#define STMPE811_IRQ_TEMP_SENS 5 +#define STMPE811_IRQ_ADC 6 +#define STMPE811_IRQ_GPIOC 7 +#define STMPE811_NR_INTERNAL_IRQS 8 + +#define STMPE811_REG_CHIP_ID 0x00 +#define STMPE811_REG_SYS_CTRL2 0x04 +#define STMPE811_REG_INT_CTRL 0x09 +#define STMPE811_REG_INT_EN 0x0A +#define STMPE811_REG_INT_STA 0x0B +#define STMPE811_REG_GPIO_INT_EN 0x0C +#define STMPE811_REG_GPIO_INT_STA 0x0D +#define STMPE811_REG_GPIO_SET_PIN 0x10 +#define STMPE811_REG_GPIO_CLR_PIN 0x11 +#define STMPE811_REG_GPIO_MP_STA 0x12 +#define STMPE811_REG_GPIO_DIR 0x13 +#define STMPE811_REG_GPIO_ED 0x14 +#define STMPE811_REG_GPIO_RE 0x15 +#define STMPE811_REG_GPIO_FE 0x16 +#define STMPE811_REG_GPIO_AF 0x17 + +#define STMPE811_SYS_CTRL2_ADC_OFF (1 << 0) +#define STMPE811_SYS_CTRL2_TSC_OFF (1 << 1) +#define STMPE811_SYS_CTRL2_GPIO_OFF (1 << 2) +#define STMPE811_SYS_CTRL2_TS_OFF (1 << 3) + +/* + * STMPE1601 + */ + +#define STMPE1601_IRQ_GPIOC 8 +#define STMPE1601_IRQ_PWM3 7 +#define STMPE1601_IRQ_PWM2 6 +#define STMPE1601_IRQ_PWM1 5 +#define STMPE1601_IRQ_PWM0 4 +#define STMPE1601_IRQ_KEYPAD_OVER 2 +#define STMPE1601_IRQ_KEYPAD 1 +#define STMPE1601_IRQ_WAKEUP 0 +#define STMPE1601_NR_INTERNAL_IRQS 9 + +#define STMPE1601_REG_SYS_CTRL 0x02 +#define STMPE1601_REG_ICR_LSB 0x11 +#define STMPE1601_REG_IER_LSB 0x13 +#define STMPE1601_REG_ISR_MSB 0x14 +#define STMPE1601_REG_CHIP_ID 0x80 +#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB 0x17 +#define STMPE1601_REG_INT_STA_GPIO_MSB 0x18 +#define STMPE1601_REG_GPIO_MP_LSB 0x87 +#define STMPE1601_REG_GPIO_SET_LSB 0x83 +#define STMPE1601_REG_GPIO_CLR_LSB 0x85 +#define STMPE1601_REG_GPIO_SET_DIR_LSB 0x89 +#define STMPE1601_REG_GPIO_ED_MSB 0x8A +#define STMPE1601_REG_GPIO_RE_LSB 0x8D +#define STMPE1601_REG_GPIO_FE_LSB 0x8F +#define STMPE1601_REG_GPIO_AF_U_MSB 0x92 + +#define STMPE1601_SYS_CTRL_ENABLE_GPIO (1 << 3) +#define STMPE1601_SYS_CTRL_ENABLE_KPC (1 << 1) +#define STMPE1601_SYSCON_ENABLE_SPWM (1 << 0) + +/* + * STMPE24xx + */ + +#define STMPE24XX_IRQ_GPIOC 8 +#define STMPE24XX_IRQ_PWM2 7 +#define STMPE24XX_IRQ_PWM1 6 +#define STMPE24XX_IRQ_PWM0 5 +#define STMPE24XX_IRQ_ROT_OVER 4 +#define STMPE24XX_IRQ_ROT 3 +#define STMPE24XX_IRQ_KEYPAD_OVER 2 +#define STMPE24XX_IRQ_KEYPAD 1 +#define STMPE24XX_IRQ_WAKEUP 0 +#define STMPE24XX_NR_INTERNAL_IRQS 9 + +#define STMPE24XX_REG_SYS_CTRL 0x02 +#define STMPE24XX_REG_ICR_LSB 0x11 +#define STMPE24XX_REG_IER_LSB 0x13 +#define STMPE24XX_REG_ISR_MSB 0x14 +#define STMPE24XX_REG_CHIP_ID 0x80 +#define STMPE24XX_REG_IEGPIOR_LSB 0x18 +#define STMPE24XX_REG_ISGPIOR_MSB 0x19 +#define STMPE24XX_REG_GPMR_LSB 0xA5 +#define STMPE24XX_REG_GPSR_LSB 0x85 +#define STMPE24XX_REG_GPCR_LSB 0x88 +#define STMPE24XX_REG_GPDR_LSB 0x8B +#define STMPE24XX_REG_GPEDR_MSB 0x8C +#define STMPE24XX_REG_GPRER_LSB 0x91 +#define STMPE24XX_REG_GPFER_LSB 0x94 +#define STMPE24XX_REG_GPAFR_U_MSB 0x9B + +#define STMPE24XX_SYS_CTRL_ENABLE_GPIO (1 << 3) +#define STMPE24XX_SYSCON_ENABLE_PWM (1 << 2) +#define STMPE24XX_SYS_CTRL_ENABLE_KPC (1 << 1) +#define STMPE24XX_SYSCON_ENABLE_ROT (1 << 0) + +#endif -- cgit v1.2.3 From 91f4debf5e2df904e7fade530bd1a6d182efd72c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 12 Jul 2010 03:48:08 +0200 Subject: mfd: Add JZ4740 ADC driver This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to demultiplex IRQs and synchronize access to shared registers between the battery, hwmon and (future) touchscreen driver. Signed-off-by: Lars-Peter Clausen Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 1 + drivers/mfd/jz4740-adc.c | 384 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 drivers/mfd/jz4740-adc.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8f5145b4b56f..d59334f34bf6 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -514,6 +514,14 @@ config MFD_JANZ_CMODIO host many different types of MODULbus daughterboards, including CAN and GPIO controllers. +config MFD_JZ4740_ADC + tristate "Support for the JZ4740 SoC ADC core" + select MFD_CORE + depends on MACH_JZ4740 + help + Say yes here if you want support for the ADC unit in the JZ4740 SoC. + This driver is necessary for jz4740-battery and jz4740-hwmon driver. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4410747f7b5b..1f7077966994 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -72,3 +72,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o +obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c new file mode 100644 index 000000000000..7a844aef5dcf --- /dev/null +++ b/drivers/mfd/jz4740-adc.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen + * JZ4740 SoC ADC driver + * + * 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. + * + * 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. + * + * This driver synchronizes access to the JZ4740 ADC core between the + * JZ4740 battery and hwmon drivers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +#define JZ_REG_ADC_ENABLE 0x00 +#define JZ_REG_ADC_CFG 0x04 +#define JZ_REG_ADC_CTRL 0x08 +#define JZ_REG_ADC_STATUS 0x0c + +#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10 +#define JZ_REG_ADC_BATTERY_BASE 0x1c +#define JZ_REG_ADC_HWMON_BASE 0x20 + +#define JZ_ADC_ENABLE_TOUCH BIT(2) +#define JZ_ADC_ENABLE_BATTERY BIT(1) +#define JZ_ADC_ENABLE_ADCIN BIT(0) + +enum { + JZ_ADC_IRQ_ADCIN = 0, + JZ_ADC_IRQ_BATTERY, + JZ_ADC_IRQ_TOUCH, + JZ_ADC_IRQ_PENUP, + JZ_ADC_IRQ_PENDOWN, +}; + +struct jz4740_adc { + struct resource *mem; + void __iomem *base; + + int irq; + int irq_base; + + struct clk *clk; + atomic_t clk_ref; + + spinlock_t lock; +}; + +static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq, + bool masked) +{ + unsigned long flags; + uint8_t val; + + irq -= adc->irq_base; + + spin_lock_irqsave(&adc->lock, flags); + + val = readb(adc->base + JZ_REG_ADC_CTRL); + if (masked) + val |= BIT(irq); + else + val &= ~BIT(irq); + writeb(val, adc->base + JZ_REG_ADC_CTRL); + + spin_unlock_irqrestore(&adc->lock, flags); +} + +static void jz4740_adc_irq_mask(unsigned int irq) +{ + struct jz4740_adc *adc = get_irq_chip_data(irq); + jz4740_adc_irq_set_masked(adc, irq, true); +} + +static void jz4740_adc_irq_unmask(unsigned int irq) +{ + struct jz4740_adc *adc = get_irq_chip_data(irq); + jz4740_adc_irq_set_masked(adc, irq, false); +} + +static void jz4740_adc_irq_ack(unsigned int irq) +{ + struct jz4740_adc *adc = get_irq_chip_data(irq); + + irq -= adc->irq_base; + writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS); +} + +static struct irq_chip jz4740_adc_irq_chip = { + .name = "jz4740-adc", + .mask = jz4740_adc_irq_mask, + .unmask = jz4740_adc_irq_unmask, + .ack = jz4740_adc_irq_ack, +}; + +static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc) +{ + struct jz4740_adc *adc = get_irq_desc_data(desc); + uint8_t status; + unsigned int i; + + status = readb(adc->base + JZ_REG_ADC_STATUS); + + for (i = 0; i < 5; ++i) { + if (status & BIT(i)) + generic_handle_irq(adc->irq_base + i); + } +} + + +/* Refcounting for the ADC clock is done in here instead of in the clock + * framework, because it is the only clock which is shared between multiple + * devices and thus is the only clock which needs refcounting */ +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc) +{ + if (atomic_inc_return(&adc->clk_ref) == 1) + clk_enable(adc->clk); +} + +static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc) +{ + if (atomic_dec_return(&adc->clk_ref) == 0) + clk_disable(adc->clk); +} + +static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine, + bool enabled) +{ + unsigned long flags; + uint8_t val; + + spin_lock_irqsave(&adc->lock, flags); + + val = readb(adc->base + JZ_REG_ADC_ENABLE); + if (enabled) + val |= BIT(engine); + else + val &= BIT(engine); + writeb(val, adc->base + JZ_REG_ADC_ENABLE); + + spin_unlock_irqrestore(&adc->lock, flags); +} + +static int jz4740_adc_cell_enable(struct platform_device *pdev) +{ + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); + + jz4740_adc_clk_enable(adc); + jz4740_adc_set_enabled(adc, pdev->id, true); + + return 0; +} + +static int jz4740_adc_cell_disable(struct platform_device *pdev) +{ + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); + + jz4740_adc_set_enabled(adc, pdev->id, false); + jz4740_adc_clk_disable(adc); + + return 0; +} + +int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val) +{ + struct jz4740_adc *adc = dev_get_drvdata(dev); + unsigned long flags; + uint32_t cfg; + + if (!adc) + return -ENODEV; + + spin_lock_irqsave(&adc->lock, flags); + + cfg = readl(adc->base + JZ_REG_ADC_CFG); + + cfg &= ~mask; + cfg |= val; + + writel(cfg, adc->base + JZ_REG_ADC_CFG); + + spin_unlock_irqrestore(&adc->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(jz4740_adc_set_config); + +static struct resource jz4740_hwmon_resources[] = { + { + .start = JZ_ADC_IRQ_ADCIN, + .flags = IORESOURCE_IRQ, + }, + { + .start = JZ_REG_ADC_HWMON_BASE, + .end = JZ_REG_ADC_HWMON_BASE + 3, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource jz4740_battery_resources[] = { + { + .start = JZ_ADC_IRQ_BATTERY, + .flags = IORESOURCE_IRQ, + }, + { + .start = JZ_REG_ADC_BATTERY_BASE, + .end = JZ_REG_ADC_BATTERY_BASE + 3, + .flags = IORESOURCE_MEM, + }, +}; + +const struct mfd_cell jz4740_adc_cells[] = { + { + .id = 0, + .name = "jz4740-hwmon", + .num_resources = ARRAY_SIZE(jz4740_hwmon_resources), + .resources = jz4740_hwmon_resources, + .platform_data = (void *)&jz4740_adc_cells[0], + .data_size = sizeof(struct mfd_cell), + + .enable = jz4740_adc_cell_enable, + .disable = jz4740_adc_cell_disable, + }, + { + .id = 1, + .name = "jz4740-battery", + .num_resources = ARRAY_SIZE(jz4740_battery_resources), + .resources = jz4740_battery_resources, + .platform_data = (void *)&jz4740_adc_cells[1], + .data_size = sizeof(struct mfd_cell), + + .enable = jz4740_adc_cell_enable, + .disable = jz4740_adc_cell_disable, + }, +}; + +static int __devinit jz4740_adc_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_adc *adc; + struct resource *mem_base; + int irq; + + adc = kmalloc(sizeof(*adc), GFP_KERNEL); + + adc->irq = platform_get_irq(pdev, 0); + if (adc->irq < 0) { + ret = adc->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + adc->irq_base = platform_get_irq(pdev, 1); + if (adc->irq_base < 0) { + ret = adc->irq_base; + dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret); + goto err_free; + } + + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_base) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); + goto err_free; + } + + /* Only request the shared registers for the MFD driver */ + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS, + pdev->name); + if (!adc->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + goto err_free; + } + + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem)); + if (!adc->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + goto err_release_mem_region; + } + + adc->clk = clk_get(&pdev->dev, "adc"); + if (IS_ERR(adc->clk)) { + ret = PTR_ERR(adc->clk); + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); + goto err_iounmap; + } + + spin_lock_init(&adc->lock); + atomic_set(&adc->clk_ref, 0); + + platform_set_drvdata(pdev, adc); + + for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) { + set_irq_chip_data(irq, adc); + set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip, + handle_level_irq); + } + + set_irq_data(adc->irq, adc); + set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux); + + writeb(0x00, adc->base + JZ_REG_ADC_ENABLE); + writeb(0xff, adc->base + JZ_REG_ADC_CTRL); + + return mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, + ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base); + +err_iounmap: + platform_set_drvdata(pdev, NULL); + iounmap(adc->base); +err_release_mem_region: + release_mem_region(adc->mem->start, resource_size(adc->mem)); +err_free: + kfree(adc); + + return ret; +} + +static int __devexit jz4740_adc_remove(struct platform_device *pdev) +{ + struct jz4740_adc *adc = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + + set_irq_data(adc->irq, NULL); + set_irq_chained_handler(adc->irq, NULL); + + iounmap(adc->base); + release_mem_region(adc->mem->start, resource_size(adc->mem)); + + clk_put(adc->clk); + + platform_set_drvdata(pdev, NULL); + + kfree(adc); + + return 0; +} + +struct platform_driver jz4740_adc_driver = { + .probe = jz4740_adc_probe, + .remove = __devexit_p(jz4740_adc_remove), + .driver = { + .name = "jz4740-adc", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_adc_init(void) +{ + return platform_driver_register(&jz4740_adc_driver); +} +module_init(jz4740_adc_init); + +static void __exit jz4740_adc_exit(void) +{ + platform_driver_unregister(&jz4740_adc_driver); +} +module_exit(jz4740_adc_exit); + +MODULE_DESCRIPTION("JZ4740 SoC ADC driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-adc"); -- cgit v1.2.3 From 31fc03df5df364100a41134783c02490fbd25745 Mon Sep 17 00:00:00 2001 From: Hemanth V Date: Wed, 14 Jul 2010 12:04:56 +0200 Subject: mfd: Add support for TWL6030 PWM TWL6030 supports PWM (Pulse Width Modulator) which is used to control charging LED. PWM allows for controlling brightness. This patch implements the APIs required by leds-pwm driver. Signed-off-by: Hemanth V Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 +++ drivers/mfd/Makefile | 1 + drivers/mfd/twl6030-pwm.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 drivers/mfd/twl6030-pwm.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d59334f34bf6..23a891f396e4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -186,6 +186,15 @@ config TWL4030_CODEC select MFD_CORE default n +config TWL6030_PWM + tristate "TWL6030 PWM (Pulse Width Modulator) Support" + depends on TWL4030_CORE + select HAVE_PWM + default n + help + Say yes here if you want support for TWL6030 PWM. + This is used to control charging LED brightness. + config MFD_STMPE bool "Support STMicroelectronics STMPE" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 1f7077966994..cc7cce0976f3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o +obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o obj-$(CONFIG_MFD_MC13783) += mc13783-core.o diff --git a/drivers/mfd/twl6030-pwm.c b/drivers/mfd/twl6030-pwm.c new file mode 100644 index 000000000000..5d25bdc78424 --- /dev/null +++ b/drivers/mfd/twl6030-pwm.c @@ -0,0 +1,163 @@ +/* + * twl6030_pwm.c + * Driver for PHOENIX (TWL6030) Pulse Width Modulator + * + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V + * + * 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. + * + * 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, see . + */ + +#include +#include +#include +#include + +#define LED_PWM_CTRL1 0xF4 +#define LED_PWM_CTRL2 0xF5 + +/* Max value for CTRL1 register */ +#define PWM_CTRL1_MAX 255 + +/* Pull down disable */ +#define PWM_CTRL2_DIS_PD (1 << 6) + +/* Current control 2.5 milli Amps */ +#define PWM_CTRL2_CURR_02 (2 << 4) + +/* LED supply source */ +#define PWM_CTRL2_SRC_VAC (1 << 2) + +/* LED modes */ +#define PWM_CTRL2_MODE_HW (0 << 0) +#define PWM_CTRL2_MODE_SW (1 << 0) +#define PWM_CTRL2_MODE_DIS (2 << 0) + +#define PWM_CTRL2_MODE_MASK 0x3 + +struct pwm_device { + const char *label; + unsigned int pwm_id; +}; + +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + u8 duty_cycle; + int ret; + + if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + duty_cycle = (duty_ns * PWM_CTRL1_MAX) / period_ns; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, duty_cycle, LED_PWM_CTRL1); + + if (ret < 0) { + pr_err("%s: Failed to configure PWM, Error %d\n", + pwm->label, ret); + return ret; + } + return 0; +} +EXPORT_SYMBOL(pwm_config); + +int pwm_enable(struct pwm_device *pwm) +{ + u8 val; + int ret; + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2); + if (ret < 0) { + pr_err("%s: Failed to enable PWM, Error %d\n", pwm->label, ret); + return ret; + } + + /* Change mode to software control */ + val &= ~PWM_CTRL2_MODE_MASK; + val |= PWM_CTRL2_MODE_SW; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2); + if (ret < 0) { + pr_err("%s: Failed to enable PWM, Error %d\n", pwm->label, ret); + return ret; + } + + twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2); + return 0; +} +EXPORT_SYMBOL(pwm_enable); + +void pwm_disable(struct pwm_device *pwm) +{ + u8 val; + int ret; + + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, LED_PWM_CTRL2); + if (ret < 0) { + pr_err("%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return; + } + + val &= ~PWM_CTRL2_MODE_MASK; + val |= PWM_CTRL2_MODE_HW; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2); + if (ret < 0) { + pr_err("%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return; + } + return; +} +EXPORT_SYMBOL(pwm_disable); + +struct pwm_device *pwm_request(int pwm_id, const char *label) +{ + u8 val; + int ret; + struct pwm_device *pwm; + + pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); + if (pwm == NULL) { + pr_err("%s: failed to allocate memory\n", label); + return NULL; + } + + pwm->label = label; + pwm->pwm_id = pwm_id; + + /* Configure PWM */ + val = PWM_CTRL2_DIS_PD | PWM_CTRL2_CURR_02 | PWM_CTRL2_SRC_VAC | + PWM_CTRL2_MODE_HW; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, LED_PWM_CTRL2); + + if (ret < 0) { + pr_err("%s: Failed to configure PWM, Error %d\n", + pwm->label, ret); + + kfree(pwm); + return NULL; + } + + return pwm; +} +EXPORT_SYMBOL(pwm_request); + +void pwm_free(struct pwm_device *pwm) +{ + pwm_disable(pwm); + kfree(pwm); +} +EXPORT_SYMBOL(pwm_free); -- cgit v1.2.3 From 5981f4e65cb455a820b3d07b8e4bac506233f3ea Mon Sep 17 00:00:00 2001 From: Sundar R Iyer Date: Wed, 21 Jul 2010 11:41:07 +0530 Subject: mfd: Add stmpe auto sleep feature Some STMPE devices support entering sleep mode automatically on a specified timeout of inactivity on the I2C bus with the host system. Acked-by: Linus Walleij Acked-by: Rabin Vincent Signed-off-by: Sundar R Iyer Signed-off-by: Samuel Ortiz --- drivers/mfd/stmpe.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/stmpe.h | 7 ++++++ 2 files changed, 77 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index a7f3099fdcfd..0754c5e91995 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -455,6 +455,67 @@ static struct stmpe_variant_block stmpe1601_blocks[] = { }, }; +/* supported autosleep timeout delay (in msecs) */ +static const int stmpe_autosleep_delay[] = { + 4, 16, 32, 64, 128, 256, 512, 1024, +}; + +static int stmpe_round_timeout(int timeout) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stmpe_autosleep_delay); i++) { + if (stmpe_autosleep_delay[i] >= timeout) + return i; + } + + /* + * requests for delays longer than supported should not return the + * longest supported delay + */ + return -EINVAL; +} + +static int stmpe_autosleep(struct stmpe *stmpe, int autosleep_timeout) +{ + int ret; + + if (!stmpe->variant->enable_autosleep) + return -ENOSYS; + + mutex_lock(&stmpe->lock); + ret = stmpe->variant->enable_autosleep(stmpe, autosleep_timeout); + mutex_unlock(&stmpe->lock); + + return ret; +} + +/* + * Both stmpe 1601/2403 support same layout for autosleep + */ +static int stmpe1601_autosleep(struct stmpe *stmpe, + int autosleep_timeout) +{ + int ret, timeout; + + /* choose the best available timeout */ + timeout = stmpe_round_timeout(autosleep_timeout); + if (timeout < 0) { + dev_err(stmpe->dev, "invalid timeout\n"); + return timeout; + } + + ret = __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + STMPE1601_AUTOSLEEP_TIMEOUT_MASK, + timeout); + if (ret < 0) + return ret; + + return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + STPME1601_AUTOSLEEP_ENABLE, + STPME1601_AUTOSLEEP_ENABLE); +} + static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks, bool enable) { @@ -497,6 +558,7 @@ static struct stmpe_variant_info stmpe1601 = { .num_irqs = STMPE1601_NR_INTERNAL_IRQS, .enable = stmpe1601_enable, .get_altfunc = stmpe1601_get_altfunc, + .enable_autosleep = stmpe1601_autosleep, }; /* @@ -589,6 +651,7 @@ static struct stmpe_variant_info stmpe2403 = { .num_irqs = STMPE24XX_NR_INTERNAL_IRQS, .enable = stmpe24xx_enable, .get_altfunc = stmpe24xx_get_altfunc, + .enable_autosleep = stmpe1601_autosleep, /* same as stmpe1601 */ }; static struct stmpe_variant_info *stmpe_variant_info[] = { @@ -731,6 +794,7 @@ static void stmpe_irq_remove(struct stmpe *stmpe) static int __devinit stmpe_chip_init(struct stmpe *stmpe) { unsigned int irq_trigger = stmpe->pdata->irq_trigger; + int autosleep_timeout = stmpe->pdata->autosleep_timeout; struct stmpe_variant_info *variant = stmpe->variant; u8 icr = STMPE_ICR_LSB_GIM; unsigned int id; @@ -766,6 +830,12 @@ static int __devinit stmpe_chip_init(struct stmpe *stmpe) if (stmpe->pdata->irq_invert_polarity) icr ^= STMPE_ICR_LSB_HIGH; + if (stmpe->pdata->autosleep) { + ret = stmpe_autosleep(stmpe, autosleep_timeout); + if (ret) + return ret; + } + return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr); } diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index 991f0ecbeb34..0dbdc4e8cd77 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -49,6 +49,7 @@ struct stmpe_variant_block { * Called with the I/O lock held. * @get_altfunc: callback to get the alternate function number for the * specific block + * @enable_autosleep: callback to configure autosleep with specified timeout */ struct stmpe_variant_info { const char *name; @@ -62,6 +63,7 @@ struct stmpe_variant_info { int num_irqs; int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable); int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block); + int (*enable_autosleep)(struct stmpe *stmpe, int autosleep_timeout); }; #define STMPE_ICR_LSB_HIGH (1 << 2) @@ -118,6 +120,7 @@ struct stmpe_variant_info { #define STMPE1601_NR_INTERNAL_IRQS 9 #define STMPE1601_REG_SYS_CTRL 0x02 +#define STMPE1601_REG_SYS_CTRL2 0x03 #define STMPE1601_REG_ICR_LSB 0x11 #define STMPE1601_REG_IER_LSB 0x13 #define STMPE1601_REG_ISR_MSB 0x14 @@ -137,6 +140,10 @@ struct stmpe_variant_info { #define STMPE1601_SYS_CTRL_ENABLE_KPC (1 << 1) #define STMPE1601_SYSCON_ENABLE_SPWM (1 << 0) +/* The 1601/2403 share the same masks */ +#define STMPE1601_AUTOSLEEP_TIMEOUT_MASK (0x7) +#define STPME1601_AUTOSLEEP_ENABLE (1 << 3) + /* * STMPE24xx */ -- cgit v1.2.3 From 889135210b794df9cea4b1e94dff78c264edc5e7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 21 Jul 2010 14:23:37 +0100 Subject: mfd: Add WM8321 support The WM8321 is a PMIC for low power, high performance applications. From a software point of view the device is identical to the WM8320, all the differences between the two devices are visible only in hardware. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 1a968f34d679..cb94e7e1e6ea 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -95,6 +95,7 @@ enum wm831x_parent { WM8311 = 0x8311, WM8312 = 0x8312, WM8320 = 0x8320, + WM8321 = 0x8321, }; static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg) @@ -1533,6 +1534,12 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) dev_info(wm831x->dev, "WM8320 revision %c\n", 'A' + rev); break; + case WM8321: + parent = WM8321; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8321 revision %c\n", 'A' + rev); + break; + default: dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret); ret = -EINVAL; @@ -1607,6 +1614,12 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) NULL, 0); break; + case WM8321: + ret = mfd_add_devices(wm831x->dev, -1, + wm8320_devs, ARRAY_SIZE(wm8320_devs), + NULL, 0); + break; + default: /* If this happens the bus probe function is buggy */ BUG(); @@ -1779,6 +1792,7 @@ static const struct i2c_device_id wm831x_i2c_id[] = { { "wm8311", WM8311 }, { "wm8312", WM8312 }, { "wm8320", WM8320 }, + { "wm8321", WM8321 }, { } }; MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id); -- cgit v1.2.3 From 48736c80e5d0c7c9db97bd6643a979a4ab11f25b Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Thu, 29 Jul 2010 16:28:25 +0800 Subject: mfd: Fix jz4740-adc resource reclaim in probe error path If mfd_add_devices() fail, we need to relese allocated resources. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/jz4740-adc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c index 7a844aef5dcf..b30e44ac4d1c 100644 --- a/drivers/mfd/jz4740-adc.c +++ b/drivers/mfd/jz4740-adc.c @@ -322,9 +322,15 @@ static int __devinit jz4740_adc_probe(struct platform_device *pdev) writeb(0x00, adc->base + JZ_REG_ADC_ENABLE); writeb(0xff, adc->base + JZ_REG_ADC_CTRL); - return mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, + ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base); + if (ret < 0) + goto err_clk_put; + return 0; + +err_clk_put: + clk_put(adc->clk); err_iounmap: platform_set_drvdata(pdev, NULL); iounmap(adc->base); -- cgit v1.2.3 From 789133b7b4dd4915550733af5dc005ca89121c73 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 2 Aug 2010 09:02:02 +0800 Subject: mfd: Check jz4740-adc kmalloc() result If kmalloc() fails exit with -ENOMEM. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/jz4740-adc.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c index b30e44ac4d1c..3ad492cb6c41 100644 --- a/drivers/mfd/jz4740-adc.c +++ b/drivers/mfd/jz4740-adc.c @@ -260,6 +260,10 @@ static int __devinit jz4740_adc_probe(struct platform_device *pdev) int irq; adc = kmalloc(sizeof(*adc), GFP_KERNEL); + if (!adc) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } adc->irq = platform_get_irq(pdev, 0); if (adc->irq < 0) { -- cgit v1.2.3 From 3b16bb539c558cd523ea380653d4bf24a8c9e833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 2 Aug 2010 11:14:17 +0200 Subject: mfd: New mc13783 function exposing flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed for the mc13783-adc driver to decide if a touch screen is connected. If so some channels are not available as generic hwmon inputs. Signed-off-by: Uwe Kleine-König Signed-off-by: Samuel Ortiz --- drivers/mfd/mc13783-core.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c index fecf38a4f025..b0778dcca917 100644 --- a/drivers/mfd/mc13783-core.c +++ b/drivers/mfd/mc13783-core.c @@ -226,6 +226,12 @@ int mc13783_reg_rmw(struct mc13783 *mc13783, unsigned int offset, } EXPORT_SYMBOL(mc13783_reg_rmw); +int mc13783_get_flags(struct mc13783 *mc13783) +{ + return mc13783->flags; +} +EXPORT_SYMBOL(mc13783_get_flags); + int mc13783_irq_mask(struct mc13783 *mc13783, int irq) { int ret; -- cgit v1.2.3 From b6e6d54cab7633dd2216ede77ccd00cdaebd77ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 2 Aug 2010 15:48:04 +0200 Subject: mfd: Get rid of now unused mc13783 private header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds all remaining definitions that are used by the core driver to the .c file. Signed-off-by: Uwe Kleine-König Signed-off-by: Samuel Ortiz --- drivers/mfd/mc13783-core.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c index b0778dcca917..6df34989c1f6 100644 --- a/drivers/mfd/mc13783-core.c +++ b/drivers/mfd/mc13783-core.c @@ -11,9 +11,31 @@ */ #include #include +#include +#include +#include #include #include -#include +#include + +struct mc13783 { + struct spi_device *spidev; + struct mutex lock; + int irq; + int flags; + + irq_handler_t irqhandler[MC13783_NUM_IRQ]; + void *irqdata[MC13783_NUM_IRQ]; + + /* XXX these should go as platformdata to the regulator subdevice */ + struct mc13783_regulator_init_data *regulators; + int num_regulators; +}; + +#define MC13783_REG_REVISION 7 +#define MC13783_REG_ADC_0 43 +#define MC13783_REG_ADC_1 44 +#define MC13783_REG_ADC_2 45 #define MC13783_IRQSTAT0 0 #define MC13783_IRQSTAT0_ADCDONEI (1 << 0) -- cgit v1.2.3 From fa6e4b181c57ae8b18758840b8a8545b3bb1f1d9 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 3 Aug 2010 11:10:41 +0800 Subject: mfd: Fix wrong goto labels for tc6393xb error handling This patch corrects the error handling path. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6393xb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index fcf9068810fb..ef6c42c8917a 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -732,9 +732,9 @@ err_gpio_add: if (tc6393xb->gpio.base != -1) temp = gpiochip_remove(&tc6393xb->gpio); tcpd->disable(dev); -err_clk_enable: - clk_disable(tc6393xb->clk); err_enable: + clk_disable(tc6393xb->clk); +err_clk_enable: iounmap(tc6393xb->scr); err_ioremap: release_resource(&tc6393xb->rscr); -- cgit v1.2.3 From 08b877b80f286e86a0034b460ac15b09f67da8a7 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 3 Aug 2010 13:44:00 +0800 Subject: mfd: Fix tc6387xb resource reclaim This patch includes below fixes: 1. add a missing iounmap in tc6387xb_probe() error path 2. fix resource reclaim in tc6387xb_remove() Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6387xb.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index 517f9bcdeaac..1bc71da5278c 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -201,6 +201,7 @@ static int tc6387xb_probe(struct platform_device *dev) if (!ret) return 0; + iounmap(tc6387xb->scr); err_ioremap: release_resource(&tc6387xb->rscr); err_resource: @@ -213,12 +214,15 @@ err_no_irq: static int tc6387xb_remove(struct platform_device *dev) { - struct clk *clk32k = platform_get_drvdata(dev); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); mfd_remove_devices(&dev->dev); - clk_disable(clk32k); - clk_put(clk32k); + iounmap(tc6387xb->scr); + release_resource(&tc6387xb->rscr); + clk_disable(tc6387xb->clk32k); + clk_put(tc6387xb->clk32k); platform_set_drvdata(dev, NULL); + kfree(tc6387xb); return 0; } -- cgit v1.2.3 From 288129f35aae3987b8afd308e63274a270532b09 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 3 Aug 2010 13:45:45 +0800 Subject: mfd: Annotate tc6387xb probe/remove routines with __devinit/__devexit Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6387xb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index 1bc71da5278c..6315f63f017d 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -137,7 +137,7 @@ static struct mfd_cell tc6387xb_cells[] = { }, }; -static int tc6387xb_probe(struct platform_device *dev) +static int __devinit tc6387xb_probe(struct platform_device *dev) { struct tc6387xb_platform_data *pdata = dev->dev.platform_data; struct resource *iomem, *rscr; @@ -212,7 +212,7 @@ err_no_irq: return ret; } -static int tc6387xb_remove(struct platform_device *dev) +static int __devexit tc6387xb_remove(struct platform_device *dev) { struct tc6387xb *tc6387xb = platform_get_drvdata(dev); @@ -233,7 +233,7 @@ static struct platform_driver tc6387xb_platform_driver = { .name = "tc6387xb", }, .probe = tc6387xb_probe, - .remove = tc6387xb_remove, + .remove = __devexit_p(tc6387xb_remove), .suspend = tc6387xb_suspend, .resume = tc6387xb_resume, }; -- cgit v1.2.3 From ef256176c7ede53741ad8c0231db0cc46c0cfce5 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 3 Aug 2010 16:34:13 +0800 Subject: mfd: Avoid calling platform_device_put() twice in ucb1400 probe error path In the case of goto err2, what we want is to call platform_device_del() instead of platform_device_unregister(). Otherwise, we call platform_device_put() twice. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/ucb1400_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c index dbe280153f9e..d73f84ba0f08 100644 --- a/drivers/mfd/ucb1400_core.c +++ b/drivers/mfd/ucb1400_core.c @@ -114,7 +114,7 @@ static int ucb1400_core_probe(struct device *dev) err3: platform_device_put(ucb->ucb1400_ts); err2: - platform_device_unregister(ucb->ucb1400_gpio); + platform_device_del(ucb->ucb1400_gpio); err1: platform_device_put(ucb->ucb1400_gpio); err0: -- cgit v1.2.3 From fccbd21f333638a33efb6fc65fff58d1bc2a90bd Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 4 Aug 2010 09:44:47 +0800 Subject: mfd: Fix wm8994_device_init() return value wm8994_device_init() will return 0 in the case of kzalloc fail in current implementation. This patch fixes the return value. Signed-off-by: Axel Lin Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index ec71c9368906..69533f5ab03e 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -326,8 +326,10 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) * ARRAY_SIZE(wm8994_main_supplies), GFP_KERNEL); - if (!wm8994->supplies) + if (!wm8994->supplies) { + ret = -ENOMEM; goto err; + } for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++) wm8994->supplies[i].supply = wm8994_main_supplies[i]; -- cgit v1.2.3 From 8c46cf30f72550da22065941c3e0f862dcbb90a6 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 4 Aug 2010 09:54:21 +0800 Subject: mfd: Fix wrong wm8350-core kfree in error path This patch includes below fixes: 1. fix wm8350_create_cache error path make sure wm8350->reg_cache is freed in error path. 2. fix wm8350_device_init error path no need to kfree(wm8350->reg_cache) in the case of goto out. Signed-off-by: Axel Lin Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8350-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index b5807484b4c9..e81cc31e4202 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -536,6 +536,7 @@ static int wm8350_create_cache(struct wm8350 *wm8350, int type, int mode) } out: + kfree(wm8350->reg_cache); return ret; } @@ -700,7 +701,7 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, ret = wm8350_irq_init(wm8350, irq, pdata); if (ret < 0) - goto err; + goto err_free; if (wm8350->irq_base) { ret = request_threaded_irq(wm8350->irq_base + @@ -738,8 +739,9 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, err_irq: wm8350_irq_exit(wm8350); -err: +err_free: kfree(wm8350->reg_cache); +err: return ret; } EXPORT_SYMBOL_GPL(wm8350_device_init); -- cgit v1.2.3 From 8102bad2c0c7492eedd86aeec97e99a866b64662 Mon Sep 17 00:00:00 2001 From: David Miller Date: Wed, 4 Aug 2010 22:57:14 -0700 Subject: mfd: Missing slab.h includes Signed-off-by: David S. Miller Signed-off-by: Samuel Ortiz --- drivers/mfd/janz-cmodio.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mfd') diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c index 9ed630799acc..36a166bcdb08 100644 --- a/drivers/mfd/janz-cmodio.c +++ b/drivers/mfd/janz-cmodio.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 214044b44ae674d9e3dbe4774ed4b91a53d4e07f Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sun, 8 Aug 2010 20:05:23 +0300 Subject: mfd: Fix menelaus mmc slot 2 misconfiguration We are modifying register value instead of return value. This fix is originally done by Carlos Eduardo Aguiar. Original fix is commit bb4e91722e29efe31587d2cc664b6def645aecd9 in git.kernel.org/pub/scm/linux/kernel/git/tmlind/linux-omap-2.6.git Author modified the fix for mainline version of menelaus. Signed-off-by: Jarkko Nikula Cc: Carlos Eduardo Aguiar Signed-off-by: Samuel Ortiz --- drivers/mfd/menelaus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/menelaus.c b/drivers/mfd/menelaus.c index a3fb4bcb9889..e02b57423ac4 100644 --- a/drivers/mfd/menelaus.c +++ b/drivers/mfd/menelaus.c @@ -356,9 +356,9 @@ int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) int b; if (enable) - ret |= 1 << 1; + val |= 1 << 1; else - ret &= ~(1 << 1); + val &= ~(1 << 1); b = menelaus_read_reg(MENELAUS_MCT_CTRL2); b &= ~0x03; b |= power; -- cgit v1.2.3 From 1c888e2e3824a3f7565b4d96ede423cb9a9a28b7 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Sun, 8 Aug 2010 20:05:24 +0300 Subject: mfd: Use macros instead of some constant magic numbers for menelaus This patch is originally done by Carlos Eduardo Aguiar. Original fix is commit 3305829b2816072b9c8ed01374b205ae4de74027 in git.kernel.org/pub/scm/linux/kernel/git/tmlind/linux-omap-2.6.git Author modified the fix for mainline version of menelaus. Signed-off-by: Jarkko Nikula Cc: Carlos Eduardo Aguiar Signed-off-by: Samuel Ortiz --- drivers/mfd/menelaus.c | 75 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 21 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/menelaus.c b/drivers/mfd/menelaus.c index e02b57423ac4..4ba85bbdb4c1 100644 --- a/drivers/mfd/menelaus.c +++ b/drivers/mfd/menelaus.c @@ -128,6 +128,39 @@ #define MENELAUS_RESERVED14_IRQ 14 /* Reserved */ #define MENELAUS_RESERVED15_IRQ 15 /* Reserved */ +/* VCORE_CTRL1 register */ +#define VCORE_CTRL1_BYP_COMP (1 << 5) +#define VCORE_CTRL1_HW_NSW (1 << 7) + +/* GPIO_CTRL register */ +#define GPIO_CTRL_SLOTSELEN (1 << 5) +#define GPIO_CTRL_SLPCTLEN (1 << 6) +#define GPIO1_DIR_INPUT (1 << 0) +#define GPIO2_DIR_INPUT (1 << 1) +#define GPIO3_DIR_INPUT (1 << 2) + +/* MCT_CTRL1 register */ +#define MCT_CTRL1_S1_CMD_OD (1 << 2) +#define MCT_CTRL1_S2_CMD_OD (1 << 3) + +/* MCT_CTRL2 register */ +#define MCT_CTRL2_VS2_SEL_D0 (1 << 0) +#define MCT_CTRL2_VS2_SEL_D1 (1 << 1) +#define MCT_CTRL2_S1CD_BUFEN (1 << 4) +#define MCT_CTRL2_S2CD_BUFEN (1 << 5) +#define MCT_CTRL2_S1CD_DBEN (1 << 6) +#define MCT_CTRL2_S2CD_BEN (1 << 7) + +/* MCT_CTRL3 register */ +#define MCT_CTRL3_SLOT1_EN (1 << 0) +#define MCT_CTRL3_SLOT2_EN (1 << 1) +#define MCT_CTRL3_S1_AUTO_EN (1 << 2) +#define MCT_CTRL3_S2_AUTO_EN (1 << 3) + +/* MCT_PIN_ST register */ +#define MCT_PIN_ST_S1_CD_ST (1 << 0) +#define MCT_PIN_ST_S2_CD_ST (1 << 1) + static void menelaus_work(struct work_struct *_menelaus); struct menelaus_chip { @@ -249,10 +282,10 @@ static void menelaus_mmc_cd_work(struct menelaus_chip *menelaus_hw) return; if (!(reg & 0x1)) - card_mask |= (1 << 0); + card_mask |= MCT_PIN_ST_S1_CD_ST; if (!(reg & 0x2)) - card_mask |= (1 << 1); + card_mask |= MCT_PIN_ST_S2_CD_ST; if (menelaus_hw->mmc_callback) menelaus_hw->mmc_callback(menelaus_hw->mmc_callback_data, @@ -277,14 +310,14 @@ int menelaus_set_mmc_opendrain(int slot, int enable) val = ret; if (slot == 1) { if (enable) - val |= 1 << 2; + val |= MCT_CTRL1_S1_CMD_OD; else - val &= ~(1 << 2); + val &= ~MCT_CTRL1_S1_CMD_OD; } else { if (enable) - val |= 1 << 3; + val |= MCT_CTRL1_S2_CMD_OD; else - val &= ~(1 << 3); + val &= ~MCT_CTRL1_S2_CMD_OD; } ret = menelaus_write_reg(MENELAUS_MCT_CTRL1, val); mutex_unlock(&the_menelaus->lock); @@ -301,11 +334,11 @@ int menelaus_set_slot_sel(int enable) ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); if (ret < 0) goto out; - ret |= 0x02; + ret |= GPIO2_DIR_INPUT; if (enable) - ret |= 1 << 5; + ret |= GPIO_CTRL_SLOTSELEN; else - ret &= ~(1 << 5); + ret &= ~GPIO_CTRL_SLOTSELEN; ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); out: mutex_unlock(&the_menelaus->lock); @@ -330,14 +363,14 @@ int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) val = ret; if (slot == 1) { if (cd_en) - val |= (1 << 4) | (1 << 6); + val |= MCT_CTRL2_S1CD_BUFEN | MCT_CTRL2_S1CD_DBEN; else - val &= ~((1 << 4) | (1 << 6)); + val &= ~(MCT_CTRL2_S1CD_BUFEN | MCT_CTRL2_S1CD_DBEN); } else { if (cd_en) - val |= (1 << 5) | (1 << 7); + val |= MCT_CTRL2_S2CD_BUFEN | MCT_CTRL2_S2CD_BEN; else - val &= ~((1 << 5) | (1 << 7)); + val &= ~(MCT_CTRL2_S2CD_BUFEN | MCT_CTRL2_S2CD_BEN); } ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, val); if (ret < 0) @@ -349,25 +382,25 @@ int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) val = ret; if (slot == 1) { if (enable) - val |= 1 << 0; + val |= MCT_CTRL3_SLOT1_EN; else - val &= ~(1 << 0); + val &= ~MCT_CTRL3_SLOT1_EN; } else { int b; if (enable) - val |= 1 << 1; + val |= MCT_CTRL3_SLOT2_EN; else - val &= ~(1 << 1); + val &= ~MCT_CTRL3_SLOT2_EN; b = menelaus_read_reg(MENELAUS_MCT_CTRL2); - b &= ~0x03; + b &= ~(MCT_CTRL2_VS2_SEL_D0 | MCT_CTRL2_VS2_SEL_D1); b |= power; ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, b); if (ret < 0) goto out; } /* Disable autonomous shutdown */ - val &= ~(0x03 << 2); + val &= ~(MCT_CTRL3_S1_AUTO_EN | MCT_CTRL3_S2_AUTO_EN); ret = menelaus_write_reg(MENELAUS_MCT_CTRL3, val); out: mutex_unlock(&the_menelaus->lock); @@ -552,7 +585,7 @@ int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV) if (!the_menelaus->vcore_hw_mode) { val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); /* HW mode, turn OFF byte comparator */ - val |= ((1 << 7) | (1 << 5)); + val |= (VCORE_CTRL1_HW_NSW | VCORE_CTRL1_BYP_COMP); ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); the_menelaus->vcore_hw_mode = 1; } @@ -749,7 +782,7 @@ int menelaus_set_regulator_sleep(int enable, u32 val) ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); if (ret < 0) goto out; - t = ((1 << 6) | 0x04); + t = (GPIO_CTRL_SLPCTLEN | GPIO3_DIR_INPUT); if (enable) ret |= t; else -- cgit v1.2.3 From c6c193326384aecfd668c8f271799a44dbc74c1a Mon Sep 17 00:00:00 2001 From: Mike Rapoport Date: Wed, 11 Aug 2010 01:11:04 +0200 Subject: mfd: Add TPS6586x driver Add mfd core driver for TPS6586x PMICs family. The driver provides I/O access for the sub-device drivers and performs regstration of the sub-devices based on the platform requirements. In addition it implements GPIOlib interface for the chip GPIOs. TODO: - add interrupt support - add platform data for PWM, backlight leds and charger Signed-off-by: Mike Rapoport Signed-off-by: Mike Rapoport Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 14 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tps6586x.c | 375 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 drivers/mfd/tps6586x.c (limited to 'drivers/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 23a891f396e4..d75909e7cf2f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -531,6 +531,20 @@ config MFD_JZ4740_ADC Say yes here if you want support for the ADC unit in the JZ4740 SoC. This driver is necessary for jz4740-battery and jz4740-hwmon driver. +config MFD_TPS6586X + tristate "TPS6586x Power Management chips" + depends on I2C && GPIOLIB + select MFD_CORE + help + If you say yes here you get support for the TPS6586X series of + Power Management chips. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + + This driver can also be built as a module. If so, the module + will be called tps6586x. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index cc7cce0976f3..1e48d7e3e884 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_LPC_SCH) += lpc_sch.o obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o +obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o diff --git a/drivers/mfd/tps6586x.c b/drivers/mfd/tps6586x.c new file mode 100644 index 000000000000..4cde31e6a252 --- /dev/null +++ b/drivers/mfd/tps6586x.c @@ -0,0 +1,375 @@ +/* + * Core driver for TI TPS6586x PMIC family + * + * Copyright (c) 2010 CompuLab Ltd. + * Mike Rapoport + * + * Based on da903x.c. + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* GPIO control registers */ +#define TPS6586X_GPIOSET1 0x5d +#define TPS6586X_GPIOSET2 0x5e + +/* device id */ +#define TPS6586X_VERSIONCRC 0xcd +#define TPS658621A_VERSIONCRC 0x15 + +struct tps6586x { + struct mutex lock; + struct device *dev; + struct i2c_client *client; + + struct gpio_chip gpio; +}; + +static inline int __tps6586x_read(struct i2c_client *client, + int reg, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); + return ret; + } + + *val = (uint8_t)ret; + + return 0; +} + +static inline int __tps6586x_reads(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed reading from 0x%02x\n", reg); + return ret; + } + + return 0; +} + +static inline int __tps6586x_write(struct i2c_client *client, + int reg, uint8_t val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", + val, reg); + return ret; + } + + return 0; +} + +static inline int __tps6586x_writes(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed writings to 0x%02x\n", reg); + return ret; + } + + return 0; +} + +int tps6586x_write(struct device *dev, int reg, uint8_t val) +{ + return __tps6586x_write(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(tps6586x_write); + +int tps6586x_writes(struct device *dev, int reg, int len, uint8_t *val) +{ + return __tps6586x_writes(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(tps6586x_writes); + +int tps6586x_read(struct device *dev, int reg, uint8_t *val) +{ + return __tps6586x_read(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(tps6586x_read); + +int tps6586x_reads(struct device *dev, int reg, int len, uint8_t *val) +{ + return __tps6586x_reads(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(tps6586x_reads); + +int tps6586x_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct tps6586x *tps6586x = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&tps6586x->lock); + + ret = __tps6586x_read(to_i2c_client(dev), reg, ®_val); + if (ret) + goto out; + + if ((reg_val & bit_mask) == 0) { + reg_val |= bit_mask; + ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val); + } +out: + mutex_unlock(&tps6586x->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tps6586x_set_bits); + +int tps6586x_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct tps6586x *tps6586x = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&tps6586x->lock); + + ret = __tps6586x_read(to_i2c_client(dev), reg, ®_val); + if (ret) + goto out; + + if (reg_val & bit_mask) { + reg_val &= ~bit_mask; + ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val); + } +out: + mutex_unlock(&tps6586x->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tps6586x_clr_bits); + +int tps6586x_update(struct device *dev, int reg, uint8_t val, uint8_t mask) +{ + struct tps6586x *tps6586x = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&tps6586x->lock); + + ret = __tps6586x_read(tps6586x->client, reg, ®_val); + if (ret) + goto out; + + if ((reg_val & mask) != val) { + reg_val = (reg_val & ~mask) | val; + ret = __tps6586x_write(tps6586x->client, reg, reg_val); + } +out: + mutex_unlock(&tps6586x->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tps6586x_update); + +static int tps6586x_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio); + uint8_t val; + int ret; + + ret = __tps6586x_read(tps6586x->client, TPS6586X_GPIOSET2, &val); + if (ret) + return ret; + + return !!(val & (1 << offset)); +} + + +static void tps6586x_gpio_set(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct tps6586x *tps6586x = container_of(chip, struct tps6586x, gpio); + + __tps6586x_write(tps6586x->client, TPS6586X_GPIOSET2, + value << offset); +} + +static int tps6586x_gpio_output(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio); + uint8_t val, mask; + + tps6586x_gpio_set(gc, offset, value); + + val = 0x1 << (offset * 2); + mask = 0x3 << (offset * 2); + + return tps6586x_update(tps6586x->dev, TPS6586X_GPIOSET1, val, mask); +} + +static void tps6586x_gpio_init(struct tps6586x *tps6586x, int gpio_base) +{ + int ret; + + if (!gpio_base) + return; + + tps6586x->gpio.owner = THIS_MODULE; + tps6586x->gpio.label = tps6586x->client->name; + tps6586x->gpio.dev = tps6586x->dev; + tps6586x->gpio.base = gpio_base; + tps6586x->gpio.ngpio = 4; + tps6586x->gpio.can_sleep = 1; + + /* FIXME: add handling of GPIOs as dedicated inputs */ + tps6586x->gpio.direction_output = tps6586x_gpio_output; + tps6586x->gpio.set = tps6586x_gpio_set; + tps6586x->gpio.get = tps6586x_gpio_get; + + ret = gpiochip_add(&tps6586x->gpio); + if (ret) + dev_warn(tps6586x->dev, "GPIO registration failed: %d\n", ret); +} + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int tps6586x_remove_subdevs(struct tps6586x *tps6586x) +{ + return device_for_each_child(tps6586x->dev, NULL, __remove_subdev); +} + +static int __devinit tps6586x_add_subdevs(struct tps6586x *tps6586x, + struct tps6586x_platform_data *pdata) +{ + struct tps6586x_subdev_info *subdev; + struct platform_device *pdev; + int i, ret = 0; + + for (i = 0; i < pdata->num_subdevs; i++) { + subdev = &pdata->subdevs[i]; + + pdev = platform_device_alloc(subdev->name, subdev->id); + + pdev->dev.parent = tps6586x->dev; + pdev->dev.platform_data = subdev->platform_data; + + ret = platform_device_add(pdev); + if (ret) + goto failed; + } + return 0; + +failed: + tps6586x_remove_subdevs(tps6586x); + return ret; +} + +static int __devinit tps6586x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps6586x_platform_data *pdata = client->dev.platform_data; + struct tps6586x *tps6586x; + int ret; + + if (!pdata) { + dev_err(&client->dev, "tps6586x requires platform data\n"); + return -ENOTSUPP; + } + + ret = i2c_smbus_read_byte_data(client, TPS6586X_VERSIONCRC); + if (ret < 0) { + dev_err(&client->dev, "Chip ID read failed: %d\n", ret); + return -EIO; + } + + if (ret != TPS658621A_VERSIONCRC) { + dev_err(&client->dev, "Unsupported chip ID: %x\n", ret); + return -ENODEV; + } + + tps6586x = kzalloc(sizeof(struct tps6586x), GFP_KERNEL); + if (tps6586x == NULL) + return -ENOMEM; + + tps6586x->client = client; + tps6586x->dev = &client->dev; + i2c_set_clientdata(client, tps6586x); + + mutex_init(&tps6586x->lock); + + ret = tps6586x_add_subdevs(tps6586x, pdata); + if (ret) { + dev_err(&client->dev, "add devices failed: %d\n", ret); + goto err_add_devs; + } + + tps6586x_gpio_init(tps6586x, pdata->gpio_base); + + return 0; + +err_add_devs: + kfree(tps6586x); + return ret; +} + +static int __devexit tps6586x_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id tps6586x_id_table[] = { + { "tps6586x", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tps6586x_id_table); + +static struct i2c_driver tps6586x_driver = { + .driver = { + .name = "tps6586x", + .owner = THIS_MODULE, + }, + .probe = tps6586x_i2c_probe, + .remove = __devexit_p(tps6586x_i2c_remove), + .id_table = tps6586x_id_table, +}; + +static int __init tps6586x_init(void) +{ + return i2c_add_driver(&tps6586x_driver); +} +subsys_initcall(tps6586x_init); + +static void __exit tps6586x_exit(void) +{ + i2c_del_driver(&tps6586x_driver); +} +module_exit(tps6586x_exit); + +MODULE_DESCRIPTION("TPS6586X core driver"); +MODULE_AUTHOR("Mike Rapoport "); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 04a064236da3d8db24232983f99cfcfa5a4e5ade Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 9 Aug 2010 14:49:46 +0800 Subject: mfd: Fix incorrect kfree(i2c) in tps6507x i2c_driver probe The i2c_client received in probe() should not be kfree()'d. Signed-off-by: Axel Lin Signed-off-by: Samuel Ortiz --- drivers/mfd/tps6507x.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/tps6507x.c b/drivers/mfd/tps6507x.c index d859dffed39f..fc0197649281 100644 --- a/drivers/mfd/tps6507x.c +++ b/drivers/mfd/tps6507x.c @@ -89,10 +89,8 @@ static int tps6507x_i2c_probe(struct i2c_client *i2c, int ret = 0; tps6507x = kzalloc(sizeof(struct tps6507x_dev), GFP_KERNEL); - if (tps6507x == NULL) { - kfree(i2c); + if (tps6507x == NULL) return -ENOMEM; - } i2c_set_clientdata(i2c, tps6507x); tps6507x->dev = &i2c->dev; -- cgit v1.2.3 From a2cddb6e416721cfe07770c264100607997fd8cb Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 9 Aug 2010 14:51:15 +0800 Subject: mfd: Fix incorrect kfree(i2c) in wm831x-core i2c_driver probe The i2c_client received in probe() should not be kfree()'d. Signed-off-by: Axel Lin Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index cb94e7e1e6ea..1e7aaaf6cc6f 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -1757,10 +1757,8 @@ static int wm831x_i2c_probe(struct i2c_client *i2c, struct wm831x *wm831x; wm831x = kzalloc(sizeof(struct wm831x), GFP_KERNEL); - if (wm831x == NULL) { - kfree(i2c); + if (wm831x == NULL) return -ENOMEM; - } i2c_set_clientdata(i2c, wm831x); wm831x->dev = &i2c->dev; -- cgit v1.2.3 From d0a11693967295772d2a7c22b6b37eb20684e709 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 9 Aug 2010 14:52:16 +0800 Subject: mfd: Fix incorrect kfree(i2c) in wm8994-core i2c_driver probe The i2c_client received in probe() should not be kfree()'d. Signed-off-by: Axel Lin Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 69533f5ab03e..b3b2aaf89dbe 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -497,10 +497,8 @@ static int wm8994_i2c_probe(struct i2c_client *i2c, struct wm8994 *wm8994; wm8994 = kzalloc(sizeof(struct wm8994), GFP_KERNEL); - if (wm8994 == NULL) { - kfree(i2c); + if (wm8994 == NULL) return -ENOMEM; - } i2c_set_clientdata(i2c, wm8994); wm8994->dev = &i2c->dev; -- cgit v1.2.3