summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLaura Lawrence <Laura.Lawrence@freescale.com>2008-01-22 15:30:13 -0600
committerDaniel Schaeffer <daniel.schaeffer@timesys.com>2008-08-25 15:20:35 -0400
commit1d42b93db5289ea18739fa4c0fb704f95a1ac3ad (patch)
treef113434770311c4f92c0fa94c2fe4b0e3f9186c6 /drivers
parentd85fc70a64153e118bb704a411044dfe9a4e4f8e (diff)
ENGR00060846 Add Wolfson WM8350 Chip Support
Add WM8350 AudioPlus driver support http://opensource.wolfsonmicro.com/node/8
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/pmic/Kconfig46
-rw-r--r--drivers/pmic/Makefile13
-rw-r--r--drivers/pmic/pmic-wm8350-bus.c1529
-rw-r--r--drivers/pmic/pmic-wm8350.c813
-rw-r--r--drivers/pmic/pmic.c509
7 files changed, 2913 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index f4076d9e9902..f12108b84c9c 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -26,6 +26,8 @@ source "drivers/scsi/Kconfig"
source "drivers/ata/Kconfig"
+source "drivers/pmic/Kconfig"
+
source "drivers/md/Kconfig"
source "drivers/message/fusion/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index ca97a62a0217..48e670990615 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -45,6 +45,7 @@ obj-y += auxdisplay/
obj-$(CONFIG_MTD) += mtd/
obj-$(CONFIG_SPI) += spi/
obj-$(CONFIG_PCCARD) += pcmcia/
+obj-$(CONFIG_PMIC) += pmic/
obj-$(CONFIG_DIO) += dio/
obj-$(CONFIG_SBUS) += sbus/
obj-$(CONFIG_KVM) += kvm/
diff --git a/drivers/pmic/Kconfig b/drivers/pmic/Kconfig
new file mode 100644
index 000000000000..b4368120e810
--- /dev/null
+++ b/drivers/pmic/Kconfig
@@ -0,0 +1,46 @@
+menu "Power Management IC's"
+
+config PMIC
+ tristate "Power Management IC support"
+
+config PMIC_DEBUG
+ bool "PMIC debug support"
+ depends on PMIC
+ help
+ Say yes here to enable debugging support in the PMIC framework
+ and individual PMIC drivers.
+
+config PMIC_WM8350
+ tristate "WOLFSON WM8350 support"
+ help
+ If you say yes here you get support for the
+ Wolfon WM8350 PMIC chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called pmic-wm8350.
+
+menu "WM8350 Config Mode"
+depends on PMIC_WM8350
+
+choice
+ prompt "WM8350 Configuration Mode"
+ default PMIC_WM8350_MODE_0
+
+config PMIC_WM8350_MODE_0
+ bool "Mode 0"
+
+config PMIC_WM8350_MODE_1
+ bool "Mode 1"
+
+config PMIC_WM8350_MODE_2
+ bool "Mode 2"
+
+config PMIC_WM8350_MODE_3
+ bool "Mode 3"
+
+endchoice
+
+endmenu
+
+endmenu
+
diff --git a/drivers/pmic/Makefile b/drivers/pmic/Makefile
new file mode 100644
index 000000000000..9b556e115b45
--- /dev/null
+++ b/drivers/pmic/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for PMIC drivers .
+#
+
+ifeq ($(CONFIG_PMIC_DEBUG),y)
+ EXTRA_CFLAGS += -DDEBUG
+endif
+
+obj-$(CONFIG_PMIC) += pmic.o
+
+obj-$(CONFIG_PMIC_WM8350) += \
+ pmic-wm8350.o pmic-wm8350-bus.o
+
diff --git a/drivers/pmic/pmic-wm8350-bus.c b/drivers/pmic/pmic-wm8350-bus.c
new file mode 100644
index 000000000000..9d707c9771ab
--- /dev/null
+++ b/drivers/pmic/pmic-wm8350-bus.c
@@ -0,0 +1,1529 @@
+/*
+ * wm8350_bus.c -- Power Management Driver for Wolfson WM8350 PMIC
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * 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.
+ *
+ * Revision history
+ * 23rd Jan 2007 Initial version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/pmic/wm8350.h>
+#include <linux/delay.h>
+
+#define WM8350_BUS_VERSION "0.4"
+#define WM8350_UNLOCK_KEY 0x0013
+#define WM8350_LOCK_KEY 0x0000
+
+/* debug */
+#define WM8350_BUS_DEBUG 0
+#if WM8350_BUS_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#define dump(regs, src) do { \
+ int i; \
+ u16 *src_ = src; \
+ for (i = 0; i < regs; i++) \
+ dbg(" 0x%4.4x", *src_++); \
+ dbg("\n"); \
+} while (0);
+#else
+#define dbg(format, arg...)
+#define dump(bytes, src)
+#endif
+
+#define WM8350_LOCK_DEBUG 0
+#if WM8350_LOCK_DEBUG
+#define ldbg(format, arg...) printk(format, ## arg)
+#else
+#define ldbg(format, arg...)
+#endif
+
+#define BYTE_SWAP16(x) (x >> 8 | x << 8) // lg replace with generic
+
+/*
+ * WM8350 can run in 1 of 4 configuration modes.
+ * Each mode has different default register values.
+ */
+#if defined(CONFIG_PMIC_WM8350_MODE_0)
+static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_0;
+#elif defined(CONFIG_PMIC_WM8350_MODE_1)
+static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_1;
+#elif defined(CONFIG_PMIC_WM8350_MODE_2)
+static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_2;
+#elif defined(CONFIG_PMIC_WM8350_MODE_3)
+static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_3;
+#else
+#error Invalid WM8350 configuration
+#endif
+
+/*
+ * WM8350 Register IO access map
+ */
+static const struct wm8350_reg_access wm8350_reg_io_map[] = WM8350_ACCESS;
+
+/*
+ * WM8350 Device IO
+ */
+static DEFINE_MUTEX(io_mutex);
+static DEFINE_MUTEX(reg_lock_mutex);
+static DEFINE_MUTEX(auxadc_mutex);
+
+static int wm8350_read_i2c_device(struct wm8350 *wm8350, char reg,
+ int bytes, char *dest)
+{
+ int ret;
+
+ ret = i2c_master_send(wm8350->i2c_client, &reg, 1);
+ if (ret < 0)
+ return ret;
+ return i2c_master_recv(wm8350->i2c_client, dest, bytes);
+}
+
+static int wm8350_write_i2c_device(struct wm8350 *wm8350, char reg,
+ int bytes, char *src)
+{
+ /* we add 1 byte for device register */
+ u8 msg[(WM8350_MAX_REGISTER << 1) + 1];
+
+ if (bytes > ((WM8350_MAX_REGISTER << 1) + 1))
+ return -EINVAL;
+
+ msg[0] = reg;
+ memcpy(&msg[1], src, bytes);
+ return i2c_master_send(wm8350->i2c_client, msg, bytes + 1);
+}
+
+static int wm8350_read_spi_device(struct wm8350 *wm8350, char reg,
+ int bytes, char *dest)
+{
+ int ret;
+ u8 tx_msg[4], rx_msg[4];
+
+ /* don't support incremental write with SPI */
+ if (bytes != 2)
+ return -EIO;
+
+ tx_msg[0] = 0x80;
+ tx_msg[1] = reg;
+ tx_msg[2] = 0;
+ tx_msg[3] = 0;
+
+// ret = spi_write_then_read(wm8350->spi_device, tx_msg, 4, rx_msg, 4);
+ ret=0;
+ rx_msg[2]=0;
+ rx_msg[3]=0;
+
+ if (ret < 0) {
+ printk(KERN_ERR "%s: io failure %d\n", __func__, ret);
+ return 0;
+ }
+
+ *dest++ = rx_msg[2];
+ *dest = rx_msg[3];
+ return 0;
+}
+
+static int wm8350_write_spi_device(struct wm8350 *wm8350, char reg,
+ int bytes, char *src)
+{
+ u8 msg[4];
+
+ /* don't support incremental write with SPI */
+ if (bytes != 2)
+ return -EIO;
+
+ msg[0] = 0;
+ msg[1] = reg;
+ msg[2] = *src++;
+ msg[3] = *src;
+ return 0;//spi_write(wm8350->spi_device, msg, 4);
+}
+
+/* mask in WM8350 read bits */
+static inline void wm8350_mask_read(u8 reg, int bytes, u16 *buf)
+{
+ int i;
+
+ for (i = reg; i < reg + (bytes >> 1); i++)
+ *buf++ &= wm8350_reg_io_map[i].readable;
+}
+
+/* mask in WM8350 write bits */
+static inline void wm8350_mask_write(u8 reg, int bytes, u16 *buf)
+{
+ int i;
+
+ for (i = reg; i < reg + (bytes >> 1); i++)
+ *buf++ &= wm8350_reg_io_map[i].writable;
+}
+
+/* WM8350 is big endian, swap if necessary */
+static inline void wm8350_endian_swap(u8 reg, int bytes, u16 *buf)
+{
+#ifdef __LITTLE_ENDIAN
+ int i;
+ u16 tmp;
+
+ for (i = reg; i < reg + (bytes >> 1); i++) {
+ tmp = BYTE_SWAP16(*buf);
+ *buf++ = tmp;
+ }
+#endif
+}
+
+static inline void wm8350_cache_mask(struct wm8350 *wm8350, u8 reg,
+ int bytes, u16 *dest)
+{
+ int i;
+ u16 mask;
+
+ for (i = reg; i < reg + (bytes >> 1); i++) {
+ *dest &= wm8350_reg_io_map[i].vol;
+ mask = wm8350->reg_cache[reg] & ~wm8350_reg_io_map[i].vol;
+ *dest |= mask;
+ dest++;
+ }
+}
+
+static int wm8350_read(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *dest)
+{
+ int i, end = reg + num_regs, ret = 0, bytes = num_regs << 1;
+
+ if (wm8350->read_dev == NULL)
+ return -ENODEV;
+
+ if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) {
+ printk(KERN_ERR "wm8350: invalid reg %x\n", reg + num_regs - 1);
+ return -EINVAL;
+ }
+
+ dbg("%s R%d(0x%2.2x) %d regs ", __FUNCTION__, reg, reg, num_regs);
+
+#if WM8350_BUS_DEBUG
+ /* we can _safely_ read any register, but warn if read not supported */
+ for (i = reg; i < end; i++) {
+ if (!wm8350_reg_io_map[i].readable)
+ printk(KERN_WARNING "wm8350: reg R%d is not readable\n", i);
+ }
+#endif
+ /* if any volatile registers are required, then read back all */
+ for (i = reg; i < end; i++) {
+ if (wm8350_reg_io_map[i].vol) {
+ dbg("volatile read ");
+ ret = wm8350->read_dev(wm8350, reg,
+ bytes, (char*)dest);
+ wm8350_endian_swap(reg, bytes, dest);
+ wm8350_cache_mask(wm8350, reg, bytes, dest);
+ wm8350_mask_read(reg, bytes, dest);
+ dump(num_regs, dest);
+ return ret;
+ }
+ }
+
+ /* no volatiles, then cache is good */
+ dbg("cache read ");
+ memcpy(dest, &wm8350->reg_cache[reg], bytes);
+ dump(num_regs, dest);
+ return ret;
+}
+
+static inline int is_reg_locked(struct wm8350 *wm8350, u8 reg)
+{
+ if (reg == WM8350_SECURITY ||
+ wm8350->reg_cache[WM8350_SECURITY] == WM8350_UNLOCK_KEY)
+ return 0;
+
+ if ((reg == WM8350_GPIO_CONFIGURATION_I_O) ||
+ (reg >= WM8350_GPIO_FUNCTION_SELECT_1 &&
+ reg <= WM8350_GPIO_FUNCTION_SELECT_4) ||
+ (reg >= WM8350_BATTERY_CHARGER_CONTROL_1 &&
+ reg <= WM8350_BATTERY_CHARGER_CONTROL_3))
+ return 1;
+ return 0;
+}
+
+static int wm8350_write(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *src)
+{
+ int ret, i, end = reg + num_regs, bytes = num_regs << 1;
+
+ if (wm8350->write_dev == NULL)
+ return -ENODEV;
+
+ if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) {
+ printk(KERN_ERR "wm8350: invalid reg %x\n", reg + num_regs - 1);
+ return -EINVAL;
+ }
+
+ wm8350_mask_write(reg, bytes, src);
+ memcpy(&wm8350->reg_cache[reg], src, bytes);
+ dbg("%s R%d(0x%2.2x) %d regs ", __FUNCTION__, reg, reg, num_regs);
+ dump(num_regs, src);
+
+ wm8350_endian_swap(reg, bytes, src);
+
+ /* it's generally not a good idea to write to RO or locked registers */
+ for (i = reg; i < end; i++) {
+ if (!wm8350_reg_io_map[i].writable) {
+ printk(KERN_ERR "wm8350: attempted write to read only reg R%d\n", i);
+ return -EINVAL;
+ }
+
+ if (is_reg_locked(wm8350, i)) {
+ printk(KERN_ERR "wm8350: attempted write to locked reg R%d\n", i);
+ return -EINVAL;
+ }
+ }
+
+ /* write registers and update cache if successful */
+ ret = wm8350->write_dev(wm8350, reg, bytes, (char*)src);
+ return ret;
+}
+
+/*
+ * Safe read, modify, write methods
+ */
+int wm8350_clear_bits(struct wm8350 *wm8350, u16 reg, u16 mask)
+{
+ u16 data;
+ int err;
+
+ mutex_lock(&io_mutex);
+ err = wm8350_read(wm8350, reg, 1, &data);
+ if (err) {
+ printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg);
+ goto out;
+ }
+
+ data &= ~mask;
+ err = wm8350_write(wm8350, reg, 1, &data);
+ if (err)
+ printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg);
+out:
+ mutex_unlock(&io_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm8350_clear_bits);
+
+int wm8350_set_bits(struct wm8350 *wm8350, u16 reg, u16 mask)
+{
+ u16 data;
+ int err;
+
+ mutex_lock(&io_mutex);
+ err = wm8350_read(wm8350, reg, 1, &data);
+ if (err) {
+ printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg);
+ goto out;
+ }
+
+ data |= mask;
+ err = wm8350_write(wm8350, reg, 1, &data);
+ if (err)
+ printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg);
+out:
+ mutex_unlock(&io_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm8350_set_bits);
+
+u16 wm8350_reg_read(struct wm8350 *wm8350, int reg)
+{
+ u16 data;
+ int err;
+
+ mutex_lock(&io_mutex);
+ err = wm8350_read(wm8350, reg, 1, &data);
+ if (err)
+ printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg);
+
+ mutex_unlock(&io_mutex);
+ return data;
+}
+EXPORT_SYMBOL_GPL(wm8350_reg_read);
+
+int wm8350_reg_write(struct wm8350 *wm8350, int reg, u16 val)
+{
+ int ret;
+ u16 data = val;
+
+ mutex_lock(&io_mutex);
+ ret = wm8350_write(wm8350, reg, 1, &data);
+ if (ret)
+ printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg);
+ mutex_unlock(&io_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_reg_write);
+
+int wm8350_block_read(struct wm8350 *wm8350, int start_reg, int regs,
+ u16 *dest)
+{
+ int err = 0;
+
+ mutex_lock(&io_mutex);
+ err = wm8350_read(wm8350, start_reg, regs, dest);
+ if (err)
+ printk(KERN_ERR "wm8350: block read starting from R%d failed\n",
+ start_reg);
+ mutex_unlock(&io_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm8350_block_read);
+
+int wm8350_block_write(struct wm8350 *wm8350, int start_reg, int regs,
+ u16 *src)
+{
+ int ret = 0;
+
+ mutex_lock(&io_mutex);
+ ret = wm8350_write(wm8350, start_reg, regs, src);
+ if (ret)
+ printk(KERN_ERR "wm8350: block write starting at R%d failed\n",
+ start_reg);
+ mutex_unlock(&io_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_block_write);
+
+int wm8350_reg_lock(struct wm8350 *wm8350)
+{
+ u16 key = WM8350_LOCK_KEY;
+ int ret;
+
+ ldbg ("%s\n", __FUNCTION__);
+ mutex_lock(&io_mutex);
+ ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key);
+ if (ret)
+ printk(KERN_ERR "wm8350: lock failed\n");
+ mutex_unlock(&io_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_reg_lock);
+
+int wm8350_reg_unlock(struct wm8350 *wm8350)
+{
+ u16 key = WM8350_UNLOCK_KEY;
+ int ret;
+
+ ldbg ("%s\n", __FUNCTION__);
+ mutex_lock(&io_mutex);
+ ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key);
+ if (ret)
+ printk(KERN_ERR "wm8350: unlock failed\n");
+ mutex_unlock(&io_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_reg_unlock);
+
+/*
+ * For Switching between SPI and I2C IO
+ */
+int wm8350_set_io(struct wm8350 *wm8350, int io, wm8350_hw_read_t read_dev,
+ wm8350_hw_write_t write_dev)
+{
+ mutex_lock(&io_mutex);
+ switch (io) {
+ case WM8350_IO_I2C:
+ wm8350->read_dev = wm8350_read_i2c_device;
+ wm8350->write_dev = wm8350_write_i2c_device;
+ break;
+ case WM8350_IO_SPI:
+ wm8350->read_dev = wm8350_read_spi_device;
+ wm8350->write_dev = wm8350_write_spi_device;
+ break;
+ case WM8350_IO_CUSTOM:
+ wm8350->read_dev = read_dev;
+ wm8350->write_dev = write_dev;
+ break;
+ default:
+ printk(KERN_ERR "wm8350: invalid IO mechanism\n");
+ wm8350->read_dev = NULL;
+ wm8350->write_dev = NULL;
+ mutex_unlock(&io_mutex);
+ return -EINVAL;
+ }
+ mutex_unlock(&io_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_set_io);
+
+/*
+ * Cache is always host endian.
+ */
+int wm8350_create_cache(struct wm8350 *wm8350)
+{
+ int i, ret = 0;
+ u16 value;
+
+ if (wm8350->read_dev == NULL)
+ return -ENODEV;
+
+ wm8350->reg_cache =
+ kzalloc(sizeof(u16) * (WM8350_MAX_REGISTER + 1), GFP_KERNEL);
+ if (wm8350->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* TODO: check if we are virgin state so we don't have to do this */
+ /* refresh cache with chip regs as some registers can survive reboot */
+ for (i = 0; i < WM8350_MAX_REGISTER; i++) {
+ if (wm8350_reg_io_map[i].readable &&
+ (i < WM8350_CLOCK_CONTROL_1 || i > WM8350_AIF_TEST)) {
+ ret = wm8350->read_dev(wm8350, i, 2, (char*)&value);
+ if (ret < 0) {
+ printk(KERN_ERR
+ "wm8350: failed to create cache\n");
+ goto out;
+ }
+ wm8350_endian_swap(i, 2, &value);
+ wm8350_mask_read(i, 2, &value);
+ wm8350->reg_cache[i] = value;
+ } else
+ wm8350->reg_cache[i] = wm8350_reg_map[i];
+ }
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_create_cache);
+
+
+static void wm8350_irq_call_worker(struct wm8350 *wm8350, int irq)
+{
+ mutex_lock(&wm8350->work_mutex);
+ if (wm8350->handler[irq])
+ wm8350->handler[irq](wm8350, irq);
+ else {
+ mutex_unlock(&wm8350->work_mutex);
+ printk(KERN_ERR "wm8350: irq %d nobody cared. now masked.\n",
+ irq);
+ wm8350_mask_irq(wm8350, irq);
+ return;
+ }
+ mutex_unlock(&wm8350->work_mutex);
+}
+
+void wm8350_irq_worker(struct work_struct *work)
+{
+ u16 level_one, status1, status2, comp, oc, gpio, uv;
+ struct wm8350 *wm8350 =
+ container_of(work, struct wm8350, work);
+
+ /* read this in 1 block read */
+ /* read 1st level irq sources and then read required 2nd sources */
+ level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS)
+ & ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK);
+ uv = wm8350_reg_read(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS)
+ & ~wm8350_reg_read(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK);
+ oc = wm8350_reg_read(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS)
+ & ~wm8350_reg_read(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK);
+ status1 = wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_1)
+ & ~wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_1_MASK);
+ status2 = wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_2)
+ & ~wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_2_MASK);
+ comp = wm8350_reg_read(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS)
+ & ~wm8350_reg_read(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK);
+ gpio = wm8350_reg_read(wm8350, WM8350_GPIO_INTERRUPT_STATUS)
+ & ~wm8350_reg_read(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK);
+
+ /* over current */
+ if (level_one & WM8350_OC_INT) {
+ if (oc & WM8350_OC_LS_EINT) /* limit switch */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_OC_LS);
+ }
+
+ /* under voltage */
+ if (level_one & WM8350_UV_INT) {
+ if (uv & WM8350_UV_DC1_EINT) /* DCDC1 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC1);
+ if (uv & WM8350_UV_DC2_EINT) /* DCDC2 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC2);
+ if (uv & WM8350_UV_DC3_EINT) /* DCDC3 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC3);
+ if (uv & WM8350_UV_DC4_EINT) /* DCDC4 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC4);
+ if (uv & WM8350_UV_DC5_EINT) /* DCDC5 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC5);
+ if (uv & WM8350_UV_DC6_EINT) /* DCDC6 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC6);
+ if (uv & WM8350_UV_LDO1_EINT) /* LDO1 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO1);
+ if (uv & WM8350_UV_LDO2_EINT) /* LDO2 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO2);
+ if (uv & WM8350_UV_LDO3_EINT) /* LDO3 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO3);
+ if (uv & WM8350_UV_LDO4_EINT) /* LDO4 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO4);
+ }
+
+ /* charger, RTC */
+ if (status1) {
+ if (status1 & WM8350_CHG_BAT_HOT_EINT) /* battery too hot */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_HOT);
+ if (status1 & WM8350_CHG_BAT_COLD_EINT) /* battery too cold */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_COLD);
+ if (status1 & WM8350_CHG_BAT_FAIL_EINT) /* battery fail */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_FAIL);
+ if (status1 & WM8350_CHG_TO_EINT) /* charger timeout */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_TO);
+ if (status1 & WM8350_CHG_END_EINT) /* fast charge current */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_END);
+ if (status1 & WM8350_CHG_START_EINT) /* charging started */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_START);
+ if (status1 & WM8350_CHG_FAST_RDY_EINT) /* fast charge ready */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_FAST_RDY);
+ if (status1 & WM8350_CHG_VBATT_LT_3P9_EINT) /* battery voltage < 3.9 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9);
+ if (status1 & WM8350_CHG_VBATT_LT_3P1_EINT) /* battery voltage < 3.1 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1);
+ if (status1 & WM8350_CHG_VBATT_LT_2P85_EINT) /* battery voltage < 2.85 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85);
+
+ if (status1 & WM8350_RTC_ALM_EINT) /* alarm */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_ALM);
+ if (status1 & WM8350_RTC_SEC_EINT) /* second rollover */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_SEC);
+ if (status1 & WM8350_RTC_PER_EINT) /* periodic */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_PER);
+ }
+
+ /* current sink, system, aux adc */
+ if (status2) {
+ if (status2 & WM8350_CS1_EINT) /* current sink 1 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CS1);
+ if (status2 & WM8350_CS2_EINT) /* current sink 2 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CS2);
+
+ if (status2 & WM8350_SYS_HYST_COMP_FAIL_EINT) /* comp fail */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_HYST_COMP_FAIL);
+ if (status2 & WM8350_SYS_CHIP_GT115_EINT) /* chip > 115 C */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_CHIP_GT115);
+ if (status2 & WM8350_SYS_CHIP_GT140_EINT) /* chip > 140 C */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_CHIP_GT140);
+ if (status2 & WM8350_SYS_WDOG_TO_EINT) /* heartbeat missed */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_WDOG_TO);
+
+ if (status2 & WM8350_AUXADC_DATARDY_EINT) /* data ready */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DATARDY);
+ if (status2 & WM8350_AUXADC_DCOMP4_EINT) /* exceeds comp 4 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP4);
+ if (status2 & WM8350_AUXADC_DCOMP3_EINT) /* exceeds comp 3 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP3);
+ if (status2 & WM8350_AUXADC_DCOMP2_EINT) /* exceeds comp 2 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP2);
+ if (status2 & WM8350_AUXADC_DCOMP1_EINT) /* exceeds comp 1 */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP1);
+
+ if (status2 & WM8350_USB_LIMIT_EINT) /* usb limit */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_USB_LIMIT);
+ }
+
+ /* wake, codec, ext */
+ if (comp) {
+ if (comp & WM8350_WKUP_OFF_STATE_EINT) /* wake from off */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_OFF_STATE);
+ if (comp & WM8350_WKUP_HIB_STATE_EINT) /* wake from hibernate */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_HIB_STATE);
+ if (comp & WM8350_WKUP_CONV_FAULT_EINT) /* wake from fault */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_CONV_FAULT);
+ if (comp & WM8350_WKUP_WDOG_RST_EINT) /* wake from reset */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_WDOG_RST);
+ if (comp & WM8350_WKUP_GP_PWR_ON_EINT) /* power on changed */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_GP_PWR_ON);
+ if (comp & WM8350_WKUP_ONKEY_EINT) /* on key > specified time */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_ONKEY);
+ if (comp & WM8350_WKUP_GP_WAKEUP_EINT) /* wake from GPIO */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_GP_WAKEUP);
+
+ if (comp & WM8350_CODEC_JCK_DET_L_EINT) /* left chn Jack detect */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
+ if (comp & WM8350_CODEC_JCK_DET_R_EINT) /* right chn Jack detect */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+ if (comp & WM8350_CODEC_MICSCD_EINT) /* mic detect */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_MICSCD);
+ if (comp & WM8350_CODEC_MICD_EINT) /* mic short circuit */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_MICD);
+
+ if (comp & WM8350_EXT_USB_FB_EINT) /* usb connect */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_USB_FB);
+ if (comp & WM8350_EXT_WALL_FB_EINT) /* wall adaptor connect */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_WALL_FB);
+ if (comp & WM8350_EXT_BAT_FB_EINT) /* battery insertion */
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_BAT_FB);
+ }
+
+ if (level_one & WM8350_GP_INT) { /* gpio */
+ int i;
+
+ for (i = 0; i < 12; i++) {
+ if (gpio & (1 << i))
+ wm8350_irq_call_worker(wm8350, WM8350_IRQ_GPIO(i));
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(wm8350_irq_worker);
+
+int wm8350_register_irq(struct wm8350 *wm8350, int irq,
+ void (*handler)(struct wm8350 *, int))
+{
+ if (irq < 0 || irq > WM8350_NUM_IRQ || !handler)
+ return -EINVAL;
+
+ if (wm8350->handler[irq])
+ return -EBUSY;
+
+ mutex_lock(&wm8350->work_mutex);
+ wm8350->handler[irq] = handler;
+ mutex_unlock(&wm8350->work_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_register_irq);
+
+int wm8350_free_irq(struct wm8350 *wm8350, int irq)
+{
+ if (irq < 0 || irq > WM8350_NUM_IRQ)
+ return -EINVAL;
+
+ mutex_lock(&wm8350->work_mutex);
+ wm8350->handler[irq] = NULL;
+ mutex_unlock(&wm8350->work_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_free_irq);
+
+int wm8350_mask_irq(struct wm8350 *wm8350, int irq)
+{
+ switch (irq) {
+ case WM8350_IRQ_CHG_BAT_HOT:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_HOT_EINT);
+ case WM8350_IRQ_CHG_BAT_COLD:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_COLD_EINT);
+ case WM8350_IRQ_CHG_BAT_FAIL:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_FAIL_EINT);
+ case WM8350_IRQ_CHG_TO:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_TO_EINT);
+ case WM8350_IRQ_CHG_END:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_END_EINT);
+ case WM8350_IRQ_CHG_START:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_START_EINT);
+ case WM8350_IRQ_CHG_FAST_RDY:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_FAST_RDY_EINT);
+ case WM8350_IRQ_RTC_PER:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_PER_EINT);
+ case WM8350_IRQ_RTC_SEC:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_SEC_EINT);
+ case WM8350_IRQ_RTC_ALM:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_ALM_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_3P9:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_3P9_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_3P1:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_3P1_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_2P85:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_2P85_EINT);
+ case WM8350_IRQ_CS1:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_CS1_EINT);
+ case WM8350_IRQ_CS2:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_CS2_EINT);
+ case WM8350_IRQ_USB_LIMIT:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_USB_LIMIT_EINT);
+ case WM8350_IRQ_AUXADC_DATARDY:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DATARDY_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP4:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP4_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP3:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP3_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP2:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP2_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP1:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP1_EINT);
+ case WM8350_IRQ_SYS_HYST_COMP_FAIL:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_HYST_COMP_FAIL_EINT);
+ case WM8350_IRQ_SYS_CHIP_GT115:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_CHIP_GT115_EINT);
+ case WM8350_IRQ_SYS_CHIP_GT140:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_CHIP_GT140_EINT);
+ case WM8350_IRQ_SYS_WDOG_TO:
+ return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_WDOG_TO_EINT);
+ case WM8350_IRQ_UV_LDO4:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO4_EINT);
+ case WM8350_IRQ_UV_LDO3:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO3_EINT);
+ case WM8350_IRQ_UV_LDO2:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO2_EINT);
+ case WM8350_IRQ_UV_LDO1:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO1_EINT);
+ case WM8350_IRQ_UV_DC6:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC6_EINT);
+ case WM8350_IRQ_UV_DC5:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC5_EINT);
+ case WM8350_IRQ_UV_DC4:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC4_EINT);
+ case WM8350_IRQ_UV_DC3:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC3_EINT);
+ case WM8350_IRQ_UV_DC2:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC2_EINT);
+ case WM8350_IRQ_UV_DC1:
+ return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC1_EINT);
+ case WM8350_IRQ_OC_LS:
+ return wm8350_set_bits(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK,
+ WM8350_IM_OC_LS_EINT);
+ case WM8350_IRQ_EXT_USB_FB:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_USB_FB_EINT);
+ case WM8350_IRQ_EXT_WALL_FB:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_WALL_FB_EINT);
+ case WM8350_IRQ_EXT_BAT_FB:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_BAT_FB_EINT);
+ case WM8350_IRQ_CODEC_JCK_DET_L:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_JCK_DET_L_EINT);
+ case WM8350_IRQ_CODEC_JCK_DET_R:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_JCK_DET_R_EINT);
+ case WM8350_IRQ_CODEC_MICSCD:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_MICSCD_EINT);
+ case WM8350_IRQ_CODEC_MICD:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_MICD_EINT);
+ case WM8350_IRQ_WKUP_OFF_STATE:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_OFF_STATE_EINT);
+ case WM8350_IRQ_WKUP_HIB_STATE:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_HIB_STATE_EINT);
+ case WM8350_IRQ_WKUP_CONV_FAULT:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_CONV_FAULT_EINT);
+ case WM8350_IRQ_WKUP_WDOG_RST:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_OFF_STATE_EINT);
+ case WM8350_IRQ_WKUP_GP_PWR_ON:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_GP_PWR_ON_EINT);
+ case WM8350_IRQ_WKUP_ONKEY:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_ONKEY_EINT);
+ case WM8350_IRQ_WKUP_GP_WAKEUP:
+ return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_GP_WAKEUP_EINT);
+ case WM8350_IRQ_GPIO(0):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP0_EINT);
+ case WM8350_IRQ_GPIO(1):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP1_EINT);
+ case WM8350_IRQ_GPIO(2):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP2_EINT);
+ case WM8350_IRQ_GPIO(3):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP3_EINT);
+ case WM8350_IRQ_GPIO(4):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP4_EINT);
+ case WM8350_IRQ_GPIO(5):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP5_EINT);
+ case WM8350_IRQ_GPIO(6):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP6_EINT);
+ case WM8350_IRQ_GPIO(7):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP7_EINT);
+ case WM8350_IRQ_GPIO(8):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP8_EINT);
+ case WM8350_IRQ_GPIO(9):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP9_EINT);
+ case WM8350_IRQ_GPIO(10):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP10_EINT);
+ case WM8350_IRQ_GPIO(11):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP11_EINT);
+ case WM8350_IRQ_GPIO(12):
+ return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP12_EINT);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_mask_irq);
+
+int wm8350_unmask_irq(struct wm8350 *wm8350, int irq)
+{
+ switch (irq) {
+ case WM8350_IRQ_CHG_BAT_HOT:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_HOT_EINT);
+ case WM8350_IRQ_CHG_BAT_COLD:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_COLD_EINT);
+ case WM8350_IRQ_CHG_BAT_FAIL:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_BAT_FAIL_EINT);
+ case WM8350_IRQ_CHG_TO:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_TO_EINT);
+ case WM8350_IRQ_CHG_END:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_END_EINT);
+ case WM8350_IRQ_CHG_START:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_START_EINT);
+ case WM8350_IRQ_CHG_FAST_RDY:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_FAST_RDY_EINT);
+ case WM8350_IRQ_RTC_PER:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_PER_EINT);
+ case WM8350_IRQ_RTC_SEC:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_SEC_EINT);
+ case WM8350_IRQ_RTC_ALM:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_RTC_ALM_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_3P9:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_3P9_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_3P1:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_3P1_EINT);
+ case WM8350_IRQ_CHG_VBATT_LT_2P85:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK,
+ WM8350_IM_CHG_VBATT_LT_2P85_EINT);
+ case WM8350_IRQ_CS1:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_CS1_EINT);
+ case WM8350_IRQ_CS2:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_CS2_EINT);
+ case WM8350_IRQ_USB_LIMIT:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_USB_LIMIT_EINT);
+ case WM8350_IRQ_AUXADC_DATARDY:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DATARDY_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP4:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP4_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP3:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP3_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP2:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP2_EINT);
+ case WM8350_IRQ_AUXADC_DCOMP1:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_AUXADC_DCOMP1_EINT);
+ case WM8350_IRQ_SYS_HYST_COMP_FAIL:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_HYST_COMP_FAIL_EINT);
+ case WM8350_IRQ_SYS_CHIP_GT115:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_CHIP_GT115_EINT);
+ case WM8350_IRQ_SYS_CHIP_GT140:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_CHIP_GT140_EINT);
+ case WM8350_IRQ_SYS_WDOG_TO:
+ return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK,
+ WM8350_IM_SYS_WDOG_TO_EINT);
+ case WM8350_IRQ_UV_LDO4:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO4_EINT);
+ case WM8350_IRQ_UV_LDO3:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO3_EINT);
+ case WM8350_IRQ_UV_LDO2:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO2_EINT);
+ case WM8350_IRQ_UV_LDO1:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_LDO1_EINT);
+ case WM8350_IRQ_UV_DC6:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC6_EINT);
+ case WM8350_IRQ_UV_DC5:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC5_EINT);
+ case WM8350_IRQ_UV_DC4:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC4_EINT);
+ case WM8350_IRQ_UV_DC3:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC3_EINT);
+ case WM8350_IRQ_UV_DC2:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC2_EINT);
+ case WM8350_IRQ_UV_DC1:
+ return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK,
+ WM8350_IM_UV_DC1_EINT);
+ case WM8350_IRQ_OC_LS:
+ return wm8350_clear_bits(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK,
+ WM8350_IM_OC_LS_EINT);
+ case WM8350_IRQ_EXT_USB_FB:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_USB_FB_EINT);
+ case WM8350_IRQ_EXT_WALL_FB:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_WALL_FB_EINT);
+ case WM8350_IRQ_EXT_BAT_FB:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_EXT_BAT_FB_EINT);
+ case WM8350_IRQ_CODEC_JCK_DET_L:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_JCK_DET_L_EINT);
+ case WM8350_IRQ_CODEC_JCK_DET_R:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_JCK_DET_R_EINT);
+ case WM8350_IRQ_CODEC_MICSCD:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_MICSCD_EINT);
+ case WM8350_IRQ_CODEC_MICD:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_CODEC_MICD_EINT);
+ case WM8350_IRQ_WKUP_OFF_STATE:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_OFF_STATE_EINT);
+ case WM8350_IRQ_WKUP_HIB_STATE:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_HIB_STATE_EINT);
+ case WM8350_IRQ_WKUP_CONV_FAULT:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_CONV_FAULT_EINT);
+ case WM8350_IRQ_WKUP_WDOG_RST:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_OFF_STATE_EINT);
+ case WM8350_IRQ_WKUP_GP_PWR_ON:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_GP_PWR_ON_EINT);
+ case WM8350_IRQ_WKUP_ONKEY:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_ONKEY_EINT);
+ case WM8350_IRQ_WKUP_GP_WAKEUP:
+ return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK,
+ WM8350_IM_WKUP_GP_WAKEUP_EINT);
+ case WM8350_IRQ_GPIO(0):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP0_EINT);
+ case WM8350_IRQ_GPIO(1):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP1_EINT);
+ case WM8350_IRQ_GPIO(2):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP2_EINT);
+ case WM8350_IRQ_GPIO(3):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP3_EINT);
+ case WM8350_IRQ_GPIO(4):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP4_EINT);
+ case WM8350_IRQ_GPIO(5):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP5_EINT);
+ case WM8350_IRQ_GPIO(6):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP6_EINT);
+ case WM8350_IRQ_GPIO(7):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP7_EINT);
+ case WM8350_IRQ_GPIO(8):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP8_EINT);
+ case WM8350_IRQ_GPIO(9):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP9_EINT);
+ case WM8350_IRQ_GPIO(10):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP10_EINT);
+ case WM8350_IRQ_GPIO(11):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP11_EINT);
+ case WM8350_IRQ_GPIO(12):
+ return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK,
+ WM8350_IM_GP12_EINT);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_unmask_irq);
+
+static int gpio_set_dir(struct wm8350 *wm8350, int gpio, int dir)
+{
+ int ret;
+
+ wm8350_reg_unlock(wm8350);
+ if (dir == WM8350_GPIO_DIR_OUT)
+ ret = wm8350_clear_bits(wm8350,
+ WM8350_GPIO_CONFIGURATION_I_O, 1 << gpio);
+ else
+ ret = wm8350_set_bits(wm8350,
+ WM8350_GPIO_CONFIGURATION_I_O, 1 << gpio);
+ wm8350_reg_lock(wm8350);
+ return ret;
+}
+
+static int gpio_set_debounce(struct wm8350 *wm8350, int gpio, int db)
+{
+ if (db == WM8350_GPIO_DEBOUNCE_ON)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_DEBOUNCE, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_DEBOUNCE, 1 << gpio);
+}
+
+static int gpio_set_func(struct wm8350 *wm8350, int gpio, int func)
+{
+ u16 reg;
+
+ wm8350_reg_unlock(wm8350);
+ switch (gpio) {
+ case 0:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1)
+ & ~WM8350_GP0_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1,
+ reg | ((func & 0xf) << 0));
+ break;
+ case 1:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1)
+ & ~WM8350_GP1_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1,
+ reg | ((func & 0xf) << 4));
+ break;
+ case 2:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1)
+ & ~WM8350_GP2_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1,
+ reg | ((func & 0xf) << 8));
+ break;
+ case 3:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1)
+ & ~WM8350_GP3_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1,
+ reg | ((func & 0xf) << 12));
+ break;
+ case 4:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2)
+ & ~WM8350_GP4_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2,
+ reg | ((func & 0xf) << 0));
+ break;
+ case 5:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2)
+ & ~WM8350_GP5_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2,
+ reg | ((func & 0xf) << 4));
+ break;
+ case 6:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2)
+ & ~WM8350_GP6_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2,
+ reg | ((func & 0xf) << 8));
+ break;
+ case 7:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2)
+ & ~WM8350_GP7_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2,
+ reg | ((func & 0xf) << 12));
+ break;
+ case 8:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3)
+ & ~WM8350_GP8_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3,
+ reg | ((func & 0xf) << 0));
+ break;
+ case 9:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3)
+ & ~WM8350_GP9_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3,
+ reg | ((func & 0xf) << 4));
+ break;
+ case 10:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3)
+ & ~WM8350_GP10_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3,
+ reg | ((func & 0xf) << 8));
+ break;
+ case 11:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3)
+ & ~WM8350_GP11_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3,
+ reg | ((func & 0xf) << 12));
+ break;
+ case 12:
+ reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_4)
+ & ~WM8350_GP12_FN_MASK;
+ wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_4,
+ reg | ((func & 0xf) << 0));
+ break;
+ default:
+ wm8350_reg_lock(wm8350);
+ return -EINVAL;
+ }
+
+ wm8350_reg_lock(wm8350);
+ return 0;
+}
+
+int wm8350_gpio_set_status(struct wm8350 *wm8350, int gpio, int status)
+{
+ if (status)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_PIN_STATUS, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_PIN_STATUS, 1 << gpio);
+}
+EXPORT_SYMBOL_GPL(wm8350_gpio_set_status);
+
+int wm8350_gpio_get_status(struct wm8350 *wm8350, int gpio)
+{
+ return (wm8350_reg_read(wm8350, WM8350_GPIO_PIN_STATUS) &
+ (1 << gpio)) ? 1: 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_gpio_get_status);
+
+static int gpio_set_pull_up(struct wm8350 *wm8350, int gpio, int up)
+{
+ if (up)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_PIN_PULL_UP_CONTROL, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_PIN_PULL_UP_CONTROL, 1 << gpio);
+}
+
+static int gpio_set_pull_down(struct wm8350 *wm8350, int gpio, int down)
+{
+ if (down)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_PULL_DOWN_CONTROL, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_PULL_DOWN_CONTROL, 1 << gpio);
+}
+
+static int gpio_set_polarity(struct wm8350 *wm8350, int gpio, int pol)
+{
+ if (pol == WM8350_GPIO_ACTIVE_HIGH)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_PIN_POLARITY_TYPE, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_PIN_POLARITY_TYPE, 1 << gpio);
+}
+
+static int gpio_set_invert(struct wm8350 *wm8350, int gpio, int invert)
+{
+ if (invert == WM8350_GPIO_INVERT_ON)
+ return wm8350_set_bits(wm8350,
+ WM8350_GPIO_INTERRUPT_MODE, 1 << gpio);
+ else
+ return wm8350_clear_bits(wm8350,
+ WM8350_GPIO_INTERRUPT_MODE, 1 << gpio);
+}
+
+int wm8350_gpio_config(struct wm8350 *wm8350, int gpio, int dir, int func,
+ int pol, int pull, int invert, int debounce)
+{
+ /* make sure we never pull up and down at the same time */
+ if (pull == WM8350_GPIO_PULL_NONE) {
+ if (gpio_set_pull_up(wm8350, gpio, 0))
+ goto err;
+ if (gpio_set_pull_down(wm8350, gpio, 0))
+ goto err;
+ } else if (pull == WM8350_GPIO_PULL_UP) {
+ if (gpio_set_pull_down(wm8350, gpio, 0))
+ goto err;
+ if (gpio_set_pull_up(wm8350, gpio, 1))
+ goto err;
+ } else if (pull == WM8350_GPIO_PULL_DOWN) {
+ if (gpio_set_pull_up(wm8350, gpio, 0))
+ goto err;
+ if (gpio_set_pull_down(wm8350, gpio, 1))
+ goto err;
+ }
+
+ if (gpio_set_invert(wm8350, gpio, invert))
+ goto err;
+ if (gpio_set_polarity(wm8350, gpio, pol))
+ goto err;
+ if (gpio_set_debounce(wm8350, gpio, debounce))
+ goto err;
+ if (gpio_set_dir(wm8350, gpio, dir))
+ goto err;
+ return gpio_set_func(wm8350, gpio, func);
+
+err:
+ return -EIO;
+}
+EXPORT_SYMBOL_GPL(wm8350_gpio_config);
+
+int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref)
+{
+ u16 reg, result = 0;
+ int tries = 5;
+
+ if (channel < WM8350_AUXADC_AUX1 || channel > WM8350_AUXADC_TEMP)
+ return -EINVAL;
+ if (channel >= WM8350_AUXADC_USB && channel <= WM8350_AUXADC_TEMP
+ && (scale != 0 || vref != 0))
+ return -EINVAL;
+
+ mutex_lock(&auxadc_mutex);
+
+ /* Turn on the ADC */
+ reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg | WM8350_AUXADC_ENA);
+
+ if (scale || vref) {
+ reg = scale << 13;
+ reg |= vref << 12;
+ wm8350_reg_write(wm8350, WM8350_AUX1_READBACK + channel, reg);
+ }
+
+ reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1);
+ reg |= 1 << channel | WM8350_AUXADC_POLL;
+ wm8350_reg_write(wm8350, WM8350_DIGITISER_CONTROL_1, reg);
+
+ do {
+ schedule_timeout_interruptible(1);
+ reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1);
+ } while (tries-- && (reg & WM8350_AUXADC_POLL));
+
+ if (!tries)
+ printk (KERN_ERR "wm8350: adc chn %d read timeout\n", channel);
+ else
+ result = wm8350_reg_read(wm8350,
+ WM8350_AUX1_READBACK + channel);
+
+ /* Turn off the ADC */
+ reg=wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg & ~WM8350_AUXADC_ENA);
+
+ mutex_unlock(&auxadc_mutex);
+ return result & WM8350_AUXADC_DATA1_MASK;
+}
+EXPORT_SYMBOL_GPL(wm8350_read_auxadc);
+
+static void wm8350_pmic_dev_release(struct device *dev){}
+
+int wm8350_device_register_pmic(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->pmic.dev.bus_id, "wm8350-pmic");
+ wm8350->pmic.dev.bus = &wm8350_bus_type;
+ wm8350->pmic.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->pmic.dev.release = wm8350_pmic_dev_release;
+
+ ret = device_register(&wm8350->pmic.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 PMIC device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_pmic);
+
+static void wm8350_rtc_dev_release(struct device *dev){}
+
+int wm8350_device_register_rtc(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->rtc.dev.bus_id, "wm8350-rtc");
+ wm8350->rtc.dev.bus = &wm8350_bus_type;
+ wm8350->rtc.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->rtc.dev.release = wm8350_rtc_dev_release;
+
+ ret = device_register(&wm8350->rtc.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 RTC device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_rtc);
+
+static void wm8350_led_dev_release(struct device *dev){}
+
+int wm8350_device_register_led(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->led.dev.bus_id, "wm8350-led");
+ wm8350->led.dev.bus = &wm8350_bus_type;
+ wm8350->led.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->led.dev.release = wm8350_led_dev_release;
+
+ ret = device_register(&wm8350->led.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 LED device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_led);
+
+static void wm8350_backlight_dev_release(struct device *dev){}
+
+int wm8350_device_register_backlight(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->backlight.dev.bus_id, "wm8350-bl");
+ wm8350->backlight.dev.bus = &wm8350_bus_type;
+ wm8350->backlight.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->backlight.dev.release = wm8350_backlight_dev_release;
+
+ ret = device_register(&wm8350->backlight.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 backlight device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_backlight);
+
+static void wm8350_wdg_dev_release(struct device *dev){}
+
+int wm8350_device_register_wdg(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->wdg.dev.bus_id, "wm8350-wdt");
+ wm8350->wdg.dev.bus = &wm8350_bus_type;
+ wm8350->wdg.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->wdg.dev.release = wm8350_wdg_dev_release;
+
+ ret = device_register(&wm8350->wdg.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 Watchdog device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_wdg);
+
+static void wm8350_power_dev_release(struct device *dev){}
+
+int wm8350_device_register_power(struct wm8350 *wm8350)
+{
+ int ret;
+
+ strcpy(wm8350->power.dev.bus_id, "wm8350-power");
+ wm8350->power.dev.bus = &wm8350_bus_type;
+ wm8350->power.dev.parent = &wm8350->i2c_client->dev;
+ wm8350->power.dev.release = wm8350_power_dev_release;
+
+ ret = device_register(&wm8350->power.dev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to register WM8350 Power device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm8350_device_register_power);
+
+static int wm8350_bus_match(struct device *dev, struct device_driver *drv)
+{
+ if(!strcmp(dev->bus_id, drv->name))
+ return 1;
+ return 0;
+}
+
+static int wm8350_bus_suspend(struct device *dev, pm_message_t state)
+{
+ int ret = 0;
+
+ if (dev->driver && dev->driver->suspend)
+ ret = dev->driver->suspend(dev, state);
+
+ return ret;
+}
+
+static int wm8350_bus_resume(struct device *dev)
+{
+ int ret = 0;
+
+ if (dev->driver && dev->driver->resume)
+ ret = dev->driver->resume(dev);
+
+ return ret;
+}
+
+struct bus_type wm8350_bus_type = {
+ .name = "wm8350",
+ .match = wm8350_bus_match,
+ .suspend = wm8350_bus_suspend,
+ .resume = wm8350_bus_resume,
+};
+EXPORT_SYMBOL(wm8350_bus_type);
+
+static int __init wm8350_bus_init(void)
+{
+ printk("WM8350 Bus Manager %s\n", WM8350_BUS_VERSION);
+ return bus_register(&wm8350_bus_type);
+}
+subsys_initcall(wm8350_bus_init);
+
+static void __exit wm8350_bus_exit(void)
+{
+ bus_unregister(&wm8350_bus_type);
+}
+
+module_exit(wm8350_bus_exit);
+
+MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("WM8350 PMIC Bus driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmic/pmic-wm8350.c b/drivers/pmic/pmic-wm8350.c
new file mode 100644
index 000000000000..7f9b304e86c9
--- /dev/null
+++ b/drivers/pmic/pmic-wm8350.c
@@ -0,0 +1,813 @@
+/*
+ * wm8350_pmu.c -- Power Managment Driver for Wolfson WM8350 PMIC
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * 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.
+ *
+ * Revision history
+ * 23rd Jan 2007 Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/pmic/wm8350.h>
+#include <linux/pmic.h>
+
+#define WM8350_PMIC_VERSION "0.3"
+
+/* debug */
+#define WM8350_DEBUG 0
+#if WM8350_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+static ssize_t pmic_reg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm8350_pmic *pmic = to_wm8350_pmic_device(dev);
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int i = 0, count = 0;
+
+ for (;i < WM8350_MAX_REGISTER + 1; i++)
+ // count += sprintf(buf, "R%d = 0x%2.2x%2.2x\n", i >> 1, reg[i], reg[i+1]);
+ printk("R%d = 0x%4.4x\n", i, wm8350_reg_read(wm8350,i)); // hack to fix
+
+ return count;
+}
+static DEVICE_ATTR(pmic_reg, 0444, pmic_reg_show, NULL);
+
+/* hundredths of uA, 405 = 4.05 uA */
+static const int isink_cur[] = {
+ 405, 482, 573, 681, 810, 963, 1146, 1362, 1620, 1927, 2291, 2725,
+ 3240, 3853, 4582, 5449, 6480, 7706, 9164, 10898, 12960, 15412, 18328,
+ 21796, 25920, 30824, 36656, 43592, 51840, 61648, 73313, 87184,
+ 103680, 123297, 146626, 174368, 207360, 246594, 293251, 348737,
+ 414720, 493188, 586503, 697473, 829440, 986376, 1173005, 1394946,
+ 1658880, 1972752, 2346011, 2789892, 3317760, 3945504, 4692021,
+ 5579785, 6635520, 7891008, 9384042, 11159570, 13271040, 15782015,
+ 18768085, 22319140,
+};
+
+static int get_isink_val(int huA)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(isink_cur); i >= 0 ; i--) {
+ if (huA > isink_cur[i])
+ return i;
+ }
+ return 0;
+}
+
+int wm8350_isink_set_current(struct wm8350_pmic *pmic, int isink,
+ int huA)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 val;
+
+ switch (isink) {
+ case WM8350_ISINK_A:
+ val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) &
+ ~WM8350_CS1_ISEL_MASK;
+ wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_A, val |
+ get_isink_val(huA));
+ //printk("val %x\n", wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A));
+ break;
+ case WM8350_ISINK_B:
+ val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) &
+ ~WM8350_CS1_ISEL_MASK;
+ wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_B, val |
+ get_isink_val(huA));
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_isink_set_current);
+
+int wm8350_isink_enable(struct wm8350_pmic *pmic, int isink, int enable,
+ int hibernate_enable)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+
+ switch (isink) {
+ case WM8350_ISINK_A:
+ if (enable)
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7,
+ WM8350_CS1_ENA);
+ else {
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7,
+ WM8350_CS1_ENA);
+ }
+ if (hibernate_enable)
+ wm8350_set_bits(wm8350, WM8350_CURRENT_SINK_DRIVER_A,
+ WM8350_CS1_HIB_MODE);
+ else
+ wm8350_clear_bits(wm8350, WM8350_CURRENT_SINK_DRIVER_A,
+ WM8350_CS1_HIB_MODE);
+ break;
+ case WM8350_ISINK_B:
+ if (enable)
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7,
+ WM8350_CS2_ENA);
+ else {
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7,
+ WM8350_CS2_ENA);
+ }
+ if (hibernate_enable)
+ wm8350_set_bits(wm8350, WM8350_CURRENT_SINK_DRIVER_B,
+ WM8350_CS2_HIB_MODE);
+ else
+ wm8350_clear_bits(wm8350, WM8350_CURRENT_SINK_DRIVER_B,
+ WM8350_CS2_HIB_MODE);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_isink_enable);
+
+int wm8350_isink_set_flash(struct wm8350_pmic *pmic, int isink, u16 mode,
+ u16 trigger, u16 duration, u16 on_ramp, u16 off_ramp, u16 drive)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+
+ switch (isink) {
+ case WM8350_ISINK_A:
+ wm8350_reg_write(wm8350, WM8350_CSA_FLASH_CONTROL,
+ (mode ? WM8350_CS1_FLASH_MODE : 0) |
+ (trigger ? WM8350_CS1_TRIGSRC : 0) |
+ duration | on_ramp | off_ramp | drive);
+ break;
+ case WM8350_ISINK_B:
+ wm8350_reg_write(wm8350, WM8350_CSB_FLASH_CONTROL,
+ (mode ? WM8350_CS2_FLASH_MODE : 0) |
+ (trigger ? WM8350_CS2_TRIGSRC : 0) |
+ duration | on_ramp | off_ramp | drive);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_isink_set_flash);
+
+int wm8350_isink_trigger_flash(struct wm8350_pmic *pmic, int isink)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 val;
+
+ switch (isink) {
+ case WM8350_ISINK_A:
+ val = wm8350_reg_read(wm8350, WM8350_CSA_FLASH_CONTROL) &
+ ~WM8350_CS1_DRIVE;
+ wm8350_reg_write(wm8350, WM8350_CSA_FLASH_CONTROL, val |
+ WM8350_CS1_DRIVE);
+ break;
+ case WM8350_ISINK_B:
+ val = wm8350_reg_read(wm8350, WM8350_CSA_FLASH_CONTROL) &
+ ~WM8350_CS2_DRIVE;
+ wm8350_reg_write(wm8350, WM8350_CSA_FLASH_CONTROL, val |
+ WM8350_CS2_DRIVE);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int wm8350_dcdc_set_voltage(struct wm8350_pmic *pmic, int dcdc, int mV)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int volt_reg;
+ u16 val;
+
+ dbg("%s %d mV %d\n", __FUNCTION__, dcdc, mV);
+
+ if (mV < 850 || mV > 4025) {
+ printk(KERN_ERR "wm8350: dcdc %d voltage %d mV out of range\n",
+ dcdc, mV);
+ return -EINVAL;
+ }
+
+ switch (dcdc) {
+ case WM8350_DCDC_1:
+ volt_reg = WM8350_DCDC1_CONTROL;
+ break;
+ case WM8350_DCDC_3:
+ volt_reg = WM8350_DCDC3_CONTROL;
+ break;
+ case WM8350_DCDC_4:
+ volt_reg = WM8350_DCDC4_CONTROL;
+ break;
+ case WM8350_DCDC_6:
+ volt_reg = WM8350_DCDC6_CONTROL;
+ break;
+ case WM8350_DCDC_2:
+ case WM8350_DCDC_5:
+ default:
+ return -EINVAL;
+ }
+
+ /* all DCDC's have same mV bits */
+ val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_DC1_VSEL_MASK;
+ wm8350_reg_write(wm8350, volt_reg, val | WM8350_DC1_VSEL(mV));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc_set_voltage);
+
+int wm8350_dcdc_set_image_voltage(struct wm8350_pmic *pmic, int dcdc, int mV,
+ int mode, int signal)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int volt_reg;
+
+ dbg("%s %d mV %d\n", __FUNCTION__, dcdc, mV);
+
+ if (mV && (mV < 850 || mV > 4025)) {
+ printk(KERN_ERR "wm8350: dcdc %d image voltage %d mV out of range\n",
+ dcdc, mV);
+ return -EINVAL;
+ }
+ if (mV == 0)
+ mV = 850;
+
+ switch (dcdc) {
+ case WM8350_DCDC_1:
+ volt_reg = WM8350_DCDC1_LOW_POWER;
+ break;
+ case WM8350_DCDC_3:
+ volt_reg = WM8350_DCDC3_LOW_POWER;
+ break;
+ case WM8350_DCDC_4:
+ volt_reg = WM8350_DCDC4_LOW_POWER;
+ break;
+ case WM8350_DCDC_6:
+ volt_reg = WM8350_DCDC6_LOW_POWER;
+ break;
+ case WM8350_DCDC_2:
+ case WM8350_DCDC_5:
+ default:
+ return -EINVAL;
+ }
+
+ /* all DCDC's have same mV bits */
+ wm8350_reg_write(wm8350, volt_reg, WM8350_DC1_VSEL(mV) | mode | signal);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc_set_image_voltage);
+
+int wm8350_ldo_set_voltage(struct wm8350_pmic *pmic, int ldo, int mV)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int volt_reg;
+ u16 val;
+
+ dbg("%s %d mV %d\n", __FUNCTION__, ldo, mV);
+
+ if (mV < 900 || mV > 3300) {
+ printk(KERN_ERR "wm8350: ldo %d voltage %d mV out of range\n",
+ ldo, mV);
+ return -EINVAL;
+ }
+
+ switch (ldo) {
+ case WM8350_LDO_1:
+ volt_reg = WM8350_LDO1_CONTROL;
+ break;
+ case WM8350_LDO_2:
+ volt_reg = WM8350_LDO2_CONTROL;
+ break;
+ case WM8350_LDO_3:
+ volt_reg = WM8350_LDO3_CONTROL;
+ break;
+ case WM8350_LDO_4:
+ volt_reg = WM8350_LDO4_CONTROL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* all LDO's have same mV bits */
+ val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_LDO1_VSEL_MASK;
+ wm8350_reg_write(wm8350, volt_reg, val | WM8350_LDO1_VSEL(mV));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_ldo_set_voltage);
+
+int wm8350_ldo_set_image_voltage(struct wm8350_pmic *pmic, int ldo, int mV,
+ int mode, int signal)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int volt_reg;
+
+
+ dbg("%s %d mV %d\n", __FUNCTION__, ldo, mV);
+
+ if (mV < 900 || mV > 3300) {
+ printk(KERN_ERR "wm8350: ldo %d voltage %d mV out of range\n",
+ ldo, mV);
+ return -EINVAL;
+ }
+
+ switch (ldo) {
+ case WM8350_LDO_1:
+ volt_reg = WM8350_LDO1_LOW_POWER;
+ break;
+ case WM8350_LDO_2:
+ volt_reg = WM8350_LDO2_LOW_POWER;
+ break;
+ case WM8350_LDO_3:
+ volt_reg = WM8350_LDO3_LOW_POWER;
+ break;
+ case WM8350_LDO_4:
+ volt_reg = WM8350_LDO4_LOW_POWER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* all LDO's have same mV bits */
+ wm8350_reg_write(wm8350, volt_reg, WM8350_LDO1_VSEL(mV) | mode | signal);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_ldo_set_image_voltage);
+
+int wm8350_dcdc_set_slot(struct wm8350_pmic *pmic, int dcdc, u16 start,
+ u16 stop, u16 fault)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int slot_reg;
+ u16 val;
+
+ dbg("%s %d start %d stop %d\n", __FUNCTION__, dcdc, start, stop);
+
+ /* slot valid ? */
+ if (start > 15 || stop > 15)
+ return -EINVAL;
+
+ switch (dcdc) {
+ case WM8350_DCDC_1:
+ slot_reg = WM8350_DCDC1_TIMEOUTS;
+ break;
+ case WM8350_DCDC_2:
+ slot_reg = WM8350_DCDC2_TIMEOUTS;
+ break;
+ case WM8350_DCDC_3:
+ slot_reg = WM8350_DCDC3_TIMEOUTS;
+ break;
+ case WM8350_DCDC_4:
+ slot_reg = WM8350_DCDC4_TIMEOUTS;
+ break;
+ case WM8350_DCDC_5:
+ slot_reg = WM8350_DCDC5_TIMEOUTS;
+ break;
+ case WM8350_DCDC_6:
+ slot_reg = WM8350_DCDC6_TIMEOUTS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = wm8350_reg_read(wm8350, slot_reg) &
+ ~(WM8350_DC1_ENSLOT_MASK | WM8350_DC1_SDSLOT_MASK |
+ WM8350_DC1_ERRACT_MASK);
+ wm8350_reg_write(wm8350, slot_reg,
+ val | (start << WM8350_DC1_ENSLOT_SHIFT) |
+ (stop << WM8350_DC1_SDSLOT_SHIFT) |
+ (fault << WM8350_DC1_ERRACT_SHIFT));
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc_set_slot);
+
+int wm8350_ldo_set_slot(struct wm8350_pmic *pmic, int ldo, u16 start,
+ u16 stop)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ int slot_reg;
+ u16 val;
+
+ dbg("%s %d start %d stop %d\n", __FUNCTION__, ldo, start, stop);
+
+ /* slot valid ? */
+ if (start > 15 || stop > 15)
+ return -EINVAL;
+
+ switch (ldo) {
+ case WM8350_LDO_1:
+ slot_reg = WM8350_LDO1_TIMEOUTS;
+ break;
+ case WM8350_LDO_2:
+ slot_reg = WM8350_LDO2_TIMEOUTS;
+ break;
+ case WM8350_LDO_3:
+ slot_reg = WM8350_LDO3_TIMEOUTS;
+ break;
+ case WM8350_LDO_4:
+ slot_reg = WM8350_LDO4_TIMEOUTS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = wm8350_reg_read(wm8350, slot_reg) & ~WM8350_LDO1_SDSLOT_MASK;
+ wm8350_reg_write(wm8350, slot_reg, val | ((start << 10) | (stop << 6)));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_ldo_set_slot);
+
+int wm8350_dcdc_set_mode(struct wm8350_pmic *pmic, int dcdc, u16 active,
+ u16 sleep)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 shift;
+
+ if (dcdc == WM8350_DCDC_2 || dcdc == WM8350_DCDC_5)
+ return -EINVAL;
+
+ dbg("%s %d mode: %s %s\n", __FUNCTION__, dcdc, active ? "on" : "off",
+ sleep ? "on" : "off");
+
+ shift = dcdc - WM8350_DCDC_1;
+
+ if (active)
+ wm8350_set_bits(wm8350, WM8350_DCDC_ACTIVE_OPTIONS, 1 << shift);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DCDC_ACTIVE_OPTIONS, 1 << shift);
+
+ if (sleep)
+ wm8350_set_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, 1 << shift);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, 1 << shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc_set_mode);
+
+int wm8350_dcdc25_set_mode(struct wm8350_pmic *pmic, int dcdc, u16 mode,
+ u16 ilim, u16 ramp, u16 feedback)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 val;
+
+ dbg("%s %d mode: %s %s\n", __FUNCTION__, dcdc, mode ? "normal" : "boost",
+ ilim ? "low" : "normal");
+
+ switch (dcdc) {
+ case WM8350_DCDC_2:
+ val = wm8350_reg_read(wm8350, WM8350_DCDC2_CONTROL)
+ & ~(WM8350_DC2_MODE_MASK | WM8350_DC2_ILIM_MASK |
+ WM8350_DC2_RMP_MASK | WM8350_DC2_FBSRC_MASK);
+ wm8350_reg_write(wm8350, WM8350_DCDC2_CONTROL, val |
+ (mode << WM8350_DC2_MODE_SHIFT) |
+ (ilim << WM8350_DC2_ILIM_SHIFT) |
+ (ramp << WM8350_DC2_RMP_SHIFT) |
+ (feedback << WM8350_DC2_FBSRC_SHIFT));
+ break;
+ case WM8350_DCDC_5:
+ val = wm8350_reg_read(wm8350, WM8350_DCDC5_CONTROL)
+ & ~(WM8350_DC5_MODE_MASK | WM8350_DC5_ILIM_MASK |
+ WM8350_DC5_RMP_MASK | WM8350_DC5_FBSRC_MASK);
+ wm8350_reg_write(wm8350, WM8350_DCDC5_CONTROL, val |
+ (mode << WM8350_DC5_MODE_SHIFT) |
+ (ilim << WM8350_DC5_ILIM_SHIFT) |
+ (ramp << WM8350_DC5_RMP_SHIFT) |
+ (feedback << WM8350_DC5_FBSRC_SHIFT));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc25_set_mode);
+
+int wm8350_dcdc_enable(struct wm8350_pmic *pmic, int dcdc, int enable)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 shift;
+
+ dbg("%s %d --> %s\n", __FUNCTION__, dcdc, enable ? "on" : "off");
+
+ if (dcdc < WM8350_DCDC_1 || dcdc > WM8350_DCDC_6)
+ return -EINVAL;
+
+ shift = dcdc - WM8350_DCDC_1;
+
+ if (enable)
+ wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, 1 << shift);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, 1 << shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_dcdc_enable);
+
+int wm8350_ldo_enable(struct wm8350_pmic *pmic, int ldo, int enable)
+{
+ struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic);
+ u16 shift;
+
+ dbg("%s %d --> %s\n", __FUNCTION__, ldo, enable ? "on" : "off");
+
+ if (ldo < WM8350_LDO_1 || ldo > WM8350_LDO_4)
+ return -EINVAL;
+
+ shift = (ldo - WM8350_LDO_1) + 8;
+
+ if (enable)
+ wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, 1 << shift);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, 1 << shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_ldo_enable);
+
+static int set_voltage (struct pm_regulator *reg, int mV, int Vid)
+{
+ struct wm8350_pmic *pmic = (struct wm8350_pmic*)reg->private;
+ int ret = 0;
+
+ switch (reg->id) {
+ case WM8350_DCDC_1:
+ case WM8350_DCDC_2:
+ case WM8350_DCDC_3:
+ case WM8350_DCDC_4:
+ case WM8350_DCDC_5:
+ case WM8350_DCDC_6:
+ ret = wm8350_dcdc_set_voltage(pmic, reg->id, mV);
+ break;
+ case WM8350_LDO_1:
+ case WM8350_LDO_2:
+ case WM8350_LDO_3:
+ case WM8350_LDO_4:
+ ret = wm8350_ldo_set_voltage(pmic, reg->id, mV);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int enable (struct pm_regulator *reg, int enable)
+{
+ struct wm8350_pmic *pmic = (struct wm8350_pmic*)reg->private;
+ int ret = 0;
+
+ switch (reg->id) {
+ case WM8350_DCDC_1:
+ case WM8350_DCDC_2:
+ case WM8350_DCDC_3:
+ case WM8350_DCDC_4:
+ case WM8350_DCDC_5:
+ case WM8350_DCDC_6:
+ ret = wm8350_dcdc_enable(pmic, reg->id, enable);
+ break;
+ case WM8350_LDO_1:
+ case WM8350_LDO_2:
+ case WM8350_LDO_3:
+ case WM8350_LDO_4:
+ ret = wm8350_ldo_enable(pmic, reg->id, enable);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+struct pm_regulator_ops wm8350_pmic_ops = {
+ .set_voltage = set_voltage,
+ .enable = enable,
+};
+
+struct pm_regulator wm8350_dcdc1 = {
+ .name = "DCDC1",
+ .id = WM8350_DCDC_1,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 850,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_dcdc6 = {
+ .name = "DCDC6",
+ .id = WM8350_DCDC_6,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 850,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_dcdc3 = {
+ .name = "DCDC3",
+ .id = WM8350_DCDC_3,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 850,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_dcdc4 = {
+ .name = "DCDC4",
+ .id = WM8350_DCDC_4,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 850,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_dcdc2 = {
+ .name = "DCDC2",
+ .id = WM8350_DCDC_2,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 5000,
+ .dmax = 20000,
+ },
+ .mV_control = PM_CONTROL_NONE,
+ .mA_range = {
+ .dmin = 0,
+ .dmax = 224,
+ },
+ .mA_control = PM_CONTROL_DYNAMIC,
+};
+
+struct pm_regulator wm8350_dcdc5 = {
+ .name = "DCDC5",
+ .id = WM8350_DCDC_5,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 5000,
+ .dmax = 20000,
+ },
+ .mV_control = PM_CONTROL_NONE,
+ .mA_range = {
+ .dmin = 0,
+ .dmax = 224,
+ },
+ .mA_control = PM_CONTROL_DYNAMIC,
+};
+
+struct pm_regulator wm8350_ldo1 = {
+ .name = "LDO1",
+ .id = WM8350_LDO_1,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 900,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_ldo2 = {
+ .name = "LDO2",
+ .id = WM8350_LDO_2,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 900,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_ldo3 = {
+ .name = "LDO3",
+ .id = WM8350_LDO_3,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 900,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+struct pm_regulator wm8350_ldo4 = {
+ .name = "LDO4",
+ .id = WM8350_LDO_4,
+ .ops = &wm8350_pmic_ops,
+ .mV_range = {
+ .dmin = 900,
+ .dmax = 3400,
+ },
+ .mV_control = PM_CONTROL_DYNAMIC,
+ .mA_control = PM_CONTROL_NONE,
+};
+
+
+static int wm8350_probe(struct device *dev)
+{
+ int err;
+
+ printk(KERN_INFO "WM8350 PMIC driver version %s\n", WM8350_PMIC_VERSION);
+ err = device_create_file(dev, &dev_attr_pmic_reg);
+ if (err < 0)
+ printk(KERN_WARNING "asoc: failed to add pmic sysfs entries\n");
+ pm_register_regulator(&wm8350_dcdc1, dev);
+ pm_register_regulator(&wm8350_dcdc2, dev);
+ pm_register_regulator(&wm8350_dcdc3, dev);
+ pm_register_regulator(&wm8350_dcdc4, dev);
+ pm_register_regulator(&wm8350_dcdc5, dev);
+ pm_register_regulator(&wm8350_dcdc6, dev);
+ pm_register_regulator(&wm8350_ldo1, dev);
+ pm_register_regulator(&wm8350_ldo2, dev);
+ pm_register_regulator(&wm8350_ldo3, dev);
+ pm_register_regulator(&wm8350_ldo4, dev);
+ return 0;
+}
+
+static int wm8350_remove (struct device *dev)
+{
+ pm_unregister_regulator(&wm8350_ldo4);
+ pm_unregister_regulator(&wm8350_ldo3);
+ pm_unregister_regulator(&wm8350_ldo2);
+ pm_unregister_regulator(&wm8350_ldo1);
+ pm_unregister_regulator(&wm8350_dcdc6);
+ pm_unregister_regulator(&wm8350_dcdc5);
+ pm_unregister_regulator(&wm8350_dcdc4);
+ pm_unregister_regulator(&wm8350_dcdc3);
+ pm_unregister_regulator(&wm8350_dcdc2);
+ pm_unregister_regulator(&wm8350_dcdc1);
+ device_remove_file(dev, &dev_attr_pmic_reg);
+ return 0;
+}
+
+static void wm8350_shutdown (struct device *dev)
+{
+}
+
+static int wm8350_suspend (struct device *dev, pm_message_t state)
+{
+ return 0;
+}
+
+static int wm8350_resume (struct device *dev)
+{
+ return 0;
+}
+
+struct device_driver wm8350_driver = {
+ .name = "wm8350-pmic",
+ .bus = &wm8350_bus_type,
+ .owner = THIS_MODULE,
+ .probe = wm8350_probe,
+ .remove = wm8350_remove,
+ .shutdown = wm8350_shutdown,
+ .suspend = wm8350_suspend,
+ .resume = wm8350_resume,
+};
+
+static int __devinit wm8350_pmu_init(void)
+{
+ return driver_register(&wm8350_driver);
+}
+
+static void wm8350_pmu_exit(void)
+{
+ driver_unregister(&wm8350_driver);
+}
+
+module_init(wm8350_pmu_init);
+module_exit(wm8350_pmu_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("WM8350 PMIC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmic/pmic.c b/drivers/pmic/pmic.c
new file mode 100644
index 000000000000..4b6c0158fcc1
--- /dev/null
+++ b/drivers/pmic/pmic.c
@@ -0,0 +1,509 @@
+/*
+ * pmic.c -- Power Management IC for SoC support.
+ *
+ * Copyright (C) 2007 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lg@opensource.wolfsonmicro.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/pmic.h>
+#include <linux/string.h>
+
+static DEFINE_MUTEX(list_mutex);
+static DEFINE_MUTEX(regulator_mutex);
+static LIST_HEAD(regulator_list);
+static LIST_HEAD(circuit_list);
+static LIST_HEAD(device_list);
+static LIST_HEAD(cpu_list);
+
+struct _pm_device {
+ struct device *dev;
+ enum pm_load load;
+ char *name;
+ struct list_head list;
+};
+
+struct _pm_cpu {
+ int cpu;
+ struct list_head list;
+};
+
+struct _pm_load_str {
+ enum pm_load load;
+ const char* str;
+};
+
+static const struct _pm_load_str sysfs_loads[] = {
+ {PM_LOAD_CPU, "cpu"},
+ {PM_LOAD_MEM, "memory"},
+ {PM_LOAD_SYS_IO, "io"},
+ {PM_LOAD_NAND, "nand"},
+ {PM_LOAD_NOR, "nor"},
+ {PM_LOAD_EEPROM, "eeprom"},
+ {PM_LOAD_LCD, "lcd"},
+ {PM_LOAD_GPU, "gpu"},
+ {PM_LOAD_TV, "tv"},
+ {PM_LOAD_BACKLIGHT, "backlight"},
+ {PM_LOAD_AUDIO, "audio"},
+ {PM_LOAD_DISK, "disk"},
+ {PM_LOAD_MMC, "mmc"},
+ {PM_LOAD_MSTICK, "memory_stick"},
+ {PM_LOAD_PCMCIA, "pcmcia"},
+ {PM_LOAD_SIM, "sim"},
+ {PM_LOAD_GSM, "gsm"},
+ {PM_LOAD_VIBRATOR, "vibrator"},
+ {PM_LOAD_ETH, "ethernet"},
+ {PM_LOAD_WIFI, "wifi"},
+ {PM_LOAD_BT, "bluetooth"},
+ {PM_LOAD_USB, "usb"},
+ {PM_LOAD_SERIAL, "serial"},
+ {PM_LOAD_IRDA, "irda"},
+ {PM_LOAD_BATTERY, "battery"},
+ {PM_LOAD_GPS, "gps"},
+ {PM_LOAD_CAMERA, "camera"},
+ {PM_LOAD_LED, "led"},
+ {PM_LOAD_TOUCH, "touchscreen"},
+};
+
+/* is the voltage valid for regulator & circuit */
+static inline int mV_valid(struct pm_regulator *r, int mV)
+{
+ struct pm_circuit *c = r->circuit;
+
+ if (mV < c->mV_range.dmin || mV > c->mV_range.dmax)
+ return 0;
+ if (mV < r->mV_range.dmin || mV > r->mV_range.dmax)
+ return 0;
+ return 1;
+}
+
+/* is the current limit valid for regulator & circuit */
+static inline int mA_valid(struct pm_regulator *r, int mA)
+{
+ struct pm_circuit *c = r->circuit;
+
+ if (mA < c->mA_range.dmin || mA > c->mA_range.dmax)
+ return 0;
+ if (mA < r->mA_range.dmin || mA > r->mA_range.dmax)
+ return 0;
+ return 1;
+}
+
+/* get a pm device from device */
+static struct _pm_device *get_pmd(struct device *dev)
+{
+ struct _pm_device *pmd;
+
+ mutex_lock(&list_mutex);
+ list_for_each_entry(pmd, &device_list, list) {
+ if (dev == pmd->dev) {
+ mutex_unlock(&list_mutex);
+ return pmd;
+ }
+ }
+ mutex_unlock(&list_mutex);
+ return NULL;
+}
+
+/* find the circuit that this regulator supplies */
+static struct pm_circuit *find_regulator_circuit(struct pm_regulator *r)
+{
+ struct pm_circuit *c;
+
+ list_for_each_entry(c, &circuit_list, list) {
+ if (c->regulator_id == r->id)
+ return c;
+ }
+ return NULL;
+}
+
+static struct pm_circuit *get_cpu_circuit(int cpu)
+{
+ struct _pm_cpu *pmc;
+ struct pm_circuit *c;
+
+ /* is cpu valid ? */
+ list_for_each_entry(pmc, &cpu_list, list) {
+ if (cpu == pmc->cpu) {
+ list_for_each_entry(c, &circuit_list, list) {
+ if (c->load & PM_LOAD_CPU)
+ return c;
+ }
+ }
+ }
+ return NULL;
+}
+
+static inline int regulator_enable(struct pm_regulator *r, int enable)
+{
+ if (!r->ops->enable)
+ return -EINVAL;
+ return r->ops->enable(r, enable);
+}
+
+static inline int regulator_voltage(struct pm_regulator *r, int mV, int Vid)
+{
+ if (!mV_valid(r, mV))
+ return -EINVAL;
+ if (!r->ops->set_voltage)
+ return -EINVAL;
+ return r->ops->set_voltage(r, mV, Vid);
+}
+
+static inline int regulator_current(struct pm_regulator *r, int mA, int Aid)
+{
+ if (!mA_valid(r, mA))
+ return -EINVAL;
+ if (!r->ops->set_current)
+ return -EINVAL;
+ return r->ops->set_current(r, mA, Aid);
+}
+
+
+static int request_power(struct _pm_device *pmd)
+{
+ struct pm_regulator *r;
+ int ret = 0;
+
+ list_for_each_entry(r, &regulator_list, list) {
+ if (r->circuit->load & pmd->load) {
+ mutex_lock(&regulator_mutex);
+ if (r->use_count == 0)
+ ret = regulator_enable(r, 1);
+ if (ret < 0) {
+ mutex_unlock(&regulator_mutex);
+ break;
+ } else
+ r->use_count++;
+ mutex_unlock(&regulator_mutex);
+ }
+ }
+ return ret;
+}
+
+static int release_power(struct _pm_device *pmd)
+{
+ struct pm_regulator *r;
+ int ret = 0;
+
+ list_for_each_entry(r, &regulator_list, list) {
+ if (r->circuit->load & pmd->load) {
+ mutex_lock(&regulator_mutex);
+ r->use_count--;
+ if (r->use_count == 0)
+ ret = regulator_enable(r, 0);
+ if (ret < 0)
+ printk("%s : can't disable regulator %s\n",
+ __func__, r->name);
+ mutex_unlock(&regulator_mutex);
+ }
+ }
+ return ret;
+}
+
+static int request_voltage(struct _pm_device *pmd, int mV)
+{
+ struct pm_regulator *r;
+ int ret = 0;
+
+ list_for_each_entry(r, &regulator_list, list) {
+ if (r->circuit->load & pmd->load) {
+ mutex_lock(&regulator_mutex);
+ ret = regulator_voltage(r, mV, 0);
+ mutex_unlock(&regulator_mutex);
+ if (ret < 0)
+ printk(KERN_ERR "%s : failed to set regulator"
+ "%d voltage to %d mV\n", __func__,
+ r->id, mV);
+ }
+ }
+ return ret;
+}
+
+static int request_current(struct _pm_device *pmd, int mA)
+{
+ struct pm_regulator *r;
+ int ret = 0;
+
+ list_for_each_entry(r, &regulator_list, list) {
+ if (r->circuit->load & pmd->load) {
+ mutex_lock(&regulator_mutex);
+ ret = regulator_current(r, mA, 0);
+ mutex_unlock(&regulator_mutex);
+ if (ret < 0)
+ printk(KERN_ERR "%s : failed to set regulator"
+ "%d current limit to %d mA\n",
+ __func__, r->id, mA);
+ }
+ }
+ return ret;
+}
+
+int pm_regulator_set_voltage(int id, int mV, int Vid)
+{
+ struct pm_regulator *r;
+ int ret = -EINVAL;
+
+ mutex_lock(&list_mutex);
+ list_for_each_entry(r, &regulator_list, list) {
+ if (id == r->id) {
+ mutex_lock(&regulator_mutex);
+ ret = regulator_voltage(r, mV, Vid);
+ mutex_unlock(&regulator_mutex);
+ goto out;;
+ }
+ }
+out:
+ mutex_unlock(&list_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pm_regulator_set_voltage);
+
+/*
+ * PMIC driver API
+ */
+int pm_register_regulator(struct pm_regulator *reg, void *data)
+{
+ struct pm_circuit *c;
+ int ret = 0;
+
+ /* some ops are mandatory */
+ if (reg->ops == NULL)
+ return -EINVAL;
+
+ mutex_lock(&list_mutex);
+
+ /* find the circuit we supply power to */
+ c = find_regulator_circuit(reg);
+ if (c != NULL) {
+ reg->circuit = c;
+ reg->private = data;
+ c->regulator = reg;
+
+ /* are we already in use */
+ if (c->boot_time == PM_BOOT_POWER_ON)
+ reg->use_count = 1;
+
+ printk(KERN_INFO "%s: added %s \n", __func__, reg->name);
+ } else
+ /* no circuit */
+ printk(KERN_INFO "%s: no circuit for %s\n", __func__,
+ reg->name);
+
+ /* add circuit to list */
+ INIT_LIST_HEAD(&reg->list);
+ list_add(&reg->list, &regulator_list);
+ mutex_unlock(&list_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pm_register_regulator);
+
+void pm_unregister_regulator(struct pm_regulator *reg)
+{
+ mutex_lock(&list_mutex);
+ list_del(&reg->list);
+ mutex_unlock(&list_mutex);
+}
+EXPORT_SYMBOL_GPL(pm_unregister_regulator);
+
+/*
+ * SoC Machine power API
+ */
+int pm_register_circuit(struct pm_circuit *circuit)
+{
+ if (circuit->load == 0)
+ return -EINVAL;
+
+ mutex_lock(&list_mutex);
+ INIT_LIST_HEAD(&circuit->list);
+ list_add(&circuit->list, &circuit_list);
+ mutex_unlock(&list_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm_register_circuit);
+
+void pm_unregister_circuit(struct pm_circuit *circuit)
+{
+ mutex_lock(&list_mutex);
+ list_del(&circuit->list);
+ mutex_unlock(&list_mutex);
+}
+EXPORT_SYMBOL_GPL(pm_unregister_circuit);
+
+/*
+ * General device driver SoC pm API
+ */
+/* load registration */
+int pm_register_load(struct device *dev, enum pm_load load, char *id)
+{
+ struct _pm_device *pmd;
+
+ pmd = kzalloc(sizeof(struct _pm_device), GFP_KERNEL);
+ if (pmd == NULL)
+ return -ENOMEM;
+
+ pmd->name = kstrdup(id, GFP_KERNEL);
+ if (pmd->name == NULL) {
+ kfree(pmd);
+ return -ENOMEM;
+ }
+
+ pmd->load = load;
+ INIT_LIST_HEAD(&pmd->list);
+ mutex_lock(&list_mutex);
+ list_add(&pmd->list, &device_list);
+ mutex_unlock(&list_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm_register_load);
+
+void pm_unregister_load(struct device *dev)
+{
+ struct _pm_device *pmd;
+
+ pmd = get_pmd(dev);
+ if (pmd) {
+ mutex_lock(&list_mutex);
+ list_del(&pmd->list);
+ kfree(pmd->name);
+ kfree(pmd);
+ mutex_unlock(&list_mutex);
+ }
+}
+EXPORT_SYMBOL_GPL(pm_unregister_load);
+
+
+/* power request/release for fixed voltage loads
+ * (mV set by machine specific code in arch/cpu/platform/machine.c ) */
+int pm_request_power(struct device *dev)
+{
+ struct _pm_device *pmd;
+
+ pmd = get_pmd(dev);
+ if (pmd == NULL) {
+ printk(KERN_ERR "%s : device not registered\n", __func__);
+ return -EINVAL;
+ } else
+ return request_power(pmd);
+}
+EXPORT_SYMBOL_GPL(pm_request_power);
+
+int pm_release_power(struct device *dev)
+{
+ struct _pm_device *pmd;
+
+ pmd = get_pmd(dev);
+ if (pmd == NULL) {
+ printk(KERN_ERR "%s : device not registered\n", __func__);
+ return -EINVAL;
+ } else
+ return release_power(pmd);
+}
+EXPORT_SYMBOL_GPL(pm_release_power);
+
+/* power request for variable voltage loads */
+int pm_request_voltage(struct device *dev, int mV)
+{
+ struct _pm_device *pmd;
+
+ pmd = get_pmd(dev);
+ if (pmd == NULL) {
+ printk(KERN_ERR "%s : device not registered\n", __func__);
+ return -EINVAL;
+ } else
+ return request_voltage(pmd, mV);
+}
+EXPORT_SYMBOL_GPL(pm_request_voltage);
+
+/* power request for variable voltage loads */
+int pm_request_current(struct device *dev, int mA)
+{
+ struct _pm_device *pmd;
+
+ pmd = get_pmd(dev);
+ if (pmd == NULL) {
+ printk(KERN_ERR "%s : device not registered\n", __func__);
+ return -EINVAL;
+ } else
+ return request_current(pmd, mA);
+}
+EXPORT_SYMBOL_GPL(pm_request_current);
+
+/*
+ * CPUFREQ Client
+ * SoC PMIC registration and voltage scaling for CPUFREQ based drivers.
+ */
+int pm_register_cpu_load(int cpu)
+{
+ struct _pm_cpu *pmc;
+
+ pmc = kzalloc(sizeof(struct _pm_cpu), GFP_KERNEL);
+ if (pmc == NULL)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&pmc->list);
+ mutex_lock(&list_mutex);
+ list_add(&pmc->list, &cpu_list);
+ mutex_unlock(&list_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm_register_cpu_load);
+
+void pm_unregister_cpu_load(int cpu)
+{
+ struct _pm_cpu *pmc;
+
+ mutex_lock(&list_mutex);
+ list_for_each_entry(pmc, &cpu_list, list) {
+ if (cpu == pmc->cpu) {
+ kfree(pmc);
+ goto out;
+ }
+ }
+
+out:
+ mutex_unlock(&list_mutex);
+}
+EXPORT_SYMBOL_GPL(pm_unregister_cpu_load);
+
+int pm_request_cpu_voltage(int cpu, int mV)
+{
+ struct pm_circuit *c;
+ int ret = 0;
+
+ mutex_lock(&list_mutex);
+
+ c = get_cpu_circuit(cpu);
+ if (c == NULL) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (c->regulator) {
+ struct pm_regulator *r = c->regulator;
+
+ if (!mV_valid(r, mV)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (r->ops->set_voltage) {
+ ret = r->ops->set_voltage(r, mV, 0);
+ goto out;
+ }
+ }
+
+out:
+ mutex_unlock(&list_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pm_request_cpu_voltage);