From 00cfa730db0d8378685148e6365b9cec7384b275 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 30 Jan 2011 12:31:30 -0800 Subject: Input: wm831x - add driver for Wolfson WM831x PMIC touchscreen controllers Some of the WM831x series of PMICs from Wolfson Microelectronics include a resistive touchscreen controller. Implement support for these controllers within the input API. Platform data is supported to allow configuration of system parameters such as selection between four and five wire touchscreens and for specification of optional direct to CPU IRQs for sample availability and for pen down. Use of this feature for at least the data IRQ is strongly recommended. Thanks to Julien Boibessot for extensive testing and detailed feedback. Signed-off-by: Mark Brown Tested-by: Julien Boibessot Signed-off-by: Dmitry Torokhov --- include/linux/mfd/wm831x/pdata.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/linux/mfd') diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h index fd322aca33b..173086d42af 100644 --- a/include/linux/mfd/wm831x/pdata.h +++ b/include/linux/mfd/wm831x/pdata.h @@ -80,7 +80,8 @@ struct wm831x_touch_pdata { int isel; /** Current for pen down (uA) */ int rpu; /** Pen down sensitivity resistor divider */ int pressure; /** Report pressure (boolean) */ - int data_irq; /** Touch data ready IRQ */ + unsigned int data_irq; /** Touch data ready IRQ */ + unsigned int pd_irq; /** Touch pendown detect IRQ */ }; enum wm831x_watchdog_action { -- cgit v1.2.3-70-g09d2 From 77bd70e9009eab6dbdef3ee08afe87ab26df8dac Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 4 Feb 2011 14:57:43 +0000 Subject: mfd: Don't suspend WM8994 if the CODEC is not suspended ASoC supports keeping the audio subsysetm active over suspend in order to support use cases such as audio passthrough from a cellular modem with the main CPU suspended. Ensure that we don't power down the CODEC when this is happening by checking to see if VMID is up and skipping suspend and resume when it is. If the CODEC has suspended then it'll turn VMID off before the core suspend() gets called. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 18 ++++++++++++++++++ include/linux/mfd/wm8994/core.h | 1 + 2 files changed, 19 insertions(+) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 41233c7fa58..f4016a075fd 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -246,6 +246,16 @@ static int wm8994_suspend(struct device *dev) struct wm8994 *wm8994 = dev_get_drvdata(dev); int ret; + /* Don't actually go through with the suspend if the CODEC is + * still active (eg, for audio passthrough from CP. */ + ret = wm8994_reg_read(wm8994, WM8994_POWER_MANAGEMENT_1); + if (ret < 0) { + dev_err(dev, "Failed to read power status: %d\n", ret); + } else if (ret & WM8994_VMID_SEL_MASK) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + /* GPIO configuration state is saved here since we may be configuring * the GPIO alternate functions even if we're not using the gpiolib * driver for them. @@ -261,6 +271,8 @@ static int wm8994_suspend(struct device *dev) if (ret < 0) dev_err(dev, "Failed to save LDO registers: %d\n", ret); + wm8994->suspended = true; + ret = regulator_bulk_disable(wm8994->num_supplies, wm8994->supplies); if (ret != 0) { @@ -276,6 +288,10 @@ static int wm8994_resume(struct device *dev) struct wm8994 *wm8994 = dev_get_drvdata(dev); int ret; + /* We may have lied to the PM core about suspending */ + if (!wm8994->suspended) + return 0; + ret = regulator_bulk_enable(wm8994->num_supplies, wm8994->supplies); if (ret != 0) { @@ -298,6 +314,8 @@ static int wm8994_resume(struct device *dev) if (ret < 0) dev_err(dev, "Failed to restore GPIO registers: %d\n", ret); + wm8994->suspended = false; + return 0; } #endif diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index 3fd36845ca4..ef4f0b6083a 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -71,6 +71,7 @@ struct wm8994 { u16 irq_masks_cache[WM8994_NUM_IRQ_REGS]; /* Used over suspend/resume */ + bool suspended; u16 ldo_regs[WM8994_NUM_LDO_REGS]; u16 gpio_regs[WM8994_NUM_GPIO_REGS]; -- cgit v1.2.3-70-g09d2 From 03df0f629483f941f83ee2fbb180a4425e99dd23 Mon Sep 17 00:00:00 2001 From: Cyril Chemparathy Date: Tue, 18 Jan 2011 19:21:35 +0000 Subject: mfd: add driver for sequencer serial port TI's sequencer serial port (TI-SSP) is a jack-of-all-trades type of serial port device. It has a built-in programmable execution engine that can be programmed to operate as almost any serial bus (I2C, SPI, EasyScale, and others). This patch adds a driver for this controller device. The driver does not expose a user-land interface. Protocol drivers built on top of this layer are expected to remain in-kernel. Signed-off-by: Cyril Chemparathy Acked-by: Samuel Ortiz Signed-off-by: Sekhar Nori Signed-off-by: Kevin Hilman --- drivers/mfd/Kconfig | 11 ++ drivers/mfd/Makefile | 1 + drivers/mfd/ti-ssp.c | 476 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/ti_ssp.h | 87 +++++++++ 4 files changed, 575 insertions(+) create mode 100644 drivers/mfd/ti-ssp.c create mode 100644 include/linux/mfd/ti_ssp.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fd018366d67..0284c53c210 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -81,6 +81,17 @@ config MFD_DM355EVM_MSP boards. MSP430 firmware manages resets and power sequencing, inputs from buttons and the IR remote, LEDs, an RTC, and more. +config MFD_TI_SSP + tristate "TI Sequencer Serial Port support" + depends on ARCH_DAVINCI_TNETV107X + select MFD_CORE + ---help--- + Say Y here if you want support for the Sequencer Serial Port + in a Texas Instruments TNETV107X SoC. + + To compile this driver as a module, choose M here: the + module will be called ti-ssp. + config HTC_EGPIO bool "HTC EGPIO support" depends on GENERIC_HARDIRQS && GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index a54e2c7c6a1..c56b6c7232e 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -14,6 +14,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_TI_SSP) += ti-ssp.o obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_MFD_TC3589X) += tc3589x.o diff --git a/drivers/mfd/ti-ssp.c b/drivers/mfd/ti-ssp.c new file mode 100644 index 00000000000..af9ab0e5ca6 --- /dev/null +++ b/drivers/mfd/ti-ssp.c @@ -0,0 +1,476 @@ +/* + * Sequencer Serial Port (SSP) driver for Texas Instruments' SoCs + * + * Copyright (C) 2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register Offsets */ +#define REG_REV 0x00 +#define REG_IOSEL_1 0x04 +#define REG_IOSEL_2 0x08 +#define REG_PREDIV 0x0c +#define REG_INTR_ST 0x10 +#define REG_INTR_EN 0x14 +#define REG_TEST_CTRL 0x18 + +/* Per port registers */ +#define PORT_CFG_2 0x00 +#define PORT_ADDR 0x04 +#define PORT_DATA 0x08 +#define PORT_CFG_1 0x0c +#define PORT_STATE 0x10 + +#define SSP_PORT_CONFIG_MASK (SSP_EARLY_DIN | SSP_DELAY_DOUT) +#define SSP_PORT_CLKRATE_MASK 0x0f + +#define SSP_SEQRAM_WR_EN BIT(4) +#define SSP_SEQRAM_RD_EN BIT(5) +#define SSP_START BIT(15) +#define SSP_BUSY BIT(10) +#define SSP_PORT_ASL BIT(7) +#define SSP_PORT_CFO1 BIT(6) + +#define SSP_PORT_SEQRAM_SIZE 32 + +static const int ssp_port_base[] = {0x040, 0x080}; +static const int ssp_port_seqram[] = {0x100, 0x180}; + +struct ti_ssp { + struct resource *res; + struct device *dev; + void __iomem *regs; + spinlock_t lock; + struct clk *clk; + int irq; + wait_queue_head_t wqh; + + /* + * Some of the iosel2 register bits always read-back as 0, we need to + * remember these values so that we don't clobber previously set + * values. + */ + u32 iosel2; +}; + +static inline struct ti_ssp *dev_to_ssp(struct device *dev) +{ + return dev_get_drvdata(dev->parent); +} + +static inline int dev_to_port(struct device *dev) +{ + return to_platform_device(dev)->id; +} + +/* Register Access Helpers, rmw() functions need to run locked */ +static inline u32 ssp_read(struct ti_ssp *ssp, int reg) +{ + return __raw_readl(ssp->regs + reg); +} + +static inline void ssp_write(struct ti_ssp *ssp, int reg, u32 val) +{ + __raw_writel(val, ssp->regs + reg); +} + +static inline void ssp_rmw(struct ti_ssp *ssp, int reg, u32 mask, u32 bits) +{ + ssp_write(ssp, reg, (ssp_read(ssp, reg) & ~mask) | bits); +} + +static inline u32 ssp_port_read(struct ti_ssp *ssp, int port, int reg) +{ + return ssp_read(ssp, ssp_port_base[port] + reg); +} + +static inline void ssp_port_write(struct ti_ssp *ssp, int port, int reg, + u32 val) +{ + ssp_write(ssp, ssp_port_base[port] + reg, val); +} + +static inline void ssp_port_rmw(struct ti_ssp *ssp, int port, int reg, + u32 mask, u32 bits) +{ + ssp_rmw(ssp, ssp_port_base[port] + reg, mask, bits); +} + +static inline void ssp_port_clr_bits(struct ti_ssp *ssp, int port, int reg, + u32 bits) +{ + ssp_port_rmw(ssp, port, reg, bits, 0); +} + +static inline void ssp_port_set_bits(struct ti_ssp *ssp, int port, int reg, + u32 bits) +{ + ssp_port_rmw(ssp, port, reg, 0, bits); +} + +/* Called to setup port clock mode, caller must hold ssp->lock */ +static int __set_mode(struct ti_ssp *ssp, int port, int mode) +{ + mode &= SSP_PORT_CONFIG_MASK; + ssp_port_rmw(ssp, port, PORT_CFG_1, SSP_PORT_CONFIG_MASK, mode); + + return 0; +} + +int ti_ssp_set_mode(struct device *dev, int mode) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int ret; + + spin_lock(&ssp->lock); + ret = __set_mode(ssp, port, mode); + spin_unlock(&ssp->lock); + + return ret; +} +EXPORT_SYMBOL(ti_ssp_set_mode); + +/* Called to setup iosel2, caller must hold ssp->lock */ +static void __set_iosel2(struct ti_ssp *ssp, u32 mask, u32 val) +{ + ssp->iosel2 = (ssp->iosel2 & ~mask) | val; + ssp_write(ssp, REG_IOSEL_2, ssp->iosel2); +} + +/* Called to setup port iosel, caller must hold ssp->lock */ +static void __set_iosel(struct ti_ssp *ssp, int port, u32 iosel) +{ + unsigned val, shift = port ? 16 : 0; + + /* IOSEL1 gets the least significant 16 bits */ + val = ssp_read(ssp, REG_IOSEL_1); + val &= 0xffff << (port ? 0 : 16); + val |= (iosel & 0xffff) << (port ? 16 : 0); + ssp_write(ssp, REG_IOSEL_1, val); + + /* IOSEL2 gets the most significant 16 bits */ + val = (iosel >> 16) & 0x7; + __set_iosel2(ssp, 0x7 << shift, val << shift); +} + +int ti_ssp_set_iosel(struct device *dev, u32 iosel) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + + spin_lock(&ssp->lock); + __set_iosel(ssp, port, iosel); + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_set_iosel); + +int ti_ssp_load(struct device *dev, int offs, u32* prog, int len) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int i; + + if (len > SSP_PORT_SEQRAM_SIZE) + return -ENOSPC; + + spin_lock(&ssp->lock); + + /* Enable SeqRAM access */ + ssp_port_set_bits(ssp, port, PORT_CFG_2, SSP_SEQRAM_WR_EN); + + /* Copy code */ + for (i = 0; i < len; i++) { + __raw_writel(prog[i], ssp->regs + offs + 4*i + + ssp_port_seqram[port]); + } + + /* Disable SeqRAM access */ + ssp_port_clr_bits(ssp, port, PORT_CFG_2, SSP_SEQRAM_WR_EN); + + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_load); + +int ti_ssp_raw_read(struct device *dev) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int shift = port ? 27 : 11; + + return (ssp_read(ssp, REG_IOSEL_2) >> shift) & 0xf; +} +EXPORT_SYMBOL(ti_ssp_raw_read); + +int ti_ssp_raw_write(struct device *dev, u32 val) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev), shift; + + spin_lock(&ssp->lock); + + shift = port ? 22 : 6; + val &= 0xf; + __set_iosel2(ssp, 0xf << shift, val << shift); + + spin_unlock(&ssp->lock); + + return 0; +} +EXPORT_SYMBOL(ti_ssp_raw_write); + +static inline int __xfer_done(struct ti_ssp *ssp, int port) +{ + return !(ssp_port_read(ssp, port, PORT_CFG_1) & SSP_BUSY); +} + +int ti_ssp_run(struct device *dev, u32 pc, u32 input, u32 *output) +{ + struct ti_ssp *ssp = dev_to_ssp(dev); + int port = dev_to_port(dev); + int ret; + + if (pc & ~(0x3f)) + return -EINVAL; + + /* Grab ssp->lock to serialize rmw on ssp registers */ + spin_lock(&ssp->lock); + + ssp_port_write(ssp, port, PORT_ADDR, input >> 16); + ssp_port_write(ssp, port, PORT_DATA, input & 0xffff); + ssp_port_rmw(ssp, port, PORT_CFG_1, 0x3f, pc); + + /* grab wait queue head lock to avoid race with the isr */ + spin_lock_irq(&ssp->wqh.lock); + + /* kick off sequence execution in hardware */ + ssp_port_set_bits(ssp, port, PORT_CFG_1, SSP_START); + + /* drop ssp lock; no register writes beyond this */ + spin_unlock(&ssp->lock); + + ret = wait_event_interruptible_locked_irq(ssp->wqh, + __xfer_done(ssp, port)); + spin_unlock_irq(&ssp->wqh.lock); + + if (ret < 0) + return ret; + + if (output) { + *output = (ssp_port_read(ssp, port, PORT_ADDR) << 16) | + (ssp_port_read(ssp, port, PORT_DATA) & 0xffff); + } + + ret = ssp_port_read(ssp, port, PORT_STATE) & 0x3f; /* stop address */ + + return ret; +} +EXPORT_SYMBOL(ti_ssp_run); + +static irqreturn_t ti_ssp_interrupt(int irq, void *dev_data) +{ + struct ti_ssp *ssp = dev_data; + + spin_lock(&ssp->wqh.lock); + + ssp_write(ssp, REG_INTR_ST, 0x3); + wake_up_locked(&ssp->wqh); + + spin_unlock(&ssp->wqh.lock); + + return IRQ_HANDLED; +} + +static int __devinit ti_ssp_probe(struct platform_device *pdev) +{ + static struct ti_ssp *ssp; + const struct ti_ssp_data *pdata = pdev->dev.platform_data; + int error = 0, prediv = 0xff, id; + unsigned long sysclk; + struct device *dev = &pdev->dev; + struct mfd_cell cells[2]; + + ssp = kzalloc(sizeof(*ssp), GFP_KERNEL); + if (!ssp) { + dev_err(dev, "cannot allocate device info\n"); + return -ENOMEM; + } + + ssp->dev = dev; + dev_set_drvdata(dev, ssp); + + ssp->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ssp->res) { + error = -ENODEV; + dev_err(dev, "cannot determine register area\n"); + goto error_res; + } + + if (!request_mem_region(ssp->res->start, resource_size(ssp->res), + pdev->name)) { + error = -ENOMEM; + dev_err(dev, "cannot claim register memory\n"); + goto error_res; + } + + ssp->regs = ioremap(ssp->res->start, resource_size(ssp->res)); + if (!ssp->regs) { + error = -ENOMEM; + dev_err(dev, "cannot map register memory\n"); + goto error_map; + } + + ssp->clk = clk_get(dev, NULL); + if (IS_ERR(ssp->clk)) { + error = PTR_ERR(ssp->clk); + dev_err(dev, "cannot claim device clock\n"); + goto error_clk; + } + + ssp->irq = platform_get_irq(pdev, 0); + if (ssp->irq < 0) { + error = -ENODEV; + dev_err(dev, "unknown irq\n"); + goto error_irq; + } + + error = request_threaded_irq(ssp->irq, NULL, ti_ssp_interrupt, 0, + dev_name(dev), ssp); + if (error < 0) { + dev_err(dev, "cannot acquire irq\n"); + goto error_irq; + } + + spin_lock_init(&ssp->lock); + init_waitqueue_head(&ssp->wqh); + + /* Power on and initialize SSP */ + error = clk_enable(ssp->clk); + if (error) { + dev_err(dev, "cannot enable device clock\n"); + goto error_enable; + } + + /* Reset registers to a sensible known state */ + ssp_write(ssp, REG_IOSEL_1, 0); + ssp_write(ssp, REG_IOSEL_2, 0); + ssp_write(ssp, REG_INTR_EN, 0x3); + ssp_write(ssp, REG_INTR_ST, 0x3); + ssp_write(ssp, REG_TEST_CTRL, 0); + ssp_port_write(ssp, 0, PORT_CFG_1, SSP_PORT_ASL); + ssp_port_write(ssp, 1, PORT_CFG_1, SSP_PORT_ASL); + ssp_port_write(ssp, 0, PORT_CFG_2, SSP_PORT_CFO1); + ssp_port_write(ssp, 1, PORT_CFG_2, SSP_PORT_CFO1); + + sysclk = clk_get_rate(ssp->clk); + if (pdata && pdata->out_clock) + prediv = (sysclk / pdata->out_clock) - 1; + prediv = clamp(prediv, 0, 0xff); + ssp_rmw(ssp, REG_PREDIV, 0xff, prediv); + + memset(cells, 0, sizeof(cells)); + for (id = 0; id < 2; id++) { + const struct ti_ssp_dev_data *data = &pdata->dev_data[id]; + + cells[id].id = id; + cells[id].name = data->dev_name; + cells[id].platform_data = data->pdata; + cells[id].data_size = data->pdata_size; + } + + error = mfd_add_devices(dev, 0, cells, 2, NULL, 0); + if (error < 0) { + dev_err(dev, "cannot add mfd cells\n"); + goto error_enable; + } + + return 0; + +error_enable: + free_irq(ssp->irq, ssp); +error_irq: + clk_put(ssp->clk); +error_clk: + iounmap(ssp->regs); +error_map: + release_mem_region(ssp->res->start, resource_size(ssp->res)); +error_res: + kfree(ssp); + return error; +} + +static int __devexit ti_ssp_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_ssp *ssp = dev_get_drvdata(dev); + + mfd_remove_devices(dev); + clk_disable(ssp->clk); + free_irq(ssp->irq, ssp); + clk_put(ssp->clk); + iounmap(ssp->regs); + release_mem_region(ssp->res->start, resource_size(ssp->res)); + kfree(ssp); + dev_set_drvdata(dev, NULL); + return 0; +} + +static struct platform_driver ti_ssp_driver = { + .probe = ti_ssp_probe, + .remove = __devexit_p(ti_ssp_remove), + .driver = { + .name = "ti-ssp", + .owner = THIS_MODULE, + } +}; + +static int __init ti_ssp_init(void) +{ + return platform_driver_register(&ti_ssp_driver); +} +module_init(ti_ssp_init); + +static void __exit ti_ssp_exit(void) +{ + platform_driver_unregister(&ti_ssp_driver); +} +module_exit(ti_ssp_exit); + +MODULE_DESCRIPTION("Sequencer Serial Port (SSP) Driver"); +MODULE_AUTHOR("Cyril Chemparathy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ti-ssp"); diff --git a/include/linux/mfd/ti_ssp.h b/include/linux/mfd/ti_ssp.h new file mode 100644 index 00000000000..021fe091243 --- /dev/null +++ b/include/linux/mfd/ti_ssp.h @@ -0,0 +1,87 @@ +/* + * Sequencer Serial Port (SSP) driver for Texas Instruments' SoCs + * + * Copyright (C) 2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __TI_SSP_H__ +#define __TI_SSP_H__ + +struct ti_ssp_dev_data { + const char *dev_name; + void *pdata; + size_t pdata_size; +}; + +struct ti_ssp_data { + unsigned long out_clock; + struct ti_ssp_dev_data dev_data[2]; +}; + +/* + * Sequencer port IO pin configuration bits. These do not correlate 1-1 with + * the hardware. The iosel field in the port data combines iosel1 and iosel2, + * and is therefore not a direct map to register space. It is best to use the + * macros below to construct iosel values. + * + * least significant 16 bits --> iosel1 + * most significant 16 bits --> iosel2 + */ + +#define SSP_IN 0x0000 +#define SSP_DATA 0x0001 +#define SSP_CLOCK 0x0002 +#define SSP_CHIPSEL 0x0003 +#define SSP_OUT 0x0004 +#define SSP_PIN_SEL(pin, v) ((v) << ((pin) * 3)) +#define SSP_PIN_MASK(pin) SSP_PIN_SEL(pin, 0x7) +#define SSP_INPUT_SEL(pin) ((pin) << 16) + +/* Sequencer port config bits */ +#define SSP_EARLY_DIN BIT(8) +#define SSP_DELAY_DOUT BIT(9) + +/* Sequence map definitions */ +#define SSP_CLK_HIGH BIT(0) +#define SSP_CLK_LOW 0 +#define SSP_DATA_HIGH BIT(1) +#define SSP_DATA_LOW 0 +#define SSP_CS_HIGH BIT(2) +#define SSP_CS_LOW 0 +#define SSP_OUT_MODE BIT(3) +#define SSP_IN_MODE 0 +#define SSP_DATA_REG BIT(4) +#define SSP_ADDR_REG 0 + +#define SSP_OPCODE_DIRECT ((0x0) << 5) +#define SSP_OPCODE_TOGGLE ((0x1) << 5) +#define SSP_OPCODE_SHIFT ((0x2) << 5) +#define SSP_OPCODE_BRANCH0 ((0x4) << 5) +#define SSP_OPCODE_BRANCH1 ((0x5) << 5) +#define SSP_OPCODE_BRANCH ((0x6) << 5) +#define SSP_OPCODE_STOP ((0x7) << 5) +#define SSP_BRANCH(addr) ((addr) << 8) +#define SSP_COUNT(cycles) ((cycles) << 8) + +int ti_ssp_raw_read(struct device *dev); +int ti_ssp_raw_write(struct device *dev, u32 val); +int ti_ssp_load(struct device *dev, int offs, u32* prog, int len); +int ti_ssp_run(struct device *dev, u32 pc, u32 input, u32 *output); +int ti_ssp_set_mode(struct device *dev, int mode); +int ti_ssp_set_iosel(struct device *dev, u32 iosel); + +#endif /* __TI_SSP_H__ */ -- cgit v1.2.3-70-g09d2 From a72aeefebe6545ad5c9c699e1121019c1709ca77 Mon Sep 17 00:00:00 2001 From: Cyril Chemparathy Date: Tue, 18 Jan 2011 19:21:36 +0000 Subject: spi: add ti-ssp spi master driver This patch adds an SPI master implementation that operates on top of an underlying TI-SSP port. Acked-by: Grant Likely Signed-off-by: Cyril Chemparathy Signed-off-by: Sekhar Nori Signed-off-by: Kevin Hilman --- drivers/spi/Kconfig | 10 ++ drivers/spi/Makefile | 1 + drivers/spi/ti-ssp-spi.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/ti_ssp.h | 6 + 4 files changed, 419 insertions(+) create mode 100644 drivers/spi/ti-ssp-spi.c (limited to 'include/linux/mfd') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index bb233a9cbad..42f5a8d7c81 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -350,6 +350,16 @@ config SPI_TEGRA help SPI driver for NVidia Tegra SoCs +config SPI_TI_SSP + tristate "TI Sequencer Serial Port - SPI Support" + depends on MFD_TI_SSP + help + This selects an SPI master implementation using a TI sequencer + serial port. + + To compile this driver as a module, choose M here: the + module will be called ti-ssp-spi. + config SPI_TOPCLIFF_PCH tristate "Topcliff PCH SPI Controller" depends on PCI diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 86d1b5f9bbd..f3f31d98835 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx_hw.o obj-$(CONFIG_SPI_S3C64XX) += spi_s3c64xx.o obj-$(CONFIG_SPI_TEGRA) += spi_tegra.o +obj-$(CONFIG_SPI_TI_SSP) += ti-ssp-spi.o obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi_topcliff_pch.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o diff --git a/drivers/spi/ti-ssp-spi.c b/drivers/spi/ti-ssp-spi.c new file mode 100644 index 00000000000..ee22795c797 --- /dev/null +++ b/drivers/spi/ti-ssp-spi.c @@ -0,0 +1,402 @@ +/* + * Sequencer Serial Port (SSP) based SPI master driver + * + * Copyright (C) 2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MODE_BITS (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) + +struct ti_ssp_spi { + struct spi_master *master; + struct device *dev; + spinlock_t lock; + struct list_head msg_queue; + struct completion complete; + bool shutdown; + struct workqueue_struct *workqueue; + struct work_struct work; + u8 mode, bpw; + int cs_active; + u32 pc_en, pc_dis, pc_wr, pc_rd; + void (*select)(int cs); +}; + +static u32 ti_ssp_spi_rx(struct ti_ssp_spi *hw) +{ + u32 ret; + + ti_ssp_run(hw->dev, hw->pc_rd, 0, &ret); + return ret; +} + +static void ti_ssp_spi_tx(struct ti_ssp_spi *hw, u32 data) +{ + ti_ssp_run(hw->dev, hw->pc_wr, data << (32 - hw->bpw), NULL); +} + +static int ti_ssp_spi_txrx(struct ti_ssp_spi *hw, struct spi_message *msg, + struct spi_transfer *t) +{ + int count; + + if (hw->bpw <= 8) { + u8 *rx = t->rx_buf; + const u8 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 1) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } else if (hw->bpw <= 16) { + u16 *rx = t->rx_buf; + const u16 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 2) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } else { + u32 *rx = t->rx_buf; + const u32 *tx = t->tx_buf; + + for (count = 0; count < t->len; count += 4) { + if (t->tx_buf) + ti_ssp_spi_tx(hw, *tx++); + if (t->rx_buf) + *rx++ = ti_ssp_spi_rx(hw); + } + } + + msg->actual_length += count; /* bytes transferred */ + + dev_dbg(&msg->spi->dev, "xfer %s%s, %d bytes, %d bpw, count %d%s\n", + t->tx_buf ? "tx" : "", t->rx_buf ? "rx" : "", t->len, + hw->bpw, count, (count < t->len) ? " (under)" : ""); + + return (count < t->len) ? -EIO : 0; /* left over data */ +} + +static void ti_ssp_spi_chip_select(struct ti_ssp_spi *hw, int cs_active) +{ + cs_active = !!cs_active; + if (cs_active == hw->cs_active) + return; + ti_ssp_run(hw->dev, cs_active ? hw->pc_en : hw->pc_dis, 0, NULL); + hw->cs_active = cs_active; +} + +#define __SHIFT_OUT(bits) (SSP_OPCODE_SHIFT | SSP_OUT_MODE | \ + cs_en | clk | SSP_COUNT((bits) * 2 - 1)) +#define __SHIFT_IN(bits) (SSP_OPCODE_SHIFT | SSP_IN_MODE | \ + cs_en | clk | SSP_COUNT((bits) * 2 - 1)) + +static int ti_ssp_spi_setup_transfer(struct ti_ssp_spi *hw, u8 bpw, u8 mode) +{ + int error, idx = 0; + u32 seqram[16]; + u32 cs_en, cs_dis, clk; + u32 topbits, botbits; + + mode &= MODE_BITS; + if (mode == hw->mode && bpw == hw->bpw) + return 0; + + cs_en = (mode & SPI_CS_HIGH) ? SSP_CS_HIGH : SSP_CS_LOW; + cs_dis = (mode & SPI_CS_HIGH) ? SSP_CS_LOW : SSP_CS_HIGH; + clk = (mode & SPI_CPOL) ? SSP_CLK_HIGH : SSP_CLK_LOW; + + /* Construct instructions */ + + /* Disable Chip Select */ + hw->pc_dis = idx; + seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_dis | clk; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_dis | clk; + + /* Enable Chip Select */ + hw->pc_en = idx; + seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_en | clk; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + /* Reads and writes need to be split for bpw > 16 */ + topbits = (bpw > 16) ? 16 : bpw; + botbits = bpw - topbits; + + /* Write */ + hw->pc_wr = idx; + seqram[idx++] = __SHIFT_OUT(topbits) | SSP_ADDR_REG; + if (botbits) + seqram[idx++] = __SHIFT_OUT(botbits) | SSP_DATA_REG; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + /* Read */ + hw->pc_rd = idx; + if (botbits) + seqram[idx++] = __SHIFT_IN(botbits) | SSP_ADDR_REG; + seqram[idx++] = __SHIFT_IN(topbits) | SSP_DATA_REG; + seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk; + + error = ti_ssp_load(hw->dev, 0, seqram, idx); + if (error < 0) + return error; + + error = ti_ssp_set_mode(hw->dev, ((mode & SPI_CPHA) ? + 0 : SSP_EARLY_DIN)); + if (error < 0) + return error; + + hw->bpw = bpw; + hw->mode = mode; + + return error; +} + +static void ti_ssp_spi_work(struct work_struct *work) +{ + struct ti_ssp_spi *hw = container_of(work, struct ti_ssp_spi, work); + + spin_lock(&hw->lock); + + while (!list_empty(&hw->msg_queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + int status = 0; + + m = container_of(hw->msg_queue.next, struct spi_message, + queue); + + list_del_init(&m->queue); + + spin_unlock(&hw->lock); + + spi = m->spi; + + if (hw->select) + hw->select(spi->chip_select); + + list_for_each_entry(t, &m->transfers, transfer_list) { + int bpw = spi->bits_per_word; + int xfer_status; + + if (t->bits_per_word) + bpw = t->bits_per_word; + + if (ti_ssp_spi_setup_transfer(hw, bpw, spi->mode) < 0) + break; + + ti_ssp_spi_chip_select(hw, 1); + + xfer_status = ti_ssp_spi_txrx(hw, m, t); + if (xfer_status < 0) + status = xfer_status; + + if (t->delay_usecs) + udelay(t->delay_usecs); + + if (t->cs_change) + ti_ssp_spi_chip_select(hw, 0); + } + + ti_ssp_spi_chip_select(hw, 0); + m->status = status; + m->complete(m->context); + + spin_lock(&hw->lock); + } + + if (hw->shutdown) + complete(&hw->complete); + + spin_unlock(&hw->lock); +} + +static int ti_ssp_spi_setup(struct spi_device *spi) +{ + if (spi->bits_per_word > 32) + return -EINVAL; + + return 0; +} + +static int ti_ssp_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct ti_ssp_spi *hw; + struct spi_transfer *t; + int error = 0; + + m->actual_length = 0; + m->status = -EINPROGRESS; + + hw = spi_master_get_devdata(spi->master); + + if (list_empty(&m->transfers) || !m->complete) + return -EINVAL; + + list_for_each_entry(t, &m->transfers, transfer_list) { + if (t->len && !(t->rx_buf || t->tx_buf)) { + dev_err(&spi->dev, "invalid xfer, no buffer\n"); + return -EINVAL; + } + + if (t->len && t->rx_buf && t->tx_buf) { + dev_err(&spi->dev, "invalid xfer, full duplex\n"); + return -EINVAL; + } + + if (t->bits_per_word > 32) { + dev_err(&spi->dev, "invalid xfer width %d\n", + t->bits_per_word); + return -EINVAL; + } + } + + spin_lock(&hw->lock); + if (hw->shutdown) { + error = -ESHUTDOWN; + goto error_unlock; + } + list_add_tail(&m->queue, &hw->msg_queue); + queue_work(hw->workqueue, &hw->work); +error_unlock: + spin_unlock(&hw->lock); + return error; +} + +static int __devinit ti_ssp_spi_probe(struct platform_device *pdev) +{ + const struct ti_ssp_spi_data *pdata; + struct ti_ssp_spi *hw; + struct spi_master *master; + struct device *dev = &pdev->dev; + int error = 0; + + pdata = dev->platform_data; + if (!pdata) { + dev_err(dev, "platform data not found\n"); + return -EINVAL; + } + + master = spi_alloc_master(dev, sizeof(struct ti_ssp_spi)); + if (!master) { + dev_err(dev, "cannot allocate SPI master\n"); + return -ENOMEM; + } + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + hw->master = master; + hw->dev = dev; + hw->select = pdata->select; + + spin_lock_init(&hw->lock); + init_completion(&hw->complete); + INIT_LIST_HEAD(&hw->msg_queue); + INIT_WORK(&hw->work, ti_ssp_spi_work); + + hw->workqueue = create_singlethread_workqueue(dev_name(dev)); + if (!hw->workqueue) { + error = -ENOMEM; + dev_err(dev, "work queue creation failed\n"); + goto error_wq; + } + + error = ti_ssp_set_iosel(hw->dev, pdata->iosel); + if (error < 0) { + dev_err(dev, "io setup failed\n"); + goto error_iosel; + } + + master->bus_num = pdev->id; + master->num_chipselect = pdata->num_cs; + master->mode_bits = MODE_BITS; + master->flags = SPI_MASTER_HALF_DUPLEX; + master->setup = ti_ssp_spi_setup; + master->transfer = ti_ssp_spi_transfer; + + error = spi_register_master(master); + if (error) { + dev_err(dev, "master registration failed\n"); + goto error_reg; + } + + return 0; + +error_reg: +error_iosel: + destroy_workqueue(hw->workqueue); +error_wq: + spi_master_put(master); + return error; +} + +static int __devexit ti_ssp_spi_remove(struct platform_device *pdev) +{ + struct ti_ssp_spi *hw = platform_get_drvdata(pdev); + int error; + + hw->shutdown = 1; + while (!list_empty(&hw->msg_queue)) { + error = wait_for_completion_interruptible(&hw->complete); + if (error < 0) { + hw->shutdown = 0; + return error; + } + } + destroy_workqueue(hw->workqueue); + spi_unregister_master(hw->master); + + return 0; +} + +static struct platform_driver ti_ssp_spi_driver = { + .probe = ti_ssp_spi_probe, + .remove = __devexit_p(ti_ssp_spi_remove), + .driver = { + .name = "ti-ssp-spi", + .owner = THIS_MODULE, + }, +}; + +static int __init ti_ssp_spi_init(void) +{ + return platform_driver_register(&ti_ssp_spi_driver); +} +module_init(ti_ssp_spi_init); + +static void __exit ti_ssp_spi_exit(void) +{ + platform_driver_unregister(&ti_ssp_spi_driver); +} +module_exit(ti_ssp_spi_exit); + +MODULE_DESCRIPTION("SSP SPI Master"); +MODULE_AUTHOR("Cyril Chemparathy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ti-ssp-spi"); diff --git a/include/linux/mfd/ti_ssp.h b/include/linux/mfd/ti_ssp.h index 021fe091243..dbb4b43bd20 100644 --- a/include/linux/mfd/ti_ssp.h +++ b/include/linux/mfd/ti_ssp.h @@ -32,6 +32,12 @@ struct ti_ssp_data { struct ti_ssp_dev_data dev_data[2]; }; +struct ti_ssp_spi_data { + unsigned long iosel; + int num_cs; + void (*select)(int cs); +}; + /* * Sequencer port IO pin configuration bits. These do not correlate 1-1 with * the hardware. The iosel field in the port data combines iosel1 and iosel2, -- cgit v1.2.3-70-g09d2 From 94fd5b7401e330498331ea3667d796e74c63d08a Mon Sep 17 00:00:00 2001 From: Matti Aaltonen Date: Tue, 1 Mar 2011 10:10:35 -0300 Subject: [media] MFD: WL1273 FM Radio: MFD driver for the FM radio This is the core of the WL1273 FM radio driver, it connects the two child modules. The two child drivers are drivers/media/radio/radio-wl1273.c and sound/soc/codecs/wl1273.c. The radio-wl1273 driver implements the V4L2 interface and communicates with the device. The ALSA codec offers digital audio, without it only analog audio is available. Signed-off-by: Matti J. Aaltonen Acked-by: Samuel Ortiz Signed-off-by: Mauro Carvalho Chehab --- drivers/mfd/Kconfig | 2 +- drivers/mfd/wl1273-core.c | 149 +++++++++++++++++++++++++++++++++++++++- include/linux/mfd/wl1273-core.h | 2 + 3 files changed, 149 insertions(+), 4 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fd018366d67..9db079be0e0 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -615,7 +615,7 @@ config MFD_VX855 and/or vx855_gpio drivers for this to do anything useful. config MFD_WL1273_CORE - tristate + tristate "Support for TI WL1273 FM radio." depends on I2C select MFD_CORE default n diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c index d2ecc243573..4025a4bec8d 100644 --- a/drivers/mfd/wl1273-core.c +++ b/drivers/mfd/wl1273-core.c @@ -1,7 +1,7 @@ /* * MFD driver for wl1273 FM radio and audio codec submodules. * - * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2011 Nokia Corporation * Author: Matti Aaltonen * * This program is free software; you can redistribute it and/or modify @@ -31,6 +31,145 @@ static struct i2c_device_id wl1273_driver_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, wl1273_driver_id_table); +static int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value) +{ + struct i2c_client *client = core->client; + u8 b[2]; + int r; + + r = i2c_smbus_read_i2c_block_data(client, reg, sizeof(b), b); + if (r != 2) { + dev_err(&client->dev, "%s: Read: %d fails.\n", __func__, reg); + return -EREMOTEIO; + } + + *value = (u16)b[0] << 8 | b[1]; + + return 0; +} + +static int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param) +{ + struct i2c_client *client = core->client; + u8 buf[] = { (param >> 8) & 0xff, param & 0xff }; + int r; + + r = i2c_smbus_write_i2c_block_data(client, cmd, sizeof(buf), buf); + if (r) { + dev_err(&client->dev, "%s: Cmd: %d fails.\n", __func__, cmd); + return r; + } + + return 0; +} + +static int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len) +{ + struct i2c_client *client = core->client; + struct i2c_msg msg; + int r; + + msg.addr = client->addr; + msg.flags = 0; + msg.buf = data; + msg.len = len; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + dev_err(&client->dev, "%s: write error.\n", __func__); + return -EREMOTEIO; + } + + return 0; +} + +/** + * wl1273_fm_set_audio() - Set audio mode. + * @core: A pointer to the device struct. + * @new_mode: The new audio mode. + * + * Audio modes are WL1273_AUDIO_DIGITAL and WL1273_AUDIO_ANALOG. + */ +static int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int new_mode) +{ + int r = 0; + + if (core->mode == WL1273_MODE_OFF || + core->mode == WL1273_MODE_SUSPENDED) + return -EPERM; + + if (core->mode == WL1273_MODE_RX && new_mode == WL1273_AUDIO_DIGITAL) { + r = wl1273_fm_write_cmd(core, WL1273_PCM_MODE_SET, + WL1273_PCM_DEF_MODE); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, + core->i2s_mode); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, + WL1273_AUDIO_ENABLE_I2S); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_RX && + new_mode == WL1273_AUDIO_ANALOG) { + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, + WL1273_AUDIO_ENABLE_ANALOG); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_TX && + new_mode == WL1273_AUDIO_DIGITAL) { + r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, + core->i2s_mode); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, + WL1273_AUDIO_IO_SET_I2S); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_TX && + new_mode == WL1273_AUDIO_ANALOG) { + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, + WL1273_AUDIO_IO_SET_ANALOG); + if (r) + goto out; + } + + core->audio_mode = new_mode; +out: + return r; +} + +/** + * wl1273_fm_set_volume() - Set volume. + * @core: A pointer to the device struct. + * @volume: The new volume value. + */ +static int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume) +{ + u16 val; + int r; + + if (volume > WL1273_MAX_VOLUME) + return -EINVAL; + + if (core->volume == volume) + return 0; + + r = wl1273_fm_write_cmd(core, WL1273_VOLUME_SET, volume); + if (r) + return r; + + core->volume = volume; + return 0; +} + static int wl1273_core_remove(struct i2c_client *client) { struct wl1273_core *core = i2c_get_clientdata(client); @@ -38,7 +177,6 @@ static int wl1273_core_remove(struct i2c_client *client) dev_dbg(&client->dev, "%s\n", __func__); mfd_remove_devices(&client->dev); - i2c_set_clientdata(client, NULL); kfree(core); return 0; @@ -83,6 +221,12 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, cell->data_size = sizeof(core); children++; + core->read = wl1273_fm_read_reg; + core->write = wl1273_fm_write_cmd; + core->write_data = wl1273_fm_write_data; + core->set_audio = wl1273_fm_set_audio; + core->set_volume = wl1273_fm_set_volume; + if (pdata->children & WL1273_CODEC_CHILD) { cell = &core->cells[children]; @@ -104,7 +248,6 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, return 0; err: - i2c_set_clientdata(client, NULL); pdata->free_resources(); kfree(core); diff --git a/include/linux/mfd/wl1273-core.h b/include/linux/mfd/wl1273-core.h index 9787293eae5..db2f3f454a1 100644 --- a/include/linux/mfd/wl1273-core.h +++ b/include/linux/mfd/wl1273-core.h @@ -280,7 +280,9 @@ struct wl1273_core { struct i2c_client *client; + int (*read)(struct wl1273_core *core, u8, u16 *); int (*write)(struct wl1273_core *core, u8, u16); + int (*write_data)(struct wl1273_core *core, u8 *, u16); int (*set_audio)(struct wl1273_core *core, unsigned int); int (*set_volume)(struct wl1273_core *core, unsigned int); }; -- cgit v1.2.3-70-g09d2 From b103e0b3c52e6edb4839ccc961cf335ca6b88918 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 21 Jan 2011 13:26:46 +0000 Subject: mfd: Support configuration of WM831x /IRQ output in CMOS mode Provide platform data allowing the system to set the /IRQ pin into CMOS mode rather than the default open drain. The default value of this platform data reflects the default hardware configuration so there should be no change to existing users. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm831x-irq.c | 8 ++++++++ include/linux/mfd/wm831x/pdata.h | 3 +++ 2 files changed, 11 insertions(+) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 2691648e607..90ad3fa9132 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -520,6 +520,14 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq) return 0; } + if (pdata->irq_cmos) + i = 0; + else + i = WM831X_IRQ_OD; + + wm831x_set_bits(wm831x, WM831X_IRQ_CONFIG, + WM831X_IRQ_OD, i); + /* Try to flag /IRQ as a wake source; there are a number of * unconditional wake sources in the PMIC so this isn't * conditional but we don't actually care *too* much if it diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h index 173086d42af..ac3aa73943e 100644 --- a/include/linux/mfd/wm831x/pdata.h +++ b/include/linux/mfd/wm831x/pdata.h @@ -109,6 +109,9 @@ struct wm831x_pdata { /** Called after subdevices are set up */ int (*post_init)(struct wm831x *wm831x); + /** Put the /IRQ line into CMOS mode */ + bool irq_cmos; + int irq_base; int gpio_base; struct wm831x_backlight_pdata *backlight; -- cgit v1.2.3-70-g09d2 From 90550d1903da8dac851d220b794e44c90a11c6ce Mon Sep 17 00:00:00 2001 From: Mattias Nilsson Date: Mon, 14 Feb 2011 11:17:12 +0100 Subject: mfd: AB8500 system control driver This adds a pretty straight-forward system control driver for the AB8500. This driver will be used from the core platform, e.g the clock tree implementation in the machine code, and is by nature singleton. There are a few simple functions to read, write, set and clear registers so that the machine code can control its own foundation. Cc: Mattias Wallin Signed-off-by: Mattias Nilsson Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/Makefile | 2 +- drivers/mfd/ab8500-sysctrl.c | 80 ++++++++++++ include/linux/mfd/ab8500/sysctrl.h | 254 +++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/ab8500-sysctrl.c create mode 100644 include/linux/mfd/ab8500/sysctrl.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f0e25cad762..61a0b0f901a 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -71,7 +71,7 @@ obj-$(CONFIG_ABX500_CORE) += abx500-core.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB3550_CORE) += ab3550-core.o -obj-$(CONFIG_AB8500_CORE) += ab8500-core.o +obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_AB8500_I2C_CORE) += ab8500-i2c.o obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c new file mode 100644 index 00000000000..392185965b3 --- /dev/null +++ b/drivers/mfd/ab8500-sysctrl.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Mattias Nilsson for ST Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include + +static struct device *sysctrl_dev; + +static inline bool valid_bank(u8 bank) +{ + return ((bank == AB8500_SYS_CTRL1_BLOCK) || + (bank == AB8500_SYS_CTRL2_BLOCK)); +} + +int ab8500_sysctrl_read(u16 reg, u8 *value) +{ + u8 bank; + + if (sysctrl_dev == NULL) + return -EAGAIN; + + bank = (reg >> 8); + if (!valid_bank(bank)) + return -EINVAL; + + return abx500_get_register_interruptible(sysctrl_dev, bank, + (u8)(reg & 0xFF), value); +} + +int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) +{ + u8 bank; + + if (sysctrl_dev == NULL) + return -EAGAIN; + + bank = (reg >> 8); + if (!valid_bank(bank)) + return -EINVAL; + + return abx500_mask_and_set_register_interruptible(sysctrl_dev, bank, + (u8)(reg & 0xFF), mask, value); +} + +static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) +{ + sysctrl_dev = &pdev->dev; + return 0; +} + +static int __devexit ab8500_sysctrl_remove(struct platform_device *pdev) +{ + sysctrl_dev = NULL; + return 0; +} + +static struct platform_driver ab8500_sysctrl_driver = { + .driver = { + .name = "ab8500-sysctrl", + .owner = THIS_MODULE, + }, + .probe = ab8500_sysctrl_probe, + .remove = __devexit_p(ab8500_sysctrl_remove), +}; + +static int __init ab8500_sysctrl_init(void) +{ + return platform_driver_register(&ab8500_sysctrl_driver); +} +subsys_initcall(ab8500_sysctrl_init); + +MODULE_AUTHOR("Mattias Nilsson for ST Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef __AB8500_SYSCTRL_H +#define __AB8500_SYSCTRL_H + +#include + +#ifdef CONFIG_AB8500_CORE + +int ab8500_sysctrl_read(u16 reg, u8 *value); +int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value); + +#else + +static inline int ab8500_sysctrl_read(u16 reg, u8 *value) +{ + return 0; +} + +static inline int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) +{ + return 0; +} + +#endif /* CONFIG_AB8500_CORE */ + +static inline int ab8500_sysctrl_set(u16 reg, u8 bits) +{ + return ab8500_sysctrl_write(reg, bits, bits); +} + +static inline int ab8500_sysctrl_clear(u16 reg, u8 bits) +{ + return ab8500_sysctrl_write(reg, bits, 0); +} + +/* Registers */ +#define AB8500_TURNONSTATUS 0x100 +#define AB8500_RESETSTATUS 0x101 +#define AB8500_PONKEY1PRESSSTATUS 0x102 +#define AB8500_SYSCLKREQSTATUS 0x142 +#define AB8500_STW4500CTRL1 0x180 +#define AB8500_STW4500CTRL2 0x181 +#define AB8500_STW4500CTRL3 0x200 +#define AB8500_MAINWDOGCTRL 0x201 +#define AB8500_MAINWDOGTIMER 0x202 +#define AB8500_LOWBAT 0x203 +#define AB8500_BATTOK 0x204 +#define AB8500_SYSCLKTIMER 0x205 +#define AB8500_SMPSCLKCTRL 0x206 +#define AB8500_SMPSCLKSEL1 0x207 +#define AB8500_SMPSCLKSEL2 0x208 +#define AB8500_SMPSCLKSEL3 0x209 +#define AB8500_SYSULPCLKCONF 0x20A +#define AB8500_SYSULPCLKCTRL1 0x20B +#define AB8500_SYSCLKCTRL 0x20C +#define AB8500_SYSCLKREQ1VALID 0x20D +#define AB8500_SYSTEMCTRLSUP 0x20F +#define AB8500_SYSCLKREQ1RFCLKBUF 0x210 +#define AB8500_SYSCLKREQ2RFCLKBUF 0x211 +#define AB8500_SYSCLKREQ3RFCLKBUF 0x212 +#define AB8500_SYSCLKREQ4RFCLKBUF 0x213 +#define AB8500_SYSCLKREQ5RFCLKBUF 0x214 +#define AB8500_SYSCLKREQ6RFCLKBUF 0x215 +#define AB8500_SYSCLKREQ7RFCLKBUF 0x216 +#define AB8500_SYSCLKREQ8RFCLKBUF 0x217 +#define AB8500_DITHERCLKCTRL 0x220 +#define AB8500_SWATCTRL 0x230 +#define AB8500_HIQCLKCTRL 0x232 +#define AB8500_VSIMSYSCLKCTRL 0x233 + +/* Bits */ +#define AB8500_TURNONSTATUS_PORNVBAT BIT(0) +#define AB8500_TURNONSTATUS_PONKEY1DBF BIT(1) +#define AB8500_TURNONSTATUS_PONKEY2DBF BIT(2) +#define AB8500_TURNONSTATUS_RTCALARM BIT(3) +#define AB8500_TURNONSTATUS_MAINCHDET BIT(4) +#define AB8500_TURNONSTATUS_VBUSDET BIT(5) +#define AB8500_TURNONSTATUS_USBIDDETECT BIT(6) + +#define AB8500_RESETSTATUS_RESETN4500NSTATUS BIT(0) +#define AB8500_RESETSTATUS_SWRESETN4500NSTATUS BIT(2) + +#define AB8500_PONKEY1PRESSSTATUS_PONKEY1PRESSTIME_MASK 0x7F +#define AB8500_PONKEY1PRESSSTATUS_PONKEY1PRESSTIME_SHIFT 0 + +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ1STATUS BIT(0) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ2STATUS BIT(1) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ3STATUS BIT(2) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ4STATUS BIT(3) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ5STATUS BIT(4) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ6STATUS BIT(5) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ7STATUS BIT(6) +#define AB8500_SYSCLKREQSTATUS_SYSCLKREQ8STATUS BIT(7) + +#define AB8500_STW4500CTRL1_SWOFF BIT(0) +#define AB8500_STW4500CTRL1_SWRESET4500N BIT(1) +#define AB8500_STW4500CTRL1_THDB8500SWOFF BIT(2) + +#define AB8500_STW4500CTRL2_RESETNVAUX1VALID BIT(0) +#define AB8500_STW4500CTRL2_RESETNVAUX2VALID BIT(1) +#define AB8500_STW4500CTRL2_RESETNVAUX3VALID BIT(2) +#define AB8500_STW4500CTRL2_RESETNVMODVALID BIT(3) +#define AB8500_STW4500CTRL2_RESETNVEXTSUPPLY1VALID BIT(4) +#define AB8500_STW4500CTRL2_RESETNVEXTSUPPLY2VALID BIT(5) +#define AB8500_STW4500CTRL2_RESETNVEXTSUPPLY3VALID BIT(6) +#define AB8500_STW4500CTRL2_RESETNVSMPS1VALID BIT(7) + +#define AB8500_STW4500CTRL3_CLK32KOUT2DIS BIT(0) +#define AB8500_STW4500CTRL3_RESETAUDN BIT(1) +#define AB8500_STW4500CTRL3_RESETDENCN BIT(2) +#define AB8500_STW4500CTRL3_THSDENA BIT(3) + +#define AB8500_MAINWDOGCTRL_MAINWDOGENA BIT(0) +#define AB8500_MAINWDOGCTRL_MAINWDOGKICK BIT(1) +#define AB8500_MAINWDOGCTRL_WDEXPTURNONVALID BIT(4) + +#define AB8500_MAINWDOGTIMER_MAINWDOGTIMER_MASK 0x7F +#define AB8500_MAINWDOGTIMER_MAINWDOGTIMER_SHIFT 0 + +#define AB8500_LOWBAT_LOWBATENA BIT(0) +#define AB8500_LOWBAT_LOWBAT_MASK 0x7E +#define AB8500_LOWBAT_LOWBAT_SHIFT 1 + +#define AB8500_BATTOK_BATTOKSEL0THF_MASK 0x0F +#define AB8500_BATTOK_BATTOKSEL0THF_SHIFT 0 +#define AB8500_BATTOK_BATTOKSEL1THF_MASK 0xF0 +#define AB8500_BATTOK_BATTOKSEL1THF_SHIFT 4 + +#define AB8500_SYSCLKTIMER_SYSCLKTIMER_MASK 0x0F +#define AB8500_SYSCLKTIMER_SYSCLKTIMER_SHIFT 0 +#define AB8500_SYSCLKTIMER_SYSCLKTIMERADJ_MASK 0xF0 +#define AB8500_SYSCLKTIMER_SYSCLKTIMERADJ_SHIFT 4 + +#define AB8500_SMPSCLKCTRL_SMPSCLKINTSEL_MASK 0x03 +#define AB8500_SMPSCLKCTRL_SMPSCLKINTSEL_SHIFT 0 +#define AB8500_SMPSCLKCTRL_3M2CLKINTENA BIT(2) + +#define AB8500_SMPSCLKSEL1_VARMCLKSEL_MASK 0x07 +#define AB8500_SMPSCLKSEL1_VARMCLKSEL_SHIFT 0 +#define AB8500_SMPSCLKSEL1_VAPECLKSEL_MASK 0x38 +#define AB8500_SMPSCLKSEL1_VAPECLKSEL_SHIFT 3 + +#define AB8500_SMPSCLKSEL2_VMODCLKSEL_MASK 0x07 +#define AB8500_SMPSCLKSEL2_VMODCLKSEL_SHIFT 0 +#define AB8500_SMPSCLKSEL2_VSMPS1CLKSEL_MASK 0x38 +#define AB8500_SMPSCLKSEL2_VSMPS1CLKSEL_SHIFT 3 + +#define AB8500_SMPSCLKSEL3_VSMPS2CLKSEL_MASK 0x07 +#define AB8500_SMPSCLKSEL3_VSMPS2CLKSEL_SHIFT 0 +#define AB8500_SMPSCLKSEL3_VSMPS3CLKSEL_MASK 0x38 +#define AB8500_SMPSCLKSEL3_VSMPS3CLKSEL_SHIFT 3 + +#define AB8500_SYSULPCLKCONF_ULPCLKCONF_MASK 0x03 +#define AB8500_SYSULPCLKCONF_ULPCLKCONF_SHIFT 0 +#define AB8500_SYSULPCLKCONF_CLK27MHZSTRE BIT(2) +#define AB8500_SYSULPCLKCONF_TVOUTCLKDELN BIT(3) +#define AB8500_SYSULPCLKCONF_TVOUTCLKINV BIT(4) +#define AB8500_SYSULPCLKCONF_ULPCLKSTRE BIT(5) +#define AB8500_SYSULPCLKCONF_CLK27MHZBUFENA BIT(6) +#define AB8500_SYSULPCLKCONF_CLK27MHZPDENA BIT(7) + +#define AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK 0x03 +#define AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT 0 +#define AB8500_SYSULPCLKCTRL1_ULPCLKREQ BIT(2) +#define AB8500_SYSULPCLKCTRL1_4500SYSCLKREQ BIT(3) +#define AB8500_SYSULPCLKCTRL1_AUDIOCLKENA BIT(4) +#define AB8500_SYSULPCLKCTRL1_SYSCLKBUF2REQ BIT(5) +#define AB8500_SYSULPCLKCTRL1_SYSCLKBUF3REQ BIT(6) +#define AB8500_SYSULPCLKCTRL1_SYSCLKBUF4REQ BIT(7) + +#define AB8500_SYSCLKCTRL_TVOUTPLLENA BIT(0) +#define AB8500_SYSCLKCTRL_TVOUTCLKENA BIT(1) +#define AB8500_SYSCLKCTRL_USBCLKENA BIT(2) + +#define AB8500_SYSCLKREQ1VALID_SYSCLKREQ1VALID BIT(0) +#define AB8500_SYSCLKREQ1VALID_ULPCLKREQ1VALID BIT(1) +#define AB8500_SYSCLKREQ1VALID_USBSYSCLKREQ1VALID BIT(2) + +#define AB8500_SYSTEMCTRLSUP_EXTSUP12LPNCLKSEL_MASK 0x03 +#define AB8500_SYSTEMCTRLSUP_EXTSUP12LPNCLKSEL_SHIFT 0 +#define AB8500_SYSTEMCTRLSUP_EXTSUP3LPNCLKSEL_MASK 0x0C +#define AB8500_SYSTEMCTRLSUP_EXTSUP3LPNCLKSEL_SHIFT 2 +#define AB8500_SYSTEMCTRLSUP_INTDB8500NOD BIT(4) + +#define AB8500_SYSCLKREQ1RFCLKBUF_SYSCLKREQ1RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ1RFCLKBUF_SYSCLKREQ1RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ1RFCLKBUF_SYSCLKREQ1RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ2RFCLKBUF_SYSCLKREQ2RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ2RFCLKBUF_SYSCLKREQ2RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ2RFCLKBUF_SYSCLKREQ2RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ3RFCLKBUF_SYSCLKREQ3RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ3RFCLKBUF_SYSCLKREQ3RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ3RFCLKBUF_SYSCLKREQ3RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ4RFCLKBUF_SYSCLKREQ4RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ4RFCLKBUF_SYSCLKREQ4RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ4RFCLKBUF_SYSCLKREQ4RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ5RFCLKBUF_SYSCLKREQ5RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ5RFCLKBUF_SYSCLKREQ5RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ5RFCLKBUF_SYSCLKREQ5RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ6RFCLKBUF_SYSCLKREQ6RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ6RFCLKBUF_SYSCLKREQ6RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ6RFCLKBUF_SYSCLKREQ6RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ7RFCLKBUF_SYSCLKREQ7RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ7RFCLKBUF_SYSCLKREQ7RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ7RFCLKBUF_SYSCLKREQ7RFCLKBUF4 BIT(4) + +#define AB8500_SYSCLKREQ8RFCLKBUF_SYSCLKREQ8RFCLKBUF2 BIT(2) +#define AB8500_SYSCLKREQ8RFCLKBUF_SYSCLKREQ8RFCLKBUF3 BIT(3) +#define AB8500_SYSCLKREQ8RFCLKBUF_SYSCLKREQ8RFCLKBUF4 BIT(4) + +#define AB8500_DITHERCLKCTRL_VARMDITHERENA BIT(0) +#define AB8500_DITHERCLKCTRL_VSMPS3DITHERENA BIT(1) +#define AB8500_DITHERCLKCTRL_VSMPS1DITHERENA BIT(2) +#define AB8500_DITHERCLKCTRL_VSMPS2DITHERENA BIT(3) +#define AB8500_DITHERCLKCTRL_VMODDITHERENA BIT(4) +#define AB8500_DITHERCLKCTRL_VAPEDITHERENA BIT(5) +#define AB8500_DITHERCLKCTRL_DITHERDEL_MASK 0xC0 +#define AB8500_DITHERCLKCTRL_DITHERDEL_SHIFT 6 + +#define AB8500_SWATCTRL_UPDATERF BIT(0) +#define AB8500_SWATCTRL_SWATENABLE BIT(1) +#define AB8500_SWATCTRL_RFOFFTIMER_MASK 0x1C +#define AB8500_SWATCTRL_RFOFFTIMER_SHIFT 2 +#define AB8500_SWATCTRL_SWATBIT5 BIT(6) + +#define AB8500_HIQCLKCTRL_SYSCLKREQ1HIQENAVALID BIT(0) +#define AB8500_HIQCLKCTRL_SYSCLKREQ2HIQENAVALID BIT(1) +#define AB8500_HIQCLKCTRL_SYSCLKREQ3HIQENAVALID BIT(2) +#define AB8500_HIQCLKCTRL_SYSCLKREQ4HIQENAVALID BIT(3) +#define AB8500_HIQCLKCTRL_SYSCLKREQ5HIQENAVALID BIT(4) +#define AB8500_HIQCLKCTRL_SYSCLKREQ6HIQENAVALID BIT(5) +#define AB8500_HIQCLKCTRL_SYSCLKREQ7HIQENAVALID BIT(6) +#define AB8500_HIQCLKCTRL_SYSCLKREQ8HIQENAVALID BIT(7) + +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ1VALID BIT(0) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ2VALID BIT(1) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ3VALID BIT(2) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ4VALID BIT(3) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ5VALID BIT(4) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ6VALID BIT(5) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ7VALID BIT(6) +#define AB8500_VSIMSYSCLKCTRL_VSIMSYSCLKREQ8VALID BIT(7) + +#endif /* __AB8500_SYSCTRL_H */ -- cgit v1.2.3-70-g09d2 From dae2db30c114cd0dec59b4130c315c9cce351741 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Tue, 22 Feb 2011 10:11:13 +0100 Subject: mfd: Add new ab8500 GPADC driver AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage Signed-off-by: Arun Murthy Acked-by: Linus Walleij Acked-by: Mattias Wallin Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 7 + drivers/mfd/Makefile | 1 + drivers/mfd/ab8500-gpadc.c | 296 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/ab8500-gpadc.h | 28 ++++ 4 files changed, 332 insertions(+) create mode 100644 drivers/mfd/ab8500-gpadc.c create mode 100644 include/linux/mfd/ab8500-gpadc.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fdca643249e..d71af4531b1 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -534,6 +534,13 @@ config AB8500_DEBUG Select this option if you want debug information using the debug filesystem, debugfs. +config AB8500_GPADC + bool "AB8500 GPADC driver" + depends on AB8500_CORE && REGULATOR_AB8500 + default y + help + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + config AB3550_CORE bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 61a0b0f901a..ad71bd59345 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_AB3550_CORE) += ab3550-core.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_AB8500_I2C_CORE) += ab8500-i2c.o obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o +obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c new file mode 100644 index 00000000000..19339bc439c --- /dev/null +++ b/drivers/mfd/ab8500-gpadc.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Arun R Murthy + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * GPADC register offsets + * Bank : 0x0A + */ +#define AB8500_GPADC_CTRL1_REG 0x00 +#define AB8500_GPADC_CTRL2_REG 0x01 +#define AB8500_GPADC_CTRL3_REG 0x02 +#define AB8500_GPADC_AUTO_TIMER_REG 0x03 +#define AB8500_GPADC_STAT_REG 0x04 +#define AB8500_GPADC_MANDATAL_REG 0x05 +#define AB8500_GPADC_MANDATAH_REG 0x06 +#define AB8500_GPADC_AUTODATAL_REG 0x07 +#define AB8500_GPADC_AUTODATAH_REG 0x08 +#define AB8500_GPADC_MUX_CTRL_REG 0x09 + +/* gpadc constants */ +#define EN_VINTCORE12 0x04 +#define EN_VTVOUT 0x02 +#define EN_GPADC 0x01 +#define DIS_GPADC 0x00 +#define SW_AVG_16 0x60 +#define ADC_SW_CONV 0x04 +#define EN_BUF 0x40 +#define DIS_ZERO 0x00 +#define GPADC_BUSY 0x01 + +/** + * struct ab8500_gpadc - ab8500 GPADC device information + * @dev: pointer to the struct device + * @parent: pointer to the parent device structure ab8500 + * @ab8500_gpadc_complete: pointer to the struct completion, to indicate + * the completion of gpadc conversion + * @ab8500_gpadc_lock: structure of type mutex + * @regu: pointer to the struct regulator + * @irq: interrupt number that is used by gpadc + */ +static struct ab8500_gpadc { + struct device *dev; + struct ab8500 *parent; + struct completion ab8500_gpadc_complete; + struct mutex ab8500_gpadc_lock; + struct regulator *regu; + int irq; +} *di; + +/** + * ab8500_gpadc_convert() - gpadc conversion + * @input: analog input to be converted to digital data + * + * This function converts the selected analog i/p to digital + * data. Thereafter calibration has to be made to obtain the + * data in the required quantity measurement. + */ +int ab8500_gpadc_convert(u8 input) +{ + int ret; + u16 data = 0; + int looplimit = 0; + u8 val, low_data, high_data; + + if (!di) + return -ENODEV; + + mutex_lock(&di->ab8500_gpadc_lock); + /* Enable VTVout LDO this is required for GPADC */ + regulator_enable(di->regu); + + /* Check if ADC is not busy, lock and proceed */ + do { + ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_STAT_REG, &val); + if (ret < 0) + goto out; + if (!(val & GPADC_BUSY)) + break; + msleep(10); + } while (++looplimit < 10); + if (looplimit >= 10 && (val & GPADC_BUSY)) { + dev_err(di->dev, "gpadc_conversion: GPADC busy"); + ret = -EINVAL; + goto out; + } + + /* Enable GPADC */ + ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC); + if (ret < 0) { + dev_err(di->dev, "gpadc_conversion: enable gpadc failed\n"); + goto out; + } + /* Select the input source and set average samples to 16 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL2_REG, (input | SW_AVG_16)); + if (ret < 0) { + dev_err(di->dev, + "gpadc_conversion: set avg samples failed\n"); + goto out; + } + /* Enable ADC, Buffering and select rising edge, start Conversion */ + ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); + if (ret < 0) { + dev_err(di->dev, + "gpadc_conversion: select falling edge failed\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); + if (ret < 0) { + dev_err(di->dev, + "gpadc_conversion: start s/w conversion failed\n"); + goto out; + } + /* wait for completion of conversion */ + if (!wait_for_completion_timeout(&di->ab8500_gpadc_complete, 2*HZ)) { + dev_err(di->dev, + "timeout: didnt recieve GPADC conversion interrupt\n"); + ret = -EINVAL; + goto out; + } + + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_MANDATAL_REG, &low_data); + if (ret < 0) { + dev_err(di->dev, "gpadc_conversion: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_MANDATAH_REG, &high_data); + if (ret < 0) { + dev_err(di->dev, "gpadc_conversion: read high data failed\n"); + goto out; + } + + data = (high_data << 8) | low_data; + /* Disable GPADC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, DIS_GPADC); + if (ret < 0) { + dev_err(di->dev, "gpadc_conversion: disable gpadc failed\n"); + goto out; + } + /* Disable VTVout LDO this is required for GPADC */ + regulator_disable(di->regu); + mutex_unlock(&di->ab8500_gpadc_lock); + return data; + +out: + /* + * It has shown to be needed to turn off the GPADC if an error occurs, + * otherwise we might have problem when waiting for the busy bit in the + * GPADC status register to go low. In V1.1 there wait_for_completion + * seems to timeout when waiting for an interrupt.. Not seen in V2.0 + */ + (void) abx500_set_register_interruptible(di->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, DIS_GPADC); + regulator_disable(di->regu); + mutex_unlock(&di->ab8500_gpadc_lock); + dev_err(di->dev, "gpadc_conversion: Failed to AD convert channel %d\n", + input); + return ret; +} +EXPORT_SYMBOL(ab8500_gpadc_convert); + +/** + * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for s/w gpadc conversion completion. + * Notifies the gpadc completion is completed and the converted raw value + * can be read from the registers. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_di) +{ + struct ab8500_gpadc *gpadc = _di; + + complete(&gpadc->ab8500_gpadc_complete); + + return IRQ_HANDLED; +} + +static int __devinit ab8500_gpadc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_gpadc *gpadc; + + gpadc = kzalloc(sizeof(struct ab8500_gpadc), GFP_KERNEL); + if (!gpadc) { + dev_err(&pdev->dev, "Error: No memory\n"); + return -ENOMEM; + } + + gpadc->parent = dev_get_drvdata(pdev->dev.parent); + gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END"); + if (gpadc->irq < 0) { + dev_err(gpadc->dev, "failed to get platform irq-%d\n", di->irq); + ret = gpadc->irq; + goto fail; + } + + gpadc->dev = &pdev->dev; + mutex_init(&di->ab8500_gpadc_lock); + + /* Initialize completion used to notify completion of conversion */ + init_completion(&gpadc->ab8500_gpadc_complete); + + /* Register interrupt - SwAdcComplete */ + ret = request_threaded_irq(gpadc->irq, NULL, + ab8500_bm_gpswadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc", gpadc); + if (ret < 0) { + dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n", + gpadc->irq); + goto fail; + } + + /* VTVout LDO used to power up ab8500-GPADC */ + gpadc->regu = regulator_get(&pdev->dev, "vddadc"); + if (IS_ERR(gpadc->regu)) { + ret = PTR_ERR(gpadc->regu); + dev_err(gpadc->dev, "failed to get vtvout LDO\n"); + goto fail; + } + di = gpadc; + dev_dbg(gpadc->dev, "probe success\n"); + return 0; +fail: + kfree(gpadc); + gpadc = NULL; + return ret; +} + +static int __devexit ab8500_gpadc_remove(struct platform_device *pdev) +{ + struct ab8500_gpadc *gpadc = platform_get_drvdata(pdev); + + /* remove interrupt - completion of Sw ADC conversion */ + free_irq(gpadc->irq, di); + /* disable VTVout LDO that is being used by GPADC */ + regulator_put(gpadc->regu); + kfree(gpadc); + gpadc = NULL; + return 0; +} + +static struct platform_driver ab8500_gpadc_driver = { + .probe = ab8500_gpadc_probe, + .remove = __devexit_p(ab8500_gpadc_remove), + .driver = { + .name = "ab8500-gpadc", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_gpadc_init(void) +{ + return platform_driver_register(&ab8500_gpadc_driver); +} + +static void __exit ab8500_gpadc_exit(void) +{ + platform_driver_unregister(&ab8500_gpadc_driver); +} + +subsys_initcall_sync(ab8500_gpadc_init); +module_exit(ab8500_gpadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Arun R Murthy"); +MODULE_ALIAS("platform:ab8500_gpadc"); +MODULE_DESCRIPTION("AB8500 GPADC driver"); diff --git a/include/linux/mfd/ab8500-gpadc.h b/include/linux/mfd/ab8500-gpadc.h new file mode 100644 index 00000000000..9f6cc26bc73 --- /dev/null +++ b/include/linux/mfd/ab8500-gpadc.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy + */ + +#ifndef _AB8500_GPADC_H +#define _AB8500_GPADC_H + +/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define BTEMP_BALL 0x02 +#define MAIN_CHARGER_V 0x03 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define ADC_AUX1 0x06 +#define ADC_AUX2 0x07 +#define MAIN_BAT_V 0x08 +#define VBUS_V 0x09 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define BK_BAT_V 0x0C +#define DIE_TEMP 0x0D + +int ab8500_gpadc_convert(u8 input); + +#endif /* _AB8500_GPADC_H */ -- cgit v1.2.3-70-g09d2 From 2798e226ad7db82725ba03da933638e981b472f7 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:08 -0800 Subject: mfd-core: Fix up typos/vagueness in comment Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- include/linux/mfd/core.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux/mfd') diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 835996e167e..1fd7c4467e5 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -39,8 +39,8 @@ struct mfd_cell { size_t data_size; /* - * This resources can be specified relatively to the parent device. - * For accessing device you should use resources from device + * These resources can be specified relative to the parent device. + * For accessing hardware you should use resources from the platform dev */ int num_resources; const struct resource *resources; -- cgit v1.2.3-70-g09d2 From fe891a008f3310be47786e87c158edebdb71e265 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:09 -0800 Subject: mfd-core: Unconditionally add mfd_cell to every platform_device Previously, one would set the mfd_cell's platform_data/data_size to point to the current mfd_cell in order to pass that information along to drivers. This causes the current mfd_cell to always be available to drivers. It also adds a wrapper function for fetching the mfd cell from a platform device, similar to what originally existed for mfd devices. Drivers who previously used platform_data for other purposes can still use it; the difference is that mfd_get_data() must be used to access it (and the pdata structure is no longer allocated in mfd_add_devices). Note that mfd_get_data is intentionally vague (in name) about where the data is stored; variable name changes can come later without having to touch brazillions of drivers. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 9 +++------ include/linux/mfd/core.h | 23 +++++++++++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index d83ad0f141a..21a39dc64ea 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -39,12 +39,9 @@ static int mfd_add_device(struct device *parent, int id, pdev->dev.parent = parent; platform_set_drvdata(pdev, cell->driver_data); - if (cell->data_size) { - ret = platform_device_add_data(pdev, - cell->platform_data, cell->data_size); - if (ret) - goto fail_res; - } + ret = platform_device_add_data(pdev, cell, sizeof(*cell)); + if (ret) + goto fail_res; for (r = 0; r < cell->num_resources; r++) { res[r].name = cell->resources[r].name; diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 1fd7c4467e5..aefc378f8dc 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -33,9 +33,10 @@ struct mfd_cell { /* driver-specific data for MFD-aware "cell" drivers */ void *driver_data; - /* platform_data can be used to either pass data to "generic" - driver or as a hook to mfd_cell for the "cell" drivers */ + /* platform_data can be used to pass data to "generic" drivers */ void *platform_data; + + /* unused */ size_t data_size; /* @@ -55,6 +56,24 @@ struct mfd_cell { bool pm_runtime_no_callbacks; }; +/* + * Given a platform device that's been created by mfd_add_devices(), fetch + * the mfd_cell that created it. + */ +static inline const struct mfd_cell *mfd_get_cell(struct platform_device *pdev) +{ + return pdev->dev.platform_data; +} + +/* + * Given a platform device that's been created by mfd_add_devices(), fetch + * the .platform_data entry from the mfd_cell that created it. + */ +static inline void *mfd_get_data(struct platform_device *pdev) +{ + return mfd_get_cell(pdev)->platform_data; +} + extern int mfd_add_devices(struct device *parent, int id, const struct mfd_cell *cells, int n_devs, struct resource *mem_base, -- cgit v1.2.3-70-g09d2 From 0ce5fabe59d7c4f51b5ad51ed178ba92531ec04d Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:11 -0800 Subject: mfd: mfd_cell is now implicitly available to ab3550 driver No clients (in mainline kernel, I'm told that drivers exist in external trees that are planned for mainline inclusion) make use of this, nor do they make use of platform_data, so nothing really had to change here. The .data_size field is unused, so its usage gets removed. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3550-core.c | 4 +--- include/linux/mfd/abx500.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c index 681984df1c2..c8e555a9ee6 100644 --- a/drivers/mfd/ab3550-core.c +++ b/drivers/mfd/ab3550-core.c @@ -1320,10 +1320,8 @@ static int __init ab3550_probe(struct i2c_client *client, goto exit_no_ops; /* Set up and register the platform devices. */ - for (i = 0; i < AB3550_NUM_DEVICES; i++) { + for (i = 0; i < AB3550_NUM_DEVICES; i++) ab3550_devs[i].platform_data = ab3550_plf_data->dev_data[i]; - ab3550_devs[i].data_size = ab3550_plf_data->dev_data_sz[i]; - } err = mfd_add_devices(&client->dev, 0, ab3550_devs, ARRAY_SIZE(ab3550_devs), NULL, diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 67bd6f7ecf3..7d9b6ae1c20 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -186,7 +186,6 @@ struct abx500_init_settings { struct ab3550_platform_data { struct {unsigned int base; unsigned int count; } irq; void *dev_data[AB3550_NUM_DEVICES]; - size_t dev_data_sz[AB3550_NUM_DEVICES]; struct abx500_init_settings *init_settings; unsigned int init_settings_sz; }; -- cgit v1.2.3-70-g09d2 From 4ec1b54c4d082d4bad19b55ca709da7e7138d542 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:23 -0800 Subject: mfd: mfd_cell is now implicitly available to mc13xxx drivers The cell's platform_data is now accessed with a helper function; change clients to use that, and remove the now-unused data_size. Note that mfd-core no longer makes a copy of platform_data, but the mc13xxx-core driver creates the pdata structures on the stack. In order to get around that, the various ARM mach types that set the pdata have been changed to hold the variable in static (global) memory. Also note that __initdata references in aforementioned pdata structs have been dropped. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- arch/arm/mach-imx/mach-mx27_3ds.c | 11 +++++++---- arch/arm/mach-imx/mach-pcm038.c | 10 ++++++---- arch/arm/mach-mx3/mach-mx31_3ds.c | 10 ++++++---- arch/arm/mach-mx3/mach-mx31moboard.c | 6 ++++-- drivers/leds/leds-mc13783.c | 7 ++++--- drivers/mfd/mc13xxx-core.c | 18 +++++------------- drivers/regulator/mc13783-regulator.c | 7 +++---- drivers/regulator/mc13892-regulator.c | 7 +++---- include/linux/mfd/mc13xxx.h | 3 +-- 9 files changed, 39 insertions(+), 40 deletions(-) (limited to 'include/linux/mfd') diff --git a/arch/arm/mach-imx/mach-mx27_3ds.c b/arch/arm/mach-imx/mach-mx27_3ds.c index 614b3c00c4a..6e1accf93f8 100644 --- a/arch/arm/mach-imx/mach-mx27_3ds.c +++ b/arch/arm/mach-imx/mach-mx27_3ds.c @@ -232,10 +232,13 @@ static struct mc13xxx_regulator_init_data mx27_3ds_regulators[] = { }; /* MC13783 */ -static struct mc13xxx_platform_data mc13783_pdata __initdata = { - .regulators = mx27_3ds_regulators, - .num_regulators = ARRAY_SIZE(mx27_3ds_regulators), - .flags = MC13XXX_USE_REGULATOR, +static struct mc13xxx_platform_data mc13783_pdata = { + .regulators = { + .regulators = mx27_3ds_regulators, + .num_regulators = ARRAY_SIZE(mx27_3ds_regulators), + + }, + .flags = MC13783_USE_REGULATOR, }; /* SPI */ diff --git a/arch/arm/mach-imx/mach-pcm038.c b/arch/arm/mach-imx/mach-pcm038.c index 38c77084b61..4cbce6d0fef 100644 --- a/arch/arm/mach-imx/mach-pcm038.c +++ b/arch/arm/mach-imx/mach-pcm038.c @@ -263,10 +263,12 @@ static struct mc13xxx_regulator_init_data pcm038_regulators[] = { }; static struct mc13xxx_platform_data pcm038_pmic = { - .regulators = pcm038_regulators, - .num_regulators = ARRAY_SIZE(pcm038_regulators), - .flags = MC13XXX_USE_ADC | MC13XXX_USE_REGULATOR | - MC13XXX_USE_TOUCHSCREEN, + .regulators = { + .regulators = pcm038_regulators, + .num_regulators = ARRAY_SIZE(pcm038_regulators), + }, + .flags = MC13783_USE_ADC | MC13783_USE_REGULATOR | + MC13783_USE_TOUCHSCREEN, }; static struct spi_board_info pcm038_spi_board_info[] __initdata = { diff --git a/arch/arm/mach-mx3/mach-mx31_3ds.c b/arch/arm/mach-mx3/mach-mx31_3ds.c index 544d3e414f5..034be624d35 100644 --- a/arch/arm/mach-mx3/mach-mx31_3ds.c +++ b/arch/arm/mach-mx3/mach-mx31_3ds.c @@ -488,10 +488,12 @@ static struct mc13xxx_regulator_init_data mx31_3ds_regulators[] = { }; /* MC13783 */ -static struct mc13xxx_platform_data mc13783_pdata __initdata = { - .regulators = mx31_3ds_regulators, - .num_regulators = ARRAY_SIZE(mx31_3ds_regulators), - .flags = MC13XXX_USE_REGULATOR | MC13XXX_USE_TOUCHSCREEN +static struct mc13xxx_platform_data mc13783_pdata = { + .regulators = { + .regulators = mx31_3ds_regulators, + .num_regulators = ARRAY_SIZE(mx31_3ds_regulators), + }, + .flags = MC13783_USE_REGULATOR | MC13783_USE_TOUCHSCREEN, }; /* SPI */ diff --git a/arch/arm/mach-mx3/mach-mx31moboard.c b/arch/arm/mach-mx3/mach-mx31moboard.c index 6f3692bccb8..3a021b01161 100644 --- a/arch/arm/mach-mx3/mach-mx31moboard.c +++ b/arch/arm/mach-mx3/mach-mx31moboard.c @@ -268,8 +268,10 @@ static struct mc13783_leds_platform_data moboard_leds = { }; static struct mc13xxx_platform_data moboard_pmic = { - .regulators = moboard_regulators, - .num_regulators = ARRAY_SIZE(moboard_regulators), + .regulators = { + .regulators = moboard_regulators, + .num_regulators = ARRAY_SIZE(moboard_regulators), + }, .leds = &moboard_leds, .flags = MC13XXX_USE_REGULATOR | MC13XXX_USE_RTC | MC13XXX_USE_ADC | MC13XXX_USE_LED, diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index f05bb08d0f0..06a5bb48470 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -22,6 +22,7 @@ #include #include #include +#include #include struct mc13783_led { @@ -183,7 +184,7 @@ static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current) static int __devinit mc13783_leds_prepare(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); int ret = 0; int reg = 0; @@ -264,7 +265,7 @@ out: static int __devinit mc13783_led_probe(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); struct mc13783_led_platform_data *led_cur; struct mc13783_led *led, *led_dat; int ret, i; @@ -351,7 +352,7 @@ err_free: static int __devexit mc13783_led_remove(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); struct mc13783_led *led = platform_get_drvdata(pdev); struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); int i; diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index b9fcaf0004d..30807d3a653 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -683,14 +683,13 @@ out: EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion); static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, - const char *format, void *pdata, size_t pdata_size) + const char *format, void *pdata) { char buf[30]; const char *name = mc13xxx_get_chipname(mc13xxx); struct mfd_cell cell = { .platform_data = pdata, - .data_size = pdata_size, }; /* there is no asnprintf in the kernel :-( */ @@ -706,7 +705,7 @@ static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, static int mc13xxx_add_subdevice(struct mc13xxx *mc13xxx, const char *format) { - return mc13xxx_add_subdevice_pdata(mc13xxx, format, NULL, 0); + return mc13xxx_add_subdevice_pdata(mc13xxx, format, NULL); } static int mc13xxx_probe(struct spi_device *spi) @@ -764,13 +763,8 @@ err_revision: mc13xxx_add_subdevice(mc13xxx, "%s-codec"); if (pdata->flags & MC13XXX_USE_REGULATOR) { - struct mc13xxx_regulator_platform_data regulator_pdata = { - .num_regulators = pdata->num_regulators, - .regulators = pdata->regulators, - }; - mc13xxx_add_subdevice_pdata(mc13xxx, "%s-regulator", - ®ulator_pdata, sizeof(regulator_pdata)); + &pdata->regulators); } if (pdata->flags & MC13XXX_USE_RTC) @@ -779,10 +773,8 @@ err_revision: if (pdata->flags & MC13XXX_USE_TOUCHSCREEN) mc13xxx_add_subdevice(mc13xxx, "%s-ts"); - if (pdata->flags & MC13XXX_USE_LED) { - mc13xxx_add_subdevice_pdata(mc13xxx, "%s-led", - pdata->leds, sizeof(*pdata->leds)); - } + if (pdata->flags & MC13XXX_USE_LED) + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-led", pdata->leds); return 0; } diff --git a/drivers/regulator/mc13783-regulator.c b/drivers/regulator/mc13783-regulator.c index 3e5d0c3b4e5..23249cb0a8b 100644 --- a/drivers/regulator/mc13783-regulator.c +++ b/drivers/regulator/mc13783-regulator.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -336,8 +337,7 @@ static int __devinit mc13783_regulator_probe(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv; struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent); - struct mc13783_regulator_platform_data *pdata = - dev_get_platdata(&pdev->dev); + struct mc13783_regulator_platform_data *pdata = mfd_get_data(pdev); struct mc13783_regulator_init_data *init_data; int i, ret; @@ -381,8 +381,7 @@ err: static int __devexit mc13783_regulator_remove(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); - struct mc13783_regulator_platform_data *pdata = - dev_get_platdata(&pdev->dev); + struct mc13783_regulator_platform_data *pdata = mfd_get_data(pdev); int i; platform_set_drvdata(pdev, NULL); diff --git a/drivers/regulator/mc13892-regulator.c b/drivers/regulator/mc13892-regulator.c index 1b8f7398a4a..6f15168e5ed 100644 --- a/drivers/regulator/mc13892-regulator.c +++ b/drivers/regulator/mc13892-regulator.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -520,8 +521,7 @@ static int __devinit mc13892_regulator_probe(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv; struct mc13xxx *mc13892 = dev_get_drvdata(pdev->dev.parent); - struct mc13xxx_regulator_platform_data *pdata = - dev_get_platdata(&pdev->dev); + struct mc13xxx_regulator_platform_data *pdata = mfd_get_data(pdev); struct mc13xxx_regulator_init_data *init_data; int i, ret; u32 val; @@ -595,8 +595,7 @@ err_free: static int __devexit mc13892_regulator_remove(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); - struct mc13xxx_regulator_platform_data *pdata = - dev_get_platdata(&pdev->dev); + struct mc13xxx_regulator_platform_data *pdata = mfd_get_data(pdev); int i; platform_set_drvdata(pdev, NULL); diff --git a/include/linux/mfd/mc13xxx.h b/include/linux/mfd/mc13xxx.h index a1d391b40e6..c064beaaccb 100644 --- a/include/linux/mfd/mc13xxx.h +++ b/include/linux/mfd/mc13xxx.h @@ -146,8 +146,7 @@ struct mc13xxx_platform_data { #define MC13XXX_USE_LED (1 << 5) unsigned int flags; - int num_regulators; - struct mc13xxx_regulator_init_data *regulators; + struct mc13xxx_regulator_platform_data regulators; struct mc13xxx_leds_platform_data *leds; }; -- cgit v1.2.3-70-g09d2 From 40e03f571b2e63827f2afb90ea9aa459612c29e3 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:24 -0800 Subject: mfd: Drop data_size from mfd_cell struct Now that there are no more users of this, drop it. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- include/linux/mfd/core.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'include/linux/mfd') diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index aefc378f8dc..923ec2591eb 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -36,9 +36,6 @@ struct mfd_cell { /* platform_data can be used to pass data to "generic" drivers */ void *platform_data; - /* unused */ - size_t data_size; - /* * These resources can be specified relative to the parent device. * For accessing hardware you should use resources from the platform dev -- cgit v1.2.3-70-g09d2 From 65e523595a31813c0f20ffd249792c60e253438e Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:25 -0800 Subject: mfd: Rename platform_data field of mfd_cell to mfd_data Rename the platform_data variable to imply a distinction between common platform_data driver usage (typically accessed via pdev->dev.platform_data) and the way MFD passes data down to clients (using a wrapper named mfd_get_data). All clients have already been changed to use the wrapper function, so this can be a quick single-commit change that only touches things in drivers/mfd. Signed-off-by: Andres Salomon Acked-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab3100-core.c | 2 +- drivers/mfd/ab3550-core.c | 2 +- drivers/mfd/janz-cmodio.c | 2 +- drivers/mfd/mc13xxx-core.c | 2 +- drivers/mfd/timberdale.c | 54 ++++++++++++++++++++++----------------------- drivers/mfd/twl4030-codec.c | 4 ++-- drivers/mfd/wl1273-core.c | 4 ++-- include/linux/mfd/core.h | 8 +++---- 8 files changed, 39 insertions(+), 39 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 2dcab8643e7..a751927047a 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -950,7 +950,7 @@ static int __devinit ab3100_probe(struct i2c_client *client, /* Set up and register the platform devices. */ for (i = 0; i < ARRAY_SIZE(ab3100_devs); i++) - ab3100_devs[i].platform_data = ab3100_plf_data; + ab3100_devs[i].mfd_data = ab3100_plf_data; err = mfd_add_devices(&client->dev, 0, ab3100_devs, ARRAY_SIZE(ab3100_devs), NULL, 0); diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c index c8e555a9ee6..c12d0428522 100644 --- a/drivers/mfd/ab3550-core.c +++ b/drivers/mfd/ab3550-core.c @@ -1321,7 +1321,7 @@ static int __init ab3550_probe(struct i2c_client *client, /* Set up and register the platform devices. */ for (i = 0; i < AB3550_NUM_DEVICES; i++) - ab3550_devs[i].platform_data = ab3550_plf_data->dev_data[i]; + ab3550_devs[i].mfd_data = ab3550_plf_data->dev_data[i]; err = mfd_add_devices(&client->dev, 0, ab3550_devs, ARRAY_SIZE(ab3550_devs), NULL, diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c index 58de1e28788..fc4191137e9 100644 --- a/drivers/mfd/janz-cmodio.c +++ b/drivers/mfd/janz-cmodio.c @@ -86,7 +86,7 @@ static int __devinit cmodio_setup_subdevice(struct cmodio_device *priv, /* Add platform data */ pdata->modno = modno; - cell->platform_data = pdata; + cell->mfd_data = pdata; /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */ res->flags = IORESOURCE_MEM; diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 30807d3a653..97a3b400ed4 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -689,7 +689,7 @@ static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, const char *name = mc13xxx_get_chipname(mc13xxx); struct mfd_cell cell = { - .platform_data = pdata, + .mfd_data = pdata, }; /* there is no asnprintf in the kernel :-( */ diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 6353921c172..94c6c8afad1 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -384,7 +384,7 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .platform_data = &timb_dma_platform_data, + .mfd_data = &timb_dma_platform_data, }, { .name = "timb-uart", @@ -395,37 +395,37 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .platform_data = &timberdale_xiic_platform_data, + .mfd_data = &timberdale_xiic_platform_data, }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, + .mfd_data = &timberdale_gpio_platform_data, }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .platform_data = &timberdale_video_platform_data, + .mfd_data = &timberdale_video_platform_data, }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .platform_data = &timberdale_radio_platform_data, + .mfd_data = &timberdale_radio_platform_data, }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .platform_data = &timberdale_xspi_platform_data, + .mfd_data = &timberdale_xspi_platform_data, }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .platform_data = &timberdale_ks8842_platform_data, + .mfd_data = &timberdale_ks8842_platform_data, }, }; @@ -434,7 +434,7 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .platform_data = &timb_dma_platform_data, + .mfd_data = &timb_dma_platform_data, }, { .name = "timb-uart", @@ -450,13 +450,13 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .platform_data = &timberdale_xiic_platform_data, + .mfd_data = &timberdale_xiic_platform_data, }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, + .mfd_data = &timberdale_gpio_platform_data, }, { .name = "timb-mlogicore", @@ -467,25 +467,25 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .platform_data = &timberdale_video_platform_data, + .mfd_data = &timberdale_video_platform_data, }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .platform_data = &timberdale_radio_platform_data, + .mfd_data = &timberdale_radio_platform_data, }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .platform_data = &timberdale_xspi_platform_data, + .mfd_data = &timberdale_xspi_platform_data, }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .platform_data = &timberdale_ks8842_platform_data, + .mfd_data = &timberdale_ks8842_platform_data, }, }; @@ -494,7 +494,7 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .platform_data = &timb_dma_platform_data, + .mfd_data = &timb_dma_platform_data, }, { .name = "timb-uart", @@ -505,31 +505,31 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .platform_data = &timberdale_xiic_platform_data, + .mfd_data = &timberdale_xiic_platform_data, }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, + .mfd_data = &timberdale_gpio_platform_data, }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .platform_data = &timberdale_video_platform_data, + .mfd_data = &timberdale_video_platform_data, }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .platform_data = &timberdale_radio_platform_data, + .mfd_data = &timberdale_radio_platform_data, }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .platform_data = &timberdale_xspi_platform_data, + .mfd_data = &timberdale_xspi_platform_data, }, }; @@ -538,7 +538,7 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .platform_data = &timb_dma_platform_data, + .mfd_data = &timb_dma_platform_data, }, { .name = "timb-uart", @@ -549,37 +549,37 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { .name = "ocores-i2c", .num_resources = ARRAY_SIZE(timberdale_ocores_resources), .resources = timberdale_ocores_resources, - .platform_data = &timberdale_ocores_platform_data, + .mfd_data = &timberdale_ocores_platform_data, }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .platform_data = &timberdale_gpio_platform_data, + .mfd_data = &timberdale_gpio_platform_data, }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .platform_data = &timberdale_video_platform_data, + .mfd_data = &timberdale_video_platform_data, }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .platform_data = &timberdale_radio_platform_data, + .mfd_data = &timberdale_radio_platform_data, }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .platform_data = &timberdale_xspi_platform_data, + .mfd_data = &timberdale_xspi_platform_data, }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .platform_data = &timberdale_ks8842_platform_data, + .mfd_data = &timberdale_ks8842_platform_data, }, }; diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c index 0f5742655b4..c02fded316c 100644 --- a/drivers/mfd/twl4030-codec.c +++ b/drivers/mfd/twl4030-codec.c @@ -208,13 +208,13 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) if (pdata->audio) { cell = &codec->cells[childs]; cell->name = "twl4030-codec"; - cell->platform_data = pdata->audio; + cell->mfd_data = pdata->audio; childs++; } if (pdata->vibra) { cell = &codec->cells[childs]; cell->name = "twl4030-vibra"; - cell->platform_data = pdata->vibra; + cell->mfd_data = pdata->vibra; childs++; } diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c index b4823bf9523..529d65ba535 100644 --- a/drivers/mfd/wl1273-core.c +++ b/drivers/mfd/wl1273-core.c @@ -78,7 +78,7 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, cell = &core->cells[children]; cell->name = "wl1273_fm_radio"; - cell->platform_data = &core; + cell->mfd_data = &core; children++; if (pdata->children & WL1273_CODEC_CHILD) { @@ -86,7 +86,7 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, dev_dbg(&client->dev, "%s: Have codec.\n", __func__); cell->name = "wl1273-codec"; - cell->platform_data = &core; + cell->mfd_data = &core; children++; } diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 923ec2591eb..f317fe4f836 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -33,8 +33,8 @@ struct mfd_cell { /* driver-specific data for MFD-aware "cell" drivers */ void *driver_data; - /* platform_data can be used to pass data to "generic" drivers */ - void *platform_data; + /* mfd_data can be used to pass data to client drivers */ + void *mfd_data; /* * These resources can be specified relative to the parent device. @@ -64,11 +64,11 @@ static inline const struct mfd_cell *mfd_get_cell(struct platform_device *pdev) /* * Given a platform device that's been created by mfd_add_devices(), fetch - * the .platform_data entry from the mfd_cell that created it. + * the .mfd_data entry from the mfd_cell that created it. */ static inline void *mfd_get_data(struct platform_device *pdev) { - return mfd_get_cell(pdev)->platform_data; + return mfd_get_cell(pdev)->mfd_data; } extern int mfd_add_devices(struct device *parent, int id, -- cgit v1.2.3-70-g09d2 From dcb50e83bb86d66d3554ba9c365488669c84d037 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:33 -0800 Subject: mfd: Remove driver_data field from mfd_cell All users of this have now been switched over to using mfd_data; it can go away now. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 1 - include/linux/mfd/core.h | 3 --- 2 files changed, 4 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 21a39dc64ea..01115f686df 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -37,7 +37,6 @@ static int mfd_add_device(struct device *parent, int id, goto fail_device; pdev->dev.parent = parent; - platform_set_drvdata(pdev, cell->driver_data); ret = platform_device_add_data(pdev, cell, sizeof(*cell)); if (ret) diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index f317fe4f836..71cd1f983cc 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -30,9 +30,6 @@ struct mfd_cell { int (*suspend)(struct platform_device *dev); int (*resume)(struct platform_device *dev); - /* driver-specific data for MFD-aware "cell" drivers */ - void *driver_data; - /* mfd_data can be used to pass data to client drivers */ void *mfd_data; -- cgit v1.2.3-70-g09d2 From 1e29af62f2b285bd18685da93c3ce8c33ca2d1db Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:34 -0800 Subject: mfd: Add refcounting support to mfd_cells This provides convenience functions for sharing of cells across multiple mfd clients. Mfd drivers can provide enable/disable hooks to actually tweak the hardware, and clients can call mfd_shared_cell_{en,dis}able without having to worry about whether or not another client happens to have enabled or disabled the cell/hardware. Note that this is purely optional; drivers can continue to use the mfd_cell's enable/disable hooks for their own purposes, if desired. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 64 +++++++++++++++++++++++++++++++++++++++++++++--- include/linux/mfd/core.h | 14 ++++++++++- 2 files changed, 73 insertions(+), 5 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 01115f686df..ca789f88230 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -18,6 +18,43 @@ #include #include +int mfd_shared_cell_enable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + int err = 0; + + /* only call enable hook if the cell wasn't previously enabled */ + if (atomic_inc_return(cell->usage_count) == 1) + err = cell->enable(pdev); + + /* if the enable hook failed, decrement counter to allow retries */ + if (err) + atomic_dec(cell->usage_count); + + return err; +} +EXPORT_SYMBOL(mfd_shared_cell_enable); + +int mfd_shared_cell_disable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + int err = 0; + + /* only disable if no other clients are using it */ + if (atomic_dec_return(cell->usage_count) == 0) + err = cell->disable(pdev); + + /* if the disable hook failed, increment to allow retries */ + if (err) + atomic_inc(cell->usage_count); + + /* sanity check; did someone call disable too many times? */ + WARN_ON(atomic_read(cell->usage_count) < 0); + + return err; +} +EXPORT_SYMBOL(mfd_shared_cell_disable); + static int mfd_add_device(struct device *parent, int id, const struct mfd_cell *cell, struct resource *mem_base, @@ -96,14 +133,22 @@ fail_alloc: } int mfd_add_devices(struct device *parent, int id, - const struct mfd_cell *cells, int n_devs, + struct mfd_cell *cells, int n_devs, struct resource *mem_base, int irq_base) { int i; int ret = 0; + atomic_t *cnts; + + /* initialize reference counting for all cells */ + cnts = kcalloc(sizeof(*cnts), n_devs, GFP_KERNEL); + if (!cnts) + return -ENOMEM; for (i = 0; i < n_devs; i++) { + atomic_set(&cnts[i], 0); + cells[i].usage_count = &cnts[i]; ret = mfd_add_device(parent, id, cells + i, mem_base, irq_base); if (ret) break; @@ -116,15 +161,26 @@ int mfd_add_devices(struct device *parent, int id, } EXPORT_SYMBOL(mfd_add_devices); -static int mfd_remove_devices_fn(struct device *dev, void *unused) +static int mfd_remove_devices_fn(struct device *dev, void *c) { - platform_device_unregister(to_platform_device(dev)); + struct platform_device *pdev = to_platform_device(dev); + const struct mfd_cell *cell = mfd_get_cell(pdev); + atomic_t **usage_count = c; + + /* find the base address of usage_count pointers (for freeing) */ + if (!*usage_count || (cell->usage_count < *usage_count)) + *usage_count = cell->usage_count; + + platform_device_unregister(pdev); return 0; } void mfd_remove_devices(struct device *parent) { - device_for_each_child(parent, NULL, mfd_remove_devices_fn); + atomic_t *cnts = NULL; + + device_for_each_child(parent, &cnts, mfd_remove_devices_fn); + kfree(cnts); } EXPORT_SYMBOL(mfd_remove_devices); diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 71cd1f983cc..22a2f5ebd9d 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -25,8 +25,11 @@ struct mfd_cell { const char *name; int id; + /* refcounting for multiple drivers to use a single cell */ + atomic_t *usage_count; int (*enable)(struct platform_device *dev); int (*disable)(struct platform_device *dev); + int (*suspend)(struct platform_device *dev); int (*resume)(struct platform_device *dev); @@ -50,6 +53,15 @@ struct mfd_cell { bool pm_runtime_no_callbacks; }; +/* + * Convenience functions for clients using shared cells. Refcounting + * happens automatically, with the cell's enable/disable callbacks + * being called only when a device is first being enabled or no other + * clients are making use of it. + */ +extern int mfd_shared_cell_enable(struct platform_device *pdev); +extern int mfd_shared_cell_disable(struct platform_device *pdev); + /* * Given a platform device that's been created by mfd_add_devices(), fetch * the mfd_cell that created it. @@ -69,7 +81,7 @@ static inline void *mfd_get_data(struct platform_device *pdev) } extern int mfd_add_devices(struct device *parent, int id, - const struct mfd_cell *cells, int n_devs, + struct mfd_cell *cells, int n_devs, struct resource *mem_base, int irq_base); -- cgit v1.2.3-70-g09d2 From a9bbba996302344b1fac7773cf8198f6fee35ac1 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 17 Feb 2011 19:07:35 -0800 Subject: mfd: add platform_device sharing support for mfd This adds functions to enable platform_device sharing for mfd clients. Each platform driver (mfd client) that wants to share an mfd_cell's platform_device uses the mfd_shared_platform_driver_{un,}register() functions instead of platform_driver_{un,}register(). Along with registering the platform driver, these also register a new platform device with the same characteristics as the original cell, but a different name. Given an mfd_cell with the name "foo", drivers that want to share access to its resources can call mfd_shared_platform_driver_register with platform drivers named (for example) "bar" and "baz". This will register two platform devices and drivers named "bar" and "baz" that share the same cell as the platform device "foo". The drivers can then call "foo" cell's enable hooks (or mfd_shared_cell_enable) to enable resources, and obtain platform resources as they normally would. This deals with platform handling only; mfd driver-specific details, hardware handling, refcounting, etc are all dealt with separately. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- drivers/mfd/mfd-core.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/core.h | 9 +++++++ 2 files changed, 70 insertions(+) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index ca789f88230..bb2264cc410 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -184,5 +184,66 @@ void mfd_remove_devices(struct device *parent) } EXPORT_SYMBOL(mfd_remove_devices); +static int add_shared_platform_device(const char *cell, const char *name) +{ + struct mfd_cell cell_entry; + struct device *dev; + struct platform_device *pdev; + int err; + + /* check if we've already registered a device (don't fail if we have) */ + if (bus_find_device_by_name(&platform_bus_type, NULL, name)) + return 0; + + /* fetch the parent cell's device (should already be registered!) */ + dev = bus_find_device_by_name(&platform_bus_type, NULL, cell); + if (!dev) { + printk(KERN_ERR "failed to find device for cell %s\n", cell); + return -ENODEV; + } + pdev = to_platform_device(dev); + memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry)); + + WARN_ON(!cell_entry.enable); + + cell_entry.name = name; + err = mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0); + if (err) + dev_err(dev, "MFD add devices failed: %d\n", err); + return err; +} + +int mfd_shared_platform_driver_register(struct platform_driver *drv, + const char *cellname) +{ + int err; + + err = add_shared_platform_device(cellname, drv->driver.name); + if (err) + printk(KERN_ERR "failed to add platform device %s\n", + drv->driver.name); + + err = platform_driver_register(drv); + if (err) + printk(KERN_ERR "failed to add platform driver %s\n", + drv->driver.name); + + return err; +} +EXPORT_SYMBOL(mfd_shared_platform_driver_register); + +void mfd_shared_platform_driver_unregister(struct platform_driver *drv) +{ + struct device *dev; + + dev = bus_find_device_by_name(&platform_bus_type, NULL, + drv->driver.name); + if (dev) + platform_device_unregister(to_platform_device(dev)); + + platform_driver_unregister(drv); +} +EXPORT_SYMBOL(mfd_shared_platform_driver_unregister); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 22a2f5ebd9d..ed9970412cc 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -87,4 +87,13 @@ extern int mfd_add_devices(struct device *parent, int id, extern void mfd_remove_devices(struct device *parent); +/* + * For MFD drivers with clients sharing access to resources, these create + * multiple platform devices per cell. Contention handling must still be + * handled via drivers (ie, with enable/disable hooks). + */ +extern int mfd_shared_platform_driver_register(struct platform_driver *drv, + const char *cellname); +extern void mfd_shared_platform_driver_unregister(struct platform_driver *drv); + #endif -- cgit v1.2.3-70-g09d2 From 93619c2106e2c968a260ebffb75ddc5efa567c16 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Mar 2011 20:12:46 +0000 Subject: mfd: Add platform data to support multiple WM831x devices per board If a system contains multiple WM831x devices we need to pass a device number through to the MFD so that we use unique device IDs when we instantiate child devices. In order to get support for this into 2.6.39 add some platform data to support the configuration, but no implementation as yet. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- include/linux/mfd/wm831x/pdata.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include/linux/mfd') diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h index ac3aa73943e..afe4db49402 100644 --- a/include/linux/mfd/wm831x/pdata.h +++ b/include/linux/mfd/wm831x/pdata.h @@ -104,6 +104,9 @@ struct wm831x_watchdog_pdata { #define WM831X_MAX_ISINK 2 struct wm831x_pdata { + /** Used to distinguish multiple WM831x chips */ + int wm831x_num; + /** Called before subdevices are set up */ int (*pre_init)(struct wm831x *wm831x); /** Called after subdevices are set up */ -- cgit v1.2.3-70-g09d2 From adceed6263887e04721b477e6504aa24789f827d Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Wed, 2 Mar 2011 11:51:11 +0100 Subject: mfd: ab8500 chip revision 3.0 support This patch adds support for ab8500 chip revision cut 3.0. Also rephrased from Changes to Author in the header. Signed-off-by: Mattias Wallin Acked-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab8500-core.c | 9 +++++---- include/linux/mfd/ab8500.h | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index b6887014d68..8d6c29251dc 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -4,7 +4,7 @@ * License Terms: GNU General Public License v2 * Author: Srinidhi Kasagar * Author: Rabin Vincent - * Changes: Mattias Wallin + * Author: Mattias Wallin */ #include @@ -686,9 +686,10 @@ int __devinit ab8500_init(struct ab8500 *ab8500) * 0x10 - Cut 1.0 * 0x11 - Cut 1.1 * 0x20 - Cut 2.0 + * 0x30 - Cut 3.0 */ - if (value == 0x0 || value == 0x10 || value == 0x11 || value == 0x20) { - ab8500->revision = value; + if (value == 0x0 || value == 0x10 || value == 0x11 || value == 0x20 || + value == 0x30) { dev_info(ab8500->dev, "detected chip, revision: %#x\n", value); } else { dev_err(ab8500->dev, "unknown chip, revision: %#x\n", value); @@ -764,6 +765,6 @@ int __devexit ab8500_exit(struct ab8500 *ab8500) return 0; } -MODULE_AUTHOR("Srinidhi Kasagar, Rabin Vincent"); +MODULE_AUTHOR("Mattias Wallin, Srinidhi Kasagar, Rabin Vincent"); MODULE_DESCRIPTION("AB8500 MFD core"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 37f56b7c4c1..56f8dea7215 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -111,8 +111,8 @@ * @dev: parent device * @lock: read/write operations lock * @irq_lock: genirq bus lock - * @revision: chip revision * @irq: irq line + * @chip_id: chip revision id * @write: register write * @read: register read * @rx_buf: rx buf for SPI @@ -124,7 +124,7 @@ struct ab8500 { struct device *dev; struct mutex lock; struct mutex irq_lock; - int revision; + int irq_base; int irq; u8 chip_id; -- cgit v1.2.3-70-g09d2 From f77289ac25b0c81acbed6f9c17cb14809a04e18b Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Thu, 3 Mar 2011 09:51:58 -0800 Subject: mfd: Rename mfd_shared_cell_{en,dis}able to drop the "shared" part As requested by Samuel, there's not really any reason to have "shared" in the name. This also modifies the only user of the function, as well. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- arch/x86/platform/olpc/olpc-xo1.c | 4 ++-- drivers/mfd/mfd-core.c | 8 ++++---- include/linux/mfd/core.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'include/linux/mfd') diff --git a/arch/x86/platform/olpc/olpc-xo1.c b/arch/x86/platform/olpc/olpc-xo1.c index f4155354a7b..99513642a0e 100644 --- a/arch/x86/platform/olpc/olpc-xo1.c +++ b/arch/x86/platform/olpc/olpc-xo1.c @@ -63,7 +63,7 @@ static int __devinit olpc_xo1_probe(struct platform_device *pdev) if (!machine_is_olpc()) return -ENODEV; - err = mfd_shared_cell_enable(pdev); + err = mfd_cell_enable(pdev); if (err) return err; @@ -88,7 +88,7 @@ static int __devinit olpc_xo1_probe(struct platform_device *pdev) static int __devexit olpc_xo1_remove(struct platform_device *pdev) { - mfd_shared_cell_disable(pdev); + mfd_cell_disable(pdev); if (strcmp(pdev->name, "olpc-xo1-pms") == 0) pms_base = 0; diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index bb2264cc410..79eda0264fb 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -18,7 +18,7 @@ #include #include -int mfd_shared_cell_enable(struct platform_device *pdev) +int mfd_cell_enable(struct platform_device *pdev) { const struct mfd_cell *cell = mfd_get_cell(pdev); int err = 0; @@ -33,9 +33,9 @@ int mfd_shared_cell_enable(struct platform_device *pdev) return err; } -EXPORT_SYMBOL(mfd_shared_cell_enable); +EXPORT_SYMBOL(mfd_cell_enable); -int mfd_shared_cell_disable(struct platform_device *pdev) +int mfd_cell_disable(struct platform_device *pdev) { const struct mfd_cell *cell = mfd_get_cell(pdev); int err = 0; @@ -53,7 +53,7 @@ int mfd_shared_cell_disable(struct platform_device *pdev) return err; } -EXPORT_SYMBOL(mfd_shared_cell_disable); +EXPORT_SYMBOL(mfd_cell_disable); static int mfd_add_device(struct device *parent, int id, const struct mfd_cell *cell, diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index ed9970412cc..1408bf8eed5 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -59,8 +59,8 @@ struct mfd_cell { * being called only when a device is first being enabled or no other * clients are making use of it. */ -extern int mfd_shared_cell_enable(struct platform_device *pdev); -extern int mfd_shared_cell_disable(struct platform_device *pdev); +extern int mfd_cell_enable(struct platform_device *pdev); +extern int mfd_cell_disable(struct platform_device *pdev); /* * Given a platform device that's been created by mfd_add_devices(), fetch -- cgit v1.2.3-70-g09d2 From 527e7e9a82ec95cdb8f694855004b3d262efd09f Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 4 Mar 2011 15:50:26 +0900 Subject: mfd: MAX8997/8966 support MAX8997/MAX8966 chip is a multi-function device with I2C bussses. The chip includes PMIC, RTC, Fuel Gauge, MUIC, Haptic, Flash control, and Battery (charging) control. This patch is an initial release of a MAX8997/8966 driver that supports to enable the chip with its primary I2C bus that connects every device mentioned above except for Fuel Gauge, which uses another I2C bus. The fuel gauge is not supported by this mfd driver and is supported by a seperated driver of MAX17042 Fuel Gauge (yes, the fuel gauge part is compatible with MAX17042). Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 12 + drivers/mfd/Makefile | 1 + drivers/mfd/max8997.c | 427 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997-private.h | 347 +++++++++++++++++++++++++++++ include/linux/mfd/max8997.h | 88 ++++++++ 5 files changed, 875 insertions(+) create mode 100644 drivers/mfd/max8997.c create mode 100644 include/linux/mfd/max8997-private.h create mode 100644 include/linux/mfd/max8997.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 50c476964e4..642168c8d3b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -314,6 +314,18 @@ config MFD_MAX8925 accessing the device, additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX8997 + bool "Maxim Semiconductor MAX8997/8966 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX8998/8966. + This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic, + MUIC controls on chip. + This driver provies common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. + config MFD_MAX8998 bool "Maxim Semiconductor MAX8998/National LP3974 PMIC Support" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 25f3c755148..0e9f0c51449 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o +obj-$(CONFIG_MFD_MAX8997) += max8997.o obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o pcf50633-objs := pcf50633-core.o pcf50633-irq.o diff --git a/drivers/mfd/max8997.c b/drivers/mfd/max8997.c new file mode 100644 index 00000000000..5d1fca0277e --- /dev/null +++ b/drivers/mfd/max8997.c @@ -0,0 +1,427 @@ +/* + * max8997.c - mfd core driver for the Maxim 8966 and 8997 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998.c + */ + +#include +#include +#include +#include +#include +#include +#include + +#define I2C_ADDR_PMIC (0xCC >> 1) +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_BATTERY (0x6C >> 1) +#define I2C_ADDR_RTC (0x0C >> 1) +#define I2C_ADDR_HAPTIC (0x90 >> 1) + +static struct mfd_cell max8997_devs[] = { + { .name = "max8997-pmic", }, + { .name = "max8997-rtc", }, + { .name = "max8997-battery", }, + { .name = "max8997-haptic", }, + { .name = "max8997-muic", }, + { .name = "max8997-flash", }, +}; + +int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max8997_read_reg); + +int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max8997_bulk_read); + +int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&max8997->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max8997_write_reg); + +int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max8997_bulk_write); + +int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&max8997->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max8997_update_reg); + +static int max8997_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max8997_dev *max8997; + struct max8997_platform_data *pdata = i2c->dev.platform_data; + int ret = 0; + + max8997 = kzalloc(sizeof(struct max8997_dev), GFP_KERNEL); + if (max8997 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max8997); + max8997->dev = &i2c->dev; + max8997->i2c = i2c; + max8997->type = id->driver_data; + + if (!pdata) + goto err; + + max8997->wakeup = pdata->wakeup; + + mutex_init(&max8997->iolock); + + max8997->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC); + i2c_set_clientdata(max8997->rtc, max8997); + max8997->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC); + i2c_set_clientdata(max8997->haptic, max8997); + max8997->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); + i2c_set_clientdata(max8997->muic, max8997); + + pm_runtime_set_active(max8997->dev); + + mfd_add_devices(max8997->dev, -1, max8997_devs, + ARRAY_SIZE(max8997_devs), + NULL, 0); + + /* + * TODO: enable others (flash, muic, rtc, battery, ...) and + * check the return value + */ + + if (ret < 0) + goto err_mfd; + + return ret; + +err_mfd: + mfd_remove_devices(max8997->dev); + i2c_unregister_device(max8997->muic); + i2c_unregister_device(max8997->haptic); + i2c_unregister_device(max8997->rtc); +err: + kfree(max8997); + return ret; +} + +static int max8997_i2c_remove(struct i2c_client *i2c) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max8997->dev); + i2c_unregister_device(max8997->muic); + i2c_unregister_device(max8997->haptic); + i2c_unregister_device(max8997->rtc); + kfree(max8997); + + return 0; +} + +static const struct i2c_device_id max8997_i2c_id[] = { + { "max8997", TYPE_MAX8997 }, + { "max8966", TYPE_MAX8966 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8998_i2c_id); + +u8 max8997_dumpaddr_pmic[] = { + MAX8997_REG_INT1MSK, + MAX8997_REG_INT2MSK, + MAX8997_REG_INT3MSK, + MAX8997_REG_INT4MSK, + MAX8997_REG_MAINCON1, + MAX8997_REG_MAINCON2, + MAX8997_REG_BUCKRAMP, + MAX8997_REG_BUCK1CTRL, + MAX8997_REG_BUCK1DVS1, + MAX8997_REG_BUCK1DVS2, + MAX8997_REG_BUCK1DVS3, + MAX8997_REG_BUCK1DVS4, + MAX8997_REG_BUCK1DVS5, + MAX8997_REG_BUCK1DVS6, + MAX8997_REG_BUCK1DVS7, + MAX8997_REG_BUCK1DVS8, + MAX8997_REG_BUCK2CTRL, + MAX8997_REG_BUCK2DVS1, + MAX8997_REG_BUCK2DVS2, + MAX8997_REG_BUCK2DVS3, + MAX8997_REG_BUCK2DVS4, + MAX8997_REG_BUCK2DVS5, + MAX8997_REG_BUCK2DVS6, + MAX8997_REG_BUCK2DVS7, + MAX8997_REG_BUCK2DVS8, + MAX8997_REG_BUCK3CTRL, + MAX8997_REG_BUCK3DVS, + MAX8997_REG_BUCK4CTRL, + MAX8997_REG_BUCK4DVS, + MAX8997_REG_BUCK5CTRL, + MAX8997_REG_BUCK5DVS1, + MAX8997_REG_BUCK5DVS2, + MAX8997_REG_BUCK5DVS3, + MAX8997_REG_BUCK5DVS4, + MAX8997_REG_BUCK5DVS5, + MAX8997_REG_BUCK5DVS6, + MAX8997_REG_BUCK5DVS7, + MAX8997_REG_BUCK5DVS8, + MAX8997_REG_BUCK6CTRL, + MAX8997_REG_BUCK6BPSKIPCTRL, + MAX8997_REG_BUCK7CTRL, + MAX8997_REG_BUCK7DVS, + MAX8997_REG_LDO1CTRL, + MAX8997_REG_LDO2CTRL, + MAX8997_REG_LDO3CTRL, + MAX8997_REG_LDO4CTRL, + MAX8997_REG_LDO5CTRL, + MAX8997_REG_LDO6CTRL, + MAX8997_REG_LDO7CTRL, + MAX8997_REG_LDO8CTRL, + MAX8997_REG_LDO9CTRL, + MAX8997_REG_LDO10CTRL, + MAX8997_REG_LDO11CTRL, + MAX8997_REG_LDO12CTRL, + MAX8997_REG_LDO13CTRL, + MAX8997_REG_LDO14CTRL, + MAX8997_REG_LDO15CTRL, + MAX8997_REG_LDO16CTRL, + MAX8997_REG_LDO17CTRL, + MAX8997_REG_LDO18CTRL, + MAX8997_REG_LDO21CTRL, + MAX8997_REG_MBCCTRL1, + MAX8997_REG_MBCCTRL2, + MAX8997_REG_MBCCTRL3, + MAX8997_REG_MBCCTRL4, + MAX8997_REG_MBCCTRL5, + MAX8997_REG_MBCCTRL6, + MAX8997_REG_OTPCGHCVS, + MAX8997_REG_SAFEOUTCTRL, + MAX8997_REG_LBCNFG1, + MAX8997_REG_LBCNFG2, + MAX8997_REG_BBCCTRL, + + MAX8997_REG_FLASH1_CUR, + MAX8997_REG_FLASH2_CUR, + MAX8997_REG_MOVIE_CUR, + MAX8997_REG_GSMB_CUR, + MAX8997_REG_BOOST_CNTL, + MAX8997_REG_LEN_CNTL, + MAX8997_REG_FLASH_CNTL, + MAX8997_REG_WDT_CNTL, + MAX8997_REG_MAXFLASH1, + MAX8997_REG_MAXFLASH2, + MAX8997_REG_FLASHSTATUSMASK, + + MAX8997_REG_GPIOCNTL1, + MAX8997_REG_GPIOCNTL2, + MAX8997_REG_GPIOCNTL3, + MAX8997_REG_GPIOCNTL4, + MAX8997_REG_GPIOCNTL5, + MAX8997_REG_GPIOCNTL6, + MAX8997_REG_GPIOCNTL7, + MAX8997_REG_GPIOCNTL8, + MAX8997_REG_GPIOCNTL9, + MAX8997_REG_GPIOCNTL10, + MAX8997_REG_GPIOCNTL11, + MAX8997_REG_GPIOCNTL12, + + MAX8997_REG_LDO1CONFIG, + MAX8997_REG_LDO2CONFIG, + MAX8997_REG_LDO3CONFIG, + MAX8997_REG_LDO4CONFIG, + MAX8997_REG_LDO5CONFIG, + MAX8997_REG_LDO6CONFIG, + MAX8997_REG_LDO7CONFIG, + MAX8997_REG_LDO8CONFIG, + MAX8997_REG_LDO9CONFIG, + MAX8997_REG_LDO10CONFIG, + MAX8997_REG_LDO11CONFIG, + MAX8997_REG_LDO12CONFIG, + MAX8997_REG_LDO13CONFIG, + MAX8997_REG_LDO14CONFIG, + MAX8997_REG_LDO15CONFIG, + MAX8997_REG_LDO16CONFIG, + MAX8997_REG_LDO17CONFIG, + MAX8997_REG_LDO18CONFIG, + MAX8997_REG_LDO21CONFIG, + + MAX8997_REG_DVSOKTIMER1, + MAX8997_REG_DVSOKTIMER2, + MAX8997_REG_DVSOKTIMER4, + MAX8997_REG_DVSOKTIMER5, +}; + +u8 max8997_dumpaddr_muic[] = { + MAX8997_MUIC_REG_INTMASK1, + MAX8997_MUIC_REG_INTMASK2, + MAX8997_MUIC_REG_INTMASK3, + MAX8997_MUIC_REG_CDETCTRL, + MAX8997_MUIC_REG_CONTROL1, + MAX8997_MUIC_REG_CONTROL2, + MAX8997_MUIC_REG_CONTROL3, +}; + +u8 max8997_dumpaddr_haptic[] = { + MAX8997_HAPTIC_REG_CONF1, + MAX8997_HAPTIC_REG_CONF2, + MAX8997_HAPTIC_REG_DRVCONF, + MAX8997_HAPTIC_REG_CYCLECONF1, + MAX8997_HAPTIC_REG_CYCLECONF2, + MAX8997_HAPTIC_REG_SIGCONF1, + MAX8997_HAPTIC_REG_SIGCONF2, + MAX8997_HAPTIC_REG_SIGCONF3, + MAX8997_HAPTIC_REG_SIGCONF4, + MAX8997_HAPTIC_REG_SIGDC1, + MAX8997_HAPTIC_REG_SIGDC2, + MAX8997_HAPTIC_REG_SIGPWMDC1, + MAX8997_HAPTIC_REG_SIGPWMDC2, + MAX8997_HAPTIC_REG_SIGPWMDC3, + MAX8997_HAPTIC_REG_SIGPWMDC4, +}; + +static int max8997_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_pmic[i], + &max8997->reg_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_muic[i], + &max8997->reg_dump[i + MAX8997_REG_PMIC_END]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_haptic[i], + &max8997->reg_dump[i + MAX8997_REG_PMIC_END + + MAX8997_MUIC_REG_END]); + + return 0; +} + +static int max8997_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_pmic[i], + max8997->reg_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_muic[i], + max8997->reg_dump[i + MAX8997_REG_PMIC_END]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_haptic[i], + max8997->reg_dump[i + MAX8997_REG_PMIC_END + + MAX8997_MUIC_REG_END]); + + return 0; +} + +const struct dev_pm_ops max8997_pm = { + .freeze = max8997_freeze, + .restore = max8997_restore, +}; + +static struct i2c_driver max8997_i2c_driver = { + .driver = { + .name = "max8997", + .owner = THIS_MODULE, + .pm = &max8997_pm, + }, + .probe = max8997_i2c_probe, + .remove = max8997_i2c_remove, + .id_table = max8997_i2c_id, +}; + +static int __init max8997_i2c_init(void) +{ + return i2c_add_driver(&max8997_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max8997_i2c_init); + +static void __exit max8997_i2c_exit(void) +{ + i2c_del_driver(&max8997_i2c_driver); +} +module_exit(max8997_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 8997 multi-function core driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h new file mode 100644 index 00000000000..93a9477e075 --- /dev/null +++ b/include/linux/mfd/max8997-private.h @@ -0,0 +1,347 @@ +/* + * max8997.h - Voltage regulator driver for the Maxim 8997 + * + * Copyright (C) 2010 Samsung Electrnoics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_MFD_MAX8997_PRIV_H +#define __LINUX_MFD_MAX8997_PRIV_H + +#include + +enum max8997_pmic_reg { + MAX8997_REG_PMIC_ID0 = 0x00, + MAX8997_REG_PMIC_ID1 = 0x01, + MAX8997_REG_INTSRC = 0x02, + MAX8997_REG_INT1 = 0x03, + MAX8997_REG_INT2 = 0x04, + MAX8997_REG_INT3 = 0x05, + MAX8997_REG_INT4 = 0x06, + + MAX8997_REG_INT1MSK = 0x08, + MAX8997_REG_INT2MSK = 0x09, + MAX8997_REG_INT3MSK = 0x0a, + MAX8997_REG_INT4MSK = 0x0b, + + MAX8997_REG_STATUS1 = 0x0d, + MAX8997_REG_STATUS2 = 0x0e, + MAX8997_REG_STATUS3 = 0x0f, + MAX8997_REG_STATUS4 = 0x10, + + MAX8997_REG_MAINCON1 = 0x13, + MAX8997_REG_MAINCON2 = 0x14, + MAX8997_REG_BUCKRAMP = 0x15, + + MAX8997_REG_BUCK1CTRL = 0x18, + MAX8997_REG_BUCK1DVS1 = 0x19, + MAX8997_REG_BUCK1DVS2 = 0x1a, + MAX8997_REG_BUCK1DVS3 = 0x1b, + MAX8997_REG_BUCK1DVS4 = 0x1c, + MAX8997_REG_BUCK1DVS5 = 0x1d, + MAX8997_REG_BUCK1DVS6 = 0x1e, + MAX8997_REG_BUCK1DVS7 = 0x1f, + MAX8997_REG_BUCK1DVS8 = 0x20, + MAX8997_REG_BUCK2CTRL = 0x21, + MAX8997_REG_BUCK2DVS1 = 0x22, + MAX8997_REG_BUCK2DVS2 = 0x23, + MAX8997_REG_BUCK2DVS3 = 0x24, + MAX8997_REG_BUCK2DVS4 = 0x25, + MAX8997_REG_BUCK2DVS5 = 0x26, + MAX8997_REG_BUCK2DVS6 = 0x27, + MAX8997_REG_BUCK2DVS7 = 0x28, + MAX8997_REG_BUCK2DVS8 = 0x29, + MAX8997_REG_BUCK3CTRL = 0x2a, + MAX8997_REG_BUCK3DVS = 0x2b, + MAX8997_REG_BUCK4CTRL = 0x2c, + MAX8997_REG_BUCK4DVS = 0x2d, + MAX8997_REG_BUCK5CTRL = 0x2e, + MAX8997_REG_BUCK5DVS1 = 0x2f, + MAX8997_REG_BUCK5DVS2 = 0x30, + MAX8997_REG_BUCK5DVS3 = 0x31, + MAX8997_REG_BUCK5DVS4 = 0x32, + MAX8997_REG_BUCK5DVS5 = 0x33, + MAX8997_REG_BUCK5DVS6 = 0x34, + MAX8997_REG_BUCK5DVS7 = 0x35, + MAX8997_REG_BUCK5DVS8 = 0x36, + MAX8997_REG_BUCK6CTRL = 0x37, + MAX8997_REG_BUCK6BPSKIPCTRL = 0x38, + MAX8997_REG_BUCK7CTRL = 0x39, + MAX8997_REG_BUCK7DVS = 0x3a, + MAX8997_REG_LDO1CTRL = 0x3b, + MAX8997_REG_LDO2CTRL = 0x3c, + MAX8997_REG_LDO3CTRL = 0x3d, + MAX8997_REG_LDO4CTRL = 0x3e, + MAX8997_REG_LDO5CTRL = 0x3f, + MAX8997_REG_LDO6CTRL = 0x40, + MAX8997_REG_LDO7CTRL = 0x41, + MAX8997_REG_LDO8CTRL = 0x42, + MAX8997_REG_LDO9CTRL = 0x43, + MAX8997_REG_LDO10CTRL = 0x44, + MAX8997_REG_LDO11CTRL = 0x45, + MAX8997_REG_LDO12CTRL = 0x46, + MAX8997_REG_LDO13CTRL = 0x47, + MAX8997_REG_LDO14CTRL = 0x48, + MAX8997_REG_LDO15CTRL = 0x49, + MAX8997_REG_LDO16CTRL = 0x4a, + MAX8997_REG_LDO17CTRL = 0x4b, + MAX8997_REG_LDO18CTRL = 0x4c, + MAX8997_REG_LDO21CTRL = 0x4d, + + MAX8997_REG_MBCCTRL1 = 0x50, + MAX8997_REG_MBCCTRL2 = 0x51, + MAX8997_REG_MBCCTRL3 = 0x52, + MAX8997_REG_MBCCTRL4 = 0x53, + MAX8997_REG_MBCCTRL5 = 0x54, + MAX8997_REG_MBCCTRL6 = 0x55, + MAX8997_REG_OTPCGHCVS = 0x56, + + MAX8997_REG_SAFEOUTCTRL = 0x5a, + + MAX8997_REG_LBCNFG1 = 0x5e, + MAX8997_REG_LBCNFG2 = 0x5f, + MAX8997_REG_BBCCTRL = 0x60, + + MAX8997_REG_FLASH1_CUR = 0x63, /* 0x63 ~ 0x6e for FLASH */ + MAX8997_REG_FLASH2_CUR = 0x64, + MAX8997_REG_MOVIE_CUR = 0x65, + MAX8997_REG_GSMB_CUR = 0x66, + MAX8997_REG_BOOST_CNTL = 0x67, + MAX8997_REG_LEN_CNTL = 0x68, + MAX8997_REG_FLASH_CNTL = 0x69, + MAX8997_REG_WDT_CNTL = 0x6a, + MAX8997_REG_MAXFLASH1 = 0x6b, + MAX8997_REG_MAXFLASH2 = 0x6c, + MAX8997_REG_FLASHSTATUS = 0x6d, + MAX8997_REG_FLASHSTATUSMASK = 0x6e, + + MAX8997_REG_GPIOCNTL1 = 0x70, + MAX8997_REG_GPIOCNTL2 = 0x71, + MAX8997_REG_GPIOCNTL3 = 0x72, + MAX8997_REG_GPIOCNTL4 = 0x73, + MAX8997_REG_GPIOCNTL5 = 0x74, + MAX8997_REG_GPIOCNTL6 = 0x75, + MAX8997_REG_GPIOCNTL7 = 0x76, + MAX8997_REG_GPIOCNTL8 = 0x77, + MAX8997_REG_GPIOCNTL9 = 0x78, + MAX8997_REG_GPIOCNTL10 = 0x79, + MAX8997_REG_GPIOCNTL11 = 0x7a, + MAX8997_REG_GPIOCNTL12 = 0x7b, + + MAX8997_REG_LDO1CONFIG = 0x80, + MAX8997_REG_LDO2CONFIG = 0x81, + MAX8997_REG_LDO3CONFIG = 0x82, + MAX8997_REG_LDO4CONFIG = 0x83, + MAX8997_REG_LDO5CONFIG = 0x84, + MAX8997_REG_LDO6CONFIG = 0x85, + MAX8997_REG_LDO7CONFIG = 0x86, + MAX8997_REG_LDO8CONFIG = 0x87, + MAX8997_REG_LDO9CONFIG = 0x88, + MAX8997_REG_LDO10CONFIG = 0x89, + MAX8997_REG_LDO11CONFIG = 0x8a, + MAX8997_REG_LDO12CONFIG = 0x8b, + MAX8997_REG_LDO13CONFIG = 0x8c, + MAX8997_REG_LDO14CONFIG = 0x8d, + MAX8997_REG_LDO15CONFIG = 0x8e, + MAX8997_REG_LDO16CONFIG = 0x8f, + MAX8997_REG_LDO17CONFIG = 0x90, + MAX8997_REG_LDO18CONFIG = 0x91, + MAX8997_REG_LDO21CONFIG = 0x92, + + MAX8997_REG_DVSOKTIMER1 = 0x97, + MAX8997_REG_DVSOKTIMER2 = 0x98, + MAX8997_REG_DVSOKTIMER4 = 0x99, + MAX8997_REG_DVSOKTIMER5 = 0x9a, + + MAX8997_REG_PMIC_END = 0x9b, +}; + +enum max8997_muic_reg { + MAX8997_MUIC_REG_ID = 0x0, + MAX8997_MUIC_REG_INT1 = 0x1, + MAX8997_MUIC_REG_INT2 = 0x2, + MAX8997_MUIC_REG_INT3 = 0x3, + MAX8997_MUIC_REG_STATUS1 = 0x4, + MAX8997_MUIC_REG_STATUS2 = 0x5, + MAX8997_MUIC_REG_STATUS3 = 0x6, + MAX8997_MUIC_REG_INTMASK1 = 0x7, + MAX8997_MUIC_REG_INTMASK2 = 0x8, + MAX8997_MUIC_REG_INTMASK3 = 0x9, + MAX8997_MUIC_REG_CDETCTRL = 0xa, + + MAX8997_MUIC_REG_CONTROL1 = 0xc, + MAX8997_MUIC_REG_CONTROL2 = 0xd, + MAX8997_MUIC_REG_CONTROL3 = 0xe, + + MAX8997_MUIC_REG_END = 0xf, +}; + +enum max8997_haptic_reg { + MAX8997_HAPTIC_REG_GENERAL = 0x00, + MAX8997_HAPTIC_REG_CONF1 = 0x01, + MAX8997_HAPTIC_REG_CONF2 = 0x02, + MAX8997_HAPTIC_REG_DRVCONF = 0x03, + MAX8997_HAPTIC_REG_CYCLECONF1 = 0x04, + MAX8997_HAPTIC_REG_CYCLECONF2 = 0x05, + MAX8997_HAPTIC_REG_SIGCONF1 = 0x06, + MAX8997_HAPTIC_REG_SIGCONF2 = 0x07, + MAX8997_HAPTIC_REG_SIGCONF3 = 0x08, + MAX8997_HAPTIC_REG_SIGCONF4 = 0x09, + MAX8997_HAPTIC_REG_SIGDC1 = 0x0a, + MAX8997_HAPTIC_REG_SIGDC2 = 0x0b, + MAX8997_HAPTIC_REG_SIGPWMDC1 = 0x0c, + MAX8997_HAPTIC_REG_SIGPWMDC2 = 0x0d, + MAX8997_HAPTIC_REG_SIGPWMDC3 = 0x0e, + MAX8997_HAPTIC_REG_SIGPWMDC4 = 0x0f, + MAX8997_HAPTIC_REG_MTR_REV = 0x10, + + MAX8997_HAPTIC_REG_END = 0x11, +}; + +/* slave addr = 0x0c: using "2nd part" of rev4 datasheet */ +enum max8997_rtc_reg { + MAX8997_RTC_CTRLMASK = 0x02, + MAX8997_RTC_CTRL = 0x03, + MAX8997_RTC_UPDATE1 = 0x04, + MAX8997_RTC_UPDATE2 = 0x05, + MAX8997_RTC_WTSR_SMPL = 0x06, + + MAX8997_RTC_SEC = 0x10, + MAX8997_RTC_MIN = 0x11, + MAX8997_RTC_HOUR = 0x12, + MAX8997_RTC_DAY_OF_WEEK = 0x13, + MAX8997_RTC_MONTH = 0x14, + MAX8997_RTC_YEAR = 0x15, + MAX8997_RTC_DAY_OF_MONTH = 0x16, + MAX8997_RTC_ALARM1_SEC = 0x17, + MAX8997_RTC_ALARM1_MIN = 0x18, + MAX8997_RTC_ALARM1_HOUR = 0x19, + MAX8997_RTC_ALARM1_DAY_OF_WEEK = 0x1a, + MAX8997_RTC_ALARM1_MONTH = 0x1b, + MAX8997_RTC_ALARM1_YEAR = 0x1c, + MAX8997_RTC_ALARM1_DAY_OF_MONTH = 0x1d, + MAX8997_RTC_ALARM2_SEC = 0x1e, + MAX8997_RTC_ALARM2_MIN = 0x1f, + MAX8997_RTC_ALARM2_HOUR = 0x20, + MAX8997_RTC_ALARM2_DAY_OF_WEEK = 0x21, + MAX8997_RTC_ALARM2_MONTH = 0x22, + MAX8997_RTC_ALARM2_YEAR = 0x23, + MAX8997_RTC_ALARM2_DAY_OF_MONTH = 0x24, +}; + +enum max8997_irq_source { + PMIC_INT1 = 0, + PMIC_INT2, + PMIC_INT3, + PMIC_INT4, + + FUEL_GAUGE, /* Ignored (MAX17042 driver handles) */ + + MUIC_INT1, + MUIC_INT2, + MUIC_INT3, + + GPIO_LOW, /* Not implemented */ + GPIO_HI, /* Not implemented */ + + FLASH_STATUS, /* Not implemented */ + + MAX8997_IRQ_GROUP_NR, +}; + +enum max8997_irq { + MAX8997_PMICIRQ_PWRONR, + MAX8997_PMICIRQ_PWRONF, + MAX8997_PMICIRQ_PWRON1SEC, + MAX8997_PMICIRQ_JIGONR, + MAX8997_PMICIRQ_JIGONF, + MAX8997_PMICIRQ_LOWBAT2, + MAX8997_PMICIRQ_LOWBAT1, + + MAX8997_PMICIRQ_JIGR, + MAX8997_PMICIRQ_JIGF, + MAX8997_PMICIRQ_MR, + MAX8997_PMICIRQ_DVS1OK, + MAX8997_PMICIRQ_DVS2OK, + MAX8997_PMICIRQ_DVS3OK, + MAX8997_PMICIRQ_DVS4OK, + + MAX8997_PMICIRQ_CHGINS, + MAX8997_PMICIRQ_CHGRM, + MAX8997_PMICIRQ_DCINOVP, + MAX8997_PMICIRQ_TOPOFFR, + MAX8997_PMICIRQ_CHGRSTF, + MAX8997_PMICIRQ_MBCHGTMEXPD, + + MAX8997_PMICIRQ_RTC60S, + MAX8997_PMICIRQ_RTCA1, + MAX8997_PMICIRQ_RTCA2, + MAX8997_PMICIRQ_SMPL_INT, + MAX8997_PMICIRQ_RTC1S, + MAX8997_PMICIRQ_WTSR, + + MAX8997_MUICIRQ_ADCError, + MAX8997_MUICIRQ_ADCLow, + MAX8997_MUICIRQ_ADC, + + MAX8997_MUICIRQ_VBVolt, + MAX8997_MUICIRQ_DBChg, + MAX8997_MUICIRQ_DCDTmr, + MAX8997_MUICIRQ_ChgDetRun, + MAX8997_MUICIRQ_ChgTyp, + + MAX8997_MUICIRQ_OVP, + + MAX8997_IRQ_NR, +}; + +#define MAX8997_REG_BUCK1DVS(x) (MAX8997_REG_BUCK1DVS1 + (x) - 1) +#define MAX8997_REG_BUCK2DVS(x) (MAX8997_REG_BUCK2DVS1 + (x) - 1) +#define MAX8997_REG_BUCK5DVS(x) (MAX8997_REG_BUCK5DVS1 + (x) - 1) + +struct max8997_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */ + struct i2c_client *rtc; /* slave addr 0x0c */ + struct i2c_client *haptic; /* slave addr 0x90 */ + struct i2c_client *muic; /* slave addr 0x4a */ + struct mutex iolock; + + int type; + struct platform_device *battery; /* battery control (not fuel gauge) */ + + bool wakeup; + + /* For hibernation */ + u8 reg_dump[MAX8997_REG_PMIC_END + MAX8997_MUIC_REG_END + + MAX8997_HAPTIC_REG_END]; +}; + +enum max8997_types { + TYPE_MAX8997, + TYPE_MAX8966, +}; + +extern int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +#endif /* __LINUX_MFD_MAX8997_PRIV_H */ diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h new file mode 100644 index 00000000000..d0d9136c104 --- /dev/null +++ b/include/linux/mfd/max8997.h @@ -0,0 +1,88 @@ +/* + * max8997.h - Driver for the Maxim 8997/8966 + * + * Copyright (C) 2009-2010 Samsung Electrnoics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998.h + * + * MAX8997 has PMIC, MUIC, HAPTIC, RTC, FLASH, and Fuel Gauge devices. + * Except Fuel Gauge, every device shares the same I2C bus and included in + * this mfd driver. Although the fuel gauge is included in the chip, it is + * excluded from the driver because a) it has a different I2C bus from + * others and b) it can be enabled simply by using MAX17042 driver. + */ + +#ifndef __LINUX_MFD_MAX8998_H +#define __LINUX_MFD_MAX8998_H + +#include + +/* MAX8997/8966 regulator IDs */ +enum max8998_regulators { + MAX8997_LDO1 = 0, + MAX8997_LDO2, + MAX8997_LDO3, + MAX8997_LDO4, + MAX8997_LDO5, + MAX8997_LDO6, + MAX8997_LDO7, + MAX8997_LDO8, + MAX8997_LDO9, + MAX8997_LDO10, + MAX8997_LDO11, + MAX8997_LDO12, + MAX8997_LDO13, + MAX8997_LDO14, + MAX8997_LDO15, + MAX8997_LDO16, + MAX8997_LDO17, + MAX8997_LDO18, + MAX8997_LDO21, + MAX8997_BUCK1, + MAX8997_BUCK2, + MAX8997_BUCK3, + MAX8997_BUCK4, + MAX8997_BUCK5, + MAX8997_BUCK6, + MAX8997_BUCK7, + MAX8997_EN32KHZ_AP, + MAX8997_EN32KHZ_CP, + MAX8997_ENVICHG, + MAX8997_ESAFEOUT1, + MAX8997_ESAFEOUT2, + MAX8997_CHARGER_CV, /* control MBCCV of MBCCTRL3 */ + MAX8997_CHARGER, /* charger current, MBCCTRL4 */ + MAX8997_CHARGER_TOPOFF, /* MBCCTRL5 */ +}; + +struct max8997_regulator_data { + int id; + struct regulator_init_data *initdata; +}; + +struct max8997_platform_data { + bool wakeup; + /* PMIC: Not implemented */ + /* MUIC: Not implemented */ + /* HAPTIC: Not implemented */ + /* RTC: Not implemented */ + /* Flash: Not implemented */ + /* Charger control: Not implemented */ +}; + +#endif /* __LINUX_MFD_MAX8998_H */ -- cgit v1.2.3-70-g09d2 From cf16943947cef089c564d2be0ae9d96a285f495e Mon Sep 17 00:00:00 2001 From: Daniel Willerud Date: Sat, 5 Mar 2011 11:46:01 +0100 Subject: mfd: Move ab8500 gpadc header to subdir This moves the ab8500-gpadc.h header down into the ab8500/ subdir in include/linux/mfd and fixes some whitespace in the header in the process. Signed-off-by: Daniel Willerud Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab8500-gpadc.c | 2 +- include/linux/mfd/ab8500-gpadc.h | 28 ---------------------------- include/linux/mfd/ab8500/ab8500-gpadc.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 include/linux/mfd/ab8500-gpadc.h create mode 100644 include/linux/mfd/ab8500/ab8500-gpadc.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 19339bc439c..f60f71f4b47 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include /* * GPADC register offsets diff --git a/include/linux/mfd/ab8500-gpadc.h b/include/linux/mfd/ab8500-gpadc.h deleted file mode 100644 index 9f6cc26bc73..00000000000 --- a/include/linux/mfd/ab8500-gpadc.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2010 ST-Ericsson SA - * Licensed under GPLv2. - * - * Author: Arun R Murthy - */ - -#ifndef _AB8500_GPADC_H -#define _AB8500_GPADC_H - -/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ -#define BAT_CTRL 0x01 -#define BTEMP_BALL 0x02 -#define MAIN_CHARGER_V 0x03 -#define ACC_DETECT1 0x04 -#define ACC_DETECT2 0x05 -#define ADC_AUX1 0x06 -#define ADC_AUX2 0x07 -#define MAIN_BAT_V 0x08 -#define VBUS_V 0x09 -#define MAIN_CHARGER_C 0x0A -#define USB_CHARGER_C 0x0B -#define BK_BAT_V 0x0C -#define DIE_TEMP 0x0D - -int ab8500_gpadc_convert(u8 input); - -#endif /* _AB8500_GPADC_H */ diff --git a/include/linux/mfd/ab8500/ab8500-gpadc.h b/include/linux/mfd/ab8500/ab8500-gpadc.h new file mode 100644 index 00000000000..74b87dde9cd --- /dev/null +++ b/include/linux/mfd/ab8500/ab8500-gpadc.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy + */ + +#ifndef _AB8500_GPADC_H +#define _AB8500_GPADC_H + +/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define BTEMP_BALL 0x02 +#define MAIN_CHARGER_V 0x03 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define ADC_AUX1 0x06 +#define ADC_AUX2 0x07 +#define MAIN_BAT_V 0x08 +#define VBUS_V 0x09 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define BK_BAT_V 0x0C +#define DIE_TEMP 0x0D + +int ab8500_gpadc_convert(u8 input); + +#endif /* _AB8500_GPADC_H */ -- cgit v1.2.3-70-g09d2 From 6321992cd3c56bab6cc52e3384951e12616805a1 Mon Sep 17 00:00:00 2001 From: Daniel Willerud Date: Sat, 5 Mar 2011 11:46:13 +0100 Subject: mfd: Reentrance and revamp ab8500 gpadc fetching interface This revamps the interface so that AB8500 GPADCs are fetched by name. Probed GPADCs are added to a list and this list is searched for a matching GPADC. This makes it possible to have multiple AB8500 GPADC instances instead of it being a singleton, and rids the need to keep a GPADC pointer around in the core AB8500 MFD struct. Currently the match is made to the device name which is by default numbered from the device instance such as "ab8500-gpadc.0" but by using the .init_name field of the device a more intiutive naming for the GPADC blocks can be achieved if desired. Signed-off-by: Daniel Willerud Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab8500-gpadc.c | 115 +++++++++++++++++++------------- include/linux/mfd/ab8500/ab8500-gpadc.h | 6 +- 2 files changed, 75 insertions(+), 46 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index f60f71f4b47..178cbc55cae 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -3,6 +3,7 @@ * * License Terms: GNU General Public License v2 * Author: Arun R Murthy + * Author: Daniel Willerud */ #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -48,21 +50,41 @@ /** * struct ab8500_gpadc - ab8500 GPADC device information * @dev: pointer to the struct device - * @parent: pointer to the parent device structure ab8500 + * @node: a list of AB8500 GPADCs, hence prepared for + reentrance * @ab8500_gpadc_complete: pointer to the struct completion, to indicate * the completion of gpadc conversion * @ab8500_gpadc_lock: structure of type mutex * @regu: pointer to the struct regulator * @irq: interrupt number that is used by gpadc */ -static struct ab8500_gpadc { +struct ab8500_gpadc { struct device *dev; - struct ab8500 *parent; + struct list_head node; struct completion ab8500_gpadc_complete; struct mutex ab8500_gpadc_lock; struct regulator *regu; int irq; -} *di; +}; + +static LIST_HEAD(ab8500_gpadc_list); + +/** + * ab8500_gpadc_get() - returns a reference to the primary AB8500 GPADC + * (i.e. the first GPADC in the instance list) + */ +struct ab8500_gpadc *ab8500_gpadc_get(char *name) +{ + struct ab8500_gpadc *gpadc; + + list_for_each_entry(gpadc, &ab8500_gpadc_list, node) { + if (!strcmp(name, dev_name(gpadc->dev))) + return gpadc; + } + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(ab8500_gpadc_get); /** * ab8500_gpadc_convert() - gpadc conversion @@ -72,24 +94,24 @@ static struct ab8500_gpadc { * data. Thereafter calibration has to be made to obtain the * data in the required quantity measurement. */ -int ab8500_gpadc_convert(u8 input) +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) { int ret; u16 data = 0; int looplimit = 0; u8 val, low_data, high_data; - if (!di) + if (!gpadc) return -ENODEV; - mutex_lock(&di->ab8500_gpadc_lock); + mutex_lock(&gpadc->ab8500_gpadc_lock); /* Enable VTVout LDO this is required for GPADC */ - regulator_enable(di->regu); + regulator_enable(gpadc->regu); /* Check if ADC is not busy, lock and proceed */ do { - ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, - AB8500_GPADC_STAT_REG, &val); + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_STAT_REG, &val); if (ret < 0) goto out; if (!(val & GPADC_BUSY)) @@ -97,75 +119,76 @@ int ab8500_gpadc_convert(u8 input) msleep(10); } while (++looplimit < 10); if (looplimit >= 10 && (val & GPADC_BUSY)) { - dev_err(di->dev, "gpadc_conversion: GPADC busy"); + dev_err(gpadc->dev, "gpadc_conversion: GPADC busy"); ret = -EINVAL; goto out; } /* Enable GPADC */ - ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, - AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC); + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC); if (ret < 0) { - dev_err(di->dev, "gpadc_conversion: enable gpadc failed\n"); + dev_err(gpadc->dev, "gpadc_conversion: enable gpadc failed\n"); goto out; } /* Select the input source and set average samples to 16 */ - ret = abx500_set_register_interruptible(di->dev, AB8500_GPADC, + ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL2_REG, (input | SW_AVG_16)); if (ret < 0) { - dev_err(di->dev, + dev_err(gpadc->dev, "gpadc_conversion: set avg samples failed\n"); goto out; } /* Enable ADC, Buffering and select rising edge, start Conversion */ - ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, - AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); if (ret < 0) { - dev_err(di->dev, + dev_err(gpadc->dev, "gpadc_conversion: select falling edge failed\n"); goto out; } - ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_GPADC, - AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); if (ret < 0) { - dev_err(di->dev, + dev_err(gpadc->dev, "gpadc_conversion: start s/w conversion failed\n"); goto out; } /* wait for completion of conversion */ - if (!wait_for_completion_timeout(&di->ab8500_gpadc_complete, 2*HZ)) { - dev_err(di->dev, + if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete, 2*HZ)) { + dev_err(gpadc->dev, "timeout: didnt recieve GPADC conversion interrupt\n"); ret = -EINVAL; goto out; } /* Read the converted RAW data */ - ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, + ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_MANDATAL_REG, &low_data); if (ret < 0) { - dev_err(di->dev, "gpadc_conversion: read low data failed\n"); + dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n"); goto out; } - ret = abx500_get_register_interruptible(di->dev, AB8500_GPADC, + ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_MANDATAH_REG, &high_data); if (ret < 0) { - dev_err(di->dev, "gpadc_conversion: read high data failed\n"); + dev_err(gpadc->dev, + "gpadc_conversion: read high data failed\n"); goto out; } data = (high_data << 8) | low_data; /* Disable GPADC */ - ret = abx500_set_register_interruptible(di->dev, AB8500_GPADC, + ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); if (ret < 0) { - dev_err(di->dev, "gpadc_conversion: disable gpadc failed\n"); + dev_err(gpadc->dev, "gpadc_conversion: disable gpadc failed\n"); goto out; } /* Disable VTVout LDO this is required for GPADC */ - regulator_disable(di->regu); - mutex_unlock(&di->ab8500_gpadc_lock); + regulator_disable(gpadc->regu); + mutex_unlock(&gpadc->ab8500_gpadc_lock); return data; out: @@ -175,12 +198,12 @@ out: * GPADC status register to go low. In V1.1 there wait_for_completion * seems to timeout when waiting for an interrupt.. Not seen in V2.0 */ - (void) abx500_set_register_interruptible(di->dev, AB8500_GPADC, + (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); - regulator_disable(di->regu); - mutex_unlock(&di->ab8500_gpadc_lock); - dev_err(di->dev, "gpadc_conversion: Failed to AD convert channel %d\n", - input); + regulator_disable(gpadc->regu); + mutex_unlock(&gpadc->ab8500_gpadc_lock); + dev_err(gpadc->dev, + "gpadc_conversion: Failed to AD convert channel %d\n", input); return ret; } EXPORT_SYMBOL(ab8500_gpadc_convert); @@ -195,9 +218,9 @@ EXPORT_SYMBOL(ab8500_gpadc_convert); * can be read from the registers. * Returns IRQ status(IRQ_HANDLED) */ -static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_di) +static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_gpadc) { - struct ab8500_gpadc *gpadc = _di; + struct ab8500_gpadc *gpadc = _gpadc; complete(&gpadc->ab8500_gpadc_complete); @@ -215,16 +238,16 @@ static int __devinit ab8500_gpadc_probe(struct platform_device *pdev) return -ENOMEM; } - gpadc->parent = dev_get_drvdata(pdev->dev.parent); gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END"); if (gpadc->irq < 0) { - dev_err(gpadc->dev, "failed to get platform irq-%d\n", di->irq); + dev_err(gpadc->dev, "failed to get platform irq-%d\n", + gpadc->irq); ret = gpadc->irq; goto fail; } gpadc->dev = &pdev->dev; - mutex_init(&di->ab8500_gpadc_lock); + mutex_init(&gpadc->ab8500_gpadc_lock); /* Initialize completion used to notify completion of conversion */ init_completion(&gpadc->ab8500_gpadc_complete); @@ -246,7 +269,7 @@ static int __devinit ab8500_gpadc_probe(struct platform_device *pdev) dev_err(gpadc->dev, "failed to get vtvout LDO\n"); goto fail; } - di = gpadc; + list_add_tail(&gpadc->node, &ab8500_gpadc_list); dev_dbg(gpadc->dev, "probe success\n"); return 0; fail: @@ -259,8 +282,10 @@ static int __devexit ab8500_gpadc_remove(struct platform_device *pdev) { struct ab8500_gpadc *gpadc = platform_get_drvdata(pdev); + /* remove this gpadc entry from the list */ + list_del(&gpadc->node); /* remove interrupt - completion of Sw ADC conversion */ - free_irq(gpadc->irq, di); + free_irq(gpadc->irq, gpadc); /* disable VTVout LDO that is being used by GPADC */ regulator_put(gpadc->regu); kfree(gpadc); @@ -291,6 +316,6 @@ subsys_initcall_sync(ab8500_gpadc_init); module_exit(ab8500_gpadc_exit); MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Arun R Murthy"); +MODULE_AUTHOR("Arun R Murthy, Daniel Willerud"); MODULE_ALIAS("platform:ab8500_gpadc"); MODULE_DESCRIPTION("AB8500 GPADC driver"); diff --git a/include/linux/mfd/ab8500/ab8500-gpadc.h b/include/linux/mfd/ab8500/ab8500-gpadc.h index 74b87dde9cd..46b954011f1 100644 --- a/include/linux/mfd/ab8500/ab8500-gpadc.h +++ b/include/linux/mfd/ab8500/ab8500-gpadc.h @@ -3,6 +3,7 @@ * Licensed under GPLv2. * * Author: Arun R Murthy + * Author: Daniel Willerud */ #ifndef _AB8500_GPADC_H @@ -23,6 +24,9 @@ #define BK_BAT_V 0x0C #define DIE_TEMP 0x0D -int ab8500_gpadc_convert(u8 input); +struct ab8500_gpadc; + +struct ab8500_gpadc *ab8500_gpadc_get(char *name); +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); #endif /* _AB8500_GPADC_H */ -- cgit v1.2.3-70-g09d2 From adb70483f4d560323db9aaca5f066fde4d96f339 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 7 Mar 2011 23:43:09 +0800 Subject: mfd: Adopt mfd_data in 88pm860x backlight Copy 88pm860x platform data into different mfd_data structure for backlight driver. So move the identification of device node from backlight driver to mfd driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 96 +++++++++++++++++++---------------- drivers/video/backlight/88pm860x_bl.c | 34 +++---------- include/linux/mfd/88pm860x.h | 2 +- 3 files changed, 60 insertions(+), 72 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 793300c554b..a88967a466c 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -20,12 +20,19 @@ #define INT_STATUS_NUM 3 -char pm860x_backlight_name[][MFD_NAME_SIZE] = { - "backlight-0", - "backlight-1", - "backlight-2", +static struct resource bk_resources[] __initdata = { + {PM8606_BACKLIGHT1, PM8606_BACKLIGHT1, "backlight-0", IORESOURCE_IO,}, + {PM8606_BACKLIGHT2, PM8606_BACKLIGHT2, "backlight-1", IORESOURCE_IO,}, + {PM8606_BACKLIGHT3, PM8606_BACKLIGHT3, "backlight-2", IORESOURCE_IO,}, }; -EXPORT_SYMBOL(pm860x_backlight_name); + +static struct mfd_cell bk_devs[] __initdata = { + {"88pm860x-backlight", 0,}, + {"88pm860x-backlight", 1,}, + {"88pm860x-backlight", 2,}, +}; + +static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)]; char pm860x_led_name[][MFD_NAME_SIZE] = { "led0-red", @@ -37,34 +44,6 @@ char pm860x_led_name[][MFD_NAME_SIZE] = { }; EXPORT_SYMBOL(pm860x_led_name); -#define PM8606_BACKLIGHT_RESOURCE(_i, _x) \ -{ \ - .name = pm860x_backlight_name[_i], \ - .start = PM8606_##_x, \ - .end = PM8606_##_x, \ - .flags = IORESOURCE_IO, \ -} - -static struct resource backlight_resources[] = { - PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT1, WLED1A), - PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT2, WLED2A), - PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT3, WLED3A), -}; - -#define PM8606_BACKLIGHT_DEVS(_i) \ -{ \ - .name = "88pm860x-backlight", \ - .num_resources = 1, \ - .resources = &backlight_resources[_i], \ - .id = _i, \ -} - -static struct mfd_cell backlight_devs[] = { - PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT1), - PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT2), - PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT3), -}; - #define PM8606_LED_RESOURCE(_i, _x) \ { \ .name = pm860x_led_name[_i], \ @@ -595,23 +574,49 @@ static void device_irq_exit(struct pm860x_chip *chip) free_irq(chip->core_irq, chip); } +static void __devinit device_bk_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ + int ret; + int i, j, id; + + if ((pdata == NULL) || (pdata->backlight == NULL)) + return; + + if (pdata->num_backlights > ARRAY_SIZE(bk_devs)) + pdata->num_backlights = ARRAY_SIZE(bk_devs); + + for (i = 0; i < pdata->num_backlights; i++) { + memcpy(&bk_pdata[i], &pdata->backlight[i], + sizeof(struct pm860x_backlight_pdata)); + bk_devs[i].mfd_data = &bk_pdata[i]; + + for (j = 0; j < ARRAY_SIZE(bk_devs); j++) { + id = bk_resources[j].start; + if (bk_pdata[i].flags != id) + continue; + + bk_devs[i].num_resources = 1; + bk_devs[i].resources = &bk_resources[j]; + ret = mfd_add_devices(chip->dev, 0, + &bk_devs[i], 1, + &bk_resources[j], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add " + "backlight subdev\n"); + return; + } + } + } +} + static void __devinit device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; - if (pdata && pdata->backlight) { - ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], - ARRAY_SIZE(backlight_devs), - &backlight_resources[0], 0); - if (ret < 0) { - dev_err(chip->dev, "Failed to add backlight " - "subdev\n"); - goto out_dev; - } - } - if (pdata && pdata->led) { ret = mfd_add_devices(chip->dev, 0, &led_devs[0], ARRAY_SIZE(led_devs), @@ -624,7 +629,6 @@ static void __devinit device_8606_init(struct pm860x_chip *chip, } return; out_dev: - mfd_remove_devices(chip->dev); device_irq_exit(chip); } @@ -743,6 +747,7 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, switch (chip->id) { case CHIP_PM8606: + device_bk_init(chip, chip->client, pdata); device_8606_init(chip, chip->client, pdata); break; case CHIP_PM8607: @@ -753,6 +758,7 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, if (chip->companion) { switch (chip->id) { case CHIP_PM8607: + device_bk_init(chip, chip->companion, pdata); device_8606_init(chip, chip->companion, pdata); break; case CHIP_PM8606: diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c index e59623a15f3..c8b520e9a11 100644 --- a/drivers/video/backlight/88pm860x_bl.c +++ b/drivers/video/backlight/88pm860x_bl.c @@ -12,11 +12,12 @@ #include #include #include +#include #include #include #include +#include #include -#include #define MAX_BRIGHTNESS (0xFF) #define MIN_BRIGHTNESS (0) @@ -161,32 +162,13 @@ static const struct backlight_ops pm860x_backlight_ops = { .get_brightness = pm860x_backlight_get_brightness, }; -static int __check_device(struct pm860x_backlight_pdata *pdata, char *name) -{ - struct pm860x_backlight_pdata *p = pdata; - int ret = -EINVAL; - - while (p && p->id) { - if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0)) - break; - - if (!strncmp(name, pm860x_backlight_name[p->flags], - MFD_NAME_SIZE)) { - ret = (int)p->flags; - break; - } - p++; - } - return ret; -} - static int pm860x_backlight_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_platform_data *pm860x_pdata; struct pm860x_backlight_pdata *pdata = NULL; struct pm860x_backlight_data *data; struct backlight_device *bl; + struct mfd_cell *cell; struct resource *res; struct backlight_properties props; unsigned char value; @@ -199,10 +181,10 @@ static int pm860x_backlight_probe(struct platform_device *pdev) return -EINVAL; } - if (pdev->dev.parent->platform_data) { - pm860x_pdata = pdev->dev.parent->platform_data; - pdata = pm860x_pdata->backlight; - } + cell = pdev->dev.platform_data; + if (cell == NULL) + return -ENODEV; + pdata = cell->mfd_data; if (pdata == NULL) { dev_err(&pdev->dev, "platform data isn't assigned to " "backlight\n"); @@ -219,7 +201,7 @@ static int pm860x_backlight_probe(struct platform_device *pdev) data->current_brightness = MAX_BRIGHTNESS; data->pwm = pdata->pwm; data->iset = pdata->iset; - data->port = __check_device(pdata, name); + data->port = pdata->flags; if (data->port < 0) { dev_err(&pdev->dev, "wrong platform data is assigned"); kfree(data); diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index 4db1fbd8969..f790d376622 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -356,10 +356,10 @@ struct pm860x_platform_data { int i2c_port; /* Controlled by GI2C or PI2C */ int irq_mode; /* Clear interrupt by read/write(0/1) */ int irq_base; /* IRQ base number of 88pm860x */ + int num_backlights; struct regulator_init_data *regulator[PM8607_MAX_REGULATOR]; }; -extern char pm860x_backlight_name[][MFD_NAME_SIZE]; extern char pm860x_led_name[][MFD_NAME_SIZE]; extern int pm860x_reg_read(struct i2c_client *, int); -- cgit v1.2.3-70-g09d2 From 3154c344696e58b7e15317cd624816dbe3832ad1 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 7 Mar 2011 23:43:10 +0800 Subject: mfd: Adopt mfd_data in 88pm860x led Copy 88pm860x platform data into different mfd_data structure for led driver. So move the identification of device node from led driver to mfd driver. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/leds/leds-88pm860x.c | 60 +++++++++++------------- drivers/mfd/88pm860x-core.c | 109 ++++++++++++++++++++----------------------- include/linux/mfd/88pm860x.h | 3 +- 3 files changed, 79 insertions(+), 93 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index e672b44ee17..416def84d04 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #define LED_PWM_SHIFT (3) @@ -118,7 +119,8 @@ static void pm860x_led_work(struct work_struct *work) struct pm860x_led *led; struct pm860x_chip *chip; - int mask; + unsigned char buf[3]; + int mask, ret; led = container_of(work, struct pm860x_led, work); chip = led->chip; @@ -128,16 +130,27 @@ static void pm860x_led_work(struct work_struct *work) pm860x_set_bits(led->i2c, __led_off(led->port), LED_CURRENT_MASK, led->iset); } + pm860x_set_bits(led->i2c, __blink_off(led->port), + LED_BLINK_MASK, LED_ON_CONTINUOUS); mask = __blink_ctl_mask(led->port); pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask); - } else if (led->brightness == 0) { - pm860x_set_bits(led->i2c, __led_off(led->port), - LED_CURRENT_MASK, 0); - mask = __blink_ctl_mask(led->port); - pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0); } pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK, led->brightness); + + if (led->brightness == 0) { + pm860x_bulk_read(led->i2c, __led_off(led->port), 3, buf); + ret = buf[0] & LED_PWM_MASK; + ret |= buf[1] & LED_PWM_MASK; + ret |= buf[2] & LED_PWM_MASK; + if (ret == 0) { + /* unset current since no led is lighting */ + pm860x_set_bits(led->i2c, __led_off(led->port), + LED_CURRENT_MASK, 0); + mask = __blink_ctl_mask(led->port); + pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0); + } + } led->current_brightness = led->brightness; dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n", __led_off(led->port), led->brightness); @@ -153,31 +166,12 @@ static void pm860x_led_set(struct led_classdev *cdev, schedule_work(&data->work); } -static int __check_device(struct pm860x_led_pdata *pdata, char *name) -{ - struct pm860x_led_pdata *p = pdata; - int ret = -EINVAL; - - while (p && p->id) { - if ((p->id != PM8606_ID_LED) || (p->flags < 0)) - break; - - if (!strncmp(name, pm860x_led_name[p->flags], - MFD_NAME_SIZE)) { - ret = (int)p->flags; - break; - } - p++; - } - return ret; -} - static int pm860x_led_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_platform_data *pm860x_pdata; struct pm860x_led_pdata *pdata; struct pm860x_led *data; + struct mfd_cell *cell; struct resource *res; int ret; @@ -187,10 +181,11 @@ static int pm860x_led_probe(struct platform_device *pdev) return -EINVAL; } - if (pdev->dev.parent->platform_data) { - pm860x_pdata = pdev->dev.parent->platform_data; - pdata = pm860x_pdata->led; - } else { + cell = pdev->dev.platform_data; + if (cell == NULL) + return -ENODEV; + pdata = cell->mfd_data; + if (pdata == NULL) { dev_err(&pdev->dev, "No platform data!\n"); return -EINVAL; } @@ -198,12 +193,12 @@ static int pm860x_led_probe(struct platform_device *pdev) data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL); if (data == NULL) return -ENOMEM; - strncpy(data->name, res->name, MFD_NAME_SIZE); + strncpy(data->name, res->name, MFD_NAME_SIZE - 1); dev_set_drvdata(&pdev->dev, data); data->chip = chip; data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; data->iset = pdata->iset; - data->port = __check_device(pdata, data->name); + data->port = pdata->flags; if (data->port < 0) { dev_err(&pdev->dev, "check device failed\n"); kfree(data); @@ -221,6 +216,7 @@ static int pm860x_led_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); goto out; } + pm860x_led_set(&data->cdev, 0); return 0; out: kfree(data); diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index a88967a466c..cec375c534b 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -26,57 +26,32 @@ static struct resource bk_resources[] __initdata = { {PM8606_BACKLIGHT3, PM8606_BACKLIGHT3, "backlight-2", IORESOURCE_IO,}, }; +static struct resource led_resources[] __initdata = { + {PM8606_LED1_RED, PM8606_LED1_RED, "led0-red", IORESOURCE_IO,}, + {PM8606_LED1_GREEN, PM8606_LED1_GREEN, "led0-green", IORESOURCE_IO,}, + {PM8606_LED1_BLUE, PM8606_LED1_BLUE, "led0-blue", IORESOURCE_IO,}, + {PM8606_LED2_RED, PM8606_LED2_RED, "led1-red", IORESOURCE_IO,}, + {PM8606_LED2_GREEN, PM8606_LED2_GREEN, "led1-green", IORESOURCE_IO,}, + {PM8606_LED2_BLUE, PM8606_LED2_BLUE, "led1-blue", IORESOURCE_IO,}, +}; + static struct mfd_cell bk_devs[] __initdata = { {"88pm860x-backlight", 0,}, {"88pm860x-backlight", 1,}, {"88pm860x-backlight", 2,}, }; -static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)]; - -char pm860x_led_name[][MFD_NAME_SIZE] = { - "led0-red", - "led0-green", - "led0-blue", - "led1-red", - "led1-green", - "led1-blue", -}; -EXPORT_SYMBOL(pm860x_led_name); - -#define PM8606_LED_RESOURCE(_i, _x) \ -{ \ - .name = pm860x_led_name[_i], \ - .start = PM8606_##_x, \ - .end = PM8606_##_x, \ - .flags = IORESOURCE_IO, \ -} - -static struct resource led_resources[] = { - PM8606_LED_RESOURCE(PM8606_LED1_RED, 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), +static struct mfd_cell led_devs[] __initdata = { + {"88pm860x-led", 0,}, + {"88pm860x-led", 1,}, + {"88pm860x-led", 2,}, + {"88pm860x-led", 3,}, + {"88pm860x-led", 4,}, + {"88pm860x-led", 5,}, }; -#define PM8606_LED_DEVS(_i) \ -{ \ - .name = "88pm860x-led", \ - .num_resources = 1, \ - .resources = &led_resources[_i], \ - .id = _i, \ -} - -static struct mfd_cell led_devs[] = { - PM8606_LED_DEVS(PM8606_LED1_RED), - PM8606_LED_DEVS(PM8606_LED1_GREEN), - PM8606_LED_DEVS(PM8606_LED1_BLUE), - PM8606_LED_DEVS(PM8606_LED2_RED), - PM8606_LED_DEVS(PM8606_LED2_GREEN), - PM8606_LED_DEVS(PM8606_LED2_BLUE), -}; +static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)]; +static struct pm860x_led_pdata led_pdata[ARRAY_SIZE(led_devs)]; static struct resource touch_resources[] = { { @@ -611,25 +586,41 @@ static void __devinit device_bk_init(struct pm860x_chip *chip, } } -static void __devinit device_8606_init(struct pm860x_chip *chip, - struct i2c_client *i2c, - struct pm860x_platform_data *pdata) +static void __devinit device_led_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) { int ret; + int i, j, id; - if (pdata && pdata->led) { - ret = mfd_add_devices(chip->dev, 0, &led_devs[0], - ARRAY_SIZE(led_devs), - &led_resources[0], 0); - if (ret < 0) { - dev_err(chip->dev, "Failed to add led " - "subdev\n"); - goto out_dev; + if ((pdata == NULL) || (pdata->led == NULL)) + return; + + if (pdata->num_leds > ARRAY_SIZE(led_devs)) + pdata->num_leds = ARRAY_SIZE(led_devs); + + for (i = 0; i < pdata->num_leds; i++) { + memcpy(&led_pdata[i], &pdata->led[i], + sizeof(struct pm860x_led_pdata)); + led_devs[i].mfd_data = &led_pdata[i]; + + for (j = 0; j < ARRAY_SIZE(led_devs); j++) { + id = led_resources[j].start; + if (led_pdata[i].flags != id) + continue; + + led_devs[i].num_resources = 1; + led_devs[i].resources = &led_resources[j], + ret = mfd_add_devices(chip->dev, 0, + &led_devs[i], 1, + &led_resources[j], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add " + "led subdev\n"); + return; + } } } - return; -out_dev: - device_irq_exit(chip); } static void __devinit device_8607_init(struct pm860x_chip *chip, @@ -748,7 +739,7 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, switch (chip->id) { case CHIP_PM8606: device_bk_init(chip, chip->client, pdata); - device_8606_init(chip, chip->client, pdata); + device_led_init(chip, chip->client, pdata); break; case CHIP_PM8607: device_8607_init(chip, chip->client, pdata); @@ -759,7 +750,7 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, switch (chip->id) { case CHIP_PM8607: device_bk_init(chip, chip->companion, pdata); - device_8606_init(chip, chip->companion, pdata); + device_led_init(chip, chip->companion, pdata); break; case CHIP_PM8606: device_8607_init(chip, chip->companion, pdata); diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index f790d376622..ff606140f4b 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -356,12 +356,11 @@ struct pm860x_platform_data { int i2c_port; /* Controlled by GI2C or PI2C */ int irq_mode; /* Clear interrupt by read/write(0/1) */ int irq_base; /* IRQ base number of 88pm860x */ + int num_leds; int num_backlights; struct regulator_init_data *regulator[PM8607_MAX_REGULATOR]; }; -extern char pm860x_led_name[][MFD_NAME_SIZE]; - extern int pm860x_reg_read(struct i2c_client *, int); extern int pm860x_reg_write(struct i2c_client *, int, unsigned char); extern int pm860x_bulk_read(struct i2c_client *, int, int, unsigned char *); -- cgit v1.2.3-70-g09d2 From 22aad0011e4728a29bf3775b6f5e2f9677abd8c0 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 7 Mar 2011 23:43:11 +0800 Subject: mfd: Adopt mfd_data in 88pm860x regulator Copy 88pm860x platform data into different mfd_data structure for regulator driver. So move the identification of device node from regulator driver to mfd driver. Signed-off-by: Haojian Zhuang Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-core.c | 165 +++++++++++++++++++++++++++---------------- drivers/regulator/88pm8607.c | 46 ++++++------ include/linux/mfd/88pm860x.h | 7 +- 3 files changed, 132 insertions(+), 86 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index cec375c534b..96ea0c64bbb 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -17,6 +17,7 @@ #include #include #include +#include #define INT_STATUS_NUM 3 @@ -35,6 +36,27 @@ static struct resource led_resources[] __initdata = { {PM8606_LED2_BLUE, PM8606_LED2_BLUE, "led1-blue", IORESOURCE_IO,}, }; +static struct resource regulator_resources[] __initdata = { + {PM8607_ID_BUCK1, PM8607_ID_BUCK1, "buck-1", IORESOURCE_IO,}, + {PM8607_ID_BUCK2, PM8607_ID_BUCK2, "buck-2", IORESOURCE_IO,}, + {PM8607_ID_BUCK3, PM8607_ID_BUCK3, "buck-3", IORESOURCE_IO,}, + {PM8607_ID_LDO1, PM8607_ID_LDO1, "ldo-01", IORESOURCE_IO,}, + {PM8607_ID_LDO2, PM8607_ID_LDO2, "ldo-02", IORESOURCE_IO,}, + {PM8607_ID_LDO3, PM8607_ID_LDO3, "ldo-03", IORESOURCE_IO,}, + {PM8607_ID_LDO4, PM8607_ID_LDO4, "ldo-04", IORESOURCE_IO,}, + {PM8607_ID_LDO5, PM8607_ID_LDO5, "ldo-05", IORESOURCE_IO,}, + {PM8607_ID_LDO6, PM8607_ID_LDO6, "ldo-06", IORESOURCE_IO,}, + {PM8607_ID_LDO7, PM8607_ID_LDO7, "ldo-07", IORESOURCE_IO,}, + {PM8607_ID_LDO8, PM8607_ID_LDO8, "ldo-08", IORESOURCE_IO,}, + {PM8607_ID_LDO9, PM8607_ID_LDO9, "ldo-09", IORESOURCE_IO,}, + {PM8607_ID_LDO10, PM8607_ID_LDO10, "ldo-10", IORESOURCE_IO,}, + {PM8607_ID_LDO11, PM8607_ID_LDO11, "ldo-11", IORESOURCE_IO,}, + {PM8607_ID_LDO12, PM8607_ID_LDO12, "ldo-12", IORESOURCE_IO,}, + {PM8607_ID_LDO13, PM8607_ID_LDO13, "ldo-13", IORESOURCE_IO,}, + {PM8607_ID_LDO14, PM8607_ID_LDO14, "ldo-14", IORESOURCE_IO,}, + {PM8607_ID_LDO15, PM8607_ID_LDO15, "ldo-15", IORESOURCE_IO,}, +}; + static struct mfd_cell bk_devs[] __initdata = { {"88pm860x-backlight", 0,}, {"88pm860x-backlight", 1,}, @@ -50,8 +72,30 @@ static struct mfd_cell led_devs[] __initdata = { {"88pm860x-led", 5,}, }; +static struct mfd_cell regulator_devs[] __initdata = { + {"88pm860x-regulator", 0,}, + {"88pm860x-regulator", 1,}, + {"88pm860x-regulator", 2,}, + {"88pm860x-regulator", 3,}, + {"88pm860x-regulator", 4,}, + {"88pm860x-regulator", 5,}, + {"88pm860x-regulator", 6,}, + {"88pm860x-regulator", 7,}, + {"88pm860x-regulator", 8,}, + {"88pm860x-regulator", 9,}, + {"88pm860x-regulator", 10,}, + {"88pm860x-regulator", 11,}, + {"88pm860x-regulator", 12,}, + {"88pm860x-regulator", 13,}, + {"88pm860x-regulator", 14,}, + {"88pm860x-regulator", 15,}, + {"88pm860x-regulator", 16,}, + {"88pm860x-regulator", 17,}, +}; + static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)]; static struct pm860x_led_pdata led_pdata[ARRAY_SIZE(led_devs)]; +static struct regulator_init_data regulator_pdata[ARRAY_SIZE(regulator_devs)]; static struct resource touch_resources[] = { { @@ -69,13 +113,6 @@ static struct mfd_cell touch_devs[] = { }, }; -#define PM8607_REG_RESOURCE(_start, _end) \ -{ \ - .start = PM8607_##_start, \ - .end = PM8607_##_end, \ - .flags = IORESOURCE_IO, \ -} - static struct resource power_supply_resources[] = { { .name = "88pm860x-power", @@ -149,52 +186,6 @@ static struct mfd_cell codec_devs[] = { }, }; -static struct resource regulator_resources[] = { - PM8607_REG_RESOURCE(BUCK1, BUCK1), - PM8607_REG_RESOURCE(BUCK2, BUCK2), - PM8607_REG_RESOURCE(BUCK3, BUCK3), - PM8607_REG_RESOURCE(LDO1, LDO1), - PM8607_REG_RESOURCE(LDO2, LDO2), - PM8607_REG_RESOURCE(LDO3, LDO3), - PM8607_REG_RESOURCE(LDO4, LDO4), - PM8607_REG_RESOURCE(LDO5, LDO5), - PM8607_REG_RESOURCE(LDO6, LDO6), - PM8607_REG_RESOURCE(LDO7, LDO7), - PM8607_REG_RESOURCE(LDO8, LDO8), - PM8607_REG_RESOURCE(LDO9, LDO9), - PM8607_REG_RESOURCE(LDO10, LDO10), - PM8607_REG_RESOURCE(LDO12, LDO12), - PM8607_REG_RESOURCE(VIBRATOR_SET, VIBRATOR_SET), - PM8607_REG_RESOURCE(LDO14, LDO14), -}; - -#define PM8607_REG_DEVS(_id) \ -{ \ - .name = "88pm860x-regulator", \ - .num_resources = 1, \ - .resources = ®ulator_resources[PM8607_ID_##_id], \ - .id = PM8607_ID_##_id, \ -} - -static struct mfd_cell regulator_devs[] = { - PM8607_REG_DEVS(BUCK1), - PM8607_REG_DEVS(BUCK2), - PM8607_REG_DEVS(BUCK3), - PM8607_REG_DEVS(LDO1), - PM8607_REG_DEVS(LDO2), - PM8607_REG_DEVS(LDO3), - PM8607_REG_DEVS(LDO4), - PM8607_REG_DEVS(LDO5), - PM8607_REG_DEVS(LDO6), - PM8607_REG_DEVS(LDO7), - PM8607_REG_DEVS(LDO8), - PM8607_REG_DEVS(LDO9), - PM8607_REG_DEVS(LDO10), - PM8607_REG_DEVS(LDO12), - PM8607_REG_DEVS(LDO13), - PM8607_REG_DEVS(LDO14), -}; - struct pm860x_irq_data { int reg; int mask_reg; @@ -623,6 +614,64 @@ static void __devinit device_led_init(struct pm860x_chip *chip, } } +static void __devinit device_regulator_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ + struct regulator_init_data *initdata; + int ret; + int i, j; + + if ((pdata == NULL) || (pdata->regulator == NULL)) + return; + + if (pdata->num_regulators > ARRAY_SIZE(regulator_devs)) + pdata->num_regulators = ARRAY_SIZE(regulator_devs); + + for (i = 0, j = -1; i < pdata->num_regulators; i++) { + initdata = &pdata->regulator[i]; + if (strstr(initdata->constraints.name, "BUCK")) { + sscanf(initdata->constraints.name, "BUCK%d", &j); + /* BUCK1 ~ BUCK3 */ + if ((j < 1) || (j > 3)) { + dev_err(chip->dev, "Failed to add constraint " + "(%s)\n", initdata->constraints.name); + goto out; + } + j = (j - 1) + PM8607_ID_BUCK1; + } + if (strstr(initdata->constraints.name, "LDO")) { + sscanf(initdata->constraints.name, "LDO%d", &j); + /* LDO1 ~ LDO15 */ + if ((j < 1) || (j > 15)) { + dev_err(chip->dev, "Failed to add constraint " + "(%s)\n", initdata->constraints.name); + goto out; + } + j = (j - 1) + PM8607_ID_LDO1; + } + if (j == -1) { + dev_err(chip->dev, "Failed to add constraint (%s)\n", + initdata->constraints.name); + goto out; + } + memcpy(®ulator_pdata[i], &pdata->regulator[i], + sizeof(struct regulator_init_data)); + regulator_devs[i].mfd_data = ®ulator_pdata[i]; + regulator_devs[i].num_resources = 1; + regulator_devs[i].resources = ®ulator_resources[j]; + + ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[i], 1, + ®ulator_resources[j], 0); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + goto out; + } + } +out: + return; +} + static void __devinit device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c, struct pm860x_platform_data *pdata) @@ -678,14 +727,6 @@ static void __devinit device_8607_init(struct pm860x_chip *chip, if (ret < 0) goto out; - ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], - ARRAY_SIZE(regulator_devs), - ®ulator_resources[0], 0); - if (ret < 0) { - dev_err(chip->dev, "Failed to add regulator subdev\n"); - goto out_dev; - } - if (pdata && pdata->touch) { ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], ARRAY_SIZE(touch_devs), @@ -723,6 +764,8 @@ static void __devinit device_8607_init(struct pm860x_chip *chip, dev_err(chip->dev, "Failed to add codec subdev\n"); goto out_dev; } + + device_regulator_init(chip, i2c, pdata); return; out_dev: mfd_remove_devices(chip->dev); diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index dd6308499bd..859251250b5 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -15,6 +15,7 @@ #include #include #include +#include #include struct pm8607_regulator_info { @@ -394,47 +395,48 @@ static struct pm8607_regulator_info pm8607_regulator_info[] = { PM8607_LDO(14, LDO14, 0, 4, SUPPLIES_EN12, 6), }; -static inline struct pm8607_regulator_info *find_regulator_info(int id) +static int __devinit pm8607_regulator_probe(struct platform_device *pdev) { - struct pm8607_regulator_info *info; + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm8607_regulator_info *info = NULL; + struct regulator_init_data *pdata; + struct mfd_cell *cell; int i; + cell = pdev->dev.platform_data; + if (cell == NULL) + return -ENODEV; + pdata = cell->mfd_data; + if (pdata == NULL) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(pm8607_regulator_info); i++) { info = &pm8607_regulator_info[i]; - if (info->desc.id == id) - return info; + if (!strcmp(info->desc.name, pdata->constraints.name)) + break; } - return NULL; -} - -static int __devinit pm8607_regulator_probe(struct platform_device *pdev) -{ - struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_platform_data *pdata = chip->dev->platform_data; - struct pm8607_regulator_info *info = NULL; - - info = find_regulator_info(pdev->id); - if (info == NULL) { - dev_err(&pdev->dev, "invalid regulator ID specified\n"); + if (i > ARRAY_SIZE(pm8607_regulator_info)) { + dev_err(&pdev->dev, "Failed to find regulator %s\n", + pdata->constraints.name); return -EINVAL; } info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; info->chip = chip; + /* check DVC ramp slope double */ + if (!strcmp(info->desc.name, "BUCK3")) + if (info->chip->buck3_double) + info->slope_double = 1; + info->regulator = regulator_register(&info->desc, &pdev->dev, - pdata->regulator[pdev->id], info); + pdata, info); if (IS_ERR(info->regulator)) { dev_err(&pdev->dev, "failed to register regulator %s\n", info->desc.name); return PTR_ERR(info->regulator); } - /* check DVC ramp slope double */ - if (info->desc.id == PM8607_ID_BUCK3) - if (info->chip->buck3_double) - info->slope_double = 1; - platform_set_drvdata(pdev, info); return 0; } diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index ff606140f4b..a6f6f81efec 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -131,9 +131,11 @@ enum { PM8607_ID_LDO8, PM8607_ID_LDO9, PM8607_ID_LDO10, + PM8607_ID_LDO11, PM8607_ID_LDO12, PM8607_ID_LDO13, PM8607_ID_LDO14, + PM8607_ID_LDO15, PM8607_ID_RG_MAX, }; @@ -310,8 +312,6 @@ struct pm860x_chip { }; -#define PM8607_MAX_REGULATOR PM8607_ID_RG_MAX /* 3 Bucks, 13 LDOs */ - enum { GI2C_PORT = 0, PI2C_PORT, @@ -351,6 +351,7 @@ struct pm860x_platform_data { struct pm860x_led_pdata *led; struct pm860x_touch_pdata *touch; struct pm860x_power_pdata *power; + struct regulator_init_data *regulator; unsigned short companion_addr; /* I2C address of companion chip */ int i2c_port; /* Controlled by GI2C or PI2C */ @@ -358,7 +359,7 @@ struct pm860x_platform_data { int irq_base; /* IRQ base number of 88pm860x */ int num_leds; int num_backlights; - struct regulator_init_data *regulator[PM8607_MAX_REGULATOR]; + int num_regulators; }; extern int pm860x_reg_read(struct i2c_client *, int); -- cgit v1.2.3-70-g09d2 From 09b034191acd1f95a749630fe366a84d3029930c Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Mon, 7 Mar 2011 23:43:16 +0800 Subject: mfd: Append additional read write on 88pm860x Append the additional read/write operation on 88pm860x for accessing test page in 88PM860x. Signed-off-by: Haojian Zhuang Signed-off-by: Samuel Ortiz --- drivers/mfd/88pm860x-i2c.c | 103 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/88pm860x.h | 8 ++++ 2 files changed, 111 insertions(+) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index bc02e6b2160..e017dc88622 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -126,6 +126,109 @@ out: } EXPORT_SYMBOL(pm860x_set_bits); +int pm860x_page_reg_read(struct i2c_client *i2c, int reg) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char zero = 0; + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + pm860x_write_device(i2c, 0xFA, 0, &zero); + pm860x_write_device(i2c, 0xFB, 0, &zero); + pm860x_write_device(i2c, 0xFF, 0, &zero); + ret = pm860x_read_device(i2c, reg, 1, &data); + if (ret >= 0) + ret = (int)data; + pm860x_write_device(i2c, 0xFE, 0, &zero); + pm860x_write_device(i2c, 0xFC, 0, &zero); + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_page_reg_read); + +int pm860x_page_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char zero; + int ret; + + mutex_lock(&chip->io_lock); + pm860x_write_device(i2c, 0xFA, 0, &zero); + pm860x_write_device(i2c, 0xFB, 0, &zero); + pm860x_write_device(i2c, 0xFF, 0, &zero); + ret = pm860x_write_device(i2c, reg, 1, &data); + pm860x_write_device(i2c, 0xFE, 0, &zero); + pm860x_write_device(i2c, 0xFC, 0, &zero); + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_page_reg_write); + +int pm860x_page_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char zero = 0; + int ret; + + mutex_lock(&chip->io_lock); + pm860x_write_device(i2c, 0xFA, 0, &zero); + pm860x_write_device(i2c, 0xFB, 0, &zero); + pm860x_write_device(i2c, 0xFF, 0, &zero); + ret = pm860x_read_device(i2c, reg, count, buf); + pm860x_write_device(i2c, 0xFE, 0, &zero); + pm860x_write_device(i2c, 0xFC, 0, &zero); + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_page_bulk_read); + +int pm860x_page_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char zero = 0; + int ret; + + mutex_lock(&chip->io_lock); + pm860x_write_device(i2c, 0xFA, 0, &zero); + pm860x_write_device(i2c, 0xFB, 0, &zero); + pm860x_write_device(i2c, 0xFF, 0, &zero); + ret = pm860x_write_device(i2c, reg, count, buf); + pm860x_write_device(i2c, 0xFE, 0, &zero); + pm860x_write_device(i2c, 0xFC, 0, &zero); + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_page_bulk_write); + +int pm860x_page_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char zero; + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + pm860x_write_device(i2c, 0xFA, 0, &zero); + pm860x_write_device(i2c, 0xFB, 0, &zero); + pm860x_write_device(i2c, 0xFF, 0, &zero); + ret = pm860x_read_device(i2c, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = pm860x_write_device(i2c, reg, 1, &value); +out: + pm860x_write_device(i2c, 0xFE, 0, &zero); + pm860x_write_device(i2c, 0xFC, 0, &zero); + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_page_set_bits); static const struct i2c_device_id pm860x_id_table[] = { { "88PM860x", 0 }, diff --git a/include/linux/mfd/88pm860x.h b/include/linux/mfd/88pm860x.h index a6f6f81efec..8fba7972ff5 100644 --- a/include/linux/mfd/88pm860x.h +++ b/include/linux/mfd/88pm860x.h @@ -368,6 +368,14 @@ extern int pm860x_bulk_read(struct i2c_client *, int, int, unsigned char *); extern int pm860x_bulk_write(struct i2c_client *, int, int, unsigned char *); extern int pm860x_set_bits(struct i2c_client *, int, unsigned char, unsigned char); +extern int pm860x_page_reg_read(struct i2c_client *, int); +extern int pm860x_page_reg_write(struct i2c_client *, int, unsigned char); +extern int pm860x_page_bulk_read(struct i2c_client *, int, int, + unsigned char *); +extern int pm860x_page_bulk_write(struct i2c_client *, int, int, + unsigned char *); +extern int pm860x_page_set_bits(struct i2c_client *, int, unsigned char, + unsigned char); extern int pm860x_device_init(struct pm860x_chip *chip, struct pm860x_platform_data *pdata) __devinit ; -- cgit v1.2.3-70-g09d2 From e93c53870c6d77c40de8981da238af947d6aa084 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Mar 2011 13:54:07 +0000 Subject: mfd: Add WM8994 bulk register write operation Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 23 +++++++++++++++++++++++ include/linux/mfd/wm8994/core.h | 2 ++ 2 files changed, 25 insertions(+) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index e673bda21f5..5220af26419 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -137,6 +137,29 @@ int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg, } EXPORT_SYMBOL_GPL(wm8994_reg_write); +/** + * wm8994_bulk_write: Write multiple WM8994 registers + * + * @wm8994: Device to write to + * @reg: First register + * @count: Number of registers + * @buf: Buffer to write from. + */ +int wm8994_bulk_write(struct wm8994 *wm8994, unsigned short reg, + int count, u16 *buf) +{ + int ret; + + mutex_lock(&wm8994->io_lock); + + ret = wm8994_write(wm8994, reg, count * 2, buf); + + mutex_unlock(&wm8994->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8994_bulk_write); + /** * wm8994_set_bits: Set the value of a bitfield in a WM8994 register * diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index ef4f0b6083a..cb7d3ae7da8 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -88,6 +88,8 @@ int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg, unsigned short mask, unsigned short val); int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, int count, u16 *buf); +int wm8994_bulk_write(struct wm8994 *wm8994, unsigned short reg, + int count, u16 *buf); /* Helper to save on boilerplate */ -- cgit v1.2.3-70-g09d2 From bd6ca2cf50fbe3cc63513d026343035c3bd2e352 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 11 Mar 2011 11:34:44 +0900 Subject: regulator: MAX8997/8966 support This patch supports PMIC/Regulator part of MAX8997/MAX8966 MFD. In this initial release, selecting voltages or current-limit and switching on/off the regulators are supported. Controlling voltages for DVS with GPIOs is not implemented fully and requires more considerations: it controls multiple bucks (selection of 1, 2, and 5) at the same time with SET1~3 gpios. Thus, when DVS-GPIO is activated, we lose the ability to control the voltage of a single buck regulator independently; i.e., contolling a buck affects other two bucks. Therefore, using the conventional regulator framework directly might be problematic. However, in this driver, we try to choose a setting without such side effect of affecting other regulators and then try to choose a setting with the minimum side effect (the sum of voltage changes in other regulators). On the other hand, controlling all the three bucks simultenously based on the voltage set table may help build cpufreq and similar system more robust; i.e., all the three voltages are consistent every time without glitches during transition. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/max8997.c | 1213 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997.h | 28 +- 4 files changed, 1250 insertions(+), 1 deletion(-) create mode 100644 drivers/regulator/max8997.c (limited to 'include/linux/mfd') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index e1d943619ab..395d35941b8 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -108,6 +108,15 @@ config REGULATOR_MAX8952 via I2C bus. Maxim 8952 has one voltage output and supports 4 DVS modes ranging from 0.77V to 1.40V by 0.01V steps. +config REGULATOR_MAX8997 + tristate "Maxim 8997/8966 regulator" + depends on MFD_MAX8997 + help + This driver controls a Maxim 8997/8966 regulator + via I2C bus. The provided regulator is suitable for S5PC110, + S5PV210, and Exynos-4 chips to control VCC_CORE and + VCC_USIM voltages. + config REGULATOR_MAX8998 tristate "Maxim 8998 voltage regulator" depends on MFD_MAX8998 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 0b5e88c2b8d..e43b8524871 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o +obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o diff --git a/drivers/regulator/max8997.c b/drivers/regulator/max8997.c new file mode 100644 index 00000000000..01ef7e9903b --- /dev/null +++ b/drivers/regulator/max8997.c @@ -0,0 +1,1213 @@ +/* + * max8997.c - Regulator driver for the Maxim 8997/8966 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct max8997_data { + struct device *dev; + struct max8997_dev *iodev; + int num_regulators; + struct regulator_dev **rdev; + int ramp_delay; /* in mV/us */ + + u8 buck1_vol[8]; + u8 buck2_vol[8]; + u8 buck5_vol[8]; + int buck125_gpioindex; + + u8 saved_states[MAX8997_REG_MAX]; +}; + +static inline void max8997_set_gpio(struct max8997_data *max8997) +{ + struct max8997_platform_data *pdata = + dev_get_platdata(max8997->iodev->dev); + int set3 = (max8997->buck125_gpioindex) & 0x1; + int set2 = ((max8997->buck125_gpioindex) >> 1) & 0x1; + int set1 = ((max8997->buck125_gpioindex) >> 2) & 0x1; + + gpio_set_value(pdata->buck125_gpios[0], set1); + gpio_set_value(pdata->buck125_gpios[1], set2); + gpio_set_value(pdata->buck125_gpios[2], set3); +} + +struct voltage_map_desc { + int min; + int max; + int step; + unsigned int n_bits; +}; + +/* Voltage maps in mV */ +static const struct voltage_map_desc ldo_voltage_map_desc = { + .min = 800, .max = 3950, .step = 50, .n_bits = 6, +}; /* LDO1 ~ 18, 21 all */ + +static const struct voltage_map_desc buck1245_voltage_map_desc = { + .min = 650, .max = 2225, .step = 25, .n_bits = 6, +}; /* Buck1, 2, 4, 5 */ + +static const struct voltage_map_desc buck37_voltage_map_desc = { + .min = 750, .max = 3900, .step = 50, .n_bits = 6, +}; /* Buck3, 7 */ + +/* current map in mA */ +static const struct voltage_map_desc charger_current_map_desc = { + .min = 200, .max = 950, .step = 50, .n_bits = 4, +}; + +static const struct voltage_map_desc topoff_current_map_desc = { + .min = 50, .max = 200, .step = 10, .n_bits = 4, +}; + +static const struct voltage_map_desc *reg_voltage_map[] = { + [MAX8997_LDO1] = &ldo_voltage_map_desc, + [MAX8997_LDO2] = &ldo_voltage_map_desc, + [MAX8997_LDO3] = &ldo_voltage_map_desc, + [MAX8997_LDO4] = &ldo_voltage_map_desc, + [MAX8997_LDO5] = &ldo_voltage_map_desc, + [MAX8997_LDO6] = &ldo_voltage_map_desc, + [MAX8997_LDO7] = &ldo_voltage_map_desc, + [MAX8997_LDO8] = &ldo_voltage_map_desc, + [MAX8997_LDO9] = &ldo_voltage_map_desc, + [MAX8997_LDO10] = &ldo_voltage_map_desc, + [MAX8997_LDO11] = &ldo_voltage_map_desc, + [MAX8997_LDO12] = &ldo_voltage_map_desc, + [MAX8997_LDO13] = &ldo_voltage_map_desc, + [MAX8997_LDO14] = &ldo_voltage_map_desc, + [MAX8997_LDO15] = &ldo_voltage_map_desc, + [MAX8997_LDO16] = &ldo_voltage_map_desc, + [MAX8997_LDO17] = &ldo_voltage_map_desc, + [MAX8997_LDO18] = &ldo_voltage_map_desc, + [MAX8997_LDO21] = &ldo_voltage_map_desc, + [MAX8997_BUCK1] = &buck1245_voltage_map_desc, + [MAX8997_BUCK2] = &buck1245_voltage_map_desc, + [MAX8997_BUCK3] = &buck37_voltage_map_desc, + [MAX8997_BUCK4] = &buck1245_voltage_map_desc, + [MAX8997_BUCK5] = &buck1245_voltage_map_desc, + [MAX8997_BUCK6] = NULL, + [MAX8997_BUCK7] = &buck37_voltage_map_desc, + [MAX8997_EN32KHZ_AP] = NULL, + [MAX8997_EN32KHZ_CP] = NULL, + [MAX8997_ENVICHG] = NULL, + [MAX8997_ESAFEOUT1] = NULL, + [MAX8997_ESAFEOUT2] = NULL, + [MAX8997_CHARGER_CV] = NULL, + [MAX8997_CHARGER] = &charger_current_map_desc, + [MAX8997_CHARGER_TOPOFF] = &topoff_current_map_desc, +}; + +static inline int max8997_get_rid(struct regulator_dev *rdev) +{ + return rdev_get_id(rdev); +} + +static int max8997_list_voltage_safeout(struct regulator_dev *rdev, + unsigned int selector) +{ + int rid = max8997_get_rid(rdev); + + if (rid == MAX8997_ESAFEOUT1 || rid == MAX8997_ESAFEOUT2) { + switch (selector) { + case 0: + return 4850000; + case 1: + return 4900000; + case 2: + return 4950000; + case 3: + return 3300000; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static int max8997_list_voltage_charger_cv(struct regulator_dev *rdev, + unsigned int selector) +{ + int rid = max8997_get_rid(rdev); + + if (rid != MAX8997_CHARGER_CV) + goto err; + + switch (selector) { + case 0x00: + return 4200000; + case 0x01 ... 0x0E: + return 4000000 + 20000 * (selector - 0x01); + case 0x0F: + return 4350000; + default: + return -EINVAL; + } +err: + return -EINVAL; +} + +static int max8997_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + const struct voltage_map_desc *desc; + int rid = max8997_get_rid(rdev); + int val; + + if (rid >= ARRAY_SIZE(reg_voltage_map) || + rid < 0) + return -EINVAL; + + desc = reg_voltage_map[rid]; + if (desc == NULL) + return -EINVAL; + + val = desc->min + desc->step * selector; + if (val > desc->max) + return -EINVAL; + + return val * 1000; +} + +static int max8997_get_enable_register(struct regulator_dev *rdev, + int *reg, int *mask, int *pattern) +{ + int rid = max8997_get_rid(rdev); + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + *reg = MAX8997_REG_LDO1CTRL + (rid - MAX8997_LDO1); + *mask = 0xC0; + *pattern = 0xC0; + break; + case MAX8997_BUCK1: + *reg = MAX8997_REG_BUCK1CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK2: + *reg = MAX8997_REG_BUCK2CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK3: + *reg = MAX8997_REG_BUCK3CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK4: + *reg = MAX8997_REG_BUCK4CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK5: + *reg = MAX8997_REG_BUCK5CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK6: + *reg = MAX8997_REG_BUCK6CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK7: + *reg = MAX8997_REG_BUCK7CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_EN32KHZ_AP ... MAX8997_EN32KHZ_CP: + *reg = MAX8997_REG_MAINCON1; + *mask = 0x01 << (rid - MAX8997_EN32KHZ_AP); + *pattern = 0x01 << (rid - MAX8997_EN32KHZ_AP); + break; + case MAX8997_ENVICHG: + *reg = MAX8997_REG_MBCCTRL1; + *mask = 0x80; + *pattern = 0x80; + break; + case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2: + *reg = MAX8997_REG_SAFEOUTCTRL; + *mask = 0x40 << (rid - MAX8997_ESAFEOUT1); + *pattern = 0x40 << (rid - MAX8997_ESAFEOUT1); + break; + case MAX8997_CHARGER: + *reg = MAX8997_REG_MBCCTRL2; + *mask = 0x40; + *pattern = 0x40; + break; + default: + /* Not controllable or not exists */ + return -EINVAL; + break; + } + + return 0; +} + +static int max8997_reg_is_enabled(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + u8 val; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret == -EINVAL) + return 1; /* "not controllable" */ + else if (ret) + return ret; + + ret = max8997_read_reg(i2c, reg, &val); + if (ret) + return ret; + + return (val & mask) == pattern; +} + +static int max8997_reg_enable(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + return max8997_update_reg(i2c, reg, pattern, mask); +} + +static int max8997_reg_disable(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + return max8997_update_reg(i2c, reg, ~pattern, mask); +} + +static int max8997_get_voltage_register(struct regulator_dev *rdev, + int *_reg, int *_shift, int *_mask) +{ + int rid = max8997_get_rid(rdev); + int reg, shift = 0, mask = 0x3f; + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + reg = MAX8997_REG_LDO1CTRL + (rid - MAX8997_LDO1); + break; + case MAX8997_BUCK1: + reg = MAX8997_REG_BUCK1DVS1; + break; + case MAX8997_BUCK2: + reg = MAX8997_REG_BUCK2DVS1; + break; + case MAX8997_BUCK3: + reg = MAX8997_REG_BUCK3DVS; + break; + case MAX8997_BUCK4: + reg = MAX8997_REG_BUCK4DVS; + break; + case MAX8997_BUCK5: + reg = MAX8997_REG_BUCK5DVS1; + break; + case MAX8997_BUCK7: + reg = MAX8997_REG_BUCK7DVS; + break; + case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2: + reg = MAX8997_REG_SAFEOUTCTRL; + shift = (rid == MAX8997_ESAFEOUT2) ? 2 : 0; + mask = 0x3; + break; + case MAX8997_CHARGER_CV: + reg = MAX8997_REG_MBCCTRL3; + shift = 0; + mask = 0xf; + break; + case MAX8997_CHARGER: + reg = MAX8997_REG_MBCCTRL4; + shift = 0; + mask = 0xf; + break; + case MAX8997_CHARGER_TOPOFF: + reg = MAX8997_REG_MBCCTRL5; + shift = 0; + mask = 0xf; + break; + default: + return -EINVAL; + } + + *_reg = reg; + *_shift = shift; + *_mask = mask; + + return 0; +} + +static int max8997_get_voltage(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct max8997_platform_data *pdata = + dev_get_platdata(max8997->iodev->dev); + struct i2c_client *i2c = max8997->iodev->i2c; + int reg, shift, mask, ret; + int rid = max8997_get_rid(rdev); + u8 val; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + if ((rid == MAX8997_BUCK1 && pdata->buck1_gpiodvs) || + (rid == MAX8997_BUCK2 && pdata->buck2_gpiodvs) || + (rid == MAX8997_BUCK5 && pdata->buck5_gpiodvs)) + reg += max8997->buck125_gpioindex; + + ret = max8997_read_reg(i2c, reg, &val); + if (ret) + return ret; + + val >>= shift; + val &= mask; + + if (rdev->desc && rdev->desc->ops && rdev->desc->ops->list_voltage) + return rdev->desc->ops->list_voltage(rdev, val); + + /* + * max8997_list_voltage returns value for any rdev with voltage_map, + * which works for "CHARGER" and "CHARGER TOPOFF" that do not have + * list_voltage ops (they are current regulators). + */ + return max8997_list_voltage(rdev, val); +} + +static inline int max8997_get_voltage_proper_val( + const struct voltage_map_desc *desc, + int min_vol, int max_vol) +{ + int i = 0; + + if (desc == NULL) + return -EINVAL; + + if (max_vol < desc->min || min_vol > desc->max) + return -EINVAL; + + while (desc->min + desc->step * i < min_vol && + desc->min + desc->step * i < desc->max) + i++; + + if (desc->min + desc->step * i > max_vol) + return -EINVAL; + + if (i >= (1 << desc->n_bits)) + return -EINVAL; + + return i; +} + +static int max8997_set_voltage_charger_cv(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int rid = max8997_get_rid(rdev); + int lb, ub; + int reg, shift = 0, mask, ret = 0; + u8 val = 0x0; + + if (rid != MAX8997_CHARGER_CV) + return -EINVAL; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + if (max_uV < 4000000 || min_uV > 4350000) + return -EINVAL; + + if (min_uV <= 4000000) { + if (max_uV >= 4000000) + return -EINVAL; + else + val = 0x1; + } else if (min_uV <= 4200000 && max_uV >= 4200000) + val = 0x0; + else { + lb = (min_uV - 4000001) / 20000 + 2; + ub = (max_uV - 4000000) / 20000 + 1; + + if (lb > ub) + return -EINVAL; + + if (lb < 0xf) + val = lb; + else { + if (ub >= 0xf) + val = 0xf; + else + return -EINVAL; + } + } + + *selector = val; + + ret = max8997_update_reg(i2c, reg, val << shift, mask); + + return ret; +} + +/* + * For LDO1 ~ LDO21, BUCK1~5, BUCK7, CHARGER, CHARGER_TOPOFF + * BUCK1, 2, and 5 are available if they are not controlled by gpio + */ +static int max8997_set_voltage_ldobuck(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int min_vol = min_uV / 1000, max_vol = max_uV / 1000; + const struct voltage_map_desc *desc; + int rid = max8997_get_rid(rdev); + int reg, shift = 0, mask, ret; + int i; + u8 org; + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + break; + case MAX8997_BUCK1 ... MAX8997_BUCK5: + break; + case MAX8997_BUCK6: + return -EINVAL; + case MAX8997_BUCK7: + break; + case MAX8997_CHARGER: + break; + case MAX8997_CHARGER_TOPOFF: + break; + default: + return -EINVAL; + } + + desc = reg_voltage_map[rid]; + + i = max8997_get_voltage_proper_val(desc, min_vol, max_vol); + if (i < 0) + return i; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + max8997_read_reg(i2c, reg, &org); + org = (org & mask) >> shift; + + ret = max8997_update_reg(i2c, reg, i << shift, mask << shift); + *selector = i; + + if (rid == MAX8997_BUCK1 || rid == MAX8997_BUCK2 || + rid == MAX8997_BUCK4 || rid == MAX8997_BUCK5) { + /* If the voltage is increasing */ + if (org < i) + udelay(desc->step * (i - org) / max8997->ramp_delay); + } + + return ret; +} + +/* + * Assess the damage on the voltage setting of BUCK1,2,5 by the change. + * + * When GPIO-DVS mode is used for multiple bucks, changing the voltage value + * of one of the bucks may affect that of another buck, which is the side + * effect of the change (set_voltage). This function examines the GPIO-DVS + * configurations and checks whether such side-effect exists. + */ +static int max8997_assess_side_effect(struct regulator_dev *rdev, + u8 new_val, int *best) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct max8997_platform_data *pdata = + dev_get_platdata(max8997->iodev->dev); + int rid = max8997_get_rid(rdev); + u8 *buckx_val[3]; + bool buckx_gpiodvs[3]; + int side_effect[8]; + int min_side_effect = INT_MAX; + int i; + + *best = -1; + + switch (rid) { + case MAX8997_BUCK1: + rid = 0; + break; + case MAX8997_BUCK2: + rid = 1; + break; + case MAX8997_BUCK5: + rid = 2; + break; + default: + return -EINVAL; + } + + buckx_val[0] = max8997->buck1_vol; + buckx_val[1] = max8997->buck2_vol; + buckx_val[2] = max8997->buck5_vol; + buckx_gpiodvs[0] = pdata->buck1_gpiodvs; + buckx_gpiodvs[1] = pdata->buck2_gpiodvs; + buckx_gpiodvs[2] = pdata->buck5_gpiodvs; + + for (i = 0; i < 8; i++) { + int others; + + if (new_val != (buckx_val[rid])[i]) { + side_effect[i] = -1; + continue; + } + + side_effect[i] = 0; + for (others = 0; others < 3; others++) { + int diff; + + if (others == rid) + continue; + if (buckx_gpiodvs[others] == false) + continue; /* Not affected */ + diff = (buckx_val[others])[i] - + (buckx_val[others])[max8997->buck125_gpioindex]; + if (diff > 0) + side_effect[i] += diff; + else if (diff < 0) + side_effect[i] -= diff; + } + if (side_effect[i] == 0) { + *best = i; + return 0; /* NO SIDE EFFECT! Use This! */ + } + if (side_effect[i] < min_side_effect) { + min_side_effect = side_effect[i]; + *best = i; + } + } + + if (*best == -1) + return -EINVAL; + + return side_effect[*best]; +} + +/* + * For Buck 1 ~ 5 and 7. If it is not controlled by GPIO, this calls + * max8997_set_voltage_ldobuck to do the job. + */ +static int max8997_set_voltage_buck(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct max8997_platform_data *pdata = + dev_get_platdata(max8997->iodev->dev); + int rid = max8997_get_rid(rdev); + const struct voltage_map_desc *desc; + int new_val, new_idx, damage, tmp_val, tmp_idx, tmp_dmg; + bool gpio_dvs_mode = false; + int min_vol = min_uV / 1000, max_vol = max_uV / 1000; + + if (rid < MAX8997_BUCK1 || rid > MAX8997_BUCK7) + return -EINVAL; + + switch (rid) { + case MAX8997_BUCK1: + if (pdata->buck1_gpiodvs) + gpio_dvs_mode = true; + break; + case MAX8997_BUCK2: + if (pdata->buck2_gpiodvs) + gpio_dvs_mode = true; + break; + case MAX8997_BUCK5: + if (pdata->buck5_gpiodvs) + gpio_dvs_mode = true; + break; + } + + if (!gpio_dvs_mode) + return max8997_set_voltage_ldobuck(rdev, min_uV, max_uV, + selector); + + desc = reg_voltage_map[rid]; + new_val = max8997_get_voltage_proper_val(desc, min_vol, max_vol); + if (new_val < 0) + return new_val; + + tmp_dmg = INT_MAX; + tmp_idx = -1; + tmp_val = -1; + do { + damage = max8997_assess_side_effect(rdev, new_val, &new_idx); + if (damage == 0) + goto out; + + if (tmp_dmg > damage) { + tmp_idx = new_idx; + tmp_val = new_val; + tmp_dmg = damage; + } + + new_val++; + } while (desc->min + desc->step + new_val <= desc->max); + + new_idx = tmp_idx; + new_val = tmp_val; + + if (pdata->ignore_gpiodvs_side_effect == false) + return -EINVAL; + + dev_warn(&rdev->dev, "MAX8997 GPIO-DVS Side Effect Warning: GPIO SET:" + " %d -> %d\n", max8997->buck125_gpioindex, tmp_idx); + +out: + if (new_idx < 0 || new_val < 0) + return -EINVAL; + + max8997->buck125_gpioindex = new_idx; + max8997_set_gpio(max8997); + *selector = new_val; + + return 0; +} + +static const int safeoutvolt[] = { + 3300000, + 4850000, + 4900000, + 4950000, +}; + +/* For SAFEOUT1 and SAFEOUT2 */ +static int max8997_set_voltage_safeout(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int rid = max8997_get_rid(rdev); + int reg, shift = 0, mask, ret; + int i = 0; + u8 val; + + if (rid != MAX8997_ESAFEOUT1 && rid != MAX8997_ESAFEOUT2) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(safeoutvolt); i++) { + if (min_uV <= safeoutvolt[i] && + max_uV >= safeoutvolt[i]) + break; + } + + if (i >= ARRAY_SIZE(safeoutvolt)) + return -EINVAL; + + if (i == 0) + val = 0x3; + else + val = i - 1; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + ret = max8997_update_reg(i2c, reg, val << shift, mask << shift); + *selector = val; + + return ret; +} + +static int max8997_reg_enable_suspend(struct regulator_dev *rdev) +{ + return 0; +} + +static int max8997_reg_disable_suspend(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + int rid = max8997_get_rid(rdev); + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + max8997_read_reg(i2c, reg, &max8997->saved_states[rid]); + + if (rid == MAX8997_LDO1 || + rid == MAX8997_LDO10 || + rid == MAX8997_LDO21) { + dev_dbg(&rdev->dev, "Conditional Power-Off for %s\n", + rdev->desc->name); + return max8997_update_reg(i2c, reg, 0x40, mask); + } + + dev_dbg(&rdev->dev, "Full Power-Off for %s (%xh -> %xh)\n", + rdev->desc->name, max8997->saved_states[rid] & mask, + (~pattern) & mask); + return max8997_update_reg(i2c, reg, ~pattern, mask); +} + +static struct regulator_ops max8997_ldo_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage = max8997_get_voltage, + .set_voltage = max8997_set_voltage_ldobuck, + .set_suspend_enable = max8997_reg_enable_suspend, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_buck_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage = max8997_get_voltage, + .set_voltage = max8997_set_voltage_buck, + .set_suspend_enable = max8997_reg_enable_suspend, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_fixedvolt_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .set_suspend_enable = max8997_reg_enable_suspend, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_safeout_ops = { + .list_voltage = max8997_list_voltage_safeout, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage = max8997_get_voltage, + .set_voltage = max8997_set_voltage_safeout, + .set_suspend_enable = max8997_reg_enable_suspend, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_fixedstate_ops = { + .list_voltage = max8997_list_voltage_charger_cv, + .get_voltage = max8997_get_voltage, + .set_voltage = max8997_set_voltage_charger_cv, +}; + +static int max8997_set_voltage_ldobuck_wrap(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + unsigned dummy; + + return max8997_set_voltage_ldobuck(rdev, min_uV, max_uV, &dummy); +} + + +static struct regulator_ops max8997_charger_ops = { + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_current_limit = max8997_get_voltage, + .set_current_limit = max8997_set_voltage_ldobuck_wrap, +}; + +static struct regulator_ops max8997_charger_fixedstate_ops = { + .is_enabled = max8997_reg_is_enabled, + .get_current_limit = max8997_get_voltage, + .set_current_limit = max8997_set_voltage_ldobuck_wrap, +}; + +#define regulator_desc_ldo(num) { \ + .name = "LDO"#num, \ + .id = MAX8997_LDO##num, \ + .ops = &max8997_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} +#define regulator_desc_buck(num) { \ + .name = "BUCK"#num, \ + .id = MAX8997_BUCK##num, \ + .ops = &max8997_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} + +static struct regulator_desc regulators[] = { + regulator_desc_ldo(1), + regulator_desc_ldo(2), + regulator_desc_ldo(3), + regulator_desc_ldo(4), + regulator_desc_ldo(5), + regulator_desc_ldo(6), + regulator_desc_ldo(7), + regulator_desc_ldo(8), + regulator_desc_ldo(9), + regulator_desc_ldo(10), + regulator_desc_ldo(11), + regulator_desc_ldo(12), + regulator_desc_ldo(13), + regulator_desc_ldo(14), + regulator_desc_ldo(15), + regulator_desc_ldo(16), + regulator_desc_ldo(17), + regulator_desc_ldo(18), + regulator_desc_ldo(21), + regulator_desc_buck(1), + regulator_desc_buck(2), + regulator_desc_buck(3), + regulator_desc_buck(4), + regulator_desc_buck(5), + { + .name = "BUCK6", + .id = MAX8997_BUCK6, + .ops = &max8997_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + regulator_desc_buck(7), + { + .name = "EN32KHz AP", + .id = MAX8997_EN32KHZ_AP, + .ops = &max8997_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "EN32KHz CP", + .id = MAX8997_EN32KHZ_CP, + .ops = &max8997_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ENVICHG", + .id = MAX8997_ENVICHG, + .ops = &max8997_fixedvolt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ESAFEOUT1", + .id = MAX8997_ESAFEOUT1, + .ops = &max8997_safeout_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ESAFEOUT2", + .id = MAX8997_ESAFEOUT2, + .ops = &max8997_safeout_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "CHARGER CV", + .id = MAX8997_CHARGER_CV, + .ops = &max8997_fixedstate_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "CHARGER", + .id = MAX8997_CHARGER, + .ops = &max8997_charger_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + }, { + .name = "CHARGER TOPOFF", + .id = MAX8997_CHARGER_TOPOFF, + .ops = &max8997_charger_fixedstate_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + }, +}; + +static __devinit int max8997_pmic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); + struct regulator_dev **rdev; + struct max8997_data *max8997; + struct i2c_client *i2c; + int i, ret, size; + u8 max_buck1 = 0, max_buck2 = 0, max_buck5 = 0; + + if (!pdata) { + dev_err(pdev->dev.parent, "No platform init data supplied.\n"); + return -ENODEV; + } + + max8997 = kzalloc(sizeof(struct max8997_data), GFP_KERNEL); + if (!max8997) + return -ENOMEM; + + size = sizeof(struct regulator_dev *) * pdata->num_regulators; + max8997->rdev = kzalloc(size, GFP_KERNEL); + if (!max8997->rdev) { + kfree(max8997); + return -ENOMEM; + } + + rdev = max8997->rdev; + max8997->dev = &pdev->dev; + max8997->iodev = iodev; + max8997->num_regulators = pdata->num_regulators; + platform_set_drvdata(pdev, max8997); + i2c = max8997->iodev->i2c; + + max8997->buck125_gpioindex = pdata->buck125_default_idx; + + for (i = 0; i < 8; i++) { + max8997->buck1_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck1_voltage[i] / 1000, + pdata->buck1_voltage[i] / 1000 + + buck1245_voltage_map_desc.step); + if (ret < 0) + goto err_alloc; + + max8997->buck2_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck2_voltage[i] / 1000, + pdata->buck2_voltage[i] / 1000 + + buck1245_voltage_map_desc.step); + if (ret < 0) + goto err_alloc; + + max8997->buck5_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck5_voltage[i] / 1000, + pdata->buck5_voltage[i] / 1000 + + buck1245_voltage_map_desc.step); + if (ret < 0) + goto err_alloc; + + if (max_buck1 < max8997->buck1_vol[i]) + max_buck1 = max8997->buck1_vol[i]; + if (max_buck2 < max8997->buck2_vol[i]) + max_buck2 = max8997->buck2_vol[i]; + if (max_buck5 < max8997->buck5_vol[i]) + max_buck5 = max8997->buck5_vol[i]; + } + + /* For the safety, set max voltage before setting up */ + for (i = 0; i < 8; i++) { + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1), + max_buck1, 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1), + max_buck2, 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1), + max_buck5, 0x3f); + } + + /* + * If buck 1, 2, and 5 do not care DVS GPIO settings, ignore them. + * If at least one of them cares, set gpios. + */ + if (pdata->buck1_gpiodvs || pdata->buck2_gpiodvs || + pdata->buck5_gpiodvs) { + bool gpio1set = false, gpio2set = false; + + if (!gpio_is_valid(pdata->buck125_gpios[0]) || + !gpio_is_valid(pdata->buck125_gpios[1]) || + !gpio_is_valid(pdata->buck125_gpios[2])) { + dev_err(&pdev->dev, "GPIO NOT VALID\n"); + ret = -EINVAL; + goto err_alloc; + } + + ret = gpio_request(pdata->buck125_gpios[0], + "MAX8997 SET1"); + if (ret == -EBUSY) + dev_warn(&pdev->dev, "Duplicated gpio request" + " on SET1\n"); + else if (ret) + goto err_alloc; + else + gpio1set = true; + + ret = gpio_request(pdata->buck125_gpios[1], + "MAX8997 SET2"); + if (ret == -EBUSY) + dev_warn(&pdev->dev, "Duplicated gpio request" + " on SET2\n"); + else if (ret) { + if (gpio1set) + gpio_free(pdata->buck125_gpios[0]); + goto err_alloc; + } else + gpio2set = true; + + ret = gpio_request(pdata->buck125_gpios[2], + "MAX8997 SET3"); + if (ret == -EBUSY) + dev_warn(&pdev->dev, "Duplicated gpio request" + " on SET3\n"); + else if (ret) { + if (gpio1set) + gpio_free(pdata->buck125_gpios[0]); + if (gpio2set) + gpio_free(pdata->buck125_gpios[1]); + goto err_alloc; + } + + gpio_direction_output(pdata->buck125_gpios[0], + (max8997->buck125_gpioindex >> 2) + & 0x1); /* SET1 */ + gpio_direction_output(pdata->buck125_gpios[1], + (max8997->buck125_gpioindex >> 1) + & 0x1); /* SET2 */ + gpio_direction_output(pdata->buck125_gpios[2], + (max8997->buck125_gpioindex >> 0) + & 0x1); /* SET3 */ + ret = 0; + } + + /* DVS-GPIO disabled */ + max8997_update_reg(i2c, MAX8997_REG_BUCK1CTRL, (pdata->buck1_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + max8997_update_reg(i2c, MAX8997_REG_BUCK2CTRL, (pdata->buck2_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + max8997_update_reg(i2c, MAX8997_REG_BUCK5CTRL, (pdata->buck5_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + + /* Initialize all the DVS related BUCK registers */ + for (i = 0; i < 8; i++) { + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1), + max8997->buck1_vol[i], + 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1), + max8997->buck2_vol[i], + 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1), + max8997->buck5_vol[i], + 0x3f); + } + + for (i = 0; i < pdata->num_regulators; i++) { + const struct voltage_map_desc *desc; + int id = pdata->regulators[i].id; + + desc = reg_voltage_map[id]; + if (desc) + regulators[id].n_voltages = + (desc->max - desc->min) / desc->step + 1; + else if (id == MAX8997_ESAFEOUT1 || id == MAX8997_ESAFEOUT2) + regulators[id].n_voltages = 4; + else if (id == MAX8997_CHARGER_CV) + regulators[id].n_voltages = 16; + + rdev[i] = regulator_register(®ulators[id], max8997->dev, + pdata->regulators[i].initdata, max8997); + if (IS_ERR(rdev[i])) { + ret = PTR_ERR(rdev[i]); + dev_err(max8997->dev, "regulator init failed for %d\n", + id); + rdev[i] = NULL; + goto err; + } + } + + /* Misc Settings */ + max8997->ramp_delay = 10; /* set 10mV/us, which is the default */ + max8997_write_reg(i2c, MAX8997_REG_BUCKRAMP, (0xf << 4) | 0x9); + + return 0; +err: + for (i = 0; i < max8997->num_regulators; i++) + if (rdev[i]) + regulator_unregister(rdev[i]); +err_alloc: + kfree(max8997->rdev); + kfree(max8997); + + return ret; +} + +static int __devexit max8997_pmic_remove(struct platform_device *pdev) +{ + struct max8997_data *max8997 = platform_get_drvdata(pdev); + struct regulator_dev **rdev = max8997->rdev; + int i; + + for (i = 0; i < max8997->num_regulators; i++) + if (rdev[i]) + regulator_unregister(rdev[i]); + + kfree(max8997->rdev); + kfree(max8997); + + return 0; +} + +static const struct platform_device_id max8997_pmic_id[] = { + { "max8997-pmic", 0}, + { }, +}; + +static struct platform_driver max8997_pmic_driver = { + .driver = { + .name = "max8997-pmic", + .owner = THIS_MODULE, + }, + .probe = max8997_pmic_probe, + .remove = __devexit_p(max8997_pmic_remove), + .id_table = max8997_pmic_id, +}; + +static int __init max8997_pmic_init(void) +{ + return platform_driver_register(&max8997_pmic_driver); +} +subsys_initcall(max8997_pmic_init); + +static void __exit max8997_pmic_cleanup(void) +{ + platform_driver_unregister(&max8997_pmic_driver); +} +module_exit(max8997_pmic_cleanup); + +MODULE_DESCRIPTION("MAXIM 8997/8966 Regulator Driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index d0d9136c104..cb671b3451b 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -68,6 +68,8 @@ enum max8998_regulators { MAX8997_CHARGER_CV, /* control MBCCV of MBCCTRL3 */ MAX8997_CHARGER, /* charger current, MBCCTRL4 */ MAX8997_CHARGER_TOPOFF, /* MBCCTRL5 */ + + MAX8997_REG_MAX, }; struct max8997_regulator_data { @@ -77,7 +79,31 @@ struct max8997_regulator_data { struct max8997_platform_data { bool wakeup; - /* PMIC: Not implemented */ + /* IRQ: Not implemented */ + /* ---- PMIC ---- */ + struct max8997_regulator_data *regulators; + int num_regulators; + + /* + * SET1~3 DVS GPIOs control Buck1, 2, and 5 simultaneously. Therefore, + * With buckx_gpiodvs enabled, the buckx cannot be controlled + * independently. To control buckx (of 1, 2, and 5) independently, + * disable buckx_gpiodvs and control with BUCKxDVS1 register. + * + * When buckx_gpiodvs and bucky_gpiodvs are both enabled, set_voltage + * on buckx will change the voltage of bucky at the same time. + * + */ + bool ignore_gpiodvs_side_effect; + int buck125_gpios[3]; /* GPIO of [0]SET1, [1]SET2, [2]SET3 */ + int buck125_default_idx; /* Default value of SET1, 2, 3 */ + unsigned int buck1_voltage[8]; /* buckx_voltage in uV */ + bool buck1_gpiodvs; + unsigned int buck2_voltage[8]; + bool buck2_gpiodvs; + unsigned int buck5_voltage[8]; + bool buck5_gpiodvs; + /* MUIC: Not implemented */ /* HAPTIC: Not implemented */ /* RTC: Not implemented */ -- cgit v1.2.3-70-g09d2 From 798a8eee44da56b4f2e000ff81dfb49d09c65b71 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 9 Mar 2011 13:02:38 +0100 Subject: mfd: Add a core driver for TI TPS61050/TPS61052 chips v2 The TPS61050/TPS61052 are boost converters, LED drivers, LED flash drivers and a simple GPIO pin chips. Cc: Liam Girdwood Cc: Mark Brown Cc: Jonas Aberg Cc: Ola Lilja Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tps6105x.c | 246 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/tps6105x.h | 95 +++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 drivers/mfd/tps6105x.c create mode 100644 include/linux/mfd/tps6105x.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 500a130857d..bc99288f8f3 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -129,6 +129,15 @@ config UCB1400_CORE To compile this driver as a module, choose M here: the module will be called ucb1400_core. +config TPS6105X + tristate "TPS61050/61052 Boost Converters" + depends on I2C + help + This option enables a driver for the TP61050/TPS61052 + high-power "white LED driver". This boost converter is + sometimes used for other things than white LEDs, and + also contains a GPIO pin. + config TPS65010 tristate "TPS6501x Power Management chips" depends on I2C && GPIOLIB diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 0e9f0c51449..47f5709f382 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o +obj-$(CONFIG_TPS6105X) += tps6105x.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_TPS6507X) += tps6507x.o obj-$(CONFIG_MENELAUS) += menelaus.o diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c new file mode 100644 index 00000000000..46d8205646b --- /dev/null +++ b/drivers/mfd/tps6105x.c @@ -0,0 +1,246 @@ +/* + * Core driver for TPS61050/61052 boost converters, used for while LED + * driving, audio power amplification, white LED flash, and generic + * boost conversion. Additionally it provides a 1-bit GPIO pin (out or in) + * and a flash synchronization pin to synchronize flash events when used as + * flashgun. + * + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int tps6105x_set(struct tps6105x *tps6105x, u8 reg, u8 value) +{ + int ret; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_write_byte_data(tps6105x->client, reg, value); + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(tps6105x_set); + +int tps6105x_get(struct tps6105x *tps6105x, u8 reg, u8 *buf) +{ + int ret; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_read_byte_data(tps6105x->client, reg); + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + *buf = ret; + return 0; +} +EXPORT_SYMBOL(tps6105x_get); + +/* + * Masks off the bits in the mask and sets the bits in the bitvalues + * parameter in one atomic operation + */ +int tps6105x_mask_and_set(struct tps6105x *tps6105x, u8 reg, + u8 bitmask, u8 bitvalues) +{ + int ret; + u8 regval; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_read_byte_data(tps6105x->client, reg); + if (ret < 0) + goto fail; + regval = ret; + regval = (~bitmask & regval) | (bitmask & bitvalues); + ret = i2c_smbus_write_byte_data(tps6105x->client, reg, regval); +fail: + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(tps6105x_mask_and_set); + +static int __devinit tps6105x_startup(struct tps6105x *tps6105x) +{ + int ret; + u8 regval; + + ret = tps6105x_get(tps6105x, TPS6105X_REG_0, ®val); + if (ret) + return ret; + switch (regval >> TPS6105X_REG0_MODE_SHIFT) { + case TPS6105X_REG0_MODE_SHUTDOWN: + dev_info(&tps6105x->client->dev, + "TPS6105x found in SHUTDOWN mode\n"); + break; + case TPS6105X_REG0_MODE_TORCH: + dev_info(&tps6105x->client->dev, + "TPS6105x found in TORCH mode\n"); + break; + case TPS6105X_REG0_MODE_TORCH_FLASH: + dev_info(&tps6105x->client->dev, + "TPS6105x found in FLASH mode\n"); + break; + case TPS6105X_REG0_MODE_VOLTAGE: + dev_info(&tps6105x->client->dev, + "TPS6105x found in VOLTAGE mode\n"); + break; + default: + break; + } + + return ret; +} + +/* + * MFD cells - we have one cell which is selected operation + * mode, and we always have a GPIO cell. + */ +static struct mfd_cell tps6105x_cells[] = { + { + /* name will be runtime assigned */ + .id = -1, + }, + { + .name = "tps6105x-gpio", + .id = -1, + }, +}; + +static int __devinit tps6105x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps6105x *tps6105x; + struct tps6105x_platform_data *pdata; + int ret; + int i; + + tps6105x = kmalloc(sizeof(*tps6105x), GFP_KERNEL); + if (!tps6105x) + return -ENOMEM; + + i2c_set_clientdata(client, tps6105x); + tps6105x->client = client; + pdata = client->dev.platform_data; + tps6105x->pdata = pdata; + mutex_init(&tps6105x->lock); + + ret = tps6105x_startup(tps6105x); + if (ret) { + dev_err(&client->dev, "chip initialization failed\n"); + goto fail; + } + + /* Remove warning texts when you implement new cell drivers */ + switch (pdata->mode) { + case TPS6105X_MODE_SHUTDOWN: + dev_info(&client->dev, + "present, not used for anything, only GPIO\n"); + break; + case TPS6105X_MODE_TORCH: + tps6105x_cells[0].name = "tps6105x-leds"; + dev_warn(&client->dev, + "torch mode is unsupported\n"); + break; + case TPS6105X_MODE_TORCH_FLASH: + tps6105x_cells[0].name = "tps6105x-flash"; + dev_warn(&client->dev, + "flash mode is unsupported\n"); + break; + case TPS6105X_MODE_VOLTAGE: + tps6105x_cells[0].name ="tps6105x-regulator"; + break; + default: + break; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < ARRAY_SIZE(tps6105x_cells); i++) { + /* One state holder for all drivers, this is simple */ + tps6105x_cells[i].mfd_data = tps6105x; + } + + ret = mfd_add_devices(&client->dev, 0, tps6105x_cells, + ARRAY_SIZE(tps6105x_cells), NULL, 0); + if (ret) + goto fail; + + return 0; + +fail: + kfree(tps6105x); + return ret; +} + +static int __devexit tps6105x_remove(struct i2c_client *client) +{ + struct tps6105x *tps6105x = i2c_get_clientdata(client); + + mfd_remove_devices(&client->dev); + + /* Put chip in shutdown mode */ + tps6105x_mask_and_set(tps6105x, TPS6105X_REG_0, + TPS6105X_REG0_MODE_MASK, + TPS6105X_MODE_SHUTDOWN << TPS6105X_REG0_MODE_SHIFT); + + kfree(tps6105x); + return 0; +} + +static const struct i2c_device_id tps6105x_id[] = { + { "tps61050", 0 }, + { "tps61052", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6105x_id); + +static struct i2c_driver tps6105x_driver = { + .driver = { + .name = "tps6105x", + }, + .probe = tps6105x_probe, + .remove = __devexit_p(tps6105x_remove), + .id_table = tps6105x_id, +}; + +static int __init tps6105x_init(void) +{ + return i2c_add_driver(&tps6105x_driver); +} +subsys_initcall(tps6105x_init); + +static void __exit tps6105x_exit(void) +{ + i2c_del_driver(&tps6105x_driver); +} +module_exit(tps6105x_exit); + +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("TPS6105x White LED Boost Converter Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/tps6105x.h b/include/linux/mfd/tps6105x.h new file mode 100644 index 00000000000..f259244a56b --- /dev/null +++ b/include/linux/mfd/tps6105x.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij + * + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef MFD_TPS6105X_H +#define MFD_TPS6105X_H + +#include + +/* + * Register definitions to all subdrivers + */ +#define TPS6105X_REG_0 0x00 +#define TPS6105X_REG0_MODE_SHIFT 6 +#define TPS6105X_REG0_MODE_MASK (0x03<<6) +/* These defines for both reg0 and reg1 */ +#define TPS6105X_REG0_MODE_SHUTDOWN 0x00 +#define TPS6105X_REG0_MODE_TORCH 0x01 +#define TPS6105X_REG0_MODE_TORCH_FLASH 0x02 +#define TPS6105X_REG0_MODE_VOLTAGE 0x03 +#define TPS6105X_REG0_VOLTAGE_SHIFT 4 +#define TPS6105X_REG0_VOLTAGE_MASK (3<<4) +#define TPS6105X_REG0_VOLTAGE_450 0 +#define TPS6105X_REG0_VOLTAGE_500 1 +#define TPS6105X_REG0_VOLTAGE_525 2 +#define TPS6105X_REG0_VOLTAGE_500_2 3 +#define TPS6105X_REG0_DIMMING_SHIFT 3 +#define TPS6105X_REG0_TORCHC_SHIFT 0 +#define TPS6105X_REG0_TORCHC_MASK (7<<0) +#define TPS6105X_REG0_TORCHC_0 0x00 +#define TPS6105X_REG0_TORCHC_50 0x01 +#define TPS6105X_REG0_TORCHC_75 0x02 +#define TPS6105X_REG0_TORCHC_100 0x03 +#define TPS6105X_REG0_TORCHC_150 0x04 +#define TPS6105X_REG0_TORCHC_200 0x05 +#define TPS6105X_REG0_TORCHC_250_400 0x06 +#define TPS6105X_REG0_TORCHC_250_500 0x07 +#define TPS6105X_REG_1 0x01 +#define TPS6105X_REG1_MODE_SHIFT 6 +#define TPS6105X_REG1_MODE_MASK (0x03<<6) +#define TPS6105X_REG1_MODE_SHUTDOWN 0x00 +#define TPS6105X_REG1_MODE_TORCH 0x01 +#define TPS6105X_REG1_MODE_TORCH_FLASH 0x02 +#define TPS6105X_REG1_MODE_VOLTAGE 0x03 +#define TPS6105X_REG_2 0x02 +#define TPS6105X_REG_3 0x03 + +/** + * enum tps6105x_mode - desired mode for the TPS6105x + * @TPS6105X_MODE_SHUTDOWN: this instance is inactive, not used for anything + * @TPS61905X_MODE_TORCH: this instance is used as a LED, usually a while + * LED, for example as backlight or flashlight. If this is set, the + * TPS6105X will register to the LED framework + * @TPS6105X_MODE_TORCH_FLASH: this instance is used as a flashgun, usually + * in a camera + * @TPS6105X_MODE_VOLTAGE: this instance is used as a voltage regulator and + * will register to the regulator framework + */ +enum tps6105x_mode { + TPS6105X_MODE_SHUTDOWN, + TPS6105X_MODE_TORCH, + TPS6105X_MODE_TORCH_FLASH, + TPS6105X_MODE_VOLTAGE, +}; + +/** + * struct tps6105x_platform_data - TPS61905x platform data + * @mode: what mode this instance shall be operated in, + * this is not selectable at runtime + */ +struct tps6105x_platform_data { + enum tps6105x_mode mode; +}; + +/** + * struct tps6105x - state holder for the TPS6105x drivers + * @mutex: mutex to serialize I2C accesses + * @i2c_client: corresponding I2C client + */ +struct tps6105x { + struct tps6105x_platform_data *pdata; + struct mutex lock; + struct i2c_client *client; +}; + +extern int tps6105x_set(struct tps6105x *tps6105x, u8 reg, u8 value); +extern int tps6105x_get(struct tps6105x *tps6105x, u8 reg, u8 *buf); +extern int tps6105x_mask_and_set(struct tps6105x *tps6105x, u8 reg, + u8 bitmask, u8 bitvalues); + +#endif -- cgit v1.2.3-70-g09d2 From 2edd3b692404efc2d3915175a2ed553e783de763 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 9 Mar 2011 12:02:55 +0000 Subject: regulator: Add a subdriver for TI TPS6105x regulator portions v2 This adds a subdriver for the regulator found inside the TPS61050 and TPS61052 chips. Cc: Samuel Ortiz Cc: Ola Lilja Cc: Jonas Aberg Signed-off-by: Linus Walleij Acked-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 2 + drivers/regulator/Kconfig | 9 ++ drivers/regulator/Makefile | 2 +- drivers/regulator/tps6105x-regulator.c | 196 +++++++++++++++++++++++++++++++++ include/linux/mfd/tps6105x.h | 6 + 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 drivers/regulator/tps6105x-regulator.c (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index bc99288f8f3..8d7d098f7a0 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -132,6 +132,8 @@ config UCB1400_CORE config TPS6105X tristate "TPS61050/61052 Boost Converters" depends on I2C + select REGULATOR + select REGULATOR_FIXED_VOLTAGE help This option enables a driver for the TP61050/TPS61052 high-power "white LED driver". This boost converter is diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 395d35941b8..de75f67f4cc 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -223,6 +223,15 @@ config REGULATOR_AB3100 AB3100 analog baseband dealing with power regulators for the system. +config REGULATOR_TPS6105X + tristate "TI TPS6105X Power regulators" + depends on TPS6105X + default y if TPS6105X + help + This driver supports TPS61050/TPS61052 voltage regulator chips. + It is a single boost converter primarily for white LEDs and + audio amplifiers. + config REGULATOR_TPS65023 tristate "TI TPS65023 Power regulators" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e43b8524871..d72a4275677 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -34,7 +34,7 @@ obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o - +obj-$(CONFIG_REGULATOR_TPS6105X) += tps6105x-regulator.o obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o diff --git a/drivers/regulator/tps6105x-regulator.c b/drivers/regulator/tps6105x-regulator.c new file mode 100644 index 00000000000..1661499feda --- /dev/null +++ b/drivers/regulator/tps6105x-regulator.c @@ -0,0 +1,196 @@ +/* + * Driver for TPS61050/61052 boost converters, typically used for white LEDs + * or audio amplifiers. + * + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const int tps6105x_voltages[] = { + 4500000, + 5000000, + 5250000, + 5000000, /* There is an additional 5V */ +}; + +static int tps6105x_regulator_enable(struct regulator_dev *rdev) +{ + struct tps6105x *tps6105x = rdev_get_drvdata(rdev); + int ret; + + /* Activate voltage mode */ + ret = tps6105x_mask_and_set(tps6105x, TPS6105X_REG_0, + TPS6105X_REG0_MODE_MASK, + TPS6105X_REG0_MODE_VOLTAGE << TPS6105X_REG0_MODE_SHIFT); + if (ret) + return ret; + + return 0; +} + +static int tps6105x_regulator_disable(struct regulator_dev *rdev) +{ + struct tps6105x *tps6105x = rdev_get_drvdata(rdev); + int ret; + + /* Set into shutdown mode */ + ret = tps6105x_mask_and_set(tps6105x, TPS6105X_REG_0, + TPS6105X_REG0_MODE_MASK, + TPS6105X_REG0_MODE_SHUTDOWN << TPS6105X_REG0_MODE_SHIFT); + if (ret) + return ret; + + return 0; +} + +static int tps6105x_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct tps6105x *tps6105x = rdev_get_drvdata(rdev); + u8 regval; + int ret; + + ret = tps6105x_get(tps6105x, TPS6105X_REG_0, ®val); + if (ret) + return ret; + regval &= TPS6105X_REG0_MODE_MASK; + regval >>= TPS6105X_REG0_MODE_SHIFT; + + if (regval == TPS6105X_REG0_MODE_VOLTAGE) + return 1; + + return 0; +} + +static int tps6105x_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct tps6105x *tps6105x = rdev_get_drvdata(rdev); + u8 regval; + int ret; + + ret = tps6105x_get(tps6105x, TPS6105X_REG_0, ®val); + if (ret) + return ret; + + regval &= TPS6105X_REG0_VOLTAGE_MASK; + regval >>= TPS6105X_REG0_VOLTAGE_SHIFT; + return (int) regval; +} + +static int tps6105x_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct tps6105x *tps6105x = rdev_get_drvdata(rdev); + int ret; + + ret = tps6105x_mask_and_set(tps6105x, TPS6105X_REG_0, + TPS6105X_REG0_VOLTAGE_MASK, + selector << TPS6105X_REG0_VOLTAGE_SHIFT); + if (ret) + return ret; + + return 0; +} + +static int tps6105x_regulator_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector >= ARRAY_SIZE(tps6105x_voltages)) + return -EINVAL; + + return tps6105x_voltages[selector]; +} + +static struct regulator_ops tps6105x_regulator_ops = { + .enable = tps6105x_regulator_enable, + .disable = tps6105x_regulator_disable, + .is_enabled = tps6105x_regulator_is_enabled, + .get_voltage_sel = tps6105x_regulator_get_voltage_sel, + .set_voltage_sel = tps6105x_regulator_set_voltage_sel, + .list_voltage = tps6105x_regulator_list_voltage, +}; + +static struct regulator_desc tps6105x_regulator_desc = { + .name = "tps6105x-boost", + .ops = &tps6105x_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = 0, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(tps6105x_voltages), +}; + +/* + * Registers the chip as a voltage regulator + */ +static int __devinit tps6105x_regulator_probe(struct platform_device *pdev) +{ + struct tps6105x *tps6105x = mfd_get_data(pdev); + struct tps6105x_platform_data *pdata = tps6105x->pdata; + int ret; + + /* This instance is not set for regulator mode so bail out */ + if (pdata->mode != TPS6105X_MODE_VOLTAGE) { + dev_info(&pdev->dev, + "chip not in voltage mode mode, exit probe \n"); + return 0; + } + + /* Register regulator with framework */ + tps6105x->regulator = regulator_register(&tps6105x_regulator_desc, + &tps6105x->client->dev, + pdata->regulator_data, tps6105x); + if (IS_ERR(tps6105x->regulator)) { + ret = PTR_ERR(tps6105x->regulator); + dev_err(&tps6105x->client->dev, + "failed to register regulator\n"); + return ret; + } + + return 0; +} + +static int __devexit tps6105x_regulator_remove(struct platform_device *pdev) +{ + struct tps6105x *tps6105x = platform_get_drvdata(pdev); + regulator_unregister(tps6105x->regulator); + return 0; +} + +static struct platform_driver tps6105x_regulator_driver = { + .driver = { + .name = "tps6105x-regulator", + .owner = THIS_MODULE, + }, + .probe = tps6105x_regulator_probe, + .remove = __devexit_p(tps6105x_regulator_remove), +}; + +static __init int tps6105x_regulator_init(void) +{ + return platform_driver_register(&tps6105x_regulator_driver); +} +subsys_initcall(tps6105x_regulator_init); + +static __exit void tps6105x_regulator_exit(void) +{ + platform_driver_unregister(&tps6105x_regulator_driver); +} +module_exit(tps6105x_regulator_exit); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("TPS6105x regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps6105x-regulator"); diff --git a/include/linux/mfd/tps6105x.h b/include/linux/mfd/tps6105x.h index f259244a56b..386743dd931 100644 --- a/include/linux/mfd/tps6105x.h +++ b/include/linux/mfd/tps6105x.h @@ -10,6 +10,7 @@ #define MFD_TPS6105X_H #include +#include /* * Register definitions to all subdrivers @@ -71,20 +72,25 @@ enum tps6105x_mode { * struct tps6105x_platform_data - TPS61905x platform data * @mode: what mode this instance shall be operated in, * this is not selectable at runtime + * @regulator_data: initialization data for the voltage + * regulator if used as a voltage source */ struct tps6105x_platform_data { enum tps6105x_mode mode; + struct regulator_init_data *regulator_data; }; /** * struct tps6105x - state holder for the TPS6105x drivers * @mutex: mutex to serialize I2C accesses * @i2c_client: corresponding I2C client + * @regulator: regulator device if used in voltage mode */ struct tps6105x { struct tps6105x_platform_data *pdata; struct mutex lock; struct i2c_client *client; + struct regulator_dev *regulator; }; extern int tps6105x_set(struct tps6105x *tps6105x, u8 reg, u8 value); -- cgit v1.2.3-70-g09d2 From 07e73fbb2d52434e6b61019326f35040357e8efb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 17 Mar 2011 21:42:30 +0000 Subject: mfd: Constify WM8994 write path Allow const buffers to be passed in without type safety issues. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 8 ++++---- include/linux/mfd/wm8994/core.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'include/linux/mfd') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 475bd50d552..3f5b7cc85f1 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -97,9 +97,9 @@ int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, EXPORT_SYMBOL_GPL(wm8994_bulk_read); static int wm8994_write(struct wm8994 *wm8994, unsigned short reg, - int bytes, void *src) + int bytes, const void *src) { - u16 *buf = src; + const u16 *buf = src; int i; BUG_ON(bytes % 2); @@ -146,7 +146,7 @@ EXPORT_SYMBOL_GPL(wm8994_reg_write); * @buf: Buffer to write from. Data must be big-endian formatted. */ int wm8994_bulk_write(struct wm8994 *wm8994, unsigned short reg, - int count, u16 *buf) + int count, const u16 *buf) { int ret; @@ -583,7 +583,7 @@ static int wm8994_i2c_read_device(struct wm8994 *wm8994, unsigned short reg, } static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg, - int bytes, void *src) + int bytes, const void *src) { struct i2c_client *i2c = wm8994->control_data; struct i2c_msg xfer[2]; diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index cb7d3ae7da8..f0b69cdae41 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -59,7 +59,7 @@ struct wm8994 { int (*read_dev)(struct wm8994 *wm8994, unsigned short reg, int bytes, void *dest); int (*write_dev)(struct wm8994 *wm8994, unsigned short reg, - int bytes, void *src); + int bytes, const void *src); void *control_data; @@ -89,7 +89,7 @@ int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg, int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, int count, u16 *buf); int wm8994_bulk_write(struct wm8994 *wm8994, unsigned short reg, - int count, u16 *buf); + int count, const u16 *buf); /* Helper to save on boilerplate */ -- cgit v1.2.3-70-g09d2 From 8bd4d7c4c500e88bff975bbcb7fe0d89da319cdd Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 15 Mar 2011 14:26:37 +0100 Subject: mfd: Rename ab8500 gpadc header Rename AB8500 GPADC header so as not to be redunantly named. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz --- drivers/mfd/ab8500-gpadc.c | 2 +- include/linux/mfd/ab8500/ab8500-gpadc.h | 32 -------------------------------- include/linux/mfd/ab8500/gpadc.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 include/linux/mfd/ab8500/ab8500-gpadc.h create mode 100644 include/linux/mfd/ab8500/gpadc.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 2ad3115f777..bc93b2e8230 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include /* * GPADC register offsets diff --git a/include/linux/mfd/ab8500/ab8500-gpadc.h b/include/linux/mfd/ab8500/ab8500-gpadc.h deleted file mode 100644 index 46b954011f1..00000000000 --- a/include/linux/mfd/ab8500/ab8500-gpadc.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2010 ST-Ericsson SA - * Licensed under GPLv2. - * - * Author: Arun R Murthy - * Author: Daniel Willerud - */ - -#ifndef _AB8500_GPADC_H -#define _AB8500_GPADC_H - -/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ -#define BAT_CTRL 0x01 -#define BTEMP_BALL 0x02 -#define MAIN_CHARGER_V 0x03 -#define ACC_DETECT1 0x04 -#define ACC_DETECT2 0x05 -#define ADC_AUX1 0x06 -#define ADC_AUX2 0x07 -#define MAIN_BAT_V 0x08 -#define VBUS_V 0x09 -#define MAIN_CHARGER_C 0x0A -#define USB_CHARGER_C 0x0B -#define BK_BAT_V 0x0C -#define DIE_TEMP 0x0D - -struct ab8500_gpadc; - -struct ab8500_gpadc *ab8500_gpadc_get(char *name); -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); - -#endif /* _AB8500_GPADC_H */ diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h new file mode 100644 index 00000000000..46b954011f1 --- /dev/null +++ b/include/linux/mfd/ab8500/gpadc.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy + * Author: Daniel Willerud + */ + +#ifndef _AB8500_GPADC_H +#define _AB8500_GPADC_H + +/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define BTEMP_BALL 0x02 +#define MAIN_CHARGER_V 0x03 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define ADC_AUX1 0x06 +#define ADC_AUX2 0x07 +#define MAIN_BAT_V 0x08 +#define VBUS_V 0x09 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define BK_BAT_V 0x0C +#define DIE_TEMP 0x0D + +struct ab8500_gpadc; + +struct ab8500_gpadc *ab8500_gpadc_get(char *name); +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); + +#endif /* _AB8500_GPADC_H */ -- cgit v1.2.3-70-g09d2 From 42051e8a7bce76ebd3cd201704ee2427120636e1 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Mon, 14 Mar 2011 09:52:33 +0100 Subject: mmc: tmio: convert the SDHI MMC driver from MFD to a platform driver On sh-mobile platforms the SDHI driver was using the tmio_mmc SD/SDIO MFD cell driver. Now that the tmio_mmc driver has been split into a core and a separate MFD glue, we can support SDHI natively without the need to emulate an MFD controller. This also allows to support systems with an on-SoC SDHI controller and a separate MFD with a TMIO core. Signed-off-by: Guennadi Liakhovetski Acked-by: Paul Mundt Signed-off-by: Chris Ball --- drivers/mfd/Kconfig | 14 --- drivers/mfd/Makefile | 1 - drivers/mfd/sh_mobile_sdhi.c | 200 ------------------------------------- drivers/mmc/host/Kconfig | 9 +- drivers/mmc/host/Makefile | 7 +- drivers/mmc/host/sh_mobile_sdhi.c | 171 +++++++++++++++++++++++++++++++ drivers/mmc/host/tmio_mmc.h | 2 +- include/linux/mfd/sh_mobile_sdhi.h | 19 +--- include/linux/mmc/sh_mobile_sdhi.h | 16 +++ 9 files changed, 206 insertions(+), 233 deletions(-) delete mode 100644 drivers/mfd/sh_mobile_sdhi.c create mode 100644 drivers/mmc/host/sh_mobile_sdhi.c create mode 100644 include/linux/mmc/sh_mobile_sdhi.h (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a9a1af49281..38cb1d4d684 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -60,15 +60,6 @@ config MFD_ASIC3 This driver supports the ASIC3 multifunction chip found on many PDAs (mainly iPAQ and HTC based ones) -config MFD_SH_MOBILE_SDHI - bool "Support for SuperH Mobile SDHI" - depends on SUPERH || ARCH_SHMOBILE - select MFD_CORE - select TMIO_MMC_DMA - ---help--- - This driver supports the SDHI hardware block found in many - SuperH Mobile SoCs. - config MFD_DAVINCI_VOICECODEC tristate select MFD_CORE @@ -265,11 +256,6 @@ config MFD_TMIO bool default n -config TMIO_MMC_DMA - bool - select DMA_ENGINE - select DMADEVICES - config MFD_T7L66XB bool "Support Toshiba T7L66XB" depends on ARM && HAVE_CLK diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 47f5709f382..ad6c4432187 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -6,7 +6,6 @@ obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o -obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c deleted file mode 100644 index 53a63024bf1..00000000000 --- a/drivers/mfd/sh_mobile_sdhi.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * SuperH Mobile SDHI - * - * Copyright (C) 2009 Magnus Damm - * - * 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. - * - * Based on "Compaq ASIC3 support": - * - * Copyright 2001 Compaq Computer Corporation. - * Copyright 2004-2005 Phil Blundell - * Copyright 2007-2008 OpenedHand Ltd. - * - * Authors: Phil Blundell , - * Samuel Ortiz - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct sh_mobile_sdhi { - struct clk *clk; - struct tmio_mmc_data mmc_data; - struct mfd_cell cell_mmc; - struct sh_dmae_slave param_tx; - struct sh_dmae_slave param_rx; - struct tmio_mmc_dma dma_priv; -}; - -static struct resource sh_mobile_sdhi_resources[] = { - { - .start = 0x000, - .end = 0x1ff, - .flags = IORESOURCE_MEM, - }, - { - .start = 0, - .end = 0, - .flags = IORESOURCE_IRQ, - }, -}; - -static struct mfd_cell sh_mobile_sdhi_cell = { - .name = "tmio-mmc", - .num_resources = ARRAY_SIZE(sh_mobile_sdhi_resources), - .resources = sh_mobile_sdhi_resources, -}; - -static void sh_mobile_sdhi_set_pwr(struct platform_device *tmio, int state) -{ - struct platform_device *pdev = to_platform_device(tmio->dev.parent); - struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; - - if (p && p->set_pwr) - p->set_pwr(pdev, state); -} - -static int sh_mobile_sdhi_get_cd(struct platform_device *tmio) -{ - struct platform_device *pdev = to_platform_device(tmio->dev.parent); - struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; - - if (p && p->get_cd) - return p->get_cd(pdev); - else - return -ENOSYS; -} - -static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) -{ - struct sh_mobile_sdhi *priv; - struct tmio_mmc_data *mmc_data; - struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; - struct resource *mem; - char clk_name[8]; - int ret, irq; - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) - dev_err(&pdev->dev, "missing MEM resource\n"); - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - dev_err(&pdev->dev, "missing IRQ resource\n"); - - if (!mem || (irq < 0)) - return -EINVAL; - - priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL); - if (priv == NULL) { - dev_err(&pdev->dev, "kzalloc failed\n"); - return -ENOMEM; - } - - mmc_data = &priv->mmc_data; - - snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id); - priv->clk = clk_get(&pdev->dev, clk_name); - if (IS_ERR(priv->clk)) { - dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); - ret = PTR_ERR(priv->clk); - kfree(priv); - return ret; - } - - clk_enable(priv->clk); - - mmc_data->hclk = clk_get_rate(priv->clk); - mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; - mmc_data->get_cd = sh_mobile_sdhi_get_cd; - mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; - if (p) { - mmc_data->flags = p->tmio_flags; - mmc_data->ocr_mask = p->tmio_ocr_mask; - mmc_data->capabilities |= p->tmio_caps; - } - - /* - * All SDHI blocks support 2-byte and larger block sizes in 4-bit - * bus width mode. - */ - mmc_data->flags |= TMIO_MMC_BLKSZ_2BYTES; - - /* - * All SDHI blocks support SDIO IRQ signalling. - */ - mmc_data->flags |= TMIO_MMC_SDIO_IRQ; - - if (p && p->dma_slave_tx >= 0 && p->dma_slave_rx >= 0) { - priv->param_tx.slave_id = p->dma_slave_tx; - priv->param_rx.slave_id = p->dma_slave_rx; - priv->dma_priv.chan_priv_tx = &priv->param_tx; - priv->dma_priv.chan_priv_rx = &priv->param_rx; - priv->dma_priv.alignment_shift = 1; /* 2-byte alignment */ - mmc_data->dma = &priv->dma_priv; - } - - memcpy(&priv->cell_mmc, &sh_mobile_sdhi_cell, sizeof(priv->cell_mmc)); - priv->cell_mmc.mfd_data = mmc_data; - - platform_set_drvdata(pdev, priv); - - ret = mfd_add_devices(&pdev->dev, pdev->id, - &priv->cell_mmc, 1, mem, irq); - if (ret) { - clk_disable(priv->clk); - clk_put(priv->clk); - kfree(priv); - } - - return ret; -} - -static int sh_mobile_sdhi_remove(struct platform_device *pdev) -{ - struct sh_mobile_sdhi *priv = platform_get_drvdata(pdev); - - mfd_remove_devices(&pdev->dev); - clk_disable(priv->clk); - clk_put(priv->clk); - kfree(priv); - - return 0; -} - -static struct platform_driver sh_mobile_sdhi_driver = { - .driver = { - .name = "sh_mobile_sdhi", - .owner = THIS_MODULE, - }, - .probe = sh_mobile_sdhi_probe, - .remove = __devexit_p(sh_mobile_sdhi_remove), -}; - -static int __init sh_mobile_sdhi_init(void) -{ - return platform_driver_register(&sh_mobile_sdhi_driver); -} - -static void __exit sh_mobile_sdhi_exit(void) -{ - platform_driver_unregister(&sh_mobile_sdhi_driver); -} - -module_init(sh_mobile_sdhi_init); -module_exit(sh_mobile_sdhi_exit); - -MODULE_DESCRIPTION("SuperH Mobile SDHI driver"); -MODULE_AUTHOR("Magnus Damm"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 4d16166386c..0b03cee9145 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -444,12 +444,19 @@ config MMC_TMIO_CORE config MMC_TMIO tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" - depends on MFD_TMIO || MFD_ASIC3 || MFD_SH_MOBILE_SDHI + depends on MFD_TMIO || MFD_ASIC3 select MMC_TMIO_CORE help This provides support for the SD/MMC cell found in TC6393XB, T7L66XB and also HTC ASIC3 +config MMC_SDHI + tristate "SH-Mobile SDHI SD/SDIO controller support" + select MMC_TMIO_CORE + help + This provides support for the SDHI SD/SDIO controller found in + SuperH and ARM SH-Mobile SoCs + config MMC_CB710 tristate "ENE CB710 MMC/SD Interface support" depends on PCI diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 79c42dd23c0..4f1df0aae57 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -31,8 +31,11 @@ obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o obj-$(CONFIG_MMC_TMIO_CORE) += tmio_mmc_core.o tmio_mmc_core-y := tmio_mmc_pio.o -tmio_mmc_core-$(CONFIG_TMIO_MMC_DMA) += tmio_mmc_dma.o -obj-$(CONFIG_MMC_CB710) += cb710-mmc.o +ifneq ($(CONFIG_MMC_SDHI),n) +tmio_mmc_core-y += tmio_mmc_dma.o +endif +obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o +obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c new file mode 100644 index 00000000000..cc701236d16 --- /dev/null +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -0,0 +1,171 @@ +/* + * SuperH Mobile SDHI + * + * Copyright (C) 2009 Magnus Damm + * + * 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. + * + * Based on "Compaq ASIC3 support": + * + * Copyright 2001 Compaq Computer Corporation. + * Copyright 2004-2005 Phil Blundell + * Copyright 2007-2008 OpenedHand Ltd. + * + * Authors: Phil Blundell , + * Samuel Ortiz + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tmio_mmc.h" + +struct sh_mobile_sdhi { + struct clk *clk; + struct tmio_mmc_data mmc_data; + struct sh_dmae_slave param_tx; + struct sh_dmae_slave param_rx; + struct tmio_mmc_dma dma_priv; +}; + +static void sh_mobile_sdhi_set_pwr(struct platform_device *pdev, int state) +{ + struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; + + if (p && p->set_pwr) + p->set_pwr(pdev, state); +} + +static int sh_mobile_sdhi_get_cd(struct platform_device *pdev) +{ + struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; + + if (p && p->get_cd) + return p->get_cd(pdev); + else + return -ENOSYS; +} + +static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) +{ + struct sh_mobile_sdhi *priv; + struct tmio_mmc_data *mmc_data; + struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; + struct tmio_mmc_host *host; + char clk_name[8]; + int ret; + + priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL); + if (priv == NULL) { + dev_err(&pdev->dev, "kzalloc failed\n"); + return -ENOMEM; + } + + mmc_data = &priv->mmc_data; + + snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id); + priv->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); + ret = PTR_ERR(priv->clk); + goto eclkget; + } + + clk_enable(priv->clk); + + mmc_data->hclk = clk_get_rate(priv->clk); + mmc_data->set_pwr = sh_mobile_sdhi_set_pwr; + mmc_data->get_cd = sh_mobile_sdhi_get_cd; + mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; + if (p) { + mmc_data->flags = p->tmio_flags; + mmc_data->ocr_mask = p->tmio_ocr_mask; + mmc_data->capabilities |= p->tmio_caps; + + if (p->dma_slave_tx >= 0 && p->dma_slave_rx >= 0) { + priv->param_tx.slave_id = p->dma_slave_tx; + priv->param_rx.slave_id = p->dma_slave_rx; + priv->dma_priv.chan_priv_tx = &priv->param_tx; + priv->dma_priv.chan_priv_rx = &priv->param_rx; + priv->dma_priv.alignment_shift = 1; /* 2-byte alignment */ + mmc_data->dma = &priv->dma_priv; + } + } + + /* + * All SDHI blocks support 2-byte and larger block sizes in 4-bit + * bus width mode. + */ + mmc_data->flags |= TMIO_MMC_BLKSZ_2BYTES; + + /* + * All SDHI blocks support SDIO IRQ signalling. + */ + mmc_data->flags |= TMIO_MMC_SDIO_IRQ; + + ret = tmio_mmc_host_probe(&host, pdev, mmc_data); + if (ret < 0) + goto eprobe; + + pr_info("%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc), + (unsigned long)host->ctl, host->irq); + + return ret; + +eprobe: + clk_disable(priv->clk); + clk_put(priv->clk); +eclkget: + kfree(priv); + return ret; +} + +static int sh_mobile_sdhi_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct tmio_mmc_host *host = mmc_priv(mmc); + struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data); + + tmio_mmc_host_remove(host); + clk_disable(priv->clk); + clk_put(priv->clk); + kfree(priv); + + return 0; +} + +static struct platform_driver sh_mobile_sdhi_driver = { + .driver = { + .name = "sh_mobile_sdhi", + .owner = THIS_MODULE, + }, + .probe = sh_mobile_sdhi_probe, + .remove = __devexit_p(sh_mobile_sdhi_remove), +}; + +static int __init sh_mobile_sdhi_init(void) +{ + return platform_driver_register(&sh_mobile_sdhi_driver); +} + +static void __exit sh_mobile_sdhi_exit(void) +{ + platform_driver_unregister(&sh_mobile_sdhi_driver); +} + +module_init(sh_mobile_sdhi_init); +module_exit(sh_mobile_sdhi_exit); + +MODULE_DESCRIPTION("SuperH Mobile SDHI driver"); +MODULE_AUTHOR("Magnus Damm"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sh_mobile_sdhi"); diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 8dedf0a6934..7d79b14d56a 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -120,7 +120,7 @@ static inline void tmio_mmc_kunmap_atomic(struct scatterlist *sg, local_irq_restore(*flags); } -#ifdef CONFIG_TMIO_MMC_DMA +#if defined(CONFIG_MMC_SDHI) || defined(CONFIG_MMC_SDHI_MODULE) void tmio_mmc_start_dma(struct tmio_mmc_host *host, struct mmc_data *data); void tmio_mmc_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata); void tmio_mmc_release_dma(struct tmio_mmc_host *host); diff --git a/include/linux/mfd/sh_mobile_sdhi.h b/include/linux/mfd/sh_mobile_sdhi.h index c981b959760..10af7f901eb 100644 --- a/include/linux/mfd/sh_mobile_sdhi.h +++ b/include/linux/mfd/sh_mobile_sdhi.h @@ -1,16 +1,7 @@ -#ifndef __SH_MOBILE_SDHI_H__ -#define __SH_MOBILE_SDHI_H__ +#ifndef MFD_SH_MOBILE_SDHI_H +#define MFD_SH_MOBILE_SDHI_H -#include +/* Compatibility header - will disappear once all platforms are converted */ +#include -struct sh_mobile_sdhi_info { - int dma_slave_tx; - int dma_slave_rx; - unsigned long tmio_flags; - unsigned long tmio_caps; - u32 tmio_ocr_mask; /* available MMC voltages */ - void (*set_pwr)(struct platform_device *pdev, int state); - int (*get_cd)(struct platform_device *pdev); -}; - -#endif /* __SH_MOBILE_SDHI_H__ */ +#endif /* MFD_SH_MOBILE_SDHI_H */ diff --git a/include/linux/mmc/sh_mobile_sdhi.h b/include/linux/mmc/sh_mobile_sdhi.h new file mode 100644 index 00000000000..c981b959760 --- /dev/null +++ b/include/linux/mmc/sh_mobile_sdhi.h @@ -0,0 +1,16 @@ +#ifndef __SH_MOBILE_SDHI_H__ +#define __SH_MOBILE_SDHI_H__ + +#include + +struct sh_mobile_sdhi_info { + int dma_slave_tx; + int dma_slave_rx; + unsigned long tmio_flags; + unsigned long tmio_caps; + u32 tmio_ocr_mask; /* available MMC voltages */ + void (*set_pwr)(struct platform_device *pdev, int state); + int (*get_cd)(struct platform_device *pdev); +}; + +#endif /* __SH_MOBILE_SDHI_H__ */ -- cgit v1.2.3-70-g09d2 From 52c6182a47fa66ad5f2647a831efa99c05b0a54e Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 9 Mar 2011 11:33:35 +0100 Subject: mfd: remove now redundant sh_mobile_sdhi.h header Signed-off-by: Guennadi Liakhovetski Acked-by: Paul Mundt Acked-by: Samuel Ortiz Signed-off-by: Chris Ball --- include/linux/mfd/sh_mobile_sdhi.h | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 include/linux/mfd/sh_mobile_sdhi.h (limited to 'include/linux/mfd') diff --git a/include/linux/mfd/sh_mobile_sdhi.h b/include/linux/mfd/sh_mobile_sdhi.h deleted file mode 100644 index 10af7f901eb..00000000000 --- a/include/linux/mfd/sh_mobile_sdhi.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef MFD_SH_MOBILE_SDHI_H -#define MFD_SH_MOBILE_SDHI_H - -/* Compatibility header - will disappear once all platforms are converted */ -#include - -#endif /* MFD_SH_MOBILE_SDHI_H */ -- cgit v1.2.3-70-g09d2 From 79568b941277b5986a8a7f0fb8578b2ccfc3e87e Mon Sep 17 00:00:00 2001 From: Bengt Jonsson Date: Fri, 11 Mar 2011 11:54:46 +0100 Subject: regulator: initialization for ab8500 regulators The regulators on the AB8500 have a lot of custom hardware control settings pertaining to 8 external signals, settings which are board-specific and need be provided from the platform at startup. Initialization added for regulators Vana, VextSupply1, VextSupply2, VextSupply3, Vaux1, Vaux2, Vaux3, VTVout, Vintcore12, Vaudio, Vdmic, Vamic1, Vamic2, VrefDDR. Signed-off-by: Bengt Jonsson Reviewed-by: Rickard Andersson Reviewed-by: Jonas Aberg Signed-off-by: Linus Walleij Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- arch/arm/mach-ux500/board-mop500-regulators.h | 2 + arch/arm/mach-ux500/board-mop500.c | 1 + drivers/regulator/ab8500.c | 223 +++++++++++++++++++++++++- include/linux/mfd/ab8500.h | 6 + include/linux/regulator/ab8500.h | 50 +++++- 5 files changed, 279 insertions(+), 3 deletions(-) (limited to 'include/linux/mfd') diff --git a/arch/arm/mach-ux500/board-mop500-regulators.h b/arch/arm/mach-ux500/board-mop500-regulators.h index 2675fae5253..f979b892e4f 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.h +++ b/arch/arm/mach-ux500/board-mop500-regulators.h @@ -14,6 +14,8 @@ #include #include +extern struct ab8500_regulator_reg_init +ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS]; extern struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS]; #endif diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 8790d984cac..d0076453d7f 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/regulator/ab8500.c b/drivers/regulator/ab8500.c index 5a77630095d..d157146c865 100644 --- a/drivers/regulator/ab8500.c +++ b/drivers/regulator/ab8500.c @@ -526,6 +526,186 @@ static struct ab8500_regulator_info }; +struct ab8500_reg_init { + u8 bank; + u8 addr; + u8 mask; +}; + +#define REG_INIT(_id, _bank, _addr, _mask) \ + [_id] = { \ + .bank = _bank, \ + .addr = _addr, \ + .mask = _mask, \ + } + +static struct ab8500_reg_init ab8500_reg_init[] = { + /* + * 0x30, VanaRequestCtrl + * 0x0C, VpllRequestCtrl + * 0xc0, VextSupply1RequestCtrl + */ + REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xfc), + /* + * 0x03, VextSupply2RequestCtrl + * 0x0c, VextSupply3RequestCtrl + * 0x30, Vaux1RequestCtrl + * 0xc0, Vaux2RequestCtrl + */ + REG_INIT(AB8500_REGUREQUESTCTRL3, 0x03, 0x05, 0xff), + /* + * 0x03, Vaux3RequestCtrl + * 0x04, SwHPReq + */ + REG_INIT(AB8500_REGUREQUESTCTRL4, 0x03, 0x06, 0x07), + /* + * 0x08, VanaSysClkReq1HPValid + * 0x20, Vaux1SysClkReq1HPValid + * 0x40, Vaux2SysClkReq1HPValid + * 0x80, Vaux3SysClkReq1HPValid + */ + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xe8), + /* + * 0x10, VextSupply1SysClkReq1HPValid + * 0x20, VextSupply2SysClkReq1HPValid + * 0x40, VextSupply3SysClkReq1HPValid + */ + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x70), + /* + * 0x08, VanaHwHPReq1Valid + * 0x20, Vaux1HwHPReq1Valid + * 0x40, Vaux2HwHPReq1Valid + * 0x80, Vaux3HwHPReq1Valid + */ + REG_INIT(AB8500_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xe8), + /* + * 0x01, VextSupply1HwHPReq1Valid + * 0x02, VextSupply2HwHPReq1Valid + * 0x04, VextSupply3HwHPReq1Valid + */ + REG_INIT(AB8500_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x07), + /* + * 0x08, VanaHwHPReq2Valid + * 0x20, Vaux1HwHPReq2Valid + * 0x40, Vaux2HwHPReq2Valid + * 0x80, Vaux3HwHPReq2Valid + */ + REG_INIT(AB8500_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xe8), + /* + * 0x01, VextSupply1HwHPReq2Valid + * 0x02, VextSupply2HwHPReq2Valid + * 0x04, VextSupply3HwHPReq2Valid + */ + REG_INIT(AB8500_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x07), + /* + * 0x20, VanaSwHPReqValid + * 0x80, Vaux1SwHPReqValid + */ + REG_INIT(AB8500_REGUSWHPREQVALID1, 0x03, 0x0d, 0xa0), + /* + * 0x01, Vaux2SwHPReqValid + * 0x02, Vaux3SwHPReqValid + * 0x04, VextSupply1SwHPReqValid + * 0x08, VextSupply2SwHPReqValid + * 0x10, VextSupply3SwHPReqValid + */ + REG_INIT(AB8500_REGUSWHPREQVALID2, 0x03, 0x0e, 0x1f), + /* + * 0x02, SysClkReq2Valid1 + * ... + * 0x80, SysClkReq8Valid1 + */ + REG_INIT(AB8500_REGUSYSCLKREQVALID1, 0x03, 0x0f, 0xfe), + /* + * 0x02, SysClkReq2Valid2 + * ... + * 0x80, SysClkReq8Valid2 + */ + REG_INIT(AB8500_REGUSYSCLKREQVALID2, 0x03, 0x10, 0xfe), + /* + * 0x02, VTVoutEna + * 0x04, Vintcore12Ena + * 0x38, Vintcore12Sel + * 0x40, Vintcore12LP + * 0x80, VTVoutLP + */ + REG_INIT(AB8500_REGUMISC1, 0x03, 0x80, 0xfe), + /* + * 0x02, VaudioEna + * 0x04, VdmicEna + * 0x08, Vamic1Ena + * 0x10, Vamic2Ena + */ + REG_INIT(AB8500_VAUDIOSUPPLY, 0x03, 0x83, 0x1e), + /* + * 0x01, Vamic1_dzout + * 0x02, Vamic2_dzout + */ + REG_INIT(AB8500_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), + /* + * 0x0c, VanaRegu + * 0x03, VpllRegu + */ + REG_INIT(AB8500_VPLLVANAREGU, 0x04, 0x06, 0x0f), + /* + * 0x01, VrefDDREna + * 0x02, VrefDDRSleepMode + */ + REG_INIT(AB8500_VREFDDR, 0x04, 0x07, 0x03), + /* + * 0x03, VextSupply1Regu + * 0x0c, VextSupply2Regu + * 0x30, VextSupply3Regu + * 0x40, ExtSupply2Bypass + * 0x80, ExtSupply3Bypass + */ + REG_INIT(AB8500_EXTSUPPLYREGU, 0x04, 0x08, 0xff), + /* + * 0x03, Vaux1Regu + * 0x0c, Vaux2Regu + */ + REG_INIT(AB8500_VAUX12REGU, 0x04, 0x09, 0x0f), + /* + * 0x03, Vaux3Regu + */ + REG_INIT(AB8500_VRF1VAUX3REGU, 0x04, 0x0a, 0x03), + /* + * 0x3f, Vsmps1Sel1 + */ + REG_INIT(AB8500_VSMPS1SEL1, 0x04, 0x13, 0x3f), + /* + * 0x0f, Vaux1Sel + */ + REG_INIT(AB8500_VAUX1SEL, 0x04, 0x1f, 0x0f), + /* + * 0x0f, Vaux2Sel + */ + REG_INIT(AB8500_VAUX2SEL, 0x04, 0x20, 0x0f), + /* + * 0x07, Vaux3Sel + */ + REG_INIT(AB8500_VRF1VAUX3SEL, 0x04, 0x21, 0x07), + /* + * 0x01, VextSupply12LP + */ + REG_INIT(AB8500_REGUCTRL2SPARE, 0x04, 0x22, 0x01), + /* + * 0x04, Vaux1Disch + * 0x08, Vaux2Disch + * 0x10, Vaux3Disch + * 0x20, Vintcore12Disch + * 0x40, VTVoutDisch + * 0x80, VaudioDisch + */ + REG_INIT(AB8500_REGUCTRLDISCH, 0x04, 0x43, 0xfc), + /* + * 0x02, VanaDisch + * 0x04, VdmicPullDownEna + * 0x10, VdmicDisch + */ + REG_INIT(AB8500_REGUCTRLDISCH2, 0x04, 0x44, 0x16), +}; + static __devinit int ab8500_regulator_probe(struct platform_device *pdev) { struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); @@ -544,10 +724,51 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) /* make sure the platform data has the correct size */ if (pdata->num_regulator != ARRAY_SIZE(ab8500_regulator_info)) { - dev_err(&pdev->dev, "platform configuration error\n"); + dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); return -EINVAL; } + /* initialize registers */ + for (i = 0; i < pdata->num_regulator_reg_init; i++) { + int id; + u8 value; + + id = pdata->regulator_reg_init[i].id; + value = pdata->regulator_reg_init[i].value; + + /* check for configuration errors */ + if (id >= AB8500_NUM_REGULATOR_REGISTERS) { + dev_err(&pdev->dev, + "Configuration error: id outside range.\n"); + return -EINVAL; + } + if (value & ~ab8500_reg_init[id].mask) { + dev_err(&pdev->dev, + "Configuration error: value outside mask.\n"); + return -EINVAL; + } + + /* initialize register */ + err = abx500_mask_and_set_register_interruptible(&pdev->dev, + ab8500_reg_init[id].bank, + ab8500_reg_init[id].addr, + ab8500_reg_init[id].mask, + value); + if (err < 0) { + dev_err(&pdev->dev, + "Failed to initialize 0x%02x, 0x%02x.\n", + ab8500_reg_init[id].bank, + ab8500_reg_init[id].addr); + return err; + } + dev_vdbg(&pdev->dev, + " init: 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + ab8500_reg_init[id].bank, + ab8500_reg_init[id].addr, + ab8500_reg_init[id].mask, + value); + } + /* register all regulators */ for (i = 0; i < ARRAY_SIZE(ab8500_regulator_info); i++) { struct ab8500_regulator_info *info = NULL; diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 56f8dea7215..6e4f77ef4d2 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -139,17 +139,23 @@ struct ab8500 { u8 oldmask[AB8500_NUM_IRQ_REGS]; }; +struct regulator_reg_init; struct regulator_init_data; /** * struct ab8500_platform_data - AB8500 platform data * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used * @init: board-specific initialization after detection of ab8500 + * @num_regulator_reg_init: number of regulator init registers + * @regulator_reg_init: regulator init registers + * @num_regulator: number of regulators * @regulator: machine-specific constraints for regulators */ struct ab8500_platform_data { int irq_base; void (*init) (struct ab8500 *); + int num_regulator_reg_init; + struct ab8500_regulator_reg_init *regulator_reg_init; int num_regulator; struct regulator_init_data *regulator; }; diff --git a/include/linux/regulator/ab8500.h b/include/linux/regulator/ab8500.h index d4eacdef201..76579f964a2 100644 --- a/include/linux/regulator/ab8500.h +++ b/include/linux/regulator/ab8500.h @@ -3,8 +3,8 @@ * * License Terms: GNU General Public License v2 * - * Author: Sundar Iyer for ST-Ericsson - * + * Authors: Sundar Iyer for ST-Ericsson + * Bengt Jonsson for ST-Ericsson */ #ifndef __LINUX_MFD_AB8500_REGULATOR_H @@ -25,4 +25,50 @@ enum ab8500_regulator_id { AB8500_LDO_ANA, AB8500_NUM_REGULATORS, }; + +/* AB8500 register initialization */ +struct ab8500_regulator_reg_init { + int id; + u8 value; +}; + +#define INIT_REGULATOR_REGISTER(_id, _value) \ + { \ + .id = _id, \ + .value = _value, \ + } + +/* AB8500 registers */ +enum ab8500_regulator_reg { + AB8500_REGUREQUESTCTRL2, + AB8500_REGUREQUESTCTRL3, + AB8500_REGUREQUESTCTRL4, + AB8500_REGUSYSCLKREQ1HPVALID1, + AB8500_REGUSYSCLKREQ1HPVALID2, + AB8500_REGUHWHPREQ1VALID1, + AB8500_REGUHWHPREQ1VALID2, + AB8500_REGUHWHPREQ2VALID1, + AB8500_REGUHWHPREQ2VALID2, + AB8500_REGUSWHPREQVALID1, + AB8500_REGUSWHPREQVALID2, + AB8500_REGUSYSCLKREQVALID1, + AB8500_REGUSYSCLKREQVALID2, + AB8500_REGUMISC1, + AB8500_VAUDIOSUPPLY, + AB8500_REGUCTRL1VAMIC, + AB8500_VPLLVANAREGU, + AB8500_VREFDDR, + AB8500_EXTSUPPLYREGU, + AB8500_VAUX12REGU, + AB8500_VRF1VAUX3REGU, + AB8500_VAUX1SEL, + AB8500_VAUX2SEL, + AB8500_VRF1VAUX3SEL, + AB8500_REGUCTRL2SPARE, + AB8500_REGUCTRLDISCH, + AB8500_REGUCTRLDISCH2, + AB8500_VSMPS1SEL1, + AB8500_NUM_REGULATOR_REGISTERS, +}; + #endif -- cgit v1.2.3-70-g09d2 From fa1df691688f34cbcd5bf77bd084bbe47e9d6bfe Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Mon, 21 Mar 2011 19:19:35 -0700 Subject: mfd: Add mfd_clone_cell(), convert cs5535-mfd/olpc-xo1 to it Replace mfd_shared_platform_driver_register with mfd_clone_cell. The former was called by an mfd client, and registered both a platform driver and device. The latter is called by an mfd driver, and registers only a platform device. The downside of this is that mfd drivers need to be modified whenever new clients are added that share a cell; the upside is that it fits Linux's driver model better. It's also simpler. This also converts cs5535-mfd/olpc-xo1 from the old API. cs5535-mfd now creates the olpc-xo1-{acpi,pms} devices, while olpc-xo1 binds to them via platform drivers. Signed-off-by: Andres Salomon Signed-off-by: Samuel Ortiz --- arch/x86/platform/olpc/olpc-xo1.c | 11 ++++---- drivers/mfd/cs5535-mfd.c | 18 +++++++++++++ drivers/mfd/mfd-core.c | 53 ++++++++------------------------------- include/linux/mfd/core.h | 27 +++++++++++++------- 4 files changed, 52 insertions(+), 57 deletions(-) (limited to 'include/linux/mfd') diff --git a/arch/x86/platform/olpc/olpc-xo1.c b/arch/x86/platform/olpc/olpc-xo1.c index 99513642a0e..386e3a159cc 100644 --- a/arch/x86/platform/olpc/olpc-xo1.c +++ b/arch/x86/platform/olpc/olpc-xo1.c @@ -121,22 +121,21 @@ static int __init olpc_xo1_init(void) { int r; - r = mfd_shared_platform_driver_register(&cs5535_pms_drv, "cs5535-pms"); + r = platform_driver_register(&cs5535_pms_drv); if (r) return r; - r = mfd_shared_platform_driver_register(&cs5535_acpi_drv, - "cs5535-acpi"); + r = platform_driver_register(&cs5535_acpi_drv); if (r) - mfd_shared_platform_driver_unregister(&cs5535_pms_drv); + platform_driver_unregister(&cs5535_pms_drv); return r; } static void __exit olpc_xo1_exit(void) { - mfd_shared_platform_driver_unregister(&cs5535_acpi_drv); - mfd_shared_platform_driver_unregister(&cs5535_pms_drv); + platform_driver_unregister(&cs5535_acpi_drv); + platform_driver_unregister(&cs5535_pms_drv); } MODULE_AUTHOR("Daniel Drake "); diff --git a/drivers/mfd/cs5535-mfd.c b/drivers/mfd/cs5535-mfd.c index 886a0687106..24959ddd932 100644 --- a/drivers/mfd/cs5535-mfd.c +++ b/drivers/mfd/cs5535-mfd.c @@ -27,6 +27,7 @@ #include #include #include +#include #define DRV_NAME "cs5535-mfd" @@ -111,6 +112,22 @@ static __devinitdata struct mfd_cell cs5535_mfd_cells[] = { }, }; +#ifdef CONFIG_OLPC +static void __devinit cs5535_clone_olpc_cells(void) +{ + const char *acpi_clones[] = { "olpc-xo1-acpi" }; + const char *pms_clones[] = { "olpc-xo1-pms" }; + + if (!machine_is_olpc()) + return; + + mfd_clone_cell("cs5535-acpi", acpi_clones, ARRAY_SIZE(acpi_clones)); + mfd_clone_cell("cs5535-pms", pms_clones, ARRAY_SIZE(pms_clones)); +} +#else +static void cs5535_clone_olpc_cells(void) { } +#endif + static int __devinit cs5535_mfd_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -139,6 +156,7 @@ static int __devinit cs5535_mfd_probe(struct pci_dev *pdev, dev_err(&pdev->dev, "MFD add devices failed: %d\n", err); goto err_disable; } + cs5535_clone_olpc_cells(); dev_info(&pdev->dev, "%zu devices registered.\n", ARRAY_SIZE(cs5535_mfd_cells)); diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 79eda0264fb..d01574d9887 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -184,16 +184,12 @@ void mfd_remove_devices(struct device *parent) } EXPORT_SYMBOL(mfd_remove_devices); -static int add_shared_platform_device(const char *cell, const char *name) +int mfd_clone_cell(const char *cell, const char **clones, size_t n_clones) { struct mfd_cell cell_entry; struct device *dev; struct platform_device *pdev; - int err; - - /* check if we've already registered a device (don't fail if we have) */ - if (bus_find_device_by_name(&platform_bus_type, NULL, name)) - return 0; + int i; /* fetch the parent cell's device (should already be registered!) */ dev = bus_find_device_by_name(&platform_bus_type, NULL, cell); @@ -206,44 +202,17 @@ static int add_shared_platform_device(const char *cell, const char *name) WARN_ON(!cell_entry.enable); - cell_entry.name = name; - err = mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0); - if (err) - dev_err(dev, "MFD add devices failed: %d\n", err); - return err; -} - -int mfd_shared_platform_driver_register(struct platform_driver *drv, - const char *cellname) -{ - int err; - - err = add_shared_platform_device(cellname, drv->driver.name); - if (err) - printk(KERN_ERR "failed to add platform device %s\n", - drv->driver.name); - - err = platform_driver_register(drv); - if (err) - printk(KERN_ERR "failed to add platform driver %s\n", - drv->driver.name); - - return err; -} -EXPORT_SYMBOL(mfd_shared_platform_driver_register); - -void mfd_shared_platform_driver_unregister(struct platform_driver *drv) -{ - struct device *dev; - - dev = bus_find_device_by_name(&platform_bus_type, NULL, - drv->driver.name); - if (dev) - platform_device_unregister(to_platform_device(dev)); + for (i = 0; i < n_clones; i++) { + cell_entry.name = clones[i]; + /* don't give up if a single call fails; just report error */ + if (mfd_add_device(pdev->dev.parent, -1, &cell_entry, NULL, 0)) + dev_err(dev, "failed to create platform device '%s'\n", + clones[i]); + } - platform_driver_unregister(drv); + return 0; } -EXPORT_SYMBOL(mfd_shared_platform_driver_unregister); +EXPORT_SYMBOL(mfd_clone_cell); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 1408bf8eed5..ad1b19aa650 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -62,6 +62,24 @@ struct mfd_cell { extern int mfd_cell_enable(struct platform_device *pdev); extern int mfd_cell_disable(struct platform_device *pdev); +/* + * "Clone" multiple platform devices for a single cell. This is to be used + * for devices that have multiple users of a cell. For example, if an mfd + * driver wants the cell "foo" to be used by a GPIO driver, an MTD driver, + * and a platform driver, the following bit of code would be use after first + * calling mfd_add_devices(): + * + * const char *fclones[] = { "foo-gpio", "foo-mtd" }; + * err = mfd_clone_cells("foo", fclones, ARRAY_SIZE(fclones)); + * + * Each driver (MTD, GPIO, and platform driver) would then register + * platform_drivers for "foo-mtd", "foo-gpio", and "foo", respectively. + * The cell's .enable/.disable hooks should be used to deal with hardware + * resource contention. + */ +extern int mfd_clone_cell(const char *cell, const char **clones, + size_t n_clones); + /* * Given a platform device that's been created by mfd_add_devices(), fetch * the mfd_cell that created it. @@ -87,13 +105,4 @@ extern int mfd_add_devices(struct device *parent, int id, extern void mfd_remove_devices(struct device *parent); -/* - * For MFD drivers with clients sharing access to resources, these create - * multiple platform devices per cell. Contention handling must still be - * handled via drivers (ie, with enable/disable hooks). - */ -extern int mfd_shared_platform_driver_register(struct platform_driver *drv, - const char *cellname); -extern void mfd_shared_platform_driver_unregister(struct platform_driver *drv); - #endif -- cgit v1.2.3-70-g09d2 From 8de6bc7f6ba58dd717e4a65e3bf4a746116fb874 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Thu, 24 Mar 2011 15:54:45 +0900 Subject: mfd: Add MAX8997/8966 IRQ control This patch enables IRQ handling for MAX8997/8966 chips. Please note that Fuel-Gauge-related IRQs are not implemented in this initial release. The fuel gauge module in MAX8997 is identical to MAX17042, which is already in Linux kernel. In order to use the already-existing MAX17042 driver for fuel gauge module in MAX8997, the main interrupt handler of MAX8997 should relay related interrupts to MAX17042 driver. However, in order to do this, we need to modify MAX17042 driver as well because MAX17042 driver does not have any interrupt handlers for now. We are not going to implement this in this initial release as it is not crucial in basic operations of MAX8997. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Samuel Ortiz --- drivers/mfd/Makefile | 2 +- drivers/mfd/max8997-irq.c | 377 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997-private.h | 21 ++ include/linux/mfd/max8997.h | 7 +- 4 files changed, 404 insertions(+), 3 deletions(-) create mode 100644 drivers/mfd/max8997-irq.c (limited to 'include/linux/mfd') diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 47f5709f382..ef489f25340 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -63,7 +63,7 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o obj-$(CONFIG_PMIC_DA903X) += da903x.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o -obj-$(CONFIG_MFD_MAX8997) += max8997.o +obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o pcf50633-objs := pcf50633-core.o pcf50633-irq.o diff --git a/drivers/mfd/max8997-irq.c b/drivers/mfd/max8997-irq.c new file mode 100644 index 00000000000..e85c874133c --- /dev/null +++ b/drivers/mfd/max8997-irq.c @@ -0,0 +1,377 @@ +/* + * max8997-irq.c - Interrupt controller support for MAX8997 + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998-irq.c + */ + +#include +#include +#include +#include +#include + +static const u8 max8997_mask_reg[] = { + [PMIC_INT1] = MAX8997_REG_INT1MSK, + [PMIC_INT2] = MAX8997_REG_INT2MSK, + [PMIC_INT3] = MAX8997_REG_INT3MSK, + [PMIC_INT4] = MAX8997_REG_INT4MSK, + [FUEL_GAUGE] = MAX8997_REG_INVALID, + [MUIC_INT1] = MAX8997_MUIC_REG_INTMASK1, + [MUIC_INT2] = MAX8997_MUIC_REG_INTMASK2, + [MUIC_INT3] = MAX8997_MUIC_REG_INTMASK3, + [GPIO_LOW] = MAX8997_REG_INVALID, + [GPIO_HI] = MAX8997_REG_INVALID, + [FLASH_STATUS] = MAX8997_REG_INVALID, +}; + +static struct i2c_client *get_i2c(struct max8997_dev *max8997, + enum max8997_irq_source src) +{ + switch (src) { + case PMIC_INT1 ... PMIC_INT4: + return max8997->i2c; + case FUEL_GAUGE: + return NULL; + case MUIC_INT1 ... MUIC_INT3: + return max8997->muic; + case GPIO_LOW ... GPIO_HI: + return max8997->i2c; + case FLASH_STATUS: + return max8997->i2c; + default: + return ERR_PTR(-EINVAL); + } + + return ERR_PTR(-EINVAL); +} + +struct max8997_irq_data { + int mask; + enum max8997_irq_source group; +}; + +#define DECLARE_IRQ(idx, _group, _mask) \ + [(idx)] = { .group = (_group), .mask = (_mask) } +static const struct max8997_irq_data max8997_irqs[] = { + DECLARE_IRQ(MAX8997_PMICIRQ_PWRONR, PMIC_INT1, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_PWRONF, PMIC_INT1, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_PWRON1SEC, PMIC_INT1, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGONR, PMIC_INT1, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGONF, PMIC_INT1, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT2, PMIC_INT1, 1 << 6), + DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT1, PMIC_INT1, 1 << 7), + + DECLARE_IRQ(MAX8997_PMICIRQ_JIGR, PMIC_INT2, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGF, PMIC_INT2, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_MR, PMIC_INT2, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS1OK, PMIC_INT2, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS2OK, PMIC_INT2, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS3OK, PMIC_INT2, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS4OK, PMIC_INT2, 1 << 6), + + DECLARE_IRQ(MAX8997_PMICIRQ_CHGINS, PMIC_INT3, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_CHGRM, PMIC_INT3, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_DCINOVP, PMIC_INT3, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_TOPOFFR, PMIC_INT3, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_CHGRSTF, PMIC_INT3, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_MBCHGTMEXPD, PMIC_INT3, 1 << 7), + + DECLARE_IRQ(MAX8997_PMICIRQ_RTC60S, PMIC_INT4, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_RTCA1, PMIC_INT4, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_RTCA2, PMIC_INT4, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_SMPL_INT, PMIC_INT4, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_RTC1S, PMIC_INT4, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_WTSR, PMIC_INT4, 1 << 5), + + DECLARE_IRQ(MAX8997_MUICIRQ_ADCError, MUIC_INT1, 1 << 2), + DECLARE_IRQ(MAX8997_MUICIRQ_ADCLow, MUIC_INT1, 1 << 1), + DECLARE_IRQ(MAX8997_MUICIRQ_ADC, MUIC_INT1, 1 << 0), + + DECLARE_IRQ(MAX8997_MUICIRQ_VBVolt, MUIC_INT2, 1 << 4), + DECLARE_IRQ(MAX8997_MUICIRQ_DBChg, MUIC_INT2, 1 << 3), + DECLARE_IRQ(MAX8997_MUICIRQ_DCDTmr, MUIC_INT2, 1 << 2), + DECLARE_IRQ(MAX8997_MUICIRQ_ChgDetRun, MUIC_INT2, 1 << 1), + DECLARE_IRQ(MAX8997_MUICIRQ_ChgTyp, MUIC_INT2, 1 << 0), + + DECLARE_IRQ(MAX8997_MUICIRQ_OVP, MUIC_INT3, 1 << 2), +}; + +static void max8997_irq_lock(struct irq_data *data) +{ + struct max8997_dev *max8997 = get_irq_chip_data(data->irq); + + mutex_lock(&max8997->irqlock); +} + +static void max8997_irq_sync_unlock(struct irq_data *data) +{ + struct max8997_dev *max8997 = get_irq_chip_data(data->irq); + int i; + + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { + u8 mask_reg = max8997_mask_reg[i]; + struct i2c_client *i2c = get_i2c(max8997, i); + + if (mask_reg == MAX8997_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max8997->irq_masks_cache[i] = max8997->irq_masks_cur[i]; + + max8997_write_reg(i2c, max8997_mask_reg[i], + max8997->irq_masks_cur[i]); + } + + mutex_unlock(&max8997->irqlock); +} + +static const inline struct max8997_irq_data * +irq_to_max8997_irq(struct max8997_dev *max8997, int irq) +{ + return &max8997_irqs[irq - max8997->irq_base]; +} + +static void max8997_irq_mask(struct irq_data *data) +{ + struct max8997_dev *max8997 = get_irq_chip_data(data->irq); + const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, + data->irq); + + max8997->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max8997_irq_unmask(struct irq_data *data) +{ + struct max8997_dev *max8997 = get_irq_chip_data(data->irq); + const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, + data->irq); + + max8997->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static struct irq_chip max8997_irq_chip = { + .name = "max8997", + .irq_bus_lock = max8997_irq_lock, + .irq_bus_sync_unlock = max8997_irq_sync_unlock, + .irq_mask = max8997_irq_mask, + .irq_unmask = max8997_irq_unmask, +}; + +#define MAX8997_IRQSRC_PMIC (1 << 1) +#define MAX8997_IRQSRC_FUELGAUGE (1 << 2) +#define MAX8997_IRQSRC_MUIC (1 << 3) +#define MAX8997_IRQSRC_GPIO (1 << 4) +#define MAX8997_IRQSRC_FLASH (1 << 5) +static irqreturn_t max8997_irq_thread(int irq, void *data) +{ + struct max8997_dev *max8997 = data; + u8 irq_reg[MAX8997_IRQ_GROUP_NR] = {}; + u8 irq_src; + int ret; + int i; + + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max8997->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (irq_src & MAX8997_IRQSRC_PMIC) { + /* PMIC INT1 ~ INT4 */ + max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4, + &irq_reg[PMIC_INT1]); + } + if (irq_src & MAX8997_IRQSRC_FUELGAUGE) { + /* + * TODO: FUEL GAUGE + * + * This is to be supported by Max17042 driver. When + * an interrupt incurs here, it should be relayed to a + * Max17042 device that is connected (probably by + * platform-data). However, we do not have interrupt + * handling in Max17042 driver currently. The Max17042 IRQ + * driver should be ready to be used as a stand-alone device and + * a Max8997-dependent device. Because it is not ready in + * Max17042-side and it is not too critical in operating + * Max8997, we do not implement this in initial releases. + */ + irq_reg[FUEL_GAUGE] = 0; + } + if (irq_src & MAX8997_IRQSRC_MUIC) { + /* MUIC INT1 ~ INT3 */ + max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3, + &irq_reg[MUIC_INT1]); + } + if (irq_src & MAX8997_IRQSRC_GPIO) { + /* GPIO Interrupt */ + u8 gpio_info[MAX8997_NUM_GPIO]; + + irq_reg[GPIO_LOW] = 0; + irq_reg[GPIO_HI] = 0; + + max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1, + MAX8997_NUM_GPIO, gpio_info); + for (i = 0; i < MAX8997_NUM_GPIO; i++) { + bool interrupt = false; + + switch (gpio_info[i] & MAX8997_GPIO_INT_MASK) { + case MAX8997_GPIO_INT_BOTH: + if (max8997->gpio_status[i] != gpio_info[i]) + interrupt = true; + break; + case MAX8997_GPIO_INT_RISE: + if ((max8997->gpio_status[i] != gpio_info[i]) && + (gpio_info[i] & MAX8997_GPIO_DATA_MASK)) + interrupt = true; + break; + case MAX8997_GPIO_INT_FALL: + if ((max8997->gpio_status[i] != gpio_info[i]) && + !(gpio_info[i] & MAX8997_GPIO_DATA_MASK)) + interrupt = true; + break; + default: + break; + } + + if (interrupt) { + if (i < 8) + irq_reg[GPIO_LOW] |= (1 << i); + else + irq_reg[GPIO_HI] |= (1 << (i - 8)); + } + + } + } + if (irq_src & MAX8997_IRQSRC_FLASH) { + /* Flash Status Interrupt */ + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_FLASHSTATUS, + &irq_reg[FLASH_STATUS]); + } + + /* Apply masking */ + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max8997->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX8997_IRQ_NR; i++) { + if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask) + handle_nested_irq(max8997->irq_base + i); + } + + return IRQ_HANDLED; +} + +int max8997_irq_resume(struct max8997_dev *max8997) +{ + if (max8997->irq && max8997->irq_base) + max8997_irq_thread(max8997->irq_base, max8997); + return 0; +} + +int max8997_irq_init(struct max8997_dev *max8997) +{ + int i; + int cur_irq; + int ret; + u8 val; + + if (!max8997->irq) { + dev_warn(max8997->dev, "No interrupt specified.\n"); + max8997->irq_base = 0; + return 0; + } + + if (!max8997->irq_base) { + dev_err(max8997->dev, "No interrupt base specified.\n"); + return 0; + } + + mutex_init(&max8997->irqlock); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + + max8997->irq_masks_cur[i] = 0xff; + max8997->irq_masks_cache[i] = 0xff; + i2c = get_i2c(max8997, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max8997_mask_reg[i] == MAX8997_REG_INVALID) + continue; + + max8997_write_reg(i2c, max8997_mask_reg[i], 0xff); + } + + for (i = 0; i < MAX8997_NUM_GPIO; i++) { + max8997->gpio_status[i] = (max8997_read_reg(max8997->i2c, + MAX8997_REG_GPIOCNTL1 + i, + &val) + & MAX8997_GPIO_DATA_MASK) ? + true : false; + } + + /* Register with genirq */ + for (i = 0; i < MAX8997_IRQ_NR; i++) { + cur_irq = i + max8997->irq_base; + set_irq_chip_data(cur_irq, max8997); + set_irq_chip_and_handler(cur_irq, &max8997_irq_chip, + handle_edge_irq); + set_irq_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(max8997->irq, NULL, max8997_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max8997-irq", max8997); + + if (ret) { + dev_err(max8997->dev, "Failed to request IRQ %d: %d\n", + max8997->irq, ret); + return ret; + } + + if (!max8997->ono) + return 0; + + ret = request_threaded_irq(max8997->ono, NULL, max8997_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "max8997-ono", max8997); + + if (ret) + dev_err(max8997->dev, "Failed to request ono-IRQ %d: %d\n", + max8997->ono, ret); + + return 0; +} + +void max8997_irq_exit(struct max8997_dev *max8997) +{ + if (max8997->ono) + free_irq(max8997->ono, max8997); + + if (max8997->irq) + free_irq(max8997->irq, max8997); +} diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index 93a9477e075..69d1010e2e5 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -24,6 +24,8 @@ #include +#define MAX8997_REG_INVALID (0xff) + enum max8997_pmic_reg { MAX8997_REG_PMIC_ID0 = 0x00, MAX8997_REG_PMIC_ID1 = 0x01, @@ -313,6 +315,7 @@ enum max8997_irq { #define MAX8997_REG_BUCK2DVS(x) (MAX8997_REG_BUCK2DVS1 + (x) - 1) #define MAX8997_REG_BUCK5DVS(x) (MAX8997_REG_BUCK5DVS1 + (x) - 1) +#define MAX8997_NUM_GPIO 12 struct max8997_dev { struct device *dev; struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */ @@ -324,11 +327,19 @@ struct max8997_dev { int type; struct platform_device *battery; /* battery control (not fuel gauge) */ + int irq; + int ono; + int irq_base; bool wakeup; + struct mutex irqlock; + int irq_masks_cur[MAX8997_IRQ_GROUP_NR]; + int irq_masks_cache[MAX8997_IRQ_GROUP_NR]; /* For hibernation */ u8 reg_dump[MAX8997_REG_PMIC_END + MAX8997_MUIC_REG_END + MAX8997_HAPTIC_REG_END]; + + bool gpio_status[MAX8997_NUM_GPIO]; }; enum max8997_types { @@ -336,6 +347,10 @@ enum max8997_types { TYPE_MAX8966, }; +extern int max8997_irq_init(struct max8997_dev *max8997); +extern void max8997_irq_exit(struct max8997_dev *max8997); +extern int max8997_irq_resume(struct max8997_dev *max8997); + extern int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); extern int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf); @@ -344,4 +359,10 @@ extern int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf); extern int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); +#define MAX8997_GPIO_INT_BOTH (0x3 << 4) +#define MAX8997_GPIO_INT_RISE (0x2 << 4) +#define MAX8997_GPIO_INT_FALL (0x1 << 4) + +#define MAX8997_GPIO_INT_MASK (0x3 << 4) +#define MAX8997_GPIO_DATA_MASK (0x1 << 2) #endif /* __LINUX_MFD_MAX8997_PRIV_H */ diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index cb671b3451b..60931d08942 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -78,8 +78,11 @@ struct max8997_regulator_data { }; struct max8997_platform_data { - bool wakeup; - /* IRQ: Not implemented */ + /* IRQ */ + int irq_base; + int ono; + int wakeup; + /* ---- PMIC ---- */ struct max8997_regulator_data *regulators; int num_regulators; -- cgit v1.2.3-70-g09d2 From 0cb3fcd72cc3e6dd88f0e769746d294e5e6bafa9 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Wed, 9 Feb 2011 11:02:35 +0530 Subject: gpio: driver for 42 AB8500 GPIO pins To get rid of port expanders, the free GPIOs of ab8500 can be used. There are 42 GPIO pins. Out of which 16 are interrupt capable.This patch implements 16 virtual IRQ mapped to 16 interrupt capable AB8500 GPIOs. Signed-off-by: Bibek Basu Acked-by: Grant Likely [Renamed header file as per MFD structure] Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 5 + drivers/gpio/Makefile | 1 + drivers/gpio/ab8500-gpio.c | 522 ++++++++++++++++++++++++++++++++++++++++ drivers/mfd/ab8500-core.c | 14 ++ drivers/mfd/ab8500-i2c.c | 2 +- include/linux/mfd/ab8500.h | 41 ++++ include/linux/mfd/ab8500/gpio.h | 21 ++ 7 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 drivers/gpio/ab8500-gpio.c create mode 100644 include/linux/mfd/ab8500/gpio.h (limited to 'include/linux/mfd') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d8d0cda2641..d3743204a7e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -414,4 +414,9 @@ config GPIO_JANZ_TTL This driver provides support for driving the pins in output mode only. Input mode is not supported. +config AB8500_GPIO + bool "ST-Ericsson AB8500 Mixed Signal Circuit gpio functions" + depends on AB8500_CORE + help + Select this to enable the AB8500 IC GPIO driver endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 3351cf87b0e..becef595435 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o obj-$(CONFIG_GPIO_SX150X) += sx150x.o obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o +obj-$(CONFIG_AB8500_GPIO) += ab8500-gpio.o diff --git a/drivers/gpio/ab8500-gpio.c b/drivers/gpio/ab8500-gpio.c new file mode 100644 index 00000000000..e7b834d054b --- /dev/null +++ b/drivers/gpio/ab8500-gpio.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: BIBEK BASU + * License terms: GNU General Public License (GPL) version 2 + * + * 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 +#include +#include +#include +#include +#include +#include + +/* + * GPIO registers offset + * Bank: 0x10 + */ +#define AB8500_GPIO_SEL1_REG 0x00 +#define AB8500_GPIO_SEL2_REG 0x01 +#define AB8500_GPIO_SEL3_REG 0x02 +#define AB8500_GPIO_SEL4_REG 0x03 +#define AB8500_GPIO_SEL5_REG 0x04 +#define AB8500_GPIO_SEL6_REG 0x05 + +#define AB8500_GPIO_DIR1_REG 0x10 +#define AB8500_GPIO_DIR2_REG 0x11 +#define AB8500_GPIO_DIR3_REG 0x12 +#define AB8500_GPIO_DIR4_REG 0x13 +#define AB8500_GPIO_DIR5_REG 0x14 +#define AB8500_GPIO_DIR6_REG 0x15 + +#define AB8500_GPIO_OUT1_REG 0x20 +#define AB8500_GPIO_OUT2_REG 0x21 +#define AB8500_GPIO_OUT3_REG 0x22 +#define AB8500_GPIO_OUT4_REG 0x23 +#define AB8500_GPIO_OUT5_REG 0x24 +#define AB8500_GPIO_OUT6_REG 0x25 + +#define AB8500_GPIO_PUD1_REG 0x30 +#define AB8500_GPIO_PUD2_REG 0x31 +#define AB8500_GPIO_PUD3_REG 0x32 +#define AB8500_GPIO_PUD4_REG 0x33 +#define AB8500_GPIO_PUD5_REG 0x34 +#define AB8500_GPIO_PUD6_REG 0x35 + +#define AB8500_GPIO_IN1_REG 0x40 +#define AB8500_GPIO_IN2_REG 0x41 +#define AB8500_GPIO_IN3_REG 0x42 +#define AB8500_GPIO_IN4_REG 0x43 +#define AB8500_GPIO_IN5_REG 0x44 +#define AB8500_GPIO_IN6_REG 0x45 +#define AB8500_GPIO_ALTFUN_REG 0x45 +#define ALTFUN_REG_INDEX 6 +#define AB8500_NUM_GPIO 42 +#define AB8500_NUM_VIR_GPIO_IRQ 16 + +enum ab8500_gpio_action { + NONE, + STARTUP, + SHUTDOWN, + MASK, + UNMASK +}; + +struct ab8500_gpio { + struct gpio_chip chip; + struct ab8500 *parent; + struct device *dev; + struct mutex lock; + u32 irq_base; + enum ab8500_gpio_action irq_action; + u16 rising; + u16 falling; +}; +/** + * to_ab8500_gpio() - get the pointer to ab8500_gpio + * @chip: Member of the structure ab8500_gpio + */ +static inline struct ab8500_gpio *to_ab8500_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct ab8500_gpio, chip); +} + +static int ab8500_gpio_set_bits(struct gpio_chip *chip, u8 reg, + unsigned offset, int val) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + u8 pos = offset % 8; + int ret; + + reg = reg + (offset / 8); + ret = abx500_mask_and_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, reg, 1 << pos, val << pos); + if (ret < 0) + dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); + return ret; +} +/** + * ab8500_gpio_get() - Get the particular GPIO value + * @chip: Gpio device + * @offset: GPIO number to read + */ +static int ab8500_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + u8 mask = 1 << (offset % 8); + u8 reg = AB8500_GPIO_OUT1_REG + (offset / 8); + int ret; + u8 data; + ret = abx500_get_register_interruptible(ab8500_gpio->dev, AB8500_MISC, + reg, &data); + if (ret < 0) { + dev_err(ab8500_gpio->dev, "%s read failed\n", __func__); + return ret; + } + return (data & mask) >> (offset % 8); +} + +static void ab8500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + int ret; + /* Write the data */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, 1); + if (ret < 0) + dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); +} + +static int ab8500_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int val) +{ + int ret; + /* set direction as output */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_DIR1_REG, offset, 1); + if (ret < 0) + return ret; + /* disable pull down */ + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_PUD1_REG, offset, 1); + if (ret < 0) + return ret; + /* set the output as 1 or 0 */ + return ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, val); + +} + +static int ab8500_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + /* set the register as input */ + return ab8500_gpio_set_bits(chip, AB8500_GPIO_DIR1_REG, offset, 0); +} + +static int ab8500_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + /* + * Only some GPIOs are interrupt capable, and they are + * organized in discontiguous clusters: + * + * GPIO6 to GPIO13 + * GPIO24 and GPIO25 + * GPIO36 to GPIO41 + */ + static struct ab8500_gpio_irq_cluster { + int start; + int end; + } clusters[] = { + {.start = 6, .end = 13}, + {.start = 24, .end = 25}, + {.start = 36, .end = 41}, + }; + struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); + int base = ab8500_gpio->irq_base; + int i; + + for (i = 0; i < ARRAY_SIZE(clusters); i++) { + struct ab8500_gpio_irq_cluster *cluster = &clusters[i]; + + if (offset >= cluster->start && offset <= cluster->end) + return base + offset - cluster->start; + + /* Advance by the number of gpios in this cluster */ + base += cluster->end - cluster->start + 1; + } + + return -EINVAL; +} + +static struct gpio_chip ab8500gpio_chip = { + .label = "ab8500_gpio", + .owner = THIS_MODULE, + .direction_input = ab8500_gpio_direction_input, + .get = ab8500_gpio_get, + .direction_output = ab8500_gpio_direction_output, + .set = ab8500_gpio_set, + .to_irq = ab8500_gpio_to_irq, +}; + +static unsigned int irq_to_rising(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + int new_irq = offset + AB8500_INT_GPIO6R + + ab8500_gpio->parent->irq_base; + return new_irq; +} + +static unsigned int irq_to_falling(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + int new_irq = offset + AB8500_INT_GPIO6F + + ab8500_gpio->parent->irq_base; + return new_irq; + +} + +static unsigned int rising_to_irq(unsigned int irq, void *dev) +{ + struct ab8500_gpio *ab8500_gpio = dev; + int offset = irq - AB8500_INT_GPIO6R + - ab8500_gpio->parent->irq_base ; + int new_irq = offset + ab8500_gpio->irq_base; + return new_irq; +} + +static unsigned int falling_to_irq(unsigned int irq, void *dev) +{ + struct ab8500_gpio *ab8500_gpio = dev; + int offset = irq - AB8500_INT_GPIO6F + - ab8500_gpio->parent->irq_base ; + int new_irq = offset + ab8500_gpio->irq_base; + return new_irq; + +} + +/* + * IRQ handler + */ + +static irqreturn_t handle_rising(int irq, void *dev) +{ + + handle_nested_irq(rising_to_irq(irq , dev)); + return IRQ_HANDLED; +} + +static irqreturn_t handle_falling(int irq, void *dev) +{ + + handle_nested_irq(falling_to_irq(irq, dev)); + return IRQ_HANDLED; +} + +static void ab8500_gpio_irq_lock(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + mutex_lock(&ab8500_gpio->lock); +} + +static void ab8500_gpio_irq_sync_unlock(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + bool rising = ab8500_gpio->rising & BIT(offset); + bool falling = ab8500_gpio->falling & BIT(offset); + int ret; + + switch (ab8500_gpio->irq_action) { + case STARTUP: + if (rising) + ret = request_threaded_irq(irq_to_rising(irq), + NULL, handle_rising, + IRQF_TRIGGER_RISING, + "ab8500-gpio-r", ab8500_gpio); + if (falling) + ret = request_threaded_irq(irq_to_falling(irq), + NULL, handle_falling, + IRQF_TRIGGER_FALLING, + "ab8500-gpio-f", ab8500_gpio); + break; + case SHUTDOWN: + if (rising) + free_irq(irq_to_rising(irq), ab8500_gpio); + if (falling) + free_irq(irq_to_falling(irq), ab8500_gpio); + break; + case MASK: + if (rising) + disable_irq(irq_to_rising(irq)); + if (falling) + disable_irq(irq_to_falling(irq)); + break; + case UNMASK: + if (rising) + enable_irq(irq_to_rising(irq)); + if (falling) + enable_irq(irq_to_falling(irq)); + break; + case NONE: + break; + } + ab8500_gpio->irq_action = NONE; + ab8500_gpio->rising &= ~(BIT(offset)); + ab8500_gpio->falling &= ~(BIT(offset)); + mutex_unlock(&ab8500_gpio->lock); +} + + +static void ab8500_gpio_irq_mask(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = MASK; +} + +static void ab8500_gpio_irq_unmask(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = UNMASK; +} + +static int ab8500_gpio_irq_set_type(unsigned int irq, unsigned int type) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + int offset = irq - ab8500_gpio->irq_base; + + if (type == IRQ_TYPE_EDGE_BOTH) { + ab8500_gpio->rising = BIT(offset); + ab8500_gpio->falling = BIT(offset); + } else if (type == IRQ_TYPE_EDGE_RISING) { + ab8500_gpio->rising = BIT(offset); + } else { + ab8500_gpio->falling = BIT(offset); + } + return 0; +} + +unsigned int ab8500_gpio_irq_startup(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = STARTUP; + return 0; +} + +void ab8500_gpio_irq_shutdown(unsigned int irq) +{ + struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + ab8500_gpio->irq_action = SHUTDOWN; +} + +static struct irq_chip ab8500_gpio_irq_chip = { + .name = "ab8500-gpio", + .startup = ab8500_gpio_irq_startup, + .shutdown = ab8500_gpio_irq_shutdown, + .bus_lock = ab8500_gpio_irq_lock, + .bus_sync_unlock = ab8500_gpio_irq_sync_unlock, + .mask = ab8500_gpio_irq_mask, + .unmask = ab8500_gpio_irq_unmask, + .set_type = ab8500_gpio_irq_set_type, +}; + +static int ab8500_gpio_irq_init(struct ab8500_gpio *ab8500_gpio) +{ + u32 base = ab8500_gpio->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NUM_VIR_GPIO_IRQ ; irq++) { + set_irq_chip_data(irq, ab8500_gpio); + set_irq_chip_and_handler(irq, &ab8500_gpio_irq_chip, + handle_simple_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void ab8500_gpio_irq_remove(struct ab8500_gpio *ab8500_gpio) +{ + int base = ab8500_gpio->irq_base; + int irq; + + for (irq = base; irq < base + AB8500_NUM_VIR_GPIO_IRQ; 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 ab8500_gpio_probe(struct platform_device *pdev) +{ + struct ab8500_platform_data *ab8500_pdata = + dev_get_platdata(pdev->dev.parent); + struct ab8500_gpio_platform_data *pdata; + struct ab8500_gpio *ab8500_gpio; + int ret; + int i; + + pdata = ab8500_pdata->gpio; + if (!pdata) { + dev_err(&pdev->dev, "gpio platform data missing\n"); + return -ENODEV; + } + + ab8500_gpio = kzalloc(sizeof(struct ab8500_gpio), GFP_KERNEL); + if (ab8500_gpio == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + ab8500_gpio->dev = &pdev->dev; + ab8500_gpio->parent = dev_get_drvdata(pdev->dev.parent); + ab8500_gpio->chip = ab8500gpio_chip; + ab8500_gpio->chip.ngpio = AB8500_NUM_GPIO; + ab8500_gpio->chip.dev = &pdev->dev; + ab8500_gpio->chip.base = pdata->gpio_base; + ab8500_gpio->irq_base = pdata->irq_base; + /* initialize the lock */ + mutex_init(&ab8500_gpio->lock); + /* + * AB8500 core will handle and clear the IRQ + * configre GPIO based on config-reg value. + * These values are for selecting the PINs as + * GPIO or alternate function + */ + for (i = AB8500_GPIO_SEL1_REG; i <= AB8500_GPIO_SEL6_REG; i++) { + ret = abx500_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, i, + pdata->config_reg[i]); + if (ret < 0) + goto out_free; + } + ret = abx500_set_register_interruptible(ab8500_gpio->dev, AB8500_MISC, + AB8500_GPIO_ALTFUN_REG, + pdata->config_reg[ALTFUN_REG_INDEX]); + if (ret < 0) + goto out_free; + + ret = ab8500_gpio_irq_init(ab8500_gpio); + if (ret) + goto out_free; + ret = gpiochip_add(&ab8500_gpio->chip); + if (ret) { + dev_err(&pdev->dev, "unable to add gpiochip: %d\n", + ret); + goto out_rem_irq; + } + platform_set_drvdata(pdev, ab8500_gpio); + return 0; + +out_rem_irq: + ab8500_gpio_irq_remove(ab8500_gpio); +out_free: + mutex_destroy(&ab8500_gpio->lock); + kfree(ab8500_gpio); + return ret; +} + +/* + * ab8500_gpio_remove() - remove Ab8500-gpio driver + * @pdev : Platform device registered + */ +static int __devexit ab8500_gpio_remove(struct platform_device *pdev) +{ + struct ab8500_gpio *ab8500_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&ab8500_gpio->chip); + if (ret < 0) { + dev_err(ab8500_gpio->dev, "unable to remove gpiochip:\ + %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, NULL); + mutex_destroy(&ab8500_gpio->lock); + kfree(ab8500_gpio); + + return 0; +} + +static struct platform_driver ab8500_gpio_driver = { + .driver = { + .name = "ab8500-gpio", + .owner = THIS_MODULE, + }, + .probe = ab8500_gpio_probe, + .remove = __devexit_p(ab8500_gpio_remove), +}; + +static int __init ab8500_gpio_init(void) +{ + return platform_driver_register(&ab8500_gpio_driver); +} +arch_initcall(ab8500_gpio_init); + +static void __exit ab8500_gpio_exit(void) +{ + platform_driver_unregister(&ab8500_gpio_driver); +} +module_exit(ab8500_gpio_exit); + +MODULE_AUTHOR("BIBEK BASU "); +MODULE_DESCRIPTION("Driver allows to use AB8500 unused pins\ + to be used as GPIO"); +MODULE_ALIAS("AB8500 GPIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 62e33e2258d..67d01c93828 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -362,6 +362,15 @@ static void ab8500_irq_remove(struct ab8500 *ab8500) } } +static struct resource ab8500_gpio_resources[] = { + { + .name = "GPIO_INT6", + .start = AB8500_INT_GPIO6R, + .end = AB8500_INT_GPIO41F, + .flags = IORESOURCE_IRQ, + } +}; + static struct resource ab8500_gpadc_resources[] = { { .name = "HW_CONV_END", @@ -595,6 +604,11 @@ static struct mfd_cell ab8500_devs[] = { { .name = "ab8500-regulator", }, + { + .name = "ab8500-gpio", + .num_resources = ARRAY_SIZE(ab8500_gpio_resources), + .resources = ab8500_gpio_resources, + }, { .name = "ab8500-gpadc", .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 6820327adf4..821e6b86afd 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -97,7 +97,7 @@ static void __exit ab8500_i2c_exit(void) { platform_driver_unregister(&ab8500_i2c_driver); } -subsys_initcall(ab8500_i2c_init); +arch_initcall(ab8500_i2c_init); module_exit(ab8500_i2c_exit); MODULE_AUTHOR("Mattias WALLIN + * Licensed under GPLv2. + */ + +#ifndef _AB8500_GPIO_H +#define _AB8500_GPIO_H + +/* + * Platform data to register a block: only the initial gpio/irq number. + */ + +struct ab8500_gpio_platform_data { + int gpio_base; + u32 irq_base; + u8 config_reg[7]; +}; + +#endif /* _AB8500_GPIO_H */ -- cgit v1.2.3-70-g09d2