diff options
author | Laura Lawrence <Laura.Lawrence@freescale.com> | 2008-01-22 15:30:13 -0600 |
---|---|---|
committer | Daniel Schaeffer <daniel.schaeffer@timesys.com> | 2008-08-25 15:20:35 -0400 |
commit | 1d42b93db5289ea18739fa4c0fb704f95a1ac3ad (patch) | |
tree | f113434770311c4f92c0fa94c2fe4b0e3f9186c6 /drivers | |
parent | d85fc70a64153e118bb704a411044dfe9a4e4f8e (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/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/pmic/Kconfig | 46 | ||||
-rw-r--r-- | drivers/pmic/Makefile | 13 | ||||
-rw-r--r-- | drivers/pmic/pmic-wm8350-bus.c | 1529 | ||||
-rw-r--r-- | drivers/pmic/pmic-wm8350.c | 813 | ||||
-rw-r--r-- | drivers/pmic/pmic.c | 509 |
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, ®, 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, ®ulator_list, list) { + if (r->circuit->load & pmd->load) { + mutex_lock(®ulator_mutex); + if (r->use_count == 0) + ret = regulator_enable(r, 1); + if (ret < 0) { + mutex_unlock(®ulator_mutex); + break; + } else + r->use_count++; + mutex_unlock(®ulator_mutex); + } + } + return ret; +} + +static int release_power(struct _pm_device *pmd) +{ + struct pm_regulator *r; + int ret = 0; + + list_for_each_entry(r, ®ulator_list, list) { + if (r->circuit->load & pmd->load) { + mutex_lock(®ulator_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(®ulator_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, ®ulator_list, list) { + if (r->circuit->load & pmd->load) { + mutex_lock(®ulator_mutex); + ret = regulator_voltage(r, mV, 0); + mutex_unlock(®ulator_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, ®ulator_list, list) { + if (r->circuit->load & pmd->load) { + mutex_lock(®ulator_mutex); + ret = regulator_current(r, mA, 0); + mutex_unlock(®ulator_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, ®ulator_list, list) { + if (id == r->id) { + mutex_lock(®ulator_mutex); + ret = regulator_voltage(r, mV, Vid); + mutex_unlock(®ulator_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(®->list); + list_add(®->list, ®ulator_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(®->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); |