From dfc3aa7221f50bf3d05c67b826414ab290b95c46 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 23 Jun 2009 10:48:36 +0200 Subject: mfd: fix ab3100 warning on x86_64 The file_operations write prototype should return a ssize_t. Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 13e7d7b..8ff10cb 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -465,14 +465,14 @@ static int ab3100_get_set_reg_open_file(struct inode *inode, struct file *file) return 0; } -static int ab3100_get_set_reg(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) +static ssize_t ab3100_get_set_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { struct ab3100_get_set_reg_priv *priv = file->private_data; struct ab3100 *ab3100 = priv->ab3100; char buf[32]; - int buf_size; + ssize_t buf_size; int regp; unsigned long user_reg; int err; -- cgit v0.10.2 From 9f7b07d6cc3ed14783c9427a5b2a69794eb2de64 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Tue, 23 Jun 2009 12:32:11 -0300 Subject: mfd: Introduce irq_to_pcap() Export an irq_to_pcap function to get pcap irq number, for the keypad driver. Signed-off-by: Daniel Ribeiro Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index c1de4af..de7e637 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -107,10 +107,11 @@ int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value) EXPORT_SYMBOL_GPL(ezx_pcap_read); /* IRQ */ -static inline unsigned int irq2pcap(struct pcap_chip *pcap, int irq) +int irq_to_pcap(struct pcap_chip *pcap, int irq) { - return 1 << (irq - pcap->irq_base); + return irq - pcap->irq_base; } +EXPORT_SYMBOL_GPL(irq_to_pcap); int pcap_to_irq(struct pcap_chip *pcap, int irq) { @@ -122,7 +123,7 @@ static void pcap_mask_irq(unsigned int irq) { struct pcap_chip *pcap = get_irq_chip_data(irq); - pcap->msr |= irq2pcap(pcap, irq); + pcap->msr |= 1 << irq_to_pcap(pcap, irq); queue_work(pcap->workqueue, &pcap->msr_work); } @@ -130,7 +131,7 @@ static void pcap_unmask_irq(unsigned int irq) { struct pcap_chip *pcap = get_irq_chip_data(irq); - pcap->msr &= ~irq2pcap(pcap, irq); + pcap->msr &= ~(1 << irq_to_pcap(pcap, irq)); queue_work(pcap->workqueue, &pcap->msr_work); } diff --git a/include/linux/mfd/ezx-pcap.h b/include/linux/mfd/ezx-pcap.h index c12c3c0..6296b49 100644 --- a/include/linux/mfd/ezx-pcap.h +++ b/include/linux/mfd/ezx-pcap.h @@ -26,6 +26,7 @@ struct pcap_chip; int ezx_pcap_write(struct pcap_chip *, u8, u32); int ezx_pcap_read(struct pcap_chip *, u8, u32 *); int pcap_to_irq(struct pcap_chip *, int); +int irq_to_pcap(struct pcap_chip *, int); int pcap_adc_async(struct pcap_chip *, u8, u32, u8[], void *, void *); int pcap_adc_sync(struct pcap_chip *, u8, u32, u8[], u16[]); -- cgit v0.10.2 From ecd78cbdb989fd593bf4fd69cdb572200e70a553 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Tue, 23 Jun 2009 12:33:10 -0300 Subject: mfd: add set_ts_bits for pcap Some TS controller bits are on the same register as the ADC control, save TS specific bits and export a set_ts_bits function so the TS driver can set it with the adc_mutex lock held. Signed-off-by: Daniel Ribeiro Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index de7e637..c512202 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -195,6 +195,19 @@ static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) } /* ADC */ +void pcap_set_ts_bits(struct pcap_chip *pcap, u32 bits) +{ + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= bits & (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); +} +EXPORT_SYMBOL_GPL(pcap_set_ts_bits); + static void pcap_disable_adc(struct pcap_chip *pcap) { u32 tmp; @@ -217,15 +230,16 @@ static void pcap_adc_trigger(struct pcap_chip *pcap) mutex_unlock(&pcap->adc_mutex); return; } - mutex_unlock(&pcap->adc_mutex); - - /* start conversion on requested bank */ - tmp = pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; + /* start conversion on requested bank, save TS_M bits */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; if (pcap->adc_queue[head]->bank == PCAP_ADC_BANK_1) tmp |= PCAP_ADC_AD_SEL1; ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); ezx_pcap_write(pcap, PCAP_REG_ADR, PCAP_ADR_ASC); } diff --git a/include/linux/mfd/ezx-pcap.h b/include/linux/mfd/ezx-pcap.h index 6296b49..b15caac 100644 --- a/include/linux/mfd/ezx-pcap.h +++ b/include/linux/mfd/ezx-pcap.h @@ -29,6 +29,7 @@ int pcap_to_irq(struct pcap_chip *, int); int irq_to_pcap(struct pcap_chip *, int); int pcap_adc_async(struct pcap_chip *, u8, u32, u8[], void *, void *); int pcap_adc_sync(struct pcap_chip *, u8, u32, u8[], u16[]); +void pcap_set_ts_bits(struct pcap_chip *, u32); #define PCAP_SECOND_PORT 1 #define PCAP_CS_AH 2 -- cgit v0.10.2 From b1148fd46c248c8f6c9f3beb79f27cdd83702621 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Tue, 23 Jun 2009 12:34:13 -0300 Subject: mfd: fix pcap irq bottom handler Mask interrupts before servicing them and loop while pcap asserts the interrupt line. Signed-off-by: Daniel Ribeiro Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index c512202..732664f 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -17,6 +17,7 @@ #include #include #include +#include #define PCAP_ADC_MAXQ 8 struct pcap_adc_request { @@ -155,34 +156,38 @@ static void pcap_isr_work(struct work_struct *work) u32 msr, isr, int_sel, service; int irq; - ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); - ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); + do { + ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); + ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); - /* We cant service/ack irqs that are assigned to port 2 */ - if (!(pdata->config & PCAP_SECOND_PORT)) { - ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); - isr &= ~int_sel; - } - ezx_pcap_write(pcap, PCAP_REG_ISR, isr); + /* We cant service/ack irqs that are assigned to port 2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) { + ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); + isr &= ~int_sel; + } - local_irq_disable(); - service = isr & ~msr; + ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr); + ezx_pcap_write(pcap, PCAP_REG_ISR, isr); - for (irq = pcap->irq_base; service; service >>= 1, irq++) { - if (service & 1) { - struct irq_desc *desc = irq_to_desc(irq); + local_irq_disable(); + service = isr & ~msr; + for (irq = pcap->irq_base; service; service >>= 1, irq++) { + if (service & 1) { + struct irq_desc *desc = irq_to_desc(irq); - if (WARN(!desc, KERN_WARNING - "Invalid PCAP IRQ %d\n", irq)) - break; + if (WARN(!desc, KERN_WARNING + "Invalid PCAP IRQ %d\n", irq)) + break; - if (desc->status & IRQ_DISABLED) - note_interrupt(irq, desc, IRQ_NONE); - else - desc->handle_irq(irq, desc); + if (desc->status & IRQ_DISABLED) + note_interrupt(irq, desc, IRQ_NONE); + else + desc->handle_irq(irq, desc); + } } - } - local_irq_enable(); + local_irq_enable(); + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); + } while (gpio_get_value(irq_to_gpio(pcap->spi->irq))); } static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) -- cgit v0.10.2 From e9a22635b0d794d0cb242ffb0249f7b2a410bca2 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Sat, 27 Jun 2009 00:17:20 -0300 Subject: mfd: add ezx_pcap_setbits Provides an atomic set_bits functions, as needed by the pcap-regulator driver. Signed-off-by: Daniel Ribeiro Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index 732664f..86d3948 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -107,6 +107,29 @@ int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value) } EXPORT_SYMBOL_GPL(ezx_pcap_read); +int ezx_pcap_set_bits(struct pcap_chip *pcap, u8 reg_num, u32 mask, u32 val) +{ + int ret; + u32 tmp = PCAP_REGISTER_READ_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + mutex_lock(&pcap->io_mutex); + ret = ezx_pcap_putget(pcap, &tmp); + if (ret) + goto out_unlock; + + tmp &= (PCAP_REGISTER_VALUE_MASK & ~mask); + tmp |= (val & mask) | PCAP_REGISTER_WRITE_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, &tmp); +out_unlock: + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_set_bits); + /* IRQ */ int irq_to_pcap(struct pcap_chip *pcap, int irq) { diff --git a/include/linux/mfd/ezx-pcap.h b/include/linux/mfd/ezx-pcap.h index b15caac..dec82b0 100644 --- a/include/linux/mfd/ezx-pcap.h +++ b/include/linux/mfd/ezx-pcap.h @@ -25,6 +25,7 @@ struct pcap_chip; int ezx_pcap_write(struct pcap_chip *, u8, u32); int ezx_pcap_read(struct pcap_chip *, u8, u32 *); +int ezx_pcap_set_bits(struct pcap_chip *, u8, u32, u32); int pcap_to_irq(struct pcap_chip *, int); int irq_to_pcap(struct pcap_chip *, int); int pcap_adc_async(struct pcap_chip *, u8, u32, u8[], void *, void *); -- cgit v0.10.2 From df10d6465f135b1e240ca7eb9565a492cf58be7c Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sun, 28 Jun 2009 09:26:30 -0700 Subject: mfd: remove unnecessary semicolons from twl4030 Signed-off-by: Joe Perches Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index 7d43083..fb194fe 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c @@ -424,7 +424,7 @@ static void twl4030_sih_do_edge(struct work_struct *work) /* see what work we have */ spin_lock_irq(&sih_agent_lock); edge_change = agent->edge_change; - agent->edge_change = 0;; + agent->edge_change = 0; sih = edge_change ? agent->sih : NULL; spin_unlock_irq(&sih_agent_lock); if (!sih) -- cgit v0.10.2 From b75ea16ae74e77244e134943a5676ca770036cac Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 2 Jul 2009 16:43:08 +0100 Subject: MAINTAINERS: Add entry for Wolfson PMIC drivers Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/MAINTAINERS b/MAINTAINERS index 9117b65..fba5e81 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5669,6 +5669,24 @@ S: Supported F: drivers/input/touchscreen/*wm97* F: include/linux/wm97xx.h +WOLFSON MICROELECTRONICS PMIC DRIVERS +P: Mark Brown +M: broonie@opensource.wolfsonmicro.com +L: linux-kernel@vger.kernel.org +T: git git://opensource.wolfsonmicro.com/linux-2.6-audioplus +W: http://opensource.wolfsonmicro.com/node/8 +S: Supported +F: drivers/leds/leds-wm83*.c +F: drivers/mfd/wm8*.c +F: drivers/power/wm83*.c +F: drivers/rtc/rtc-wm83*.c +F: drivers/regulator/wm8*.c +F: drivers/watchdog/wm83*_wdt.c +F: include/linux/mfd/wm8350/ +F: include/linux/mfd/wm8400/ +F: sound/soc/codecs/wm8350.c +F: sound/soc/codecs/wm8400.c + X.25 NETWORK LAYER M: Henner Eisen L: linux-x25@vger.kernel.org -- cgit v0.10.2 From 39b1772a24126d74699cea623f96b50ca6b6f08f Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Sat, 27 Jun 2009 00:18:02 -0300 Subject: regulator: add pcap driver Add (partial) support for the voltage regulators on the PCAP2 PMIC. Signed-off-by: Daniel Ribeiro Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f431779..da7483e 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -117,4 +117,11 @@ config REGULATOR_LP3971 Say Y here to support the voltage regulators and convertors on National Semiconductors LP3971 PMIC +config REGULATOR_PCAP + tristate "PCAP2 regulator driver" + depends on EZX_PCAP + help + This driver provides support for the voltage regulators of the + PCAP2 PMIC. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 4d762c4..3a9748f 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -16,5 +16,6 @@ obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o +obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c new file mode 100644 index 0000000..fdb9094 --- /dev/null +++ b/drivers/regulator/pcap-regulator.c @@ -0,0 +1,318 @@ +/* + * PCAP2 Regulator Driver + * + * Copyright (c) 2009 Daniel Ribeiro + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const u16 V1_table[] = { + 2775, 1275, 1600, 1725, 1825, 1925, 2075, 2275, +}; + +static const u16 V2_table[] = { + 2500, 2775, +}; + +static const u16 V3_table[] = { + 1075, 1275, 1550, 1725, 1876, 1950, 2075, 2275, +}; + +static const u16 V4_table[] = { + 1275, 1550, 1725, 1875, 1950, 2075, 2275, 2775, +}; + +static const u16 V5_table[] = { + 1875, 2275, 2475, 2775, +}; + +static const u16 V6_table[] = { + 2475, 2775, +}; + +static const u16 V7_table[] = { + 1875, 2775, +}; + +#define V8_table V4_table + +static const u16 V9_table[] = { + 1575, 1875, 2475, 2775, +}; + +static const u16 V10_table[] = { + 5000, +}; + +static const u16 VAUX1_table[] = { + 1875, 2475, 2775, 3000, +}; + +#define VAUX2_table VAUX1_table + +static const u16 VAUX3_table[] = { + 1200, 1200, 1200, 1200, 1400, 1600, 1800, 2000, + 2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600, +}; + +static const u16 VAUX4_table[] = { + 1800, 1800, 3000, 5000, +}; + +static const u16 VSIM_table[] = { + 1875, 3000, +}; + +static const u16 VSIM2_table[] = { + 1875, +}; + +static const u16 VVIB_table[] = { + 1300, 1800, 2000, 3000, +}; + +static const u16 SW1_table[] = { + 900, 950, 1000, 1050, 1100, 1150, 1200, 1250, + 1300, 1350, 1400, 1450, 1500, 1600, 1875, 2250, +}; + +#define SW2_table SW1_table + +static const u16 SW3_table[] = { + 4000, 4500, 5000, 5500, +}; + +struct pcap_regulator { + const u8 reg; + const u8 en; + const u8 index; + const u8 stby; + const u8 lowpwr; + const u8 n_voltages; + const u16 *voltage_table; +}; + +#define NA 0xff + +#define VREG_INFO(_vreg, _reg, _en, _index, _stby, _lowpwr) \ + [_vreg] = { \ + .reg = _reg, \ + .en = _en, \ + .index = _index, \ + .stby = _stby, \ + .lowpwr = _lowpwr, \ + .n_voltages = ARRAY_SIZE(_vreg##_table), \ + .voltage_table = _vreg##_table, \ + } + +static struct pcap_regulator vreg_table[] = { + VREG_INFO(V1, PCAP_REG_VREG1, 1, 2, 18, 0), + VREG_INFO(V2, PCAP_REG_VREG1, 5, 6, 19, 22), + VREG_INFO(V3, PCAP_REG_VREG1, 7, 8, 20, 23), + VREG_INFO(V4, PCAP_REG_VREG1, 11, 12, 21, 24), + /* V5 STBY and LOWPWR are on PCAP_REG_VREG2 */ + VREG_INFO(V5, PCAP_REG_VREG1, 15, 16, 12, 19), + + VREG_INFO(V6, PCAP_REG_VREG2, 1, 2, 14, 20), + VREG_INFO(V7, PCAP_REG_VREG2, 3, 4, 15, 21), + VREG_INFO(V8, PCAP_REG_VREG2, 5, 6, 16, 22), + VREG_INFO(V9, PCAP_REG_VREG2, 9, 10, 17, 23), + VREG_INFO(V10, PCAP_REG_VREG2, 10, NA, 18, 24), + + VREG_INFO(VAUX1, PCAP_REG_AUXVREG, 1, 2, 22, 23), + /* VAUX2 ... VSIM2 STBY and LOWPWR are on PCAP_REG_LOWPWR */ + VREG_INFO(VAUX2, PCAP_REG_AUXVREG, 4, 5, 0, 1), + VREG_INFO(VAUX3, PCAP_REG_AUXVREG, 7, 8, 2, 3), + VREG_INFO(VAUX4, PCAP_REG_AUXVREG, 12, 13, 4, 5), + VREG_INFO(VSIM, PCAP_REG_AUXVREG, 17, 18, NA, 6), + VREG_INFO(VSIM2, PCAP_REG_AUXVREG, 16, NA, NA, 7), + VREG_INFO(VVIB, PCAP_REG_AUXVREG, 19, 20, NA, NA), + + VREG_INFO(SW1, PCAP_REG_SWCTRL, 1, 2, NA, NA), + VREG_INFO(SW2, PCAP_REG_SWCTRL, 6, 7, NA, NA), + /* SW3 STBY is on PCAP_REG_AUXVREG */ + VREG_INFO(SW3, PCAP_REG_SWCTRL, 11, 12, 24, NA), + + /* SWxS used to control SWx voltage on standby */ +/* VREG_INFO(SW1S, PCAP_REG_LOWPWR, NA, 12, NA, NA), + VREG_INFO(SW2S, PCAP_REG_LOWPWR, NA, 20, NA, NA), */ +}; + +static int pcap_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + int uV; + u8 i; + + /* the regulator doesn't support voltage switching */ + if (vreg->n_voltages == 1) + return -EINVAL; + + for (i = 0; i < vreg->n_voltages; i++) { + /* For V1 the first is not the best match */ + if (i == 0 && rdev_get_id(rdev) == V1) + i = 1; + else if (i + 1 == vreg->n_voltages && rdev_get_id(rdev) == V1) + i = 0; + + uV = vreg->voltage_table[i] * 1000; + if (min_uV <= uV && uV <= max_uV) + return ezx_pcap_set_bits(pcap, vreg->reg, + (vreg->n_voltages - 1) << vreg->index, + i << vreg->index); + + if (i == 0 && rdev_get_id(rdev) == V1) + i = vreg->n_voltages - 1; + } + + /* the requested voltage range is not supported by this regulator */ + return -EINVAL; +} + +static int pcap_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + int mV; + + if (vreg->n_voltages == 1) + return vreg->voltage_table[0] * 1000; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + tmp = ((tmp >> vreg->index) & (vreg->n_voltages - 1)); + mV = vreg->voltage_table[tmp]; + + return mV * 1000; +} + +static int pcap_regulator_enable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 1 << vreg->en); +} + +static int pcap_regulator_disable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 0); +} + +static int pcap_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + + if (vreg->en == NA) + return -EINVAL; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + return (tmp >> vreg->en) & 1; +} + +static int pcap_regulator_list_voltage(struct regulator_dev *rdev, + unsigned int index) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + + return vreg->voltage_table[index] * 1000; +} + +static struct regulator_ops pcap_regulator_ops = { + .list_voltage = pcap_regulator_list_voltage, + .set_voltage = pcap_regulator_set_voltage, + .get_voltage = pcap_regulator_get_voltage, + .enable = pcap_regulator_enable, + .disable = pcap_regulator_disable, + .is_enabled = pcap_regulator_is_enabled, +}; + +#define VREG(_vreg) \ + [_vreg] = { \ + .name = #_vreg, \ + .id = _vreg, \ + .n_voltages = ARRAY_SIZE(_vreg##_table), \ + .ops = &pcap_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pcap_regulators[] = { + VREG(V1), VREG(V2), VREG(V3), VREG(V4), VREG(V5), VREG(V6), VREG(V7), + VREG(V8), VREG(V9), VREG(V10), VREG(VAUX1), VREG(VAUX2), VREG(VAUX3), + VREG(VAUX4), VREG(VSIM), VREG(VSIM2), VREG(VVIB), VREG(SW1), VREG(SW2), +}; + +static int __devinit pcap_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + void *pcap = platform_get_drvdata(pdev); + + rdev = regulator_register(&pcap_regulators[pdev->id], &pdev->dev, + pdev->dev.platform_data, pcap); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int __devexit pcap_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver pcap_regulator_driver = { + .driver = { + .name = "pcap-regulator", + }, + .probe = pcap_regulator_probe, + .remove = __devexit_p(pcap_regulator_remove), +}; + +static int __init pcap_regulator_init(void) +{ + return platform_driver_register(&pcap_regulator_driver); +} + +static void __exit pcap_regulator_exit(void) +{ + platform_driver_unregister(&pcap_regulator_driver); +} + +module_init(pcap_regulator_init); +module_exit(pcap_regulator_exit); + +MODULE_AUTHOR("Daniel Ribeiro "); +MODULE_DESCRIPTION("PCAP2 Regulator Driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From fb6c023a2b845df1ec383b74644ac35a4bbb76b6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 20 Jul 2009 12:43:45 +0100 Subject: hwmon: Add WM835x PMIC hardware monitoring driver This driver provides reporting of the status supply voltage rails of the WM835x series of PMICs via the hwmon API. Signed-off-by: Mark Brown Acked-by: Jean Delvare Signed-off-by: Samuel Ortiz diff --git a/Documentation/hwmon/wm8350 b/Documentation/hwmon/wm8350 new file mode 100644 index 0000000..98f923bd --- /dev/null +++ b/Documentation/hwmon/wm8350 @@ -0,0 +1,26 @@ +Kernel driver wm8350-hwmon +========================== + +Supported chips: + * Wolfson Microelectronics WM835x PMICs + Prefix: 'wm8350' + Datasheet: + http://www.wolfsonmicro.com/products/WM8350 + http://www.wolfsonmicro.com/products/WM8351 + http://www.wolfsonmicro.com/products/WM8352 + +Authors: Mark Brown + +Description +----------- + +The WM835x series of PMICs include an AUXADC which can be used to +monitor a range of system operating parameters, including the voltages +of the major supplies within the system. Currently the driver provides +simple access to these major supplies. + +Voltage Monitoring +------------------ + +Voltages are sampled by a 12 bit ADC. For the internal supplies the ADC +is referenced to the system VRTC. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2e25b7a..120085c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -937,6 +937,16 @@ config SENSORS_W83627EHF This driver can also be built as a module. If so, the module will be called w83627ehf. +config SENSORS_WM8350 + tristate "Wolfson Microelectronics WM835x" + depends on MFD_WM8350 + help + If you say yes here you get support for the hardware + monitoring features of the WM835x series of PMICs. + + This driver can also be built as a module. If so, the module + will be called wm8350-hwmon. + config SENSORS_ULTRA45 tristate "Sun Ultra45 PIC16F747" depends on SPARC64 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 7f239a2..ed5f178 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/hwmon/wm8350-hwmon.c b/drivers/hwmon/wm8350-hwmon.c new file mode 100644 index 0000000..1329059 --- /dev/null +++ b/drivers/hwmon/wm8350-hwmon.c @@ -0,0 +1,151 @@ +/* + * drivers/hwmon/wm8350-hwmon.c - Wolfson Microelectronics WM8350 PMIC + * hardware monitoring features. + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "wm8350\n"); +} + +static const char *input_names[] = { + [WM8350_AUXADC_USB] = "USB", + [WM8350_AUXADC_LINE] = "Line", + [WM8350_AUXADC_BATT] = "Battery", +}; + + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int val; + + val = wm8350_read_auxadc(wm8350, channel, 0, 0) * WM8350_AUX_COEFF; + val = DIV_ROUND_CLOSEST(val, 1000); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", input_names[channel]); +} + +#define WM8350_NAMED_VOLTAGE(id, name) \ + static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage,\ + NULL, name); \ + static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ + NULL, name) + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +WM8350_NAMED_VOLTAGE(0, WM8350_AUXADC_USB); +WM8350_NAMED_VOLTAGE(1, WM8350_AUXADC_BATT); +WM8350_NAMED_VOLTAGE(2, WM8350_AUXADC_LINE); + +static struct attribute *wm8350_attributes[] = { + &dev_attr_name.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group wm8350_attr_group = { + .attrs = wm8350_attributes, +}; + +static int __devinit wm8350_hwmon_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + int ret; + + ret = sysfs_create_group(&pdev->dev.kobj, &wm8350_attr_group); + if (ret) + goto err; + + wm8350->hwmon.classdev = hwmon_device_register(&pdev->dev); + if (IS_ERR(wm8350->hwmon.classdev)) { + ret = PTR_ERR(wm8350->hwmon.classdev); + goto err_group; + } + + return 0; + +err_group: + sysfs_remove_group(&pdev->dev.kobj, &wm8350_attr_group); +err: + return ret; +} + +static int __devexit wm8350_hwmon_remove(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + + hwmon_device_unregister(wm8350->hwmon.classdev); + sysfs_remove_group(&pdev->dev.kobj, &wm8350_attr_group); + + return 0; +} + +static struct platform_driver wm8350_hwmon_driver = { + .probe = wm8350_hwmon_probe, + .remove = __devexit_p(wm8350_hwmon_remove), + .driver = { + .name = "wm8350-hwmon", + .owner = THIS_MODULE, + }, +}; + +static int __init wm8350_hwmon_init(void) +{ + return platform_driver_register(&wm8350_hwmon_driver); +} +module_init(wm8350_hwmon_init); + +static void __exit wm8350_hwmon_exit(void) +{ + platform_driver_unregister(&wm8350_hwmon_driver); +} +module_exit(wm8350_hwmon_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM8350 Hardware Monitoring"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-hwmon"); diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index fe24079..9d662a5 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -1472,6 +1472,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, &(wm8350->codec.pdev)); wm8350_client_dev_register(wm8350, "wm8350-gpio", &(wm8350->gpio.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-hwmon", + &(wm8350->hwmon.pdev)); wm8350_client_dev_register(wm8350, "wm8350-power", &(wm8350->power.pdev)); wm8350_client_dev_register(wm8350, "wm8350-rtc", &(wm8350->rtc.pdev)); @@ -1498,6 +1500,7 @@ void wm8350_device_exit(struct wm8350 *wm8350) platform_device_unregister(wm8350->wdt.pdev); platform_device_unregister(wm8350->rtc.pdev); platform_device_unregister(wm8350->power.pdev); + platform_device_unregister(wm8350->hwmon.pdev); platform_device_unregister(wm8350->gpio.pdev); platform_device_unregister(wm8350->codec.pdev); diff --git a/include/linux/mfd/wm8350/core.h b/include/linux/mfd/wm8350/core.h index 42cca67..969b0b5 100644 --- a/include/linux/mfd/wm8350/core.h +++ b/include/linux/mfd/wm8350/core.h @@ -605,6 +605,11 @@ struct wm8350_irq { void *data; }; +struct wm8350_hwmon { + struct platform_device *pdev; + struct device *classdev; +}; + struct wm8350 { struct device *dev; @@ -629,6 +634,7 @@ struct wm8350 { /* Client devices */ struct wm8350_codec codec; struct wm8350_gpio gpio; + struct wm8350_hwmon hwmon; struct wm8350_pmic pmic; struct wm8350_power power; struct wm8350_rtc rtc; -- cgit v0.10.2 From 9c3664ddcee8d56c54bc6a735bbc3cf77723d85d Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 3 Aug 2009 18:16:38 +0200 Subject: mfd: Add twl4030-pwrbutton as a twl4030 child Make that twl4030-pwrbutton.c driver probe with current child creation api for twl4030. Cc: David Brownell Signed-off-by: Felipe Balbi Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index ca54996..1fd2620 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -115,6 +115,12 @@ #define TWL4030_NUM_SLAVES 4 +#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \ + || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE) +#define twl_has_pwrbutton() true +#else +#define twl_has_pwrbutton() false +#endif /* Base Address defns for twl4030_map[] */ @@ -538,6 +544,13 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) return PTR_ERR(child); } + if (twl_has_pwrbutton()) { + child = add_child(1, "twl4030_pwrbutton", + NULL, 0, true, pdata->irq_base + 8 + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + if (twl_has_regulator()) { /* child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); -- cgit v0.10.2 From ed52e62ebec9e703eb0b69704feaf1b6e847d882 Mon Sep 17 00:00:00 2001 From: Paul Fertser Date: Tue, 28 Jul 2009 00:41:15 +0400 Subject: mfd: use a dedicated workqueue for pcf50633 irq processing Using the default kernel "events" workqueue causes problems with synchronous adc readings if initiated from some task on the same workqueue. I had a deadlock trying to use pcf50633_adc_sync_read from a power_supply class driver because the reading was initiated from the workqueue and it waited for the irq processing to complete (to get the result) and that was put on the same workqueue. Signed-off-by: Paul Fertser Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index 8d3c38b..d26d774 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -444,7 +444,7 @@ static irqreturn_t pcf50633_irq(int irq, void *data) get_device(pcf->dev); disable_irq_nosync(pcf->irq); - schedule_work(&pcf->irq_work); + queue_work(pcf->work_queue, &pcf->irq_work); return IRQ_HANDLED; } @@ -575,6 +575,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, pcf->dev = &client->dev; pcf->i2c_client = client; pcf->irq = client->irq; + pcf->work_queue = create_singlethread_workqueue("pcf50633"); INIT_WORK(&pcf->irq_work, pcf50633_irq_worker); @@ -651,6 +652,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client, return 0; err: + destroy_workqueue(pcf->work_queue); kfree(pcf); return ret; } @@ -661,6 +663,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client) int i; free_irq(pcf->irq, pcf); + destroy_workqueue(pcf->work_queue); platform_device_unregister(pcf->input_pdev); platform_device_unregister(pcf->rtc_pdev); diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h index c8f51c3..9aba7b7 100644 --- a/include/linux/mfd/pcf50633/core.h +++ b/include/linux/mfd/pcf50633/core.h @@ -136,6 +136,7 @@ struct pcf50633 { int irq; struct pcf50633_irq irq_handler[PCF50633_NUM_IRQ]; struct work_struct irq_work; + struct workqueue_struct *work_queue; struct mutex lock; u8 mask_regs[5]; -- cgit v0.10.2 From bd8ef10261d7ae92ad2b4925afd2b56f46175c47 Mon Sep 17 00:00:00 2001 From: Paul Fertser Date: Tue, 28 Jul 2009 00:58:48 +0400 Subject: mfd: revise locking for pcf50633 ADC Current implementation is prone to races, this patch attempts to remove all but one (in pcf50633_adc_sync_read). The idea is that we need to guard the queue access only on inserting and removing items. If we insert and there're no more items in the queue it means that the last irq already happened and we need to trigger ADC manually. If not, then the next conversion will be triggered by the irq handler upon completion of the previous. Signed-off-by: Paul Fertser Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c index c2d05be..3d31e97 100644 --- a/drivers/mfd/pcf50633-adc.c +++ b/drivers/mfd/pcf50633-adc.c @@ -73,15 +73,10 @@ static void trigger_next_adc_job_if_any(struct pcf50633 *pcf) struct pcf50633_adc *adc = __to_adc(pcf); int head; - mutex_lock(&adc->queue_mutex); - head = adc->queue_head; - if (!adc->queue[head]) { - mutex_unlock(&adc->queue_mutex); + if (!adc->queue[head]) return; - } - mutex_unlock(&adc->queue_mutex); adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg); } @@ -99,16 +94,17 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) if (adc->queue[tail]) { mutex_unlock(&adc->queue_mutex); + dev_err(pcf->dev, "ADC queue is full, dropping request\n"); return -EBUSY; } adc->queue[tail] = req; + if (head == tail) + trigger_next_adc_job_if_any(pcf); adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); mutex_unlock(&adc->queue_mutex); - trigger_next_adc_job_if_any(pcf); - return 0; } @@ -124,6 +120,7 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result) int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) { struct pcf50633_adc_request *req; + int err; /* req is freed when the result is ready, in interrupt handler */ req = kzalloc(sizeof(*req), GFP_KERNEL); @@ -136,9 +133,13 @@ int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) req->callback_param = req; init_completion(&req->completion); - adc_enqueue_request(pcf, req); + err = adc_enqueue_request(pcf, req); + if (err) + return err; + wait_for_completion(&req->completion); + /* FIXME by this time req might be already freed */ return req->result; } EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); @@ -159,9 +160,7 @@ int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg, req->callback = callback; req->callback_param = callback_param; - adc_enqueue_request(pcf, req); - - return 0; + return adc_enqueue_request(pcf, req); } EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); @@ -184,7 +183,7 @@ static void pcf50633_adc_irq(int irq, void *data) struct pcf50633_adc *adc = data; struct pcf50633 *pcf = adc->pcf; struct pcf50633_adc_request *req; - int head; + int head, res; mutex_lock(&adc->queue_mutex); head = adc->queue_head; @@ -199,12 +198,13 @@ static void pcf50633_adc_irq(int irq, void *data) adc->queue_head = (head + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + res = adc_result(pcf); + trigger_next_adc_job_if_any(pcf); + mutex_unlock(&adc->queue_mutex); - req->callback(pcf, req->callback_param, adc_result(pcf)); + req->callback(pcf, req->callback_param, res); kfree(req); - - trigger_next_adc_job_if_any(pcf); } static int __devinit pcf50633_adc_probe(struct platform_device *pdev) -- cgit v0.10.2 From 8d360d8c03e1e8514bbaf606b1cd3b818dfc445d Mon Sep 17 00:00:00 2001 From: Paul Fertser Date: Tue, 28 Jul 2009 01:09:04 +0400 Subject: mfd: fix wrong define for 10bit pcf50633 ADC mode The 10 bits definition was the 8 bits one. Signed-off-by: Paul Fertser Signed-off-by: Samuel Ortiz diff --git a/include/linux/mfd/pcf50633/adc.h b/include/linux/mfd/pcf50633/adc.h index 56669b4..b35e628 100644 --- a/include/linux/mfd/pcf50633/adc.h +++ b/include/linux/mfd/pcf50633/adc.h @@ -25,7 +25,8 @@ #define PCF50633_REG_ADCS3 0x57 #define PCF50633_ADCC1_ADCSTART 0x01 -#define PCF50633_ADCC1_RES_10BIT 0x02 +#define PCF50633_ADCC1_RES_8BIT 0x02 +#define PCF50633_ADCC1_RES_10BIT 0x00 #define PCF50633_ADCC1_AVERAGE_NO 0x00 #define PCF50633_ADCC1_AVERAGE_4 0x04 #define PCF50633_ADCC1_AVERAGE_8 0x08 -- cgit v0.10.2 From 89a99e76f6888166edb145f576cc4a761ee0e0a9 Mon Sep 17 00:00:00 2001 From: Vipin Bhandari Date: Thu, 30 Jul 2009 04:19:17 -0400 Subject: mfd: Correct ro and cd implemantion on DM355 This patch corrects the support for MMCSD card detection and read only feature for SoC DM355. EVMDM355_ECP_VA4.pdf, from Spectrum digital, suggests that Bit 2 and 4 should be checked for card detection. However on the EVM, bits 1 and 3 gives this status, for MMC/SD instance 0 and 1 respectively. The pdf also suggests that Bit 1 and 3 should be checked for write protection. However on the EVM bits 2 and 4 gives this status. This document can be downloaded from http://c6000.spectrumdigital.com/evmdm355/reve/files/EVMDM355_ECP_VA4.pdf Signed-off-by: Vipin Bhandari Acked-by: David Brownell Signed-off-by: Kevin Hilman Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/dm355evm_msp.c b/drivers/mfd/dm355evm_msp.c index 5b6e58a..3d4a861 100644 --- a/drivers/mfd/dm355evm_msp.c +++ b/drivers/mfd/dm355evm_msp.c @@ -107,8 +107,16 @@ static const u8 msp_gpios[] = { MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), MSP_GPIO(4, SWITCH1), /* switches on MMC/SD sockets */ - MSP_GPIO(1, SDMMC), MSP_GPIO(2, SDMMC), /* mmc0 WP, nCD */ - MSP_GPIO(3, SDMMC), MSP_GPIO(4, SDMMC), /* mmc1 WP, nCD */ + /* + * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be + * checked for card detection. However on the EVM bit 1 and 3 gives + * this status, for 0 and 1 instance respectively. The pdf also + * suggests that Bit 1 and 3 should be checked for write protection. + * However on the EVM bit 2 and 4 gives this status,for 0 and 1 + * instance respectively. + */ + MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */ + MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */ }; #define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) -- cgit v0.10.2 From 327bc3a3efa408fb285948bfef112a6c58dfb375 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Fri, 31 Jul 2009 12:38:02 +0200 Subject: mfd: Remove VIB defines from pcap header file Vibrator will be accessed via the pcap-regulator driver, no need to expose its bits in the header file. Signed-off-by: Daniel Ribeiro Signed-off-by: Samuel Ortiz diff --git a/include/linux/mfd/ezx-pcap.h b/include/linux/mfd/ezx-pcap.h index dec82b0..e5124ce 100644 --- a/include/linux/mfd/ezx-pcap.h +++ b/include/linux/mfd/ezx-pcap.h @@ -227,7 +227,6 @@ void pcap_set_ts_bits(struct pcap_chip *, u32); #define PCAP_LED1 1 #define PCAP_BL0 2 #define PCAP_BL1 3 -#define PCAP_VIB 4 #define PCAP_LED_3MA 0 #define PCAP_LED_4MA 1 #define PCAP_LED_5MA 2 @@ -246,9 +245,6 @@ void pcap_set_ts_bits(struct pcap_chip *, u32); #define PCAP_LED0_C_SHIFT 15 #define PCAP_LED1_C_SHIFT 17 #define PCAP_BL1_SHIFT 20 -#define PCAP_VIB_MASK 0x3 -#define PCAP_VIB_SHIFT 20 -#define PCAP_VIB_EN (1 << 19) /* RTC */ #define PCAP_RTC_DAY_MASK 0x3fff -- cgit v0.10.2 From f078237bcf6d5ffe322f6de7f05c0541989a8d35 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 31 Jul 2009 15:55:45 -0700 Subject: mfd: register ezx-pcap earlier Register ezx-pcap earlier so it can be used with cpufreq Signed-off-by: Daniel Ribeiro Acked-by: Mark Brown Signed-off-by: Andrew Morton Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c index 86d3948..016be49 100644 --- a/drivers/mfd/ezx-pcap.c +++ b/drivers/mfd/ezx-pcap.c @@ -542,7 +542,7 @@ static void __exit ezx_pcap_exit(void) spi_unregister_driver(&ezxpcap_driver); } -module_init(ezx_pcap_init); +subsys_initcall(ezx_pcap_init); module_exit(ezx_pcap_exit); MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 3bed6e415fc2cbf8d706848a62a48aebe84435e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:51 +0100 Subject: mfd: Allow multiple MFD cells with the same name Provide basic support for MFDs having multiple cells of a given type with different IDs by adding an id to the mfd_cell structure and then adding that to the id passed in to mfd_add_devices(). As it stands this approach requires that MFDs using this feature deal with ensuring that there aren't any ID collisions resulting from multiple MFDs of the same type being instantiated. This needs to happen with the existing code too, but with this approach there is a knock on effect on the IDs for non-duplicated devices. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 54ddf37..ae15e49 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -25,7 +25,7 @@ static int mfd_add_device(struct device *parent, int id, int ret = -ENOMEM; int r; - pdev = platform_device_alloc(cell->name, id); + pdev = platform_device_alloc(cell->name, id + cell->id); if (!pdev) goto fail_alloc; diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 49ef857..11d740b 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -23,6 +23,7 @@ */ struct mfd_cell { const char *name; + int id; int (*enable)(struct platform_device *dev); int (*disable)(struct platform_device *dev); -- cgit v0.10.2 From d2bedfe7a8b2f34beee2cad9cae74a088ee8ed07 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:52 +0100 Subject: mfd: Initial core support for WM831x series devices The WM831x series of devices are register compatible processor power management subsystems, providing regulator and power path management facilities along with other services like watchdog, RTC and touch panel controllers. This patch adds very basic support, providing basic single register I2C access, handling of the security key and registration of the devices. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c new file mode 100644 index 0000000..cc1040c --- /dev/null +++ b/drivers/mfd/wm831x-core.c @@ -0,0 +1,1357 @@ +/* + * wm831x-core.c -- Device access for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include + +#include +#include + +enum wm831x_parent { + WM8310 = 0, + WM8311 = 1, + WM8312 = 2, +}; + +static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg) +{ + if (!wm831x->locked) + return 0; + + switch (reg) { + case WM831X_WATCHDOG: + case WM831X_DC4_CONTROL: + case WM831X_ON_PIN_CONTROL: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + return 1; + + default: + return 0; + } +} + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * allowing writes to them. + */ +void wm831x_reg_lock(struct wm831x *wm831x) +{ + int ret; + + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers locked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(wm831x->locked); + wm831x->locked = 1; + mutex_unlock(&wm831x->io_lock); + } else { + dev_err(wm831x->dev, "Failed to lock registers: %d\n", ret); + } + +} +EXPORT_SYMBOL_GPL(wm831x_reg_lock); + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * preventing spurious writes. + */ +int wm831x_reg_unlock(struct wm831x *wm831x) +{ + int ret; + + /* 0x9716 is the value required to unlock the registers */ + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0x9716); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers unlocked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(!wm831x->locked); + wm831x->locked = 0; + mutex_unlock(&wm831x->io_lock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_unlock); + +static int wm831x_read(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + int ret, i; + u16 *buf = dest; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + ret = wm831x->read_dev(wm831x, reg, bytes, dest); + if (ret < 0) + return ret; + + for (i = 0; i < bytes / 2; i++) { + buf[i] = be16_to_cpu(buf[i]); + + dev_vdbg(wm831x->dev, "Read %04x from R%d(0x%x)\n", + buf[i], reg + i, reg + i); + } + + return 0; +} + +/** + * wm831x_reg_read: Read a single WM831x register. + * + * @wm831x: Device to read from. + * @reg: Register to read. + */ +int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg) +{ + unsigned short val; + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wm831x_reg_read); + +/** + * wm831x_bulk_read: Read multiple WM831x registers + * + * @wm831x: Device to read from + * @reg: First register + * @count: Number of registers + * @buf: Buffer to fill. + */ +int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, + int count, u16 *buf) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, count * 2, buf); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_bulk_read); + +static int wm831x_write(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + u16 *buf = src; + int i; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + for (i = 0; i < bytes / 2; i++) { + if (wm831x_reg_locked(wm831x, reg)) + return -EPERM; + + dev_vdbg(wm831x->dev, "Write %04x to R%d(0x%x)\n", + buf[i], reg + i, reg + i); + + buf[i] = cpu_to_be16(buf[i]); + } + + return wm831x->write_dev(wm831x, reg, bytes, src); +} + +/** + * wm831x_reg_write: Write a single WM831x register. + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + */ +int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg, + unsigned short val) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_write(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_write); + +/** + * wm831x_set_bits: Set the value of a bitfield in a WM831x register + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @mask: Mask of bits to set. + * @val: Value to set (unshifted) + */ +int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, + unsigned short mask, unsigned short val) +{ + int ret; + u16 r; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_read(wm831x, reg, 2, &r); + if (ret < 0) + goto out; + + r &= ~mask; + r |= val; + + ret = wm831x_write(wm831x, reg, 2, &r); + +out: + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_set_bits); + +static struct resource wm831x_dcdc1_resources[] = { + { + .start = WM831X_DC1_CONTROL_1, + .end = WM831X_DC1_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC1, + .end = WM831X_IRQ_UV_DC1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC1, + .end = WM831X_IRQ_HC_DC1, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_dcdc2_resources[] = { + { + .start = WM831X_DC2_CONTROL_1, + .end = WM831X_DC2_DVS_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC2, + .end = WM831X_IRQ_UV_DC2, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC2, + .end = WM831X_IRQ_HC_DC2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc3_resources[] = { + { + .start = WM831X_DC3_CONTROL_1, + .end = WM831X_DC3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC3, + .end = WM831X_IRQ_UV_DC3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc4_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM831X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_gpio_resources[] = { + { + .start = WM831X_IRQ_GPIO_1, + .end = WM831X_IRQ_GPIO_16, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink1_resources[] = { + { + .start = WM831X_CURRENT_SINK_1, + .end = WM831X_CURRENT_SINK_1, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS1, + .end = WM831X_IRQ_CS1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink2_resources[] = { + { + .start = WM831X_CURRENT_SINK_2, + .end = WM831X_CURRENT_SINK_2, + .flags = IORESOURCE_IO, + }, + { + .start = WM831X_IRQ_CS2, + .end = WM831X_IRQ_CS2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo1_resources[] = { + { + .start = WM831X_LDO1_CONTROL, + .end = WM831X_LDO1_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO1, + .end = WM831X_IRQ_UV_LDO1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo2_resources[] = { + { + .start = WM831X_LDO2_CONTROL, + .end = WM831X_LDO2_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO2, + .end = WM831X_IRQ_UV_LDO2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo3_resources[] = { + { + .start = WM831X_LDO3_CONTROL, + .end = WM831X_LDO3_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO3, + .end = WM831X_IRQ_UV_LDO3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo4_resources[] = { + { + .start = WM831X_LDO4_CONTROL, + .end = WM831X_LDO4_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO4, + .end = WM831X_IRQ_UV_LDO4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo5_resources[] = { + { + .start = WM831X_LDO5_CONTROL, + .end = WM831X_LDO5_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO5, + .end = WM831X_IRQ_UV_LDO5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo6_resources[] = { + { + .start = WM831X_LDO6_CONTROL, + .end = WM831X_LDO6_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO6, + .end = WM831X_IRQ_UV_LDO6, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo7_resources[] = { + { + .start = WM831X_LDO7_CONTROL, + .end = WM831X_LDO7_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO7, + .end = WM831X_IRQ_UV_LDO7, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo8_resources[] = { + { + .start = WM831X_LDO8_CONTROL, + .end = WM831X_LDO8_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO8, + .end = WM831X_IRQ_UV_LDO8, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo9_resources[] = { + { + .start = WM831X_LDO9_CONTROL, + .end = WM831X_LDO9_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO9, + .end = WM831X_IRQ_UV_LDO9, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo10_resources[] = { + { + .start = WM831X_LDO10_CONTROL, + .end = WM831X_LDO10_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO10, + .end = WM831X_IRQ_UV_LDO10, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo11_resources[] = { + { + .start = WM831X_LDO11_ON_CONTROL, + .end = WM831X_LDO11_SLEEP_CONTROL, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_on_resources[] = { + { + .start = WM831X_IRQ_ON, + .end = WM831X_IRQ_ON, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_power_resources[] = { + { + .name = "SYSLO", + .start = WM831X_IRQ_PPM_SYSLO, + .end = WM831X_IRQ_PPM_SYSLO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "PWR SRC", + .start = WM831X_IRQ_PPM_PWR_SRC, + .end = WM831X_IRQ_PPM_PWR_SRC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB CURR", + .start = WM831X_IRQ_PPM_USB_CURR, + .end = WM831X_IRQ_PPM_USB_CURR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT HOT", + .start = WM831X_IRQ_CHG_BATT_HOT, + .end = WM831X_IRQ_CHG_BATT_HOT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT COLD", + .start = WM831X_IRQ_CHG_BATT_COLD, + .end = WM831X_IRQ_CHG_BATT_COLD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT FAIL", + .start = WM831X_IRQ_CHG_BATT_FAIL, + .end = WM831X_IRQ_CHG_BATT_FAIL, + .flags = IORESOURCE_IRQ, + }, + { + .name = "OV", + .start = WM831X_IRQ_CHG_OV, + .end = WM831X_IRQ_CHG_OV, + .flags = IORESOURCE_IRQ, + }, + { + .name = "END", + .start = WM831X_IRQ_CHG_END, + .end = WM831X_IRQ_CHG_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TO", + .start = WM831X_IRQ_CHG_TO, + .end = WM831X_IRQ_CHG_TO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MODE", + .start = WM831X_IRQ_CHG_MODE, + .end = WM831X_IRQ_CHG_MODE, + .flags = IORESOURCE_IRQ, + }, + { + .name = "START", + .start = WM831X_IRQ_CHG_START, + .end = WM831X_IRQ_CHG_START, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_rtc_resources[] = { + { + .name = "PER", + .start = WM831X_IRQ_RTC_PER, + .end = WM831X_IRQ_RTC_PER, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALM", + .start = WM831X_IRQ_RTC_ALM, + .end = WM831X_IRQ_RTC_ALM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_status1_resources[] = { + { + .start = WM831X_STATUS_LED_1, + .end = WM831X_STATUS_LED_1, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_status2_resources[] = { + { + .start = WM831X_STATUS_LED_2, + .end = WM831X_STATUS_LED_2, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource wm831x_touch_resources[] = { + { + .name = "TCHPD", + .start = WM831X_IRQ_TCHPD, + .end = WM831X_IRQ_TCHPD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TCHDATA", + .start = WM831X_IRQ_TCHDATA, + .end = WM831X_IRQ_TCHDATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_wdt_resources[] = { + { + .start = WM831X_IRQ_WDOG_TO, + .end = WM831X_IRQ_WDOG_TO, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell wm8310_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8311_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static struct mfd_cell wm8312_devs[] = { + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +/* + * Instantiate the generic non-control parts of the device. + */ +static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) +{ + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int rev; + enum wm831x_parent parent; + int ret; + + mutex_init(&wm831x->io_lock); + mutex_init(&wm831x->key_lock); + dev_set_drvdata(wm831x->dev, wm831x); + + ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read parent ID: %d\n", ret); + goto err; + } + if (ret != 0x6204) { + dev_err(wm831x->dev, "Device is not a WM831x: ID %x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wm831x_reg_read(wm831x, WM831X_REVISION); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read revision: %d\n", ret); + goto err; + } + rev = (ret & WM831X_PARENT_REV_MASK) >> WM831X_PARENT_REV_SHIFT; + + ret = wm831x_reg_read(wm831x, WM831X_RESET_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read device ID: %d\n", ret); + goto err; + } + + switch (ret) { + case 0x8310: + parent = WM8310; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8310 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0x8311: + parent = WM8311; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8311 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0x8312: + parent = WM8312; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM8312 revision %c\n", + 'A' + rev); + break; + } + break; + + case 0: + /* Some engineering samples do not have the ID set, + * rely on the device being registered correctly. + * This will need revisiting for future devices with + * multiple dies. + */ + parent = id; + switch (rev) { + case 0: + dev_info(wm831x->dev, "WM831%d ES revision %c\n", + parent, 'A' + rev); + break; + } + break; + + default: + dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret); + ret = -EINVAL; + goto err; + } + + /* This will need revisiting in future but is OK for all + * current parts. + */ + if (parent != id) + dev_warn(wm831x->dev, "Device was registered as a WM831%lu\n", + id); + + /* Bootstrap the user key */ + ret = wm831x_reg_read(wm831x, WM831X_SECURITY_KEY); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read security key: %d\n", ret); + goto err; + } + if (ret != 0) { + dev_warn(wm831x->dev, "Security key had non-zero value %x\n", + ret); + wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + } + wm831x->locked = 1; + + if (pdata && pdata->pre_init) { + ret = pdata->pre_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "pre_init() failed: %d\n", ret); + goto err; + } + } + + /* The core device is up, instantiate the subdevices. */ + switch (parent) { + case WM8310: + ret = mfd_add_devices(wm831x->dev, -1, + wm8310_devs, ARRAY_SIZE(wm8310_devs), + NULL, 0); + break; + + case WM8311: + ret = mfd_add_devices(wm831x->dev, -1, + wm8311_devs, ARRAY_SIZE(wm8311_devs), + NULL, 0); + break; + + case WM8312: + ret = mfd_add_devices(wm831x->dev, -1, + wm8312_devs, ARRAY_SIZE(wm8312_devs), + NULL, 0); + break; + + default: + /* If this happens the bus probe function is buggy */ + BUG(); + } + + if (ret != 0) { + dev_err(wm831x->dev, "Failed to add children\n"); + goto err; + } + + if (pdata && pdata->post_init) { + ret = pdata->post_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "post_init() failed: %d\n", ret); + goto err; + } + } + + return 0; + +err: + mfd_remove_devices(wm831x->dev); + kfree(wm831x); + return ret; +} + +static void wm831x_device_exit(struct wm831x *wm831x) +{ + mfd_remove_devices(wm831x->dev); + kfree(wm831x); +} + +static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = wm831x->control_data; + int ret; + u16 r = cpu_to_be16(reg); + + ret = i2c_master_send(i2c, (unsigned char *)&r, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + if (ret != bytes) + return -EIO; + return 0; +} + +/* Currently we allocate the write buffer on the stack; this is OK for + * small writes - if we need to do large writes this will need to be + * revised. + */ +static int wm831x_i2c_write_device(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + struct i2c_client *i2c = wm831x->control_data; + unsigned char msg[bytes + 2]; + int ret; + + reg = cpu_to_be16(reg); + memcpy(&msg[0], ®, 2); + memcpy(&msg[2], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 2); + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; +} + +static int wm831x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm831x *wm831x; + + wm831x = kzalloc(sizeof(struct wm831x), GFP_KERNEL); + if (wm831x == NULL) { + kfree(i2c); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, wm831x); + wm831x->dev = &i2c->dev; + wm831x->control_data = i2c; + wm831x->read_dev = wm831x_i2c_read_device; + wm831x->write_dev = wm831x_i2c_write_device; + + return wm831x_device_init(wm831x, id->driver_data, i2c->irq); +} + +static int wm831x_i2c_remove(struct i2c_client *i2c) +{ + struct wm831x *wm831x = i2c_get_clientdata(i2c); + + wm831x_device_exit(wm831x); + + return 0; +} + +static const struct i2c_device_id wm831x_i2c_id[] = { + { "wm8310", WM8310 }, + { "wm8311", WM8311 }, + { "wm8312", WM8312 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id); + + +static struct i2c_driver wm831x_i2c_driver = { + .driver = { + .name = "wm831x", + .owner = THIS_MODULE, + }, + .probe = wm831x_i2c_probe, + .remove = wm831x_i2c_remove, + .id_table = wm831x_i2c_id, +}; + +static int __init wm831x_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&wm831x_i2c_driver); + if (ret != 0) + pr_err("Failed to register wm831x I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_i2c_init); + +static void __exit wm831x_i2c_exit(void) +{ + i2c_del_driver(&wm831x_i2c_driver); +} +module_exit(wm831x_i2c_exit); + +MODULE_DESCRIPTION("I2C support for the WM831X AudioPlus PMIC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown"); diff --git a/include/linux/mfd/wm831x/core.h b/include/linux/mfd/wm831x/core.h new file mode 100644 index 0000000..d90e693 --- /dev/null +++ b/include/linux/mfd/wm831x/core.h @@ -0,0 +1,247 @@ +/* + * include/linux/mfd/wm831x/core.h -- Core interface for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_CORE_H__ +#define __MFD_WM831X_CORE_H__ + +/* + * Register values. + */ +#define WM831X_RESET_ID 0x00 +#define WM831X_REVISION 0x01 +#define WM831X_PARENT_ID 0x4000 +#define WM831X_SYSVDD_CONTROL 0x4001 +#define WM831X_THERMAL_MONITORING 0x4002 +#define WM831X_POWER_STATE 0x4003 +#define WM831X_WATCHDOG 0x4004 +#define WM831X_ON_PIN_CONTROL 0x4005 +#define WM831X_RESET_CONTROL 0x4006 +#define WM831X_CONTROL_INTERFACE 0x4007 +#define WM831X_SECURITY_KEY 0x4008 +#define WM831X_SOFTWARE_SCRATCH 0x4009 +#define WM831X_OTP_CONTROL 0x400A +#define WM831X_GPIO_LEVEL 0x400C +#define WM831X_SYSTEM_STATUS 0x400D +#define WM831X_ON_SOURCE 0x400E +#define WM831X_OFF_SOURCE 0x400F +#define WM831X_SYSTEM_INTERRUPTS 0x4010 +#define WM831X_INTERRUPT_STATUS_1 0x4011 +#define WM831X_INTERRUPT_STATUS_2 0x4012 +#define WM831X_INTERRUPT_STATUS_3 0x4013 +#define WM831X_INTERRUPT_STATUS_4 0x4014 +#define WM831X_INTERRUPT_STATUS_5 0x4015 +#define WM831X_IRQ_CONFIG 0x4017 +#define WM831X_SYSTEM_INTERRUPTS_MASK 0x4018 +#define WM831X_INTERRUPT_STATUS_1_MASK 0x4019 +#define WM831X_INTERRUPT_STATUS_2_MASK 0x401A +#define WM831X_INTERRUPT_STATUS_3_MASK 0x401B +#define WM831X_INTERRUPT_STATUS_4_MASK 0x401C +#define WM831X_INTERRUPT_STATUS_5_MASK 0x401D +#define WM831X_RTC_WRITE_COUNTER 0x4020 +#define WM831X_RTC_TIME_1 0x4021 +#define WM831X_RTC_TIME_2 0x4022 +#define WM831X_RTC_ALARM_1 0x4023 +#define WM831X_RTC_ALARM_2 0x4024 +#define WM831X_RTC_CONTROL 0x4025 +#define WM831X_RTC_TRIM 0x4026 +#define WM831X_TOUCH_CONTROL_1 0x4028 +#define WM831X_TOUCH_CONTROL_2 0x4029 +#define WM831X_TOUCH_DATA_X 0x402A +#define WM831X_TOUCH_DATA_Y 0x402B +#define WM831X_TOUCH_DATA_Z 0x402C +#define WM831X_AUXADC_DATA 0x402D +#define WM831X_AUXADC_CONTROL 0x402E +#define WM831X_AUXADC_SOURCE 0x402F +#define WM831X_COMPARATOR_CONTROL 0x4030 +#define WM831X_COMPARATOR_1 0x4031 +#define WM831X_COMPARATOR_2 0x4032 +#define WM831X_COMPARATOR_3 0x4033 +#define WM831X_COMPARATOR_4 0x4034 +#define WM831X_GPIO1_CONTROL 0x4038 +#define WM831X_GPIO2_CONTROL 0x4039 +#define WM831X_GPIO3_CONTROL 0x403A +#define WM831X_GPIO4_CONTROL 0x403B +#define WM831X_GPIO5_CONTROL 0x403C +#define WM831X_GPIO6_CONTROL 0x403D +#define WM831X_GPIO7_CONTROL 0x403E +#define WM831X_GPIO8_CONTROL 0x403F +#define WM831X_GPIO9_CONTROL 0x4040 +#define WM831X_GPIO10_CONTROL 0x4041 +#define WM831X_GPIO11_CONTROL 0x4042 +#define WM831X_GPIO12_CONTROL 0x4043 +#define WM831X_GPIO13_CONTROL 0x4044 +#define WM831X_GPIO14_CONTROL 0x4045 +#define WM831X_GPIO15_CONTROL 0x4046 +#define WM831X_GPIO16_CONTROL 0x4047 +#define WM831X_CHARGER_CONTROL_1 0x4048 +#define WM831X_CHARGER_CONTROL_2 0x4049 +#define WM831X_CHARGER_STATUS 0x404A +#define WM831X_BACKUP_CHARGER_CONTROL 0x404B +#define WM831X_STATUS_LED_1 0x404C +#define WM831X_STATUS_LED_2 0x404D +#define WM831X_CURRENT_SINK_1 0x404E +#define WM831X_CURRENT_SINK_2 0x404F +#define WM831X_DCDC_ENABLE 0x4050 +#define WM831X_LDO_ENABLE 0x4051 +#define WM831X_DCDC_STATUS 0x4052 +#define WM831X_LDO_STATUS 0x4053 +#define WM831X_DCDC_UV_STATUS 0x4054 +#define WM831X_LDO_UV_STATUS 0x4055 +#define WM831X_DC1_CONTROL_1 0x4056 +#define WM831X_DC1_CONTROL_2 0x4057 +#define WM831X_DC1_ON_CONFIG 0x4058 +#define WM831X_DC1_SLEEP_CONTROL 0x4059 +#define WM831X_DC1_DVS_CONTROL 0x405A +#define WM831X_DC2_CONTROL_1 0x405B +#define WM831X_DC2_CONTROL_2 0x405C +#define WM831X_DC2_ON_CONFIG 0x405D +#define WM831X_DC2_SLEEP_CONTROL 0x405E +#define WM831X_DC2_DVS_CONTROL 0x405F +#define WM831X_DC3_CONTROL_1 0x4060 +#define WM831X_DC3_CONTROL_2 0x4061 +#define WM831X_DC3_ON_CONFIG 0x4062 +#define WM831X_DC3_SLEEP_CONTROL 0x4063 +#define WM831X_DC4_CONTROL 0x4064 +#define WM831X_DC4_SLEEP_CONTROL 0x4065 +#define WM831X_EPE1_CONTROL 0x4066 +#define WM831X_EPE2_CONTROL 0x4067 +#define WM831X_LDO1_CONTROL 0x4068 +#define WM831X_LDO1_ON_CONTROL 0x4069 +#define WM831X_LDO1_SLEEP_CONTROL 0x406A +#define WM831X_LDO2_CONTROL 0x406B +#define WM831X_LDO2_ON_CONTROL 0x406C +#define WM831X_LDO2_SLEEP_CONTROL 0x406D +#define WM831X_LDO3_CONTROL 0x406E +#define WM831X_LDO3_ON_CONTROL 0x406F +#define WM831X_LDO3_SLEEP_CONTROL 0x4070 +#define WM831X_LDO4_CONTROL 0x4071 +#define WM831X_LDO4_ON_CONTROL 0x4072 +#define WM831X_LDO4_SLEEP_CONTROL 0x4073 +#define WM831X_LDO5_CONTROL 0x4074 +#define WM831X_LDO5_ON_CONTROL 0x4075 +#define WM831X_LDO5_SLEEP_CONTROL 0x4076 +#define WM831X_LDO6_CONTROL 0x4077 +#define WM831X_LDO6_ON_CONTROL 0x4078 +#define WM831X_LDO6_SLEEP_CONTROL 0x4079 +#define WM831X_LDO7_CONTROL 0x407A +#define WM831X_LDO7_ON_CONTROL 0x407B +#define WM831X_LDO7_SLEEP_CONTROL 0x407C +#define WM831X_LDO8_CONTROL 0x407D +#define WM831X_LDO8_ON_CONTROL 0x407E +#define WM831X_LDO8_SLEEP_CONTROL 0x407F +#define WM831X_LDO9_CONTROL 0x4080 +#define WM831X_LDO9_ON_CONTROL 0x4081 +#define WM831X_LDO9_SLEEP_CONTROL 0x4082 +#define WM831X_LDO10_CONTROL 0x4083 +#define WM831X_LDO10_ON_CONTROL 0x4084 +#define WM831X_LDO10_SLEEP_CONTROL 0x4085 +#define WM831X_LDO11_ON_CONTROL 0x4087 +#define WM831X_LDO11_SLEEP_CONTROL 0x4088 +#define WM831X_POWER_GOOD_SOURCE_1 0x408E +#define WM831X_POWER_GOOD_SOURCE_2 0x408F +#define WM831X_CLOCK_CONTROL_1 0x4090 +#define WM831X_CLOCK_CONTROL_2 0x4091 +#define WM831X_FLL_CONTROL_1 0x4092 +#define WM831X_FLL_CONTROL_2 0x4093 +#define WM831X_FLL_CONTROL_3 0x4094 +#define WM831X_FLL_CONTROL_4 0x4095 +#define WM831X_FLL_CONTROL_5 0x4096 +#define WM831X_UNIQUE_ID_1 0x7800 +#define WM831X_UNIQUE_ID_2 0x7801 +#define WM831X_UNIQUE_ID_3 0x7802 +#define WM831X_UNIQUE_ID_4 0x7803 +#define WM831X_UNIQUE_ID_5 0x7804 +#define WM831X_UNIQUE_ID_6 0x7805 +#define WM831X_UNIQUE_ID_7 0x7806 +#define WM831X_UNIQUE_ID_8 0x7807 +#define WM831X_FACTORY_OTP_ID 0x7808 +#define WM831X_FACTORY_OTP_1 0x7809 +#define WM831X_FACTORY_OTP_2 0x780A +#define WM831X_FACTORY_OTP_3 0x780B +#define WM831X_FACTORY_OTP_4 0x780C +#define WM831X_FACTORY_OTP_5 0x780D +#define WM831X_CUSTOMER_OTP_ID 0x7810 +#define WM831X_DC1_OTP_CONTROL 0x7811 +#define WM831X_DC2_OTP_CONTROL 0x7812 +#define WM831X_DC3_OTP_CONTROL 0x7813 +#define WM831X_LDO1_2_OTP_CONTROL 0x7814 +#define WM831X_LDO3_4_OTP_CONTROL 0x7815 +#define WM831X_LDO5_6_OTP_CONTROL 0x7816 +#define WM831X_LDO7_8_OTP_CONTROL 0x7817 +#define WM831X_LDO9_10_OTP_CONTROL 0x7818 +#define WM831X_LDO11_EPE_CONTROL 0x7819 +#define WM831X_GPIO1_OTP_CONTROL 0x781A +#define WM831X_GPIO2_OTP_CONTROL 0x781B +#define WM831X_GPIO3_OTP_CONTROL 0x781C +#define WM831X_GPIO4_OTP_CONTROL 0x781D +#define WM831X_GPIO5_OTP_CONTROL 0x781E +#define WM831X_GPIO6_OTP_CONTROL 0x781F +#define WM831X_DBE_CHECK_DATA 0x7827 + +/* + * R0 (0x00) - Reset ID + */ +#define WM831X_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */ +#define WM831X_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */ +#define WM831X_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - Revision + */ +#define WM831X_PARENT_REV_MASK 0xFF00 /* PARENT_REV - [15:8] */ +#define WM831X_PARENT_REV_SHIFT 8 /* PARENT_REV - [15:8] */ +#define WM831X_PARENT_REV_WIDTH 8 /* PARENT_REV - [15:8] */ +#define WM831X_CHILD_REV_MASK 0x00FF /* CHILD_REV - [7:0] */ +#define WM831X_CHILD_REV_SHIFT 0 /* CHILD_REV - [7:0] */ +#define WM831X_CHILD_REV_WIDTH 8 /* CHILD_REV - [7:0] */ + +/* + * R16384 (0x4000) - Parent ID + */ +#define WM831X_PARENT_ID_MASK 0xFFFF /* PARENT_ID - [15:0] */ +#define WM831X_PARENT_ID_SHIFT 0 /* PARENT_ID - [15:0] */ +#define WM831X_PARENT_ID_WIDTH 16 /* PARENT_ID - [15:0] */ + +struct wm831x { + struct mutex io_lock; + + struct device *dev; + int (*read_dev)(struct wm831x *wm831x, unsigned short reg, + int bytes, void *dest); + int (*write_dev)(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src); + + void *control_data; + + /* The WM831x has a security key blocking access to certain + * registers. The mutex is taken by the accessors for locking + * and unlocking the security key, locked is used to fail + * writes if the lock is held. + */ + struct mutex key_lock; + unsigned int locked:1; +}; + +/* Device I/O API */ +int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg); +int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg, + unsigned short val); +void wm831x_reg_lock(struct wm831x *wm831x); +int wm831x_reg_unlock(struct wm831x *wm831x); +int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, + unsigned short mask, unsigned short val); +int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, + int count, u16 *buf); + +#endif diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h new file mode 100644 index 0000000..571e601 --- /dev/null +++ b/include/linux/mfd/wm831x/pdata.h @@ -0,0 +1,107 @@ +/* + * include/linux/mfd/wm831x/pdata.h -- Platform data for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_PDATA_H__ +#define __MFD_WM831X_PDATA_H__ + +struct wm831x; +struct regulator_init_data; + +struct wm831x_backup_pdata { + int charger_enable; + int no_constant_voltage; /** Disable constant voltage charging */ + int vlim; /** Voltage limit in milivolts */ + int ilim; /** Current limit in microamps */ +}; + +struct wm831x_battery_pdata { + int enable; /** Enable charging */ + int fast_enable; /** Enable fast charging */ + int off_mask; /** Mask OFF while charging */ + int trickle_ilim; /** Trickle charge current limit, in mA */ + int vsel; /** Target voltage, in mV */ + int eoc_iterm; /** End of trickle charge current, in mA */ + int fast_ilim; /** Fast charge current limit, in mA */ + int timeout; /** Charge cycle timeout, in minutes */ +}; + +/* Sources for status LED configuration. Values are register values + * plus 1 to allow for a zero default for preserve. + */ +enum wm831x_status_src { + WM831X_STATUS_PRESERVE = 0, /* Keep the current hardware setting */ + WM831X_STATUS_OTP = 1, + WM831X_STATUS_POWER = 2, + WM831X_STATUS_CHARGER = 3, + WM831X_STATUS_MANUAL = 4, +}; + +struct wm831x_status_pdata { + enum wm831x_status_src default_src; + const char *name; + const char *default_trigger; +}; + +struct wm831x_touch_pdata { + int fivewire; /** 1 for five wire mode, 0 for 4 wire */ + int isel; /** Current for pen down (uA) */ + int rpu; /** Pen down sensitivity resistor divider */ + int pressure; /** Report pressure (boolean) */ + int data_irq; /** Touch data ready IRQ */ +}; + +enum wm831x_watchdog_action { + WM831X_WDOG_NONE = 0, + WM831X_WDOG_INTERRUPT = 1, + WM831X_WDOG_RESET = 2, + WM831X_WDOG_WAKE = 3, +}; + +struct wm831x_watchdog_pdata { + enum wm831x_watchdog_action primary, secondary; + int update_gpio; + unsigned int software:1; +}; + +#define WM831X_MAX_STATUS 2 +#define WM831X_MAX_DCDC 4 +#define WM831X_MAX_EPE 2 +#define WM831X_MAX_LDO 11 +#define WM831X_MAX_ISINK 2 + +struct wm831x_pdata { + /** Called before subdevices are set up */ + int (*pre_init)(struct wm831x *wm831x); + /** Called after subdevices are set up */ + int (*post_init)(struct wm831x *wm831x); + + int gpio_base; + struct wm831x_backup_pdata *backup; + struct wm831x_battery_pdata *battery; + struct wm831x_touch_pdata *touch; + struct wm831x_watchdog_pdata *watchdog; + + /** LED1 = 0 and so on */ + struct wm831x_status_pdata *status[WM831X_MAX_STATUS]; + /** DCDC1 = 0 and so on */ + struct regulator_init_data *dcdc[WM831X_MAX_DCDC]; + /** EPE1 = 0 and so on */ + struct regulator_init_data *epe[WM831X_MAX_EPE]; + /** LDO1 = 0 and so on */ + struct regulator_init_data *ldo[WM831X_MAX_LDO]; + /** ISINK1 = 0 and so on*/ + struct regulator_init_data *isink[WM831X_MAX_ISINK]; +}; + +#endif -- cgit v0.10.2 From 7d4d0a3e7343e3190afaa17253073db58e3d9bff Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:53 +0100 Subject: mfd: Add WM831x interrupt support The WM831x includes an interrupt controller managing interrupts for the various functions on the chip. This patch adds support for the core interrupt block on the device. Ideally this would be supported by genirq, particularly for the GPIOs, but currently genirq is unable to cope with controllers on interrupt driven buses so we cut'n'paste the generic interface. Once genirq is able to cope chips like this it should be a case of filing the prefixes off the code and redoing wm831x-irq.c to move over. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index cc1040c..eb63d22 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -19,6 +19,7 @@ #include #include +#include enum wm831x_parent { WM8310 = 0, @@ -1189,6 +1190,10 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) } } + ret = wm831x_irq_init(wm831x, irq); + if (ret != 0) + goto err; + /* The core device is up, instantiate the subdevices. */ switch (parent) { case WM8310: @@ -1216,19 +1221,21 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) if (ret != 0) { dev_err(wm831x->dev, "Failed to add children\n"); - goto err; + goto err_irq; } if (pdata && pdata->post_init) { ret = pdata->post_init(wm831x); if (ret != 0) { dev_err(wm831x->dev, "post_init() failed: %d\n", ret); - goto err; + goto err_irq; } } return 0; +err_irq: + wm831x_irq_exit(wm831x); err: mfd_remove_devices(wm831x->dev); kfree(wm831x); @@ -1238,6 +1245,7 @@ err: static void wm831x_device_exit(struct wm831x *wm831x) { mfd_remove_devices(wm831x->dev); + wm831x_irq_exit(wm831x); kfree(wm831x); } diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c new file mode 100644 index 0000000..d3015df --- /dev/null +++ b/drivers/mfd/wm831x-irq.c @@ -0,0 +1,559 @@ +/* + * wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* + * Since generic IRQs don't currently support interrupt controllers on + * interrupt driven buses we don't use genirq but instead provide an + * interface that looks very much like the standard ones. This leads + * to some bodges, including storing interrupt handler information in + * the static irq_data table we use to look up the data for individual + * interrupts, but hopefully won't last too long. + */ + +struct wm831x_irq_data { + int primary; + int reg; + int mask; + irq_handler_t handler; + void *handler_data; +}; + +static struct wm831x_irq_data wm831x_irqs[] = { + [WM831X_IRQ_TEMP_THW] = { + .primary = WM831X_TEMP_INT, + .reg = 1, + .mask = WM831X_TEMP_THW_EINT, + }, + [WM831X_IRQ_GPIO_1] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP1_EINT, + }, + [WM831X_IRQ_GPIO_2] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP2_EINT, + }, + [WM831X_IRQ_GPIO_3] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP3_EINT, + }, + [WM831X_IRQ_GPIO_4] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP4_EINT, + }, + [WM831X_IRQ_GPIO_5] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP5_EINT, + }, + [WM831X_IRQ_GPIO_6] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP6_EINT, + }, + [WM831X_IRQ_GPIO_7] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP7_EINT, + }, + [WM831X_IRQ_GPIO_8] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP8_EINT, + }, + [WM831X_IRQ_GPIO_9] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP9_EINT, + }, + [WM831X_IRQ_GPIO_10] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP10_EINT, + }, + [WM831X_IRQ_GPIO_11] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP11_EINT, + }, + [WM831X_IRQ_GPIO_12] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP12_EINT, + }, + [WM831X_IRQ_GPIO_13] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP13_EINT, + }, + [WM831X_IRQ_GPIO_14] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP14_EINT, + }, + [WM831X_IRQ_GPIO_15] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP15_EINT, + }, + [WM831X_IRQ_GPIO_16] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP16_EINT, + }, + [WM831X_IRQ_ON] = { + .primary = WM831X_ON_PIN_INT, + .reg = 1, + .mask = WM831X_ON_PIN_EINT, + }, + [WM831X_IRQ_PPM_SYSLO] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_SYSLO_EINT, + }, + [WM831X_IRQ_PPM_PWR_SRC] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_PWR_SRC_EINT, + }, + [WM831X_IRQ_PPM_USB_CURR] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_USB_CURR_EINT, + }, + [WM831X_IRQ_WDOG_TO] = { + .primary = WM831X_WDOG_INT, + .reg = 1, + .mask = WM831X_WDOG_TO_EINT, + }, + [WM831X_IRQ_RTC_PER] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_PER_EINT, + }, + [WM831X_IRQ_RTC_ALM] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_ALM_EINT, + }, + [WM831X_IRQ_CHG_BATT_HOT] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_HOT_EINT, + }, + [WM831X_IRQ_CHG_BATT_COLD] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_COLD_EINT, + }, + [WM831X_IRQ_CHG_BATT_FAIL] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_FAIL_EINT, + }, + [WM831X_IRQ_CHG_OV] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_OV_EINT, + }, + [WM831X_IRQ_CHG_END] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_END_EINT, + }, + [WM831X_IRQ_CHG_TO] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_TO_EINT, + }, + [WM831X_IRQ_CHG_MODE] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_MODE_EINT, + }, + [WM831X_IRQ_CHG_START] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_START_EINT, + }, + [WM831X_IRQ_TCHDATA] = { + .primary = WM831X_TCHDATA_INT, + .reg = 1, + .mask = WM831X_TCHDATA_EINT, + }, + [WM831X_IRQ_TCHPD] = { + .primary = WM831X_TCHPD_INT, + .reg = 1, + .mask = WM831X_TCHPD_EINT, + }, + [WM831X_IRQ_AUXADC_DATA] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DATA_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP1] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP1_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP2] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP2_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP3] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP3_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP4] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP4_EINT, + }, + [WM831X_IRQ_CS1] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS1_EINT, + }, + [WM831X_IRQ_CS2] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS2_EINT, + }, + [WM831X_IRQ_HC_DC1] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC1_EINT, + }, + [WM831X_IRQ_HC_DC2] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC2_EINT, + }, + [WM831X_IRQ_UV_LDO1] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO1_EINT, + }, + [WM831X_IRQ_UV_LDO2] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO2_EINT, + }, + [WM831X_IRQ_UV_LDO3] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO3_EINT, + }, + [WM831X_IRQ_UV_LDO4] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO4_EINT, + }, + [WM831X_IRQ_UV_LDO5] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO5_EINT, + }, + [WM831X_IRQ_UV_LDO6] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO6_EINT, + }, + [WM831X_IRQ_UV_LDO7] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO7_EINT, + }, + [WM831X_IRQ_UV_LDO8] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO8_EINT, + }, + [WM831X_IRQ_UV_LDO9] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO9_EINT, + }, + [WM831X_IRQ_UV_LDO10] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO10_EINT, + }, + [WM831X_IRQ_UV_DC1] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC1_EINT, + }, + [WM831X_IRQ_UV_DC2] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC2_EINT, + }, + [WM831X_IRQ_UV_DC3] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC3_EINT, + }, + [WM831X_IRQ_UV_DC4] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC4_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline int irq_data_to_mask_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; +} + +static void __wm831x_enable_irq(struct wm831x *wm831x, int irq) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + wm831x->irq_masks[irq_data->reg - 1] &= ~irq_data->mask; + wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), + wm831x->irq_masks[irq_data->reg - 1]); +} + +void wm831x_enable_irq(struct wm831x *wm831x, int irq) +{ + mutex_lock(&wm831x->irq_lock); + __wm831x_enable_irq(wm831x, irq); + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_enable_irq); + +static void __wm831x_disable_irq(struct wm831x *wm831x, int irq) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + wm831x->irq_masks[irq_data->reg - 1] |= irq_data->mask; + wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), + wm831x->irq_masks[irq_data->reg - 1]); +} + +void wm831x_disable_irq(struct wm831x *wm831x, int irq) +{ + mutex_lock(&wm831x->irq_lock); + __wm831x_disable_irq(wm831x, irq); + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_disable_irq); + +int wm831x_request_irq(struct wm831x *wm831x, + unsigned int irq, irq_handler_t handler, + unsigned long flags, const char *name, + void *dev) +{ + int ret = 0; + + if (irq < 0 || irq >= WM831X_NUM_IRQS) + return -EINVAL; + + mutex_lock(&wm831x->irq_lock); + + if (wm831x_irqs[irq].handler) { + dev_err(wm831x->dev, "Already have handler for IRQ %d\n", irq); + ret = -EINVAL; + goto out; + } + + wm831x_irqs[irq].handler = handler; + wm831x_irqs[irq].handler_data = dev; + + __wm831x_enable_irq(wm831x, irq); + +out: + mutex_unlock(&wm831x->irq_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_request_irq); + +void wm831x_free_irq(struct wm831x *wm831x, unsigned int irq, void *data) +{ + if (irq < 0 || irq >= WM831X_NUM_IRQS) + return; + + mutex_lock(&wm831x->irq_lock); + + wm831x_irqs[irq].handler = NULL; + wm831x_irqs[irq].handler_data = NULL; + + __wm831x_disable_irq(wm831x, irq); + + mutex_unlock(&wm831x->irq_lock); +} +EXPORT_SYMBOL_GPL(wm831x_free_irq); + + +static void wm831x_handle_irq(struct wm831x *wm831x, int irq, int status) +{ + struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + + if (irq_data->handler) { + irq_data->handler(irq, irq_data->handler_data); + wm831x_reg_write(wm831x, irq_data_to_status_reg(irq_data), + irq_data->mask); + } else { + dev_err(wm831x->dev, "Unhandled IRQ %d, masking\n", irq); + __wm831x_disable_irq(wm831x, irq); + } +} + +/* Main interrupt handling occurs in a workqueue since we need + * interrupts enabled to interact with the chip. */ +static void wm831x_irq_worker(struct work_struct *work) +{ + struct wm831x *wm831x = container_of(work, struct wm831x, irq_work); + unsigned int i; + int primary; + int status_regs[5]; + int read[5] = { 0 }; + int *status; + + primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS); + if (primary < 0) { + dev_err(wm831x->dev, "Failed to read system interrupt: %d\n", + primary); + goto out; + } + + mutex_lock(&wm831x->irq_lock); + + for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) { + int offset = wm831x_irqs[i].reg - 1; + + if (!(primary & wm831x_irqs[i].primary)) + continue; + + status = &status_regs[offset]; + + /* Hopefully there should only be one register to read + * each time otherwise we ought to do a block read. */ + if (!read[offset]) { + *status = wm831x_reg_read(wm831x, + irq_data_to_status_reg(&wm831x_irqs[i])); + if (*status < 0) { + dev_err(wm831x->dev, + "Failed to read IRQ status: %d\n", + *status); + goto out_lock; + } + + /* Mask out the disabled IRQs */ + *status &= ~wm831x->irq_masks[offset]; + read[offset] = 1; + } + + if (*status & wm831x_irqs[i].mask) + wm831x_handle_irq(wm831x, i, *status); + } + +out_lock: + mutex_unlock(&wm831x->irq_lock); +out: + enable_irq(wm831x->irq); +} + + +static irqreturn_t wm831x_cpu_irq(int irq, void *data) +{ + struct wm831x *wm831x = data; + + /* Shut the interrupt to the CPU up and schedule the actual + * handler; we can't check that the IRQ is asserted. */ + disable_irq_nosync(irq); + + queue_work(wm831x->irq_wq, &wm831x->irq_work); + + return IRQ_HANDLED; +} + +int wm831x_irq_init(struct wm831x *wm831x, int irq) +{ + int i, ret; + + if (!irq) { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); + return 0; + } + + + wm831x->irq_wq = create_singlethread_workqueue("wm831x-irq"); + if (!wm831x->irq_wq) { + dev_err(wm831x->dev, "Failed to allocate IRQ worker\n"); + return -ESRCH; + } + + wm831x->irq = irq; + mutex_init(&wm831x->irq_lock); + INIT_WORK(&wm831x->irq_work, wm831x_irq_worker); + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks); i++) { + wm831x->irq_masks[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + /* Enable top level interrupts, we mask at secondary level */ + wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); + + /* We're good to go. We set IRQF_SHARED since there's a + * chance the driver will interoperate with another driver but + * the need to disable the IRQ while handing via I2C/SPI means + * that this may break and performance will be impacted. If + * this does happen it's a hardware design issue and the only + * other alternative would be polling. + */ + ret = request_irq(irq, wm831x_cpu_irq, IRQF_TRIGGER_LOW | IRQF_SHARED, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + + return 0; +} + +void wm831x_irq_exit(struct wm831x *wm831x) +{ + if (wm831x->irq) + free_irq(wm831x->irq, wm831x); +} diff --git a/include/linux/mfd/wm831x/core.h b/include/linux/mfd/wm831x/core.h index d90e693..b96c935 100644 --- a/include/linux/mfd/wm831x/core.h +++ b/include/linux/mfd/wm831x/core.h @@ -15,6 +15,9 @@ #ifndef __MFD_WM831X_CORE_H__ #define __MFD_WM831X_CORE_H__ +#include +#include + /* * Register values. */ @@ -224,6 +227,13 @@ struct wm831x { void *control_data; + int irq; /* Our chip IRQ */ + struct mutex irq_lock; + struct workqueue_struct *irq_wq; + struct work_struct irq_work; + unsigned int irq_base; + int irq_masks[5]; + /* The WM831x has a security key blocking access to certain * registers. The mutex is taken by the accessors for locking * and unlocking the security key, locked is used to fail @@ -244,4 +254,15 @@ int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, int count, u16 *buf); +int wm831x_irq_init(struct wm831x *wm831x, int irq); +void wm831x_irq_exit(struct wm831x *wm831x); + +int __must_check wm831x_request_irq(struct wm831x *wm831x, + unsigned int irq, irq_handler_t handler, + unsigned long flags, const char *name, + void *dev); +void wm831x_free_irq(struct wm831x *wm831x, unsigned int, void *); +void wm831x_disable_irq(struct wm831x *wm831x, int irq); +void wm831x_enable_irq(struct wm831x *wm831x, int irq); + #endif diff --git a/include/linux/mfd/wm831x/irq.h b/include/linux/mfd/wm831x/irq.h new file mode 100644 index 0000000..3a8c976 --- /dev/null +++ b/include/linux/mfd/wm831x/irq.h @@ -0,0 +1,764 @@ +/* + * include/linux/mfd/wm831x/irq.h -- Interrupt controller for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_IRQ_H__ +#define __MFD_WM831X_IRQ_H__ + +/* Interrupt number assignments within Linux */ +#define WM831X_IRQ_TEMP_THW 0 +#define WM831X_IRQ_GPIO_1 1 +#define WM831X_IRQ_GPIO_2 2 +#define WM831X_IRQ_GPIO_3 3 +#define WM831X_IRQ_GPIO_4 4 +#define WM831X_IRQ_GPIO_5 5 +#define WM831X_IRQ_GPIO_6 6 +#define WM831X_IRQ_GPIO_7 7 +#define WM831X_IRQ_GPIO_8 8 +#define WM831X_IRQ_GPIO_9 9 +#define WM831X_IRQ_GPIO_10 10 +#define WM831X_IRQ_GPIO_11 11 +#define WM831X_IRQ_GPIO_12 12 +#define WM831X_IRQ_GPIO_13 13 +#define WM831X_IRQ_GPIO_14 14 +#define WM831X_IRQ_GPIO_15 15 +#define WM831X_IRQ_GPIO_16 16 +#define WM831X_IRQ_ON 17 +#define WM831X_IRQ_PPM_SYSLO 18 +#define WM831X_IRQ_PPM_PWR_SRC 19 +#define WM831X_IRQ_PPM_USB_CURR 20 +#define WM831X_IRQ_WDOG_TO 21 +#define WM831X_IRQ_RTC_PER 22 +#define WM831X_IRQ_RTC_ALM 23 +#define WM831X_IRQ_CHG_BATT_HOT 24 +#define WM831X_IRQ_CHG_BATT_COLD 25 +#define WM831X_IRQ_CHG_BATT_FAIL 26 +#define WM831X_IRQ_CHG_OV 27 +#define WM831X_IRQ_CHG_END 29 +#define WM831X_IRQ_CHG_TO 30 +#define WM831X_IRQ_CHG_MODE 31 +#define WM831X_IRQ_CHG_START 32 +#define WM831X_IRQ_TCHDATA 33 +#define WM831X_IRQ_TCHPD 34 +#define WM831X_IRQ_AUXADC_DATA 35 +#define WM831X_IRQ_AUXADC_DCOMP1 36 +#define WM831X_IRQ_AUXADC_DCOMP2 37 +#define WM831X_IRQ_AUXADC_DCOMP3 38 +#define WM831X_IRQ_AUXADC_DCOMP4 39 +#define WM831X_IRQ_CS1 40 +#define WM831X_IRQ_CS2 41 +#define WM831X_IRQ_HC_DC1 42 +#define WM831X_IRQ_HC_DC2 43 +#define WM831X_IRQ_UV_LDO1 44 +#define WM831X_IRQ_UV_LDO2 45 +#define WM831X_IRQ_UV_LDO3 46 +#define WM831X_IRQ_UV_LDO4 47 +#define WM831X_IRQ_UV_LDO5 48 +#define WM831X_IRQ_UV_LDO6 49 +#define WM831X_IRQ_UV_LDO7 50 +#define WM831X_IRQ_UV_LDO8 51 +#define WM831X_IRQ_UV_LDO9 52 +#define WM831X_IRQ_UV_LDO10 53 +#define WM831X_IRQ_UV_DC1 54 +#define WM831X_IRQ_UV_DC2 55 +#define WM831X_IRQ_UV_DC3 56 +#define WM831X_IRQ_UV_DC4 57 + +#define WM831X_NUM_IRQS 58 + +/* + * R16400 (0x4010) - System Interrupts + */ +#define WM831X_PS_INT 0x8000 /* PS_INT */ +#define WM831X_PS_INT_MASK 0x8000 /* PS_INT */ +#define WM831X_PS_INT_SHIFT 15 /* PS_INT */ +#define WM831X_PS_INT_WIDTH 1 /* PS_INT */ +#define WM831X_TEMP_INT 0x4000 /* TEMP_INT */ +#define WM831X_TEMP_INT_MASK 0x4000 /* TEMP_INT */ +#define WM831X_TEMP_INT_SHIFT 14 /* TEMP_INT */ +#define WM831X_TEMP_INT_WIDTH 1 /* TEMP_INT */ +#define WM831X_GP_INT 0x2000 /* GP_INT */ +#define WM831X_GP_INT_MASK 0x2000 /* GP_INT */ +#define WM831X_GP_INT_SHIFT 13 /* GP_INT */ +#define WM831X_GP_INT_WIDTH 1 /* GP_INT */ +#define WM831X_ON_PIN_INT 0x1000 /* ON_PIN_INT */ +#define WM831X_ON_PIN_INT_MASK 0x1000 /* ON_PIN_INT */ +#define WM831X_ON_PIN_INT_SHIFT 12 /* ON_PIN_INT */ +#define WM831X_ON_PIN_INT_WIDTH 1 /* ON_PIN_INT */ +#define WM831X_WDOG_INT 0x0800 /* WDOG_INT */ +#define WM831X_WDOG_INT_MASK 0x0800 /* WDOG_INT */ +#define WM831X_WDOG_INT_SHIFT 11 /* WDOG_INT */ +#define WM831X_WDOG_INT_WIDTH 1 /* WDOG_INT */ +#define WM831X_TCHDATA_INT 0x0400 /* TCHDATA_INT */ +#define WM831X_TCHDATA_INT_MASK 0x0400 /* TCHDATA_INT */ +#define WM831X_TCHDATA_INT_SHIFT 10 /* TCHDATA_INT */ +#define WM831X_TCHDATA_INT_WIDTH 1 /* TCHDATA_INT */ +#define WM831X_TCHPD_INT 0x0200 /* TCHPD_INT */ +#define WM831X_TCHPD_INT_MASK 0x0200 /* TCHPD_INT */ +#define WM831X_TCHPD_INT_SHIFT 9 /* TCHPD_INT */ +#define WM831X_TCHPD_INT_WIDTH 1 /* TCHPD_INT */ +#define WM831X_AUXADC_INT 0x0100 /* AUXADC_INT */ +#define WM831X_AUXADC_INT_MASK 0x0100 /* AUXADC_INT */ +#define WM831X_AUXADC_INT_SHIFT 8 /* AUXADC_INT */ +#define WM831X_AUXADC_INT_WIDTH 1 /* AUXADC_INT */ +#define WM831X_PPM_INT 0x0080 /* PPM_INT */ +#define WM831X_PPM_INT_MASK 0x0080 /* PPM_INT */ +#define WM831X_PPM_INT_SHIFT 7 /* PPM_INT */ +#define WM831X_PPM_INT_WIDTH 1 /* PPM_INT */ +#define WM831X_CS_INT 0x0040 /* CS_INT */ +#define WM831X_CS_INT_MASK 0x0040 /* CS_INT */ +#define WM831X_CS_INT_SHIFT 6 /* CS_INT */ +#define WM831X_CS_INT_WIDTH 1 /* CS_INT */ +#define WM831X_RTC_INT 0x0020 /* RTC_INT */ +#define WM831X_RTC_INT_MASK 0x0020 /* RTC_INT */ +#define WM831X_RTC_INT_SHIFT 5 /* RTC_INT */ +#define WM831X_RTC_INT_WIDTH 1 /* RTC_INT */ +#define WM831X_OTP_INT 0x0010 /* OTP_INT */ +#define WM831X_OTP_INT_MASK 0x0010 /* OTP_INT */ +#define WM831X_OTP_INT_SHIFT 4 /* OTP_INT */ +#define WM831X_OTP_INT_WIDTH 1 /* OTP_INT */ +#define WM831X_CHILD_INT 0x0008 /* CHILD_INT */ +#define WM831X_CHILD_INT_MASK 0x0008 /* CHILD_INT */ +#define WM831X_CHILD_INT_SHIFT 3 /* CHILD_INT */ +#define WM831X_CHILD_INT_WIDTH 1 /* CHILD_INT */ +#define WM831X_CHG_INT 0x0004 /* CHG_INT */ +#define WM831X_CHG_INT_MASK 0x0004 /* CHG_INT */ +#define WM831X_CHG_INT_SHIFT 2 /* CHG_INT */ +#define WM831X_CHG_INT_WIDTH 1 /* CHG_INT */ +#define WM831X_HC_INT 0x0002 /* HC_INT */ +#define WM831X_HC_INT_MASK 0x0002 /* HC_INT */ +#define WM831X_HC_INT_SHIFT 1 /* HC_INT */ +#define WM831X_HC_INT_WIDTH 1 /* HC_INT */ +#define WM831X_UV_INT 0x0001 /* UV_INT */ +#define WM831X_UV_INT_MASK 0x0001 /* UV_INT */ +#define WM831X_UV_INT_SHIFT 0 /* UV_INT */ +#define WM831X_UV_INT_WIDTH 1 /* UV_INT */ + +/* + * R16401 (0x4011) - Interrupt Status 1 + */ +#define WM831X_PPM_SYSLO_EINT 0x8000 /* PPM_SYSLO_EINT */ +#define WM831X_PPM_SYSLO_EINT_MASK 0x8000 /* PPM_SYSLO_EINT */ +#define WM831X_PPM_SYSLO_EINT_SHIFT 15 /* PPM_SYSLO_EINT */ +#define WM831X_PPM_SYSLO_EINT_WIDTH 1 /* PPM_SYSLO_EINT */ +#define WM831X_PPM_PWR_SRC_EINT 0x4000 /* PPM_PWR_SRC_EINT */ +#define WM831X_PPM_PWR_SRC_EINT_MASK 0x4000 /* PPM_PWR_SRC_EINT */ +#define WM831X_PPM_PWR_SRC_EINT_SHIFT 14 /* PPM_PWR_SRC_EINT */ +#define WM831X_PPM_PWR_SRC_EINT_WIDTH 1 /* PPM_PWR_SRC_EINT */ +#define WM831X_PPM_USB_CURR_EINT 0x2000 /* PPM_USB_CURR_EINT */ +#define WM831X_PPM_USB_CURR_EINT_MASK 0x2000 /* PPM_USB_CURR_EINT */ +#define WM831X_PPM_USB_CURR_EINT_SHIFT 13 /* PPM_USB_CURR_EINT */ +#define WM831X_PPM_USB_CURR_EINT_WIDTH 1 /* PPM_USB_CURR_EINT */ +#define WM831X_ON_PIN_EINT 0x1000 /* ON_PIN_EINT */ +#define WM831X_ON_PIN_EINT_MASK 0x1000 /* ON_PIN_EINT */ +#define WM831X_ON_PIN_EINT_SHIFT 12 /* ON_PIN_EINT */ +#define WM831X_ON_PIN_EINT_WIDTH 1 /* ON_PIN_EINT */ +#define WM831X_WDOG_TO_EINT 0x0800 /* WDOG_TO_EINT */ +#define WM831X_WDOG_TO_EINT_MASK 0x0800 /* WDOG_TO_EINT */ +#define WM831X_WDOG_TO_EINT_SHIFT 11 /* WDOG_TO_EINT */ +#define WM831X_WDOG_TO_EINT_WIDTH 1 /* WDOG_TO_EINT */ +#define WM831X_TCHDATA_EINT 0x0400 /* TCHDATA_EINT */ +#define WM831X_TCHDATA_EINT_MASK 0x0400 /* TCHDATA_EINT */ +#define WM831X_TCHDATA_EINT_SHIFT 10 /* TCHDATA_EINT */ +#define WM831X_TCHDATA_EINT_WIDTH 1 /* TCHDATA_EINT */ +#define WM831X_TCHPD_EINT 0x0200 /* TCHPD_EINT */ +#define WM831X_TCHPD_EINT_MASK 0x0200 /* TCHPD_EINT */ +#define WM831X_TCHPD_EINT_SHIFT 9 /* TCHPD_EINT */ +#define WM831X_TCHPD_EINT_WIDTH 1 /* TCHPD_EINT */ +#define WM831X_AUXADC_DATA_EINT 0x0100 /* AUXADC_DATA_EINT */ +#define WM831X_AUXADC_DATA_EINT_MASK 0x0100 /* AUXADC_DATA_EINT */ +#define WM831X_AUXADC_DATA_EINT_SHIFT 8 /* AUXADC_DATA_EINT */ +#define WM831X_AUXADC_DATA_EINT_WIDTH 1 /* AUXADC_DATA_EINT */ +#define WM831X_AUXADC_DCOMP4_EINT 0x0080 /* AUXADC_DCOMP4_EINT */ +#define WM831X_AUXADC_DCOMP4_EINT_MASK 0x0080 /* AUXADC_DCOMP4_EINT */ +#define WM831X_AUXADC_DCOMP4_EINT_SHIFT 7 /* AUXADC_DCOMP4_EINT */ +#define WM831X_AUXADC_DCOMP4_EINT_WIDTH 1 /* AUXADC_DCOMP4_EINT */ +#define WM831X_AUXADC_DCOMP3_EINT 0x0040 /* AUXADC_DCOMP3_EINT */ +#define WM831X_AUXADC_DCOMP3_EINT_MASK 0x0040 /* AUXADC_DCOMP3_EINT */ +#define WM831X_AUXADC_DCOMP3_EINT_SHIFT 6 /* AUXADC_DCOMP3_EINT */ +#define WM831X_AUXADC_DCOMP3_EINT_WIDTH 1 /* AUXADC_DCOMP3_EINT */ +#define WM831X_AUXADC_DCOMP2_EINT 0x0020 /* AUXADC_DCOMP2_EINT */ +#define WM831X_AUXADC_DCOMP2_EINT_MASK 0x0020 /* AUXADC_DCOMP2_EINT */ +#define WM831X_AUXADC_DCOMP2_EINT_SHIFT 5 /* AUXADC_DCOMP2_EINT */ +#define WM831X_AUXADC_DCOMP2_EINT_WIDTH 1 /* AUXADC_DCOMP2_EINT */ +#define WM831X_AUXADC_DCOMP1_EINT 0x0010 /* AUXADC_DCOMP1_EINT */ +#define WM831X_AUXADC_DCOMP1_EINT_MASK 0x0010 /* AUXADC_DCOMP1_EINT */ +#define WM831X_AUXADC_DCOMP1_EINT_SHIFT 4 /* AUXADC_DCOMP1_EINT */ +#define WM831X_AUXADC_DCOMP1_EINT_WIDTH 1 /* AUXADC_DCOMP1_EINT */ +#define WM831X_RTC_PER_EINT 0x0008 /* RTC_PER_EINT */ +#define WM831X_RTC_PER_EINT_MASK 0x0008 /* RTC_PER_EINT */ +#define WM831X_RTC_PER_EINT_SHIFT 3 /* RTC_PER_EINT */ +#define WM831X_RTC_PER_EINT_WIDTH 1 /* RTC_PER_EINT */ +#define WM831X_RTC_ALM_EINT 0x0004 /* RTC_ALM_EINT */ +#define WM831X_RTC_ALM_EINT_MASK 0x0004 /* RTC_ALM_EINT */ +#define WM831X_RTC_ALM_EINT_SHIFT 2 /* RTC_ALM_EINT */ +#define WM831X_RTC_ALM_EINT_WIDTH 1 /* RTC_ALM_EINT */ +#define WM831X_TEMP_THW_EINT 0x0002 /* TEMP_THW_EINT */ +#define WM831X_TEMP_THW_EINT_MASK 0x0002 /* TEMP_THW_EINT */ +#define WM831X_TEMP_THW_EINT_SHIFT 1 /* TEMP_THW_EINT */ +#define WM831X_TEMP_THW_EINT_WIDTH 1 /* TEMP_THW_EINT */ + +/* + * R16402 (0x4012) - Interrupt Status 2 + */ +#define WM831X_CHG_BATT_HOT_EINT 0x8000 /* CHG_BATT_HOT_EINT */ +#define WM831X_CHG_BATT_HOT_EINT_MASK 0x8000 /* CHG_BATT_HOT_EINT */ +#define WM831X_CHG_BATT_HOT_EINT_SHIFT 15 /* CHG_BATT_HOT_EINT */ +#define WM831X_CHG_BATT_HOT_EINT_WIDTH 1 /* CHG_BATT_HOT_EINT */ +#define WM831X_CHG_BATT_COLD_EINT 0x4000 /* CHG_BATT_COLD_EINT */ +#define WM831X_CHG_BATT_COLD_EINT_MASK 0x4000 /* CHG_BATT_COLD_EINT */ +#define WM831X_CHG_BATT_COLD_EINT_SHIFT 14 /* CHG_BATT_COLD_EINT */ +#define WM831X_CHG_BATT_COLD_EINT_WIDTH 1 /* CHG_BATT_COLD_EINT */ +#define WM831X_CHG_BATT_FAIL_EINT 0x2000 /* CHG_BATT_FAIL_EINT */ +#define WM831X_CHG_BATT_FAIL_EINT_MASK 0x2000 /* CHG_BATT_FAIL_EINT */ +#define WM831X_CHG_BATT_FAIL_EINT_SHIFT 13 /* CHG_BATT_FAIL_EINT */ +#define WM831X_CHG_BATT_FAIL_EINT_WIDTH 1 /* CHG_BATT_FAIL_EINT */ +#define WM831X_CHG_OV_EINT 0x1000 /* CHG_OV_EINT */ +#define WM831X_CHG_OV_EINT_MASK 0x1000 /* CHG_OV_EINT */ +#define WM831X_CHG_OV_EINT_SHIFT 12 /* CHG_OV_EINT */ +#define WM831X_CHG_OV_EINT_WIDTH 1 /* CHG_OV_EINT */ +#define WM831X_CHG_END_EINT 0x0800 /* CHG_END_EINT */ +#define WM831X_CHG_END_EINT_MASK 0x0800 /* CHG_END_EINT */ +#define WM831X_CHG_END_EINT_SHIFT 11 /* CHG_END_EINT */ +#define WM831X_CHG_END_EINT_WIDTH 1 /* CHG_END_EINT */ +#define WM831X_CHG_TO_EINT 0x0400 /* CHG_TO_EINT */ +#define WM831X_CHG_TO_EINT_MASK 0x0400 /* CHG_TO_EINT */ +#define WM831X_CHG_TO_EINT_SHIFT 10 /* CHG_TO_EINT */ +#define WM831X_CHG_TO_EINT_WIDTH 1 /* CHG_TO_EINT */ +#define WM831X_CHG_MODE_EINT 0x0200 /* CHG_MODE_EINT */ +#define WM831X_CHG_MODE_EINT_MASK 0x0200 /* CHG_MODE_EINT */ +#define WM831X_CHG_MODE_EINT_SHIFT 9 /* CHG_MODE_EINT */ +#define WM831X_CHG_MODE_EINT_WIDTH 1 /* CHG_MODE_EINT */ +#define WM831X_CHG_START_EINT 0x0100 /* CHG_START_EINT */ +#define WM831X_CHG_START_EINT_MASK 0x0100 /* CHG_START_EINT */ +#define WM831X_CHG_START_EINT_SHIFT 8 /* CHG_START_EINT */ +#define WM831X_CHG_START_EINT_WIDTH 1 /* CHG_START_EINT */ +#define WM831X_CS2_EINT 0x0080 /* CS2_EINT */ +#define WM831X_CS2_EINT_MASK 0x0080 /* CS2_EINT */ +#define WM831X_CS2_EINT_SHIFT 7 /* CS2_EINT */ +#define WM831X_CS2_EINT_WIDTH 1 /* CS2_EINT */ +#define WM831X_CS1_EINT 0x0040 /* CS1_EINT */ +#define WM831X_CS1_EINT_MASK 0x0040 /* CS1_EINT */ +#define WM831X_CS1_EINT_SHIFT 6 /* CS1_EINT */ +#define WM831X_CS1_EINT_WIDTH 1 /* CS1_EINT */ +#define WM831X_OTP_CMD_END_EINT 0x0020 /* OTP_CMD_END_EINT */ +#define WM831X_OTP_CMD_END_EINT_MASK 0x0020 /* OTP_CMD_END_EINT */ +#define WM831X_OTP_CMD_END_EINT_SHIFT 5 /* OTP_CMD_END_EINT */ +#define WM831X_OTP_CMD_END_EINT_WIDTH 1 /* OTP_CMD_END_EINT */ +#define WM831X_OTP_ERR_EINT 0x0010 /* OTP_ERR_EINT */ +#define WM831X_OTP_ERR_EINT_MASK 0x0010 /* OTP_ERR_EINT */ +#define WM831X_OTP_ERR_EINT_SHIFT 4 /* OTP_ERR_EINT */ +#define WM831X_OTP_ERR_EINT_WIDTH 1 /* OTP_ERR_EINT */ +#define WM831X_PS_POR_EINT 0x0004 /* PS_POR_EINT */ +#define WM831X_PS_POR_EINT_MASK 0x0004 /* PS_POR_EINT */ +#define WM831X_PS_POR_EINT_SHIFT 2 /* PS_POR_EINT */ +#define WM831X_PS_POR_EINT_WIDTH 1 /* PS_POR_EINT */ +#define WM831X_PS_SLEEP_OFF_EINT 0x0002 /* PS_SLEEP_OFF_EINT */ +#define WM831X_PS_SLEEP_OFF_EINT_MASK 0x0002 /* PS_SLEEP_OFF_EINT */ +#define WM831X_PS_SLEEP_OFF_EINT_SHIFT 1 /* PS_SLEEP_OFF_EINT */ +#define WM831X_PS_SLEEP_OFF_EINT_WIDTH 1 /* PS_SLEEP_OFF_EINT */ +#define WM831X_PS_ON_WAKE_EINT 0x0001 /* PS_ON_WAKE_EINT */ +#define WM831X_PS_ON_WAKE_EINT_MASK 0x0001 /* PS_ON_WAKE_EINT */ +#define WM831X_PS_ON_WAKE_EINT_SHIFT 0 /* PS_ON_WAKE_EINT */ +#define WM831X_PS_ON_WAKE_EINT_WIDTH 1 /* PS_ON_WAKE_EINT */ + +/* + * R16403 (0x4013) - Interrupt Status 3 + */ +#define WM831X_UV_LDO10_EINT 0x0200 /* UV_LDO10_EINT */ +#define WM831X_UV_LDO10_EINT_MASK 0x0200 /* UV_LDO10_EINT */ +#define WM831X_UV_LDO10_EINT_SHIFT 9 /* UV_LDO10_EINT */ +#define WM831X_UV_LDO10_EINT_WIDTH 1 /* UV_LDO10_EINT */ +#define WM831X_UV_LDO9_EINT 0x0100 /* UV_LDO9_EINT */ +#define WM831X_UV_LDO9_EINT_MASK 0x0100 /* UV_LDO9_EINT */ +#define WM831X_UV_LDO9_EINT_SHIFT 8 /* UV_LDO9_EINT */ +#define WM831X_UV_LDO9_EINT_WIDTH 1 /* UV_LDO9_EINT */ +#define WM831X_UV_LDO8_EINT 0x0080 /* UV_LDO8_EINT */ +#define WM831X_UV_LDO8_EINT_MASK 0x0080 /* UV_LDO8_EINT */ +#define WM831X_UV_LDO8_EINT_SHIFT 7 /* UV_LDO8_EINT */ +#define WM831X_UV_LDO8_EINT_WIDTH 1 /* UV_LDO8_EINT */ +#define WM831X_UV_LDO7_EINT 0x0040 /* UV_LDO7_EINT */ +#define WM831X_UV_LDO7_EINT_MASK 0x0040 /* UV_LDO7_EINT */ +#define WM831X_UV_LDO7_EINT_SHIFT 6 /* UV_LDO7_EINT */ +#define WM831X_UV_LDO7_EINT_WIDTH 1 /* UV_LDO7_EINT */ +#define WM831X_UV_LDO6_EINT 0x0020 /* UV_LDO6_EINT */ +#define WM831X_UV_LDO6_EINT_MASK 0x0020 /* UV_LDO6_EINT */ +#define WM831X_UV_LDO6_EINT_SHIFT 5 /* UV_LDO6_EINT */ +#define WM831X_UV_LDO6_EINT_WIDTH 1 /* UV_LDO6_EINT */ +#define WM831X_UV_LDO5_EINT 0x0010 /* UV_LDO5_EINT */ +#define WM831X_UV_LDO5_EINT_MASK 0x0010 /* UV_LDO5_EINT */ +#define WM831X_UV_LDO5_EINT_SHIFT 4 /* UV_LDO5_EINT */ +#define WM831X_UV_LDO5_EINT_WIDTH 1 /* UV_LDO5_EINT */ +#define WM831X_UV_LDO4_EINT 0x0008 /* UV_LDO4_EINT */ +#define WM831X_UV_LDO4_EINT_MASK 0x0008 /* UV_LDO4_EINT */ +#define WM831X_UV_LDO4_EINT_SHIFT 3 /* UV_LDO4_EINT */ +#define WM831X_UV_LDO4_EINT_WIDTH 1 /* UV_LDO4_EINT */ +#define WM831X_UV_LDO3_EINT 0x0004 /* UV_LDO3_EINT */ +#define WM831X_UV_LDO3_EINT_MASK 0x0004 /* UV_LDO3_EINT */ +#define WM831X_UV_LDO3_EINT_SHIFT 2 /* UV_LDO3_EINT */ +#define WM831X_UV_LDO3_EINT_WIDTH 1 /* UV_LDO3_EINT */ +#define WM831X_UV_LDO2_EINT 0x0002 /* UV_LDO2_EINT */ +#define WM831X_UV_LDO2_EINT_MASK 0x0002 /* UV_LDO2_EINT */ +#define WM831X_UV_LDO2_EINT_SHIFT 1 /* UV_LDO2_EINT */ +#define WM831X_UV_LDO2_EINT_WIDTH 1 /* UV_LDO2_EINT */ +#define WM831X_UV_LDO1_EINT 0x0001 /* UV_LDO1_EINT */ +#define WM831X_UV_LDO1_EINT_MASK 0x0001 /* UV_LDO1_EINT */ +#define WM831X_UV_LDO1_EINT_SHIFT 0 /* UV_LDO1_EINT */ +#define WM831X_UV_LDO1_EINT_WIDTH 1 /* UV_LDO1_EINT */ + +/* + * R16404 (0x4014) - Interrupt Status 4 + */ +#define WM831X_HC_DC2_EINT 0x0200 /* HC_DC2_EINT */ +#define WM831X_HC_DC2_EINT_MASK 0x0200 /* HC_DC2_EINT */ +#define WM831X_HC_DC2_EINT_SHIFT 9 /* HC_DC2_EINT */ +#define WM831X_HC_DC2_EINT_WIDTH 1 /* HC_DC2_EINT */ +#define WM831X_HC_DC1_EINT 0x0100 /* HC_DC1_EINT */ +#define WM831X_HC_DC1_EINT_MASK 0x0100 /* HC_DC1_EINT */ +#define WM831X_HC_DC1_EINT_SHIFT 8 /* HC_DC1_EINT */ +#define WM831X_HC_DC1_EINT_WIDTH 1 /* HC_DC1_EINT */ +#define WM831X_UV_DC4_EINT 0x0008 /* UV_DC4_EINT */ +#define WM831X_UV_DC4_EINT_MASK 0x0008 /* UV_DC4_EINT */ +#define WM831X_UV_DC4_EINT_SHIFT 3 /* UV_DC4_EINT */ +#define WM831X_UV_DC4_EINT_WIDTH 1 /* UV_DC4_EINT */ +#define WM831X_UV_DC3_EINT 0x0004 /* UV_DC3_EINT */ +#define WM831X_UV_DC3_EINT_MASK 0x0004 /* UV_DC3_EINT */ +#define WM831X_UV_DC3_EINT_SHIFT 2 /* UV_DC3_EINT */ +#define WM831X_UV_DC3_EINT_WIDTH 1 /* UV_DC3_EINT */ +#define WM831X_UV_DC2_EINT 0x0002 /* UV_DC2_EINT */ +#define WM831X_UV_DC2_EINT_MASK 0x0002 /* UV_DC2_EINT */ +#define WM831X_UV_DC2_EINT_SHIFT 1 /* UV_DC2_EINT */ +#define WM831X_UV_DC2_EINT_WIDTH 1 /* UV_DC2_EINT */ +#define WM831X_UV_DC1_EINT 0x0001 /* UV_DC1_EINT */ +#define WM831X_UV_DC1_EINT_MASK 0x0001 /* UV_DC1_EINT */ +#define WM831X_UV_DC1_EINT_SHIFT 0 /* UV_DC1_EINT */ +#define WM831X_UV_DC1_EINT_WIDTH 1 /* UV_DC1_EINT */ + +/* + * R16405 (0x4015) - Interrupt Status 5 + */ +#define WM831X_GP16_EINT 0x8000 /* GP16_EINT */ +#define WM831X_GP16_EINT_MASK 0x8000 /* GP16_EINT */ +#define WM831X_GP16_EINT_SHIFT 15 /* GP16_EINT */ +#define WM831X_GP16_EINT_WIDTH 1 /* GP16_EINT */ +#define WM831X_GP15_EINT 0x4000 /* GP15_EINT */ +#define WM831X_GP15_EINT_MASK 0x4000 /* GP15_EINT */ +#define WM831X_GP15_EINT_SHIFT 14 /* GP15_EINT */ +#define WM831X_GP15_EINT_WIDTH 1 /* GP15_EINT */ +#define WM831X_GP14_EINT 0x2000 /* GP14_EINT */ +#define WM831X_GP14_EINT_MASK 0x2000 /* GP14_EINT */ +#define WM831X_GP14_EINT_SHIFT 13 /* GP14_EINT */ +#define WM831X_GP14_EINT_WIDTH 1 /* GP14_EINT */ +#define WM831X_GP13_EINT 0x1000 /* GP13_EINT */ +#define WM831X_GP13_EINT_MASK 0x1000 /* GP13_EINT */ +#define WM831X_GP13_EINT_SHIFT 12 /* GP13_EINT */ +#define WM831X_GP13_EINT_WIDTH 1 /* GP13_EINT */ +#define WM831X_GP12_EINT 0x0800 /* GP12_EINT */ +#define WM831X_GP12_EINT_MASK 0x0800 /* GP12_EINT */ +#define WM831X_GP12_EINT_SHIFT 11 /* GP12_EINT */ +#define WM831X_GP12_EINT_WIDTH 1 /* GP12_EINT */ +#define WM831X_GP11_EINT 0x0400 /* GP11_EINT */ +#define WM831X_GP11_EINT_MASK 0x0400 /* GP11_EINT */ +#define WM831X_GP11_EINT_SHIFT 10 /* GP11_EINT */ +#define WM831X_GP11_EINT_WIDTH 1 /* GP11_EINT */ +#define WM831X_GP10_EINT 0x0200 /* GP10_EINT */ +#define WM831X_GP10_EINT_MASK 0x0200 /* GP10_EINT */ +#define WM831X_GP10_EINT_SHIFT 9 /* GP10_EINT */ +#define WM831X_GP10_EINT_WIDTH 1 /* GP10_EINT */ +#define WM831X_GP9_EINT 0x0100 /* GP9_EINT */ +#define WM831X_GP9_EINT_MASK 0x0100 /* GP9_EINT */ +#define WM831X_GP9_EINT_SHIFT 8 /* GP9_EINT */ +#define WM831X_GP9_EINT_WIDTH 1 /* GP9_EINT */ +#define WM831X_GP8_EINT 0x0080 /* GP8_EINT */ +#define WM831X_GP8_EINT_MASK 0x0080 /* GP8_EINT */ +#define WM831X_GP8_EINT_SHIFT 7 /* GP8_EINT */ +#define WM831X_GP8_EINT_WIDTH 1 /* GP8_EINT */ +#define WM831X_GP7_EINT 0x0040 /* GP7_EINT */ +#define WM831X_GP7_EINT_MASK 0x0040 /* GP7_EINT */ +#define WM831X_GP7_EINT_SHIFT 6 /* GP7_EINT */ +#define WM831X_GP7_EINT_WIDTH 1 /* GP7_EINT */ +#define WM831X_GP6_EINT 0x0020 /* GP6_EINT */ +#define WM831X_GP6_EINT_MASK 0x0020 /* GP6_EINT */ +#define WM831X_GP6_EINT_SHIFT 5 /* GP6_EINT */ +#define WM831X_GP6_EINT_WIDTH 1 /* GP6_EINT */ +#define WM831X_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM831X_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM831X_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM831X_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM831X_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM831X_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM831X_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM831X_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM831X_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM831X_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM831X_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM831X_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM831X_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM831X_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM831X_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM831X_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM831X_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM831X_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM831X_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM831X_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R16407 (0x4017) - IRQ Config + */ +#define WM831X_IRQ_OD 0x0002 /* IRQ_OD */ +#define WM831X_IRQ_OD_MASK 0x0002 /* IRQ_OD */ +#define WM831X_IRQ_OD_SHIFT 1 /* IRQ_OD */ +#define WM831X_IRQ_OD_WIDTH 1 /* IRQ_OD */ +#define WM831X_IM_IRQ 0x0001 /* IM_IRQ */ +#define WM831X_IM_IRQ_MASK 0x0001 /* IM_IRQ */ +#define WM831X_IM_IRQ_SHIFT 0 /* IM_IRQ */ +#define WM831X_IM_IRQ_WIDTH 1 /* IM_IRQ */ + +/* + * R16408 (0x4018) - System Interrupts Mask + */ +#define WM831X_IM_PS_INT 0x8000 /* IM_PS_INT */ +#define WM831X_IM_PS_INT_MASK 0x8000 /* IM_PS_INT */ +#define WM831X_IM_PS_INT_SHIFT 15 /* IM_PS_INT */ +#define WM831X_IM_PS_INT_WIDTH 1 /* IM_PS_INT */ +#define WM831X_IM_TEMP_INT 0x4000 /* IM_TEMP_INT */ +#define WM831X_IM_TEMP_INT_MASK 0x4000 /* IM_TEMP_INT */ +#define WM831X_IM_TEMP_INT_SHIFT 14 /* IM_TEMP_INT */ +#define WM831X_IM_TEMP_INT_WIDTH 1 /* IM_TEMP_INT */ +#define WM831X_IM_GP_INT 0x2000 /* IM_GP_INT */ +#define WM831X_IM_GP_INT_MASK 0x2000 /* IM_GP_INT */ +#define WM831X_IM_GP_INT_SHIFT 13 /* IM_GP_INT */ +#define WM831X_IM_GP_INT_WIDTH 1 /* IM_GP_INT */ +#define WM831X_IM_ON_PIN_INT 0x1000 /* IM_ON_PIN_INT */ +#define WM831X_IM_ON_PIN_INT_MASK 0x1000 /* IM_ON_PIN_INT */ +#define WM831X_IM_ON_PIN_INT_SHIFT 12 /* IM_ON_PIN_INT */ +#define WM831X_IM_ON_PIN_INT_WIDTH 1 /* IM_ON_PIN_INT */ +#define WM831X_IM_WDOG_INT 0x0800 /* IM_WDOG_INT */ +#define WM831X_IM_WDOG_INT_MASK 0x0800 /* IM_WDOG_INT */ +#define WM831X_IM_WDOG_INT_SHIFT 11 /* IM_WDOG_INT */ +#define WM831X_IM_WDOG_INT_WIDTH 1 /* IM_WDOG_INT */ +#define WM831X_IM_TCHDATA_INT 0x0400 /* IM_TCHDATA_INT */ +#define WM831X_IM_TCHDATA_INT_MASK 0x0400 /* IM_TCHDATA_INT */ +#define WM831X_IM_TCHDATA_INT_SHIFT 10 /* IM_TCHDATA_INT */ +#define WM831X_IM_TCHDATA_INT_WIDTH 1 /* IM_TCHDATA_INT */ +#define WM831X_IM_TCHPD_INT 0x0200 /* IM_TCHPD_INT */ +#define WM831X_IM_TCHPD_INT_MASK 0x0200 /* IM_TCHPD_INT */ +#define WM831X_IM_TCHPD_INT_SHIFT 9 /* IM_TCHPD_INT */ +#define WM831X_IM_TCHPD_INT_WIDTH 1 /* IM_TCHPD_INT */ +#define WM831X_IM_AUXADC_INT 0x0100 /* IM_AUXADC_INT */ +#define WM831X_IM_AUXADC_INT_MASK 0x0100 /* IM_AUXADC_INT */ +#define WM831X_IM_AUXADC_INT_SHIFT 8 /* IM_AUXADC_INT */ +#define WM831X_IM_AUXADC_INT_WIDTH 1 /* IM_AUXADC_INT */ +#define WM831X_IM_PPM_INT 0x0080 /* IM_PPM_INT */ +#define WM831X_IM_PPM_INT_MASK 0x0080 /* IM_PPM_INT */ +#define WM831X_IM_PPM_INT_SHIFT 7 /* IM_PPM_INT */ +#define WM831X_IM_PPM_INT_WIDTH 1 /* IM_PPM_INT */ +#define WM831X_IM_CS_INT 0x0040 /* IM_CS_INT */ +#define WM831X_IM_CS_INT_MASK 0x0040 /* IM_CS_INT */ +#define WM831X_IM_CS_INT_SHIFT 6 /* IM_CS_INT */ +#define WM831X_IM_CS_INT_WIDTH 1 /* IM_CS_INT */ +#define WM831X_IM_RTC_INT 0x0020 /* IM_RTC_INT */ +#define WM831X_IM_RTC_INT_MASK 0x0020 /* IM_RTC_INT */ +#define WM831X_IM_RTC_INT_SHIFT 5 /* IM_RTC_INT */ +#define WM831X_IM_RTC_INT_WIDTH 1 /* IM_RTC_INT */ +#define WM831X_IM_OTP_INT 0x0010 /* IM_OTP_INT */ +#define WM831X_IM_OTP_INT_MASK 0x0010 /* IM_OTP_INT */ +#define WM831X_IM_OTP_INT_SHIFT 4 /* IM_OTP_INT */ +#define WM831X_IM_OTP_INT_WIDTH 1 /* IM_OTP_INT */ +#define WM831X_IM_CHILD_INT 0x0008 /* IM_CHILD_INT */ +#define WM831X_IM_CHILD_INT_MASK 0x0008 /* IM_CHILD_INT */ +#define WM831X_IM_CHILD_INT_SHIFT 3 /* IM_CHILD_INT */ +#define WM831X_IM_CHILD_INT_WIDTH 1 /* IM_CHILD_INT */ +#define WM831X_IM_CHG_INT 0x0004 /* IM_CHG_INT */ +#define WM831X_IM_CHG_INT_MASK 0x0004 /* IM_CHG_INT */ +#define WM831X_IM_CHG_INT_SHIFT 2 /* IM_CHG_INT */ +#define WM831X_IM_CHG_INT_WIDTH 1 /* IM_CHG_INT */ +#define WM831X_IM_HC_INT 0x0002 /* IM_HC_INT */ +#define WM831X_IM_HC_INT_MASK 0x0002 /* IM_HC_INT */ +#define WM831X_IM_HC_INT_SHIFT 1 /* IM_HC_INT */ +#define WM831X_IM_HC_INT_WIDTH 1 /* IM_HC_INT */ +#define WM831X_IM_UV_INT 0x0001 /* IM_UV_INT */ +#define WM831X_IM_UV_INT_MASK 0x0001 /* IM_UV_INT */ +#define WM831X_IM_UV_INT_SHIFT 0 /* IM_UV_INT */ +#define WM831X_IM_UV_INT_WIDTH 1 /* IM_UV_INT */ + +/* + * R16409 (0x4019) - Interrupt Status 1 Mask + */ +#define WM831X_IM_PPM_SYSLO_EINT 0x8000 /* IM_PPM_SYSLO_EINT */ +#define WM831X_IM_PPM_SYSLO_EINT_MASK 0x8000 /* IM_PPM_SYSLO_EINT */ +#define WM831X_IM_PPM_SYSLO_EINT_SHIFT 15 /* IM_PPM_SYSLO_EINT */ +#define WM831X_IM_PPM_SYSLO_EINT_WIDTH 1 /* IM_PPM_SYSLO_EINT */ +#define WM831X_IM_PPM_PWR_SRC_EINT 0x4000 /* IM_PPM_PWR_SRC_EINT */ +#define WM831X_IM_PPM_PWR_SRC_EINT_MASK 0x4000 /* IM_PPM_PWR_SRC_EINT */ +#define WM831X_IM_PPM_PWR_SRC_EINT_SHIFT 14 /* IM_PPM_PWR_SRC_EINT */ +#define WM831X_IM_PPM_PWR_SRC_EINT_WIDTH 1 /* IM_PPM_PWR_SRC_EINT */ +#define WM831X_IM_PPM_USB_CURR_EINT 0x2000 /* IM_PPM_USB_CURR_EINT */ +#define WM831X_IM_PPM_USB_CURR_EINT_MASK 0x2000 /* IM_PPM_USB_CURR_EINT */ +#define WM831X_IM_PPM_USB_CURR_EINT_SHIFT 13 /* IM_PPM_USB_CURR_EINT */ +#define WM831X_IM_PPM_USB_CURR_EINT_WIDTH 1 /* IM_PPM_USB_CURR_EINT */ +#define WM831X_IM_ON_PIN_EINT 0x1000 /* IM_ON_PIN_EINT */ +#define WM831X_IM_ON_PIN_EINT_MASK 0x1000 /* IM_ON_PIN_EINT */ +#define WM831X_IM_ON_PIN_EINT_SHIFT 12 /* IM_ON_PIN_EINT */ +#define WM831X_IM_ON_PIN_EINT_WIDTH 1 /* IM_ON_PIN_EINT */ +#define WM831X_IM_WDOG_TO_EINT 0x0800 /* IM_WDOG_TO_EINT */ +#define WM831X_IM_WDOG_TO_EINT_MASK 0x0800 /* IM_WDOG_TO_EINT */ +#define WM831X_IM_WDOG_TO_EINT_SHIFT 11 /* IM_WDOG_TO_EINT */ +#define WM831X_IM_WDOG_TO_EINT_WIDTH 1 /* IM_WDOG_TO_EINT */ +#define WM831X_IM_TCHDATA_EINT 0x0400 /* IM_TCHDATA_EINT */ +#define WM831X_IM_TCHDATA_EINT_MASK 0x0400 /* IM_TCHDATA_EINT */ +#define WM831X_IM_TCHDATA_EINT_SHIFT 10 /* IM_TCHDATA_EINT */ +#define WM831X_IM_TCHDATA_EINT_WIDTH 1 /* IM_TCHDATA_EINT */ +#define WM831X_IM_TCHPD_EINT 0x0200 /* IM_TCHPD_EINT */ +#define WM831X_IM_TCHPD_EINT_MASK 0x0200 /* IM_TCHPD_EINT */ +#define WM831X_IM_TCHPD_EINT_SHIFT 9 /* IM_TCHPD_EINT */ +#define WM831X_IM_TCHPD_EINT_WIDTH 1 /* IM_TCHPD_EINT */ +#define WM831X_IM_AUXADC_DATA_EINT 0x0100 /* IM_AUXADC_DATA_EINT */ +#define WM831X_IM_AUXADC_DATA_EINT_MASK 0x0100 /* IM_AUXADC_DATA_EINT */ +#define WM831X_IM_AUXADC_DATA_EINT_SHIFT 8 /* IM_AUXADC_DATA_EINT */ +#define WM831X_IM_AUXADC_DATA_EINT_WIDTH 1 /* IM_AUXADC_DATA_EINT */ +#define WM831X_IM_AUXADC_DCOMP4_EINT 0x0080 /* IM_AUXADC_DCOMP4_EINT */ +#define WM831X_IM_AUXADC_DCOMP4_EINT_MASK 0x0080 /* IM_AUXADC_DCOMP4_EINT */ +#define WM831X_IM_AUXADC_DCOMP4_EINT_SHIFT 7 /* IM_AUXADC_DCOMP4_EINT */ +#define WM831X_IM_AUXADC_DCOMP4_EINT_WIDTH 1 /* IM_AUXADC_DCOMP4_EINT */ +#define WM831X_IM_AUXADC_DCOMP3_EINT 0x0040 /* IM_AUXADC_DCOMP3_EINT */ +#define WM831X_IM_AUXADC_DCOMP3_EINT_MASK 0x0040 /* IM_AUXADC_DCOMP3_EINT */ +#define WM831X_IM_AUXADC_DCOMP3_EINT_SHIFT 6 /* IM_AUXADC_DCOMP3_EINT */ +#define WM831X_IM_AUXADC_DCOMP3_EINT_WIDTH 1 /* IM_AUXADC_DCOMP3_EINT */ +#define WM831X_IM_AUXADC_DCOMP2_EINT 0x0020 /* IM_AUXADC_DCOMP2_EINT */ +#define WM831X_IM_AUXADC_DCOMP2_EINT_MASK 0x0020 /* IM_AUXADC_DCOMP2_EINT */ +#define WM831X_IM_AUXADC_DCOMP2_EINT_SHIFT 5 /* IM_AUXADC_DCOMP2_EINT */ +#define WM831X_IM_AUXADC_DCOMP2_EINT_WIDTH 1 /* IM_AUXADC_DCOMP2_EINT */ +#define WM831X_IM_AUXADC_DCOMP1_EINT 0x0010 /* IM_AUXADC_DCOMP1_EINT */ +#define WM831X_IM_AUXADC_DCOMP1_EINT_MASK 0x0010 /* IM_AUXADC_DCOMP1_EINT */ +#define WM831X_IM_AUXADC_DCOMP1_EINT_SHIFT 4 /* IM_AUXADC_DCOMP1_EINT */ +#define WM831X_IM_AUXADC_DCOMP1_EINT_WIDTH 1 /* IM_AUXADC_DCOMP1_EINT */ +#define WM831X_IM_RTC_PER_EINT 0x0008 /* IM_RTC_PER_EINT */ +#define WM831X_IM_RTC_PER_EINT_MASK 0x0008 /* IM_RTC_PER_EINT */ +#define WM831X_IM_RTC_PER_EINT_SHIFT 3 /* IM_RTC_PER_EINT */ +#define WM831X_IM_RTC_PER_EINT_WIDTH 1 /* IM_RTC_PER_EINT */ +#define WM831X_IM_RTC_ALM_EINT 0x0004 /* IM_RTC_ALM_EINT */ +#define WM831X_IM_RTC_ALM_EINT_MASK 0x0004 /* IM_RTC_ALM_EINT */ +#define WM831X_IM_RTC_ALM_EINT_SHIFT 2 /* IM_RTC_ALM_EINT */ +#define WM831X_IM_RTC_ALM_EINT_WIDTH 1 /* IM_RTC_ALM_EINT */ +#define WM831X_IM_TEMP_THW_EINT 0x0002 /* IM_TEMP_THW_EINT */ +#define WM831X_IM_TEMP_THW_EINT_MASK 0x0002 /* IM_TEMP_THW_EINT */ +#define WM831X_IM_TEMP_THW_EINT_SHIFT 1 /* IM_TEMP_THW_EINT */ +#define WM831X_IM_TEMP_THW_EINT_WIDTH 1 /* IM_TEMP_THW_EINT */ + +/* + * R16410 (0x401A) - Interrupt Status 2 Mask + */ +#define WM831X_IM_CHG_BATT_HOT_EINT 0x8000 /* IM_CHG_BATT_HOT_EINT */ +#define WM831X_IM_CHG_BATT_HOT_EINT_MASK 0x8000 /* IM_CHG_BATT_HOT_EINT */ +#define WM831X_IM_CHG_BATT_HOT_EINT_SHIFT 15 /* IM_CHG_BATT_HOT_EINT */ +#define WM831X_IM_CHG_BATT_HOT_EINT_WIDTH 1 /* IM_CHG_BATT_HOT_EINT */ +#define WM831X_IM_CHG_BATT_COLD_EINT 0x4000 /* IM_CHG_BATT_COLD_EINT */ +#define WM831X_IM_CHG_BATT_COLD_EINT_MASK 0x4000 /* IM_CHG_BATT_COLD_EINT */ +#define WM831X_IM_CHG_BATT_COLD_EINT_SHIFT 14 /* IM_CHG_BATT_COLD_EINT */ +#define WM831X_IM_CHG_BATT_COLD_EINT_WIDTH 1 /* IM_CHG_BATT_COLD_EINT */ +#define WM831X_IM_CHG_BATT_FAIL_EINT 0x2000 /* IM_CHG_BATT_FAIL_EINT */ +#define WM831X_IM_CHG_BATT_FAIL_EINT_MASK 0x2000 /* IM_CHG_BATT_FAIL_EINT */ +#define WM831X_IM_CHG_BATT_FAIL_EINT_SHIFT 13 /* IM_CHG_BATT_FAIL_EINT */ +#define WM831X_IM_CHG_BATT_FAIL_EINT_WIDTH 1 /* IM_CHG_BATT_FAIL_EINT */ +#define WM831X_IM_CHG_OV_EINT 0x1000 /* IM_CHG_OV_EINT */ +#define WM831X_IM_CHG_OV_EINT_MASK 0x1000 /* IM_CHG_OV_EINT */ +#define WM831X_IM_CHG_OV_EINT_SHIFT 12 /* IM_CHG_OV_EINT */ +#define WM831X_IM_CHG_OV_EINT_WIDTH 1 /* IM_CHG_OV_EINT */ +#define WM831X_IM_CHG_END_EINT 0x0800 /* IM_CHG_END_EINT */ +#define WM831X_IM_CHG_END_EINT_MASK 0x0800 /* IM_CHG_END_EINT */ +#define WM831X_IM_CHG_END_EINT_SHIFT 11 /* IM_CHG_END_EINT */ +#define WM831X_IM_CHG_END_EINT_WIDTH 1 /* IM_CHG_END_EINT */ +#define WM831X_IM_CHG_TO_EINT 0x0400 /* IM_CHG_TO_EINT */ +#define WM831X_IM_CHG_TO_EINT_MASK 0x0400 /* IM_CHG_TO_EINT */ +#define WM831X_IM_CHG_TO_EINT_SHIFT 10 /* IM_CHG_TO_EINT */ +#define WM831X_IM_CHG_TO_EINT_WIDTH 1 /* IM_CHG_TO_EINT */ +#define WM831X_IM_CHG_MODE_EINT 0x0200 /* IM_CHG_MODE_EINT */ +#define WM831X_IM_CHG_MODE_EINT_MASK 0x0200 /* IM_CHG_MODE_EINT */ +#define WM831X_IM_CHG_MODE_EINT_SHIFT 9 /* IM_CHG_MODE_EINT */ +#define WM831X_IM_CHG_MODE_EINT_WIDTH 1 /* IM_CHG_MODE_EINT */ +#define WM831X_IM_CHG_START_EINT 0x0100 /* IM_CHG_START_EINT */ +#define WM831X_IM_CHG_START_EINT_MASK 0x0100 /* IM_CHG_START_EINT */ +#define WM831X_IM_CHG_START_EINT_SHIFT 8 /* IM_CHG_START_EINT */ +#define WM831X_IM_CHG_START_EINT_WIDTH 1 /* IM_CHG_START_EINT */ +#define WM831X_IM_CS2_EINT 0x0080 /* IM_CS2_EINT */ +#define WM831X_IM_CS2_EINT_MASK 0x0080 /* IM_CS2_EINT */ +#define WM831X_IM_CS2_EINT_SHIFT 7 /* IM_CS2_EINT */ +#define WM831X_IM_CS2_EINT_WIDTH 1 /* IM_CS2_EINT */ +#define WM831X_IM_CS1_EINT 0x0040 /* IM_CS1_EINT */ +#define WM831X_IM_CS1_EINT_MASK 0x0040 /* IM_CS1_EINT */ +#define WM831X_IM_CS1_EINT_SHIFT 6 /* IM_CS1_EINT */ +#define WM831X_IM_CS1_EINT_WIDTH 1 /* IM_CS1_EINT */ +#define WM831X_IM_OTP_CMD_END_EINT 0x0020 /* IM_OTP_CMD_END_EINT */ +#define WM831X_IM_OTP_CMD_END_EINT_MASK 0x0020 /* IM_OTP_CMD_END_EINT */ +#define WM831X_IM_OTP_CMD_END_EINT_SHIFT 5 /* IM_OTP_CMD_END_EINT */ +#define WM831X_IM_OTP_CMD_END_EINT_WIDTH 1 /* IM_OTP_CMD_END_EINT */ +#define WM831X_IM_OTP_ERR_EINT 0x0010 /* IM_OTP_ERR_EINT */ +#define WM831X_IM_OTP_ERR_EINT_MASK 0x0010 /* IM_OTP_ERR_EINT */ +#define WM831X_IM_OTP_ERR_EINT_SHIFT 4 /* IM_OTP_ERR_EINT */ +#define WM831X_IM_OTP_ERR_EINT_WIDTH 1 /* IM_OTP_ERR_EINT */ +#define WM831X_IM_PS_POR_EINT 0x0004 /* IM_PS_POR_EINT */ +#define WM831X_IM_PS_POR_EINT_MASK 0x0004 /* IM_PS_POR_EINT */ +#define WM831X_IM_PS_POR_EINT_SHIFT 2 /* IM_PS_POR_EINT */ +#define WM831X_IM_PS_POR_EINT_WIDTH 1 /* IM_PS_POR_EINT */ +#define WM831X_IM_PS_SLEEP_OFF_EINT 0x0002 /* IM_PS_SLEEP_OFF_EINT */ +#define WM831X_IM_PS_SLEEP_OFF_EINT_MASK 0x0002 /* IM_PS_SLEEP_OFF_EINT */ +#define WM831X_IM_PS_SLEEP_OFF_EINT_SHIFT 1 /* IM_PS_SLEEP_OFF_EINT */ +#define WM831X_IM_PS_SLEEP_OFF_EINT_WIDTH 1 /* IM_PS_SLEEP_OFF_EINT */ +#define WM831X_IM_PS_ON_WAKE_EINT 0x0001 /* IM_PS_ON_WAKE_EINT */ +#define WM831X_IM_PS_ON_WAKE_EINT_MASK 0x0001 /* IM_PS_ON_WAKE_EINT */ +#define WM831X_IM_PS_ON_WAKE_EINT_SHIFT 0 /* IM_PS_ON_WAKE_EINT */ +#define WM831X_IM_PS_ON_WAKE_EINT_WIDTH 1 /* IM_PS_ON_WAKE_EINT */ + +/* + * R16411 (0x401B) - Interrupt Status 3 Mask + */ +#define WM831X_IM_UV_LDO10_EINT 0x0200 /* IM_UV_LDO10_EINT */ +#define WM831X_IM_UV_LDO10_EINT_MASK 0x0200 /* IM_UV_LDO10_EINT */ +#define WM831X_IM_UV_LDO10_EINT_SHIFT 9 /* IM_UV_LDO10_EINT */ +#define WM831X_IM_UV_LDO10_EINT_WIDTH 1 /* IM_UV_LDO10_EINT */ +#define WM831X_IM_UV_LDO9_EINT 0x0100 /* IM_UV_LDO9_EINT */ +#define WM831X_IM_UV_LDO9_EINT_MASK 0x0100 /* IM_UV_LDO9_EINT */ +#define WM831X_IM_UV_LDO9_EINT_SHIFT 8 /* IM_UV_LDO9_EINT */ +#define WM831X_IM_UV_LDO9_EINT_WIDTH 1 /* IM_UV_LDO9_EINT */ +#define WM831X_IM_UV_LDO8_EINT 0x0080 /* IM_UV_LDO8_EINT */ +#define WM831X_IM_UV_LDO8_EINT_MASK 0x0080 /* IM_UV_LDO8_EINT */ +#define WM831X_IM_UV_LDO8_EINT_SHIFT 7 /* IM_UV_LDO8_EINT */ +#define WM831X_IM_UV_LDO8_EINT_WIDTH 1 /* IM_UV_LDO8_EINT */ +#define WM831X_IM_UV_LDO7_EINT 0x0040 /* IM_UV_LDO7_EINT */ +#define WM831X_IM_UV_LDO7_EINT_MASK 0x0040 /* IM_UV_LDO7_EINT */ +#define WM831X_IM_UV_LDO7_EINT_SHIFT 6 /* IM_UV_LDO7_EINT */ +#define WM831X_IM_UV_LDO7_EINT_WIDTH 1 /* IM_UV_LDO7_EINT */ +#define WM831X_IM_UV_LDO6_EINT 0x0020 /* IM_UV_LDO6_EINT */ +#define WM831X_IM_UV_LDO6_EINT_MASK 0x0020 /* IM_UV_LDO6_EINT */ +#define WM831X_IM_UV_LDO6_EINT_SHIFT 5 /* IM_UV_LDO6_EINT */ +#define WM831X_IM_UV_LDO6_EINT_WIDTH 1 /* IM_UV_LDO6_EINT */ +#define WM831X_IM_UV_LDO5_EINT 0x0010 /* IM_UV_LDO5_EINT */ +#define WM831X_IM_UV_LDO5_EINT_MASK 0x0010 /* IM_UV_LDO5_EINT */ +#define WM831X_IM_UV_LDO5_EINT_SHIFT 4 /* IM_UV_LDO5_EINT */ +#define WM831X_IM_UV_LDO5_EINT_WIDTH 1 /* IM_UV_LDO5_EINT */ +#define WM831X_IM_UV_LDO4_EINT 0x0008 /* IM_UV_LDO4_EINT */ +#define WM831X_IM_UV_LDO4_EINT_MASK 0x0008 /* IM_UV_LDO4_EINT */ +#define WM831X_IM_UV_LDO4_EINT_SHIFT 3 /* IM_UV_LDO4_EINT */ +#define WM831X_IM_UV_LDO4_EINT_WIDTH 1 /* IM_UV_LDO4_EINT */ +#define WM831X_IM_UV_LDO3_EINT 0x0004 /* IM_UV_LDO3_EINT */ +#define WM831X_IM_UV_LDO3_EINT_MASK 0x0004 /* IM_UV_LDO3_EINT */ +#define WM831X_IM_UV_LDO3_EINT_SHIFT 2 /* IM_UV_LDO3_EINT */ +#define WM831X_IM_UV_LDO3_EINT_WIDTH 1 /* IM_UV_LDO3_EINT */ +#define WM831X_IM_UV_LDO2_EINT 0x0002 /* IM_UV_LDO2_EINT */ +#define WM831X_IM_UV_LDO2_EINT_MASK 0x0002 /* IM_UV_LDO2_EINT */ +#define WM831X_IM_UV_LDO2_EINT_SHIFT 1 /* IM_UV_LDO2_EINT */ +#define WM831X_IM_UV_LDO2_EINT_WIDTH 1 /* IM_UV_LDO2_EINT */ +#define WM831X_IM_UV_LDO1_EINT 0x0001 /* IM_UV_LDO1_EINT */ +#define WM831X_IM_UV_LDO1_EINT_MASK 0x0001 /* IM_UV_LDO1_EINT */ +#define WM831X_IM_UV_LDO1_EINT_SHIFT 0 /* IM_UV_LDO1_EINT */ +#define WM831X_IM_UV_LDO1_EINT_WIDTH 1 /* IM_UV_LDO1_EINT */ + +/* + * R16412 (0x401C) - Interrupt Status 4 Mask + */ +#define WM831X_IM_HC_DC2_EINT 0x0200 /* IM_HC_DC2_EINT */ +#define WM831X_IM_HC_DC2_EINT_MASK 0x0200 /* IM_HC_DC2_EINT */ +#define WM831X_IM_HC_DC2_EINT_SHIFT 9 /* IM_HC_DC2_EINT */ +#define WM831X_IM_HC_DC2_EINT_WIDTH 1 /* IM_HC_DC2_EINT */ +#define WM831X_IM_HC_DC1_EINT 0x0100 /* IM_HC_DC1_EINT */ +#define WM831X_IM_HC_DC1_EINT_MASK 0x0100 /* IM_HC_DC1_EINT */ +#define WM831X_IM_HC_DC1_EINT_SHIFT 8 /* IM_HC_DC1_EINT */ +#define WM831X_IM_HC_DC1_EINT_WIDTH 1 /* IM_HC_DC1_EINT */ +#define WM831X_IM_UV_DC4_EINT 0x0008 /* IM_UV_DC4_EINT */ +#define WM831X_IM_UV_DC4_EINT_MASK 0x0008 /* IM_UV_DC4_EINT */ +#define WM831X_IM_UV_DC4_EINT_SHIFT 3 /* IM_UV_DC4_EINT */ +#define WM831X_IM_UV_DC4_EINT_WIDTH 1 /* IM_UV_DC4_EINT */ +#define WM831X_IM_UV_DC3_EINT 0x0004 /* IM_UV_DC3_EINT */ +#define WM831X_IM_UV_DC3_EINT_MASK 0x0004 /* IM_UV_DC3_EINT */ +#define WM831X_IM_UV_DC3_EINT_SHIFT 2 /* IM_UV_DC3_EINT */ +#define WM831X_IM_UV_DC3_EINT_WIDTH 1 /* IM_UV_DC3_EINT */ +#define WM831X_IM_UV_DC2_EINT 0x0002 /* IM_UV_DC2_EINT */ +#define WM831X_IM_UV_DC2_EINT_MASK 0x0002 /* IM_UV_DC2_EINT */ +#define WM831X_IM_UV_DC2_EINT_SHIFT 1 /* IM_UV_DC2_EINT */ +#define WM831X_IM_UV_DC2_EINT_WIDTH 1 /* IM_UV_DC2_EINT */ +#define WM831X_IM_UV_DC1_EINT 0x0001 /* IM_UV_DC1_EINT */ +#define WM831X_IM_UV_DC1_EINT_MASK 0x0001 /* IM_UV_DC1_EINT */ +#define WM831X_IM_UV_DC1_EINT_SHIFT 0 /* IM_UV_DC1_EINT */ +#define WM831X_IM_UV_DC1_EINT_WIDTH 1 /* IM_UV_DC1_EINT */ + +/* + * R16413 (0x401D) - Interrupt Status 5 Mask + */ +#define WM831X_IM_GP16_EINT 0x8000 /* IM_GP16_EINT */ +#define WM831X_IM_GP16_EINT_MASK 0x8000 /* IM_GP16_EINT */ +#define WM831X_IM_GP16_EINT_SHIFT 15 /* IM_GP16_EINT */ +#define WM831X_IM_GP16_EINT_WIDTH 1 /* IM_GP16_EINT */ +#define WM831X_IM_GP15_EINT 0x4000 /* IM_GP15_EINT */ +#define WM831X_IM_GP15_EINT_MASK 0x4000 /* IM_GP15_EINT */ +#define WM831X_IM_GP15_EINT_SHIFT 14 /* IM_GP15_EINT */ +#define WM831X_IM_GP15_EINT_WIDTH 1 /* IM_GP15_EINT */ +#define WM831X_IM_GP14_EINT 0x2000 /* IM_GP14_EINT */ +#define WM831X_IM_GP14_EINT_MASK 0x2000 /* IM_GP14_EINT */ +#define WM831X_IM_GP14_EINT_SHIFT 13 /* IM_GP14_EINT */ +#define WM831X_IM_GP14_EINT_WIDTH 1 /* IM_GP14_EINT */ +#define WM831X_IM_GP13_EINT 0x1000 /* IM_GP13_EINT */ +#define WM831X_IM_GP13_EINT_MASK 0x1000 /* IM_GP13_EINT */ +#define WM831X_IM_GP13_EINT_SHIFT 12 /* IM_GP13_EINT */ +#define WM831X_IM_GP13_EINT_WIDTH 1 /* IM_GP13_EINT */ +#define WM831X_IM_GP12_EINT 0x0800 /* IM_GP12_EINT */ +#define WM831X_IM_GP12_EINT_MASK 0x0800 /* IM_GP12_EINT */ +#define WM831X_IM_GP12_EINT_SHIFT 11 /* IM_GP12_EINT */ +#define WM831X_IM_GP12_EINT_WIDTH 1 /* IM_GP12_EINT */ +#define WM831X_IM_GP11_EINT 0x0400 /* IM_GP11_EINT */ +#define WM831X_IM_GP11_EINT_MASK 0x0400 /* IM_GP11_EINT */ +#define WM831X_IM_GP11_EINT_SHIFT 10 /* IM_GP11_EINT */ +#define WM831X_IM_GP11_EINT_WIDTH 1 /* IM_GP11_EINT */ +#define WM831X_IM_GP10_EINT 0x0200 /* IM_GP10_EINT */ +#define WM831X_IM_GP10_EINT_MASK 0x0200 /* IM_GP10_EINT */ +#define WM831X_IM_GP10_EINT_SHIFT 9 /* IM_GP10_EINT */ +#define WM831X_IM_GP10_EINT_WIDTH 1 /* IM_GP10_EINT */ +#define WM831X_IM_GP9_EINT 0x0100 /* IM_GP9_EINT */ +#define WM831X_IM_GP9_EINT_MASK 0x0100 /* IM_GP9_EINT */ +#define WM831X_IM_GP9_EINT_SHIFT 8 /* IM_GP9_EINT */ +#define WM831X_IM_GP9_EINT_WIDTH 1 /* IM_GP9_EINT */ +#define WM831X_IM_GP8_EINT 0x0080 /* IM_GP8_EINT */ +#define WM831X_IM_GP8_EINT_MASK 0x0080 /* IM_GP8_EINT */ +#define WM831X_IM_GP8_EINT_SHIFT 7 /* IM_GP8_EINT */ +#define WM831X_IM_GP8_EINT_WIDTH 1 /* IM_GP8_EINT */ +#define WM831X_IM_GP7_EINT 0x0040 /* IM_GP7_EINT */ +#define WM831X_IM_GP7_EINT_MASK 0x0040 /* IM_GP7_EINT */ +#define WM831X_IM_GP7_EINT_SHIFT 6 /* IM_GP7_EINT */ +#define WM831X_IM_GP7_EINT_WIDTH 1 /* IM_GP7_EINT */ +#define WM831X_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */ +#define WM831X_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */ +#define WM831X_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */ +#define WM831X_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */ +#define WM831X_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM831X_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM831X_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM831X_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM831X_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM831X_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM831X_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM831X_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM831X_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM831X_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM831X_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM831X_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM831X_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM831X_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM831X_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM831X_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM831X_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM831X_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM831X_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM831X_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + + +#endif -- cgit v0.10.2 From 7e9f9fd4b8285c52c0950a1929864346de5caa6d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:54 +0100 Subject: mfd: Add WM831x AUXADC support The WM831x contains an auxiliary ADC with a number of switchable inputs which is used to monitor some of the voltages and temperatures in the system and has some external inputs which can be used for machine specific purposes. Provide an API allowing drivers to read values from the ADC. An internal reference voltage is provided to allow callibration of the ADC. This is used to calibrate the device at startup. The hardware also supports continuous readings and digital comparators. These are not yet supported by the driver. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index eb63d22..42bef1d 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -15,11 +15,14 @@ #include #include #include +#include +#include #include #include #include #include +#include enum wm831x_parent { WM8310 = 0, @@ -244,6 +247,103 @@ out: } EXPORT_SYMBOL_GPL(wm831x_set_bits); +/** + * wm831x_auxadc_read: Read a value from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int tries = 10; + int ret, src; + + mutex_lock(&wm831x->auxadc_lock); + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_ENA, WM831X_AUX_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); + goto out; + } + + /* We force a single source at present */ + src = input; + ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, + 1 << src); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); + goto out; + } + + ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, + WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); + goto disable; + } + + do { + msleep(1); + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL); + if (ret < 0) + ret = WM831X_AUX_CVT_ENA; + } while ((ret & WM831X_AUX_CVT_ENA) && --tries); + + if (ret & WM831X_AUX_CVT_ENA) { + dev_err(wm831x->dev, "Timed out reading AUXADC\n"); + ret = -EBUSY; + goto disable; + } + + ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); + } else { + src = ((ret & WM831X_AUX_DATA_SRC_MASK) + >> WM831X_AUX_DATA_SRC_SHIFT) - 1; + + if (src == 14) + src = WM831X_AUX_CAL; + + if (src != input) { + dev_err(wm831x->dev, "Data from source %d not %d\n", + src, input); + ret = -EINVAL; + } else { + ret &= WM831X_AUX_DATA_MASK; + } + } + +disable: + wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); +out: + mutex_unlock(&wm831x->auxadc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read); + +/** + * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) +{ + int ret; + + ret = wm831x_auxadc_read(wm831x, input); + if (ret < 0) + return ret; + + ret *= 1465; + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); + static struct resource wm831x_dcdc1_resources[] = { { .start = WM831X_DC1_CONTROL_1, @@ -1084,6 +1184,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) mutex_init(&wm831x->io_lock); mutex_init(&wm831x->key_lock); + mutex_init(&wm831x->auxadc_lock); dev_set_drvdata(wm831x->dev, wm831x); ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); diff --git a/include/linux/mfd/wm831x/auxadc.h b/include/linux/mfd/wm831x/auxadc.h new file mode 100644 index 0000000..b132067 --- /dev/null +++ b/include/linux/mfd/wm831x/auxadc.h @@ -0,0 +1,216 @@ +/* + * include/linux/mfd/wm831x/auxadc.h -- Auxiliary ADC interface for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_AUXADC_H__ +#define __MFD_WM831X_AUXADC_H__ + +/* + * R16429 (0x402D) - AuxADC Data + */ +#define WM831X_AUX_DATA_SRC_MASK 0xF000 /* AUX_DATA_SRC - [15:12] */ +#define WM831X_AUX_DATA_SRC_SHIFT 12 /* AUX_DATA_SRC - [15:12] */ +#define WM831X_AUX_DATA_SRC_WIDTH 4 /* AUX_DATA_SRC - [15:12] */ +#define WM831X_AUX_DATA_MASK 0x0FFF /* AUX_DATA - [11:0] */ +#define WM831X_AUX_DATA_SHIFT 0 /* AUX_DATA - [11:0] */ +#define WM831X_AUX_DATA_WIDTH 12 /* AUX_DATA - [11:0] */ + +/* + * R16430 (0x402E) - AuxADC Control + */ +#define WM831X_AUX_ENA 0x8000 /* AUX_ENA */ +#define WM831X_AUX_ENA_MASK 0x8000 /* AUX_ENA */ +#define WM831X_AUX_ENA_SHIFT 15 /* AUX_ENA */ +#define WM831X_AUX_ENA_WIDTH 1 /* AUX_ENA */ +#define WM831X_AUX_CVT_ENA 0x4000 /* AUX_CVT_ENA */ +#define WM831X_AUX_CVT_ENA_MASK 0x4000 /* AUX_CVT_ENA */ +#define WM831X_AUX_CVT_ENA_SHIFT 14 /* AUX_CVT_ENA */ +#define WM831X_AUX_CVT_ENA_WIDTH 1 /* AUX_CVT_ENA */ +#define WM831X_AUX_SLPENA 0x1000 /* AUX_SLPENA */ +#define WM831X_AUX_SLPENA_MASK 0x1000 /* AUX_SLPENA */ +#define WM831X_AUX_SLPENA_SHIFT 12 /* AUX_SLPENA */ +#define WM831X_AUX_SLPENA_WIDTH 1 /* AUX_SLPENA */ +#define WM831X_AUX_FRC_ENA 0x0800 /* AUX_FRC_ENA */ +#define WM831X_AUX_FRC_ENA_MASK 0x0800 /* AUX_FRC_ENA */ +#define WM831X_AUX_FRC_ENA_SHIFT 11 /* AUX_FRC_ENA */ +#define WM831X_AUX_FRC_ENA_WIDTH 1 /* AUX_FRC_ENA */ +#define WM831X_AUX_RATE_MASK 0x003F /* AUX_RATE - [5:0] */ +#define WM831X_AUX_RATE_SHIFT 0 /* AUX_RATE - [5:0] */ +#define WM831X_AUX_RATE_WIDTH 6 /* AUX_RATE - [5:0] */ + +/* + * R16431 (0x402F) - AuxADC Source + */ +#define WM831X_AUX_CAL_SEL 0x8000 /* AUX_CAL_SEL */ +#define WM831X_AUX_CAL_SEL_MASK 0x8000 /* AUX_CAL_SEL */ +#define WM831X_AUX_CAL_SEL_SHIFT 15 /* AUX_CAL_SEL */ +#define WM831X_AUX_CAL_SEL_WIDTH 1 /* AUX_CAL_SEL */ +#define WM831X_AUX_BKUP_BATT_SEL 0x0400 /* AUX_BKUP_BATT_SEL */ +#define WM831X_AUX_BKUP_BATT_SEL_MASK 0x0400 /* AUX_BKUP_BATT_SEL */ +#define WM831X_AUX_BKUP_BATT_SEL_SHIFT 10 /* AUX_BKUP_BATT_SEL */ +#define WM831X_AUX_BKUP_BATT_SEL_WIDTH 1 /* AUX_BKUP_BATT_SEL */ +#define WM831X_AUX_WALL_SEL 0x0200 /* AUX_WALL_SEL */ +#define WM831X_AUX_WALL_SEL_MASK 0x0200 /* AUX_WALL_SEL */ +#define WM831X_AUX_WALL_SEL_SHIFT 9 /* AUX_WALL_SEL */ +#define WM831X_AUX_WALL_SEL_WIDTH 1 /* AUX_WALL_SEL */ +#define WM831X_AUX_BATT_SEL 0x0100 /* AUX_BATT_SEL */ +#define WM831X_AUX_BATT_SEL_MASK 0x0100 /* AUX_BATT_SEL */ +#define WM831X_AUX_BATT_SEL_SHIFT 8 /* AUX_BATT_SEL */ +#define WM831X_AUX_BATT_SEL_WIDTH 1 /* AUX_BATT_SEL */ +#define WM831X_AUX_USB_SEL 0x0080 /* AUX_USB_SEL */ +#define WM831X_AUX_USB_SEL_MASK 0x0080 /* AUX_USB_SEL */ +#define WM831X_AUX_USB_SEL_SHIFT 7 /* AUX_USB_SEL */ +#define WM831X_AUX_USB_SEL_WIDTH 1 /* AUX_USB_SEL */ +#define WM831X_AUX_SYSVDD_SEL 0x0040 /* AUX_SYSVDD_SEL */ +#define WM831X_AUX_SYSVDD_SEL_MASK 0x0040 /* AUX_SYSVDD_SEL */ +#define WM831X_AUX_SYSVDD_SEL_SHIFT 6 /* AUX_SYSVDD_SEL */ +#define WM831X_AUX_SYSVDD_SEL_WIDTH 1 /* AUX_SYSVDD_SEL */ +#define WM831X_AUX_BATT_TEMP_SEL 0x0020 /* AUX_BATT_TEMP_SEL */ +#define WM831X_AUX_BATT_TEMP_SEL_MASK 0x0020 /* AUX_BATT_TEMP_SEL */ +#define WM831X_AUX_BATT_TEMP_SEL_SHIFT 5 /* AUX_BATT_TEMP_SEL */ +#define WM831X_AUX_BATT_TEMP_SEL_WIDTH 1 /* AUX_BATT_TEMP_SEL */ +#define WM831X_AUX_CHIP_TEMP_SEL 0x0010 /* AUX_CHIP_TEMP_SEL */ +#define WM831X_AUX_CHIP_TEMP_SEL_MASK 0x0010 /* AUX_CHIP_TEMP_SEL */ +#define WM831X_AUX_CHIP_TEMP_SEL_SHIFT 4 /* AUX_CHIP_TEMP_SEL */ +#define WM831X_AUX_CHIP_TEMP_SEL_WIDTH 1 /* AUX_CHIP_TEMP_SEL */ +#define WM831X_AUX_AUX4_SEL 0x0008 /* AUX_AUX4_SEL */ +#define WM831X_AUX_AUX4_SEL_MASK 0x0008 /* AUX_AUX4_SEL */ +#define WM831X_AUX_AUX4_SEL_SHIFT 3 /* AUX_AUX4_SEL */ +#define WM831X_AUX_AUX4_SEL_WIDTH 1 /* AUX_AUX4_SEL */ +#define WM831X_AUX_AUX3_SEL 0x0004 /* AUX_AUX3_SEL */ +#define WM831X_AUX_AUX3_SEL_MASK 0x0004 /* AUX_AUX3_SEL */ +#define WM831X_AUX_AUX3_SEL_SHIFT 2 /* AUX_AUX3_SEL */ +#define WM831X_AUX_AUX3_SEL_WIDTH 1 /* AUX_AUX3_SEL */ +#define WM831X_AUX_AUX2_SEL 0x0002 /* AUX_AUX2_SEL */ +#define WM831X_AUX_AUX2_SEL_MASK 0x0002 /* AUX_AUX2_SEL */ +#define WM831X_AUX_AUX2_SEL_SHIFT 1 /* AUX_AUX2_SEL */ +#define WM831X_AUX_AUX2_SEL_WIDTH 1 /* AUX_AUX2_SEL */ +#define WM831X_AUX_AUX1_SEL 0x0001 /* AUX_AUX1_SEL */ +#define WM831X_AUX_AUX1_SEL_MASK 0x0001 /* AUX_AUX1_SEL */ +#define WM831X_AUX_AUX1_SEL_SHIFT 0 /* AUX_AUX1_SEL */ +#define WM831X_AUX_AUX1_SEL_WIDTH 1 /* AUX_AUX1_SEL */ + +/* + * R16432 (0x4030) - Comparator Control + */ +#define WM831X_DCOMP4_STS 0x0800 /* DCOMP4_STS */ +#define WM831X_DCOMP4_STS_MASK 0x0800 /* DCOMP4_STS */ +#define WM831X_DCOMP4_STS_SHIFT 11 /* DCOMP4_STS */ +#define WM831X_DCOMP4_STS_WIDTH 1 /* DCOMP4_STS */ +#define WM831X_DCOMP3_STS 0x0400 /* DCOMP3_STS */ +#define WM831X_DCOMP3_STS_MASK 0x0400 /* DCOMP3_STS */ +#define WM831X_DCOMP3_STS_SHIFT 10 /* DCOMP3_STS */ +#define WM831X_DCOMP3_STS_WIDTH 1 /* DCOMP3_STS */ +#define WM831X_DCOMP2_STS 0x0200 /* DCOMP2_STS */ +#define WM831X_DCOMP2_STS_MASK 0x0200 /* DCOMP2_STS */ +#define WM831X_DCOMP2_STS_SHIFT 9 /* DCOMP2_STS */ +#define WM831X_DCOMP2_STS_WIDTH 1 /* DCOMP2_STS */ +#define WM831X_DCOMP1_STS 0x0100 /* DCOMP1_STS */ +#define WM831X_DCOMP1_STS_MASK 0x0100 /* DCOMP1_STS */ +#define WM831X_DCOMP1_STS_SHIFT 8 /* DCOMP1_STS */ +#define WM831X_DCOMP1_STS_WIDTH 1 /* DCOMP1_STS */ +#define WM831X_DCMP4_ENA 0x0008 /* DCMP4_ENA */ +#define WM831X_DCMP4_ENA_MASK 0x0008 /* DCMP4_ENA */ +#define WM831X_DCMP4_ENA_SHIFT 3 /* DCMP4_ENA */ +#define WM831X_DCMP4_ENA_WIDTH 1 /* DCMP4_ENA */ +#define WM831X_DCMP3_ENA 0x0004 /* DCMP3_ENA */ +#define WM831X_DCMP3_ENA_MASK 0x0004 /* DCMP3_ENA */ +#define WM831X_DCMP3_ENA_SHIFT 2 /* DCMP3_ENA */ +#define WM831X_DCMP3_ENA_WIDTH 1 /* DCMP3_ENA */ +#define WM831X_DCMP2_ENA 0x0002 /* DCMP2_ENA */ +#define WM831X_DCMP2_ENA_MASK 0x0002 /* DCMP2_ENA */ +#define WM831X_DCMP2_ENA_SHIFT 1 /* DCMP2_ENA */ +#define WM831X_DCMP2_ENA_WIDTH 1 /* DCMP2_ENA */ +#define WM831X_DCMP1_ENA 0x0001 /* DCMP1_ENA */ +#define WM831X_DCMP1_ENA_MASK 0x0001 /* DCMP1_ENA */ +#define WM831X_DCMP1_ENA_SHIFT 0 /* DCMP1_ENA */ +#define WM831X_DCMP1_ENA_WIDTH 1 /* DCMP1_ENA */ + +/* + * R16433 (0x4031) - Comparator 1 + */ +#define WM831X_DCMP1_SRC_MASK 0xE000 /* DCMP1_SRC - [15:13] */ +#define WM831X_DCMP1_SRC_SHIFT 13 /* DCMP1_SRC - [15:13] */ +#define WM831X_DCMP1_SRC_WIDTH 3 /* DCMP1_SRC - [15:13] */ +#define WM831X_DCMP1_GT 0x1000 /* DCMP1_GT */ +#define WM831X_DCMP1_GT_MASK 0x1000 /* DCMP1_GT */ +#define WM831X_DCMP1_GT_SHIFT 12 /* DCMP1_GT */ +#define WM831X_DCMP1_GT_WIDTH 1 /* DCMP1_GT */ +#define WM831X_DCMP1_THR_MASK 0x0FFF /* DCMP1_THR - [11:0] */ +#define WM831X_DCMP1_THR_SHIFT 0 /* DCMP1_THR - [11:0] */ +#define WM831X_DCMP1_THR_WIDTH 12 /* DCMP1_THR - [11:0] */ + +/* + * R16434 (0x4032) - Comparator 2 + */ +#define WM831X_DCMP2_SRC_MASK 0xE000 /* DCMP2_SRC - [15:13] */ +#define WM831X_DCMP2_SRC_SHIFT 13 /* DCMP2_SRC - [15:13] */ +#define WM831X_DCMP2_SRC_WIDTH 3 /* DCMP2_SRC - [15:13] */ +#define WM831X_DCMP2_GT 0x1000 /* DCMP2_GT */ +#define WM831X_DCMP2_GT_MASK 0x1000 /* DCMP2_GT */ +#define WM831X_DCMP2_GT_SHIFT 12 /* DCMP2_GT */ +#define WM831X_DCMP2_GT_WIDTH 1 /* DCMP2_GT */ +#define WM831X_DCMP2_THR_MASK 0x0FFF /* DCMP2_THR - [11:0] */ +#define WM831X_DCMP2_THR_SHIFT 0 /* DCMP2_THR - [11:0] */ +#define WM831X_DCMP2_THR_WIDTH 12 /* DCMP2_THR - [11:0] */ + +/* + * R16435 (0x4033) - Comparator 3 + */ +#define WM831X_DCMP3_SRC_MASK 0xE000 /* DCMP3_SRC - [15:13] */ +#define WM831X_DCMP3_SRC_SHIFT 13 /* DCMP3_SRC - [15:13] */ +#define WM831X_DCMP3_SRC_WIDTH 3 /* DCMP3_SRC - [15:13] */ +#define WM831X_DCMP3_GT 0x1000 /* DCMP3_GT */ +#define WM831X_DCMP3_GT_MASK 0x1000 /* DCMP3_GT */ +#define WM831X_DCMP3_GT_SHIFT 12 /* DCMP3_GT */ +#define WM831X_DCMP3_GT_WIDTH 1 /* DCMP3_GT */ +#define WM831X_DCMP3_THR_MASK 0x0FFF /* DCMP3_THR - [11:0] */ +#define WM831X_DCMP3_THR_SHIFT 0 /* DCMP3_THR - [11:0] */ +#define WM831X_DCMP3_THR_WIDTH 12 /* DCMP3_THR - [11:0] */ + +/* + * R16436 (0x4034) - Comparator 4 + */ +#define WM831X_DCMP4_SRC_MASK 0xE000 /* DCMP4_SRC - [15:13] */ +#define WM831X_DCMP4_SRC_SHIFT 13 /* DCMP4_SRC - [15:13] */ +#define WM831X_DCMP4_SRC_WIDTH 3 /* DCMP4_SRC - [15:13] */ +#define WM831X_DCMP4_GT 0x1000 /* DCMP4_GT */ +#define WM831X_DCMP4_GT_MASK 0x1000 /* DCMP4_GT */ +#define WM831X_DCMP4_GT_SHIFT 12 /* DCMP4_GT */ +#define WM831X_DCMP4_GT_WIDTH 1 /* DCMP4_GT */ +#define WM831X_DCMP4_THR_MASK 0x0FFF /* DCMP4_THR - [11:0] */ +#define WM831X_DCMP4_THR_SHIFT 0 /* DCMP4_THR - [11:0] */ +#define WM831X_DCMP4_THR_WIDTH 12 /* DCMP4_THR - [11:0] */ + +#define WM831X_AUX_CAL_FACTOR 0xfff +#define WM831X_AUX_CAL_NOMINAL 0x222 + +enum wm831x_auxadc { + WM831X_AUX_CAL = 15, + WM831X_AUX_BKUP_BATT = 10, + WM831X_AUX_WALL = 9, + WM831X_AUX_BATT = 8, + WM831X_AUX_USB = 7, + WM831X_AUX_SYSVDD = 6, + WM831X_AUX_BATT_TEMP = 5, + WM831X_AUX_CHIP_TEMP = 4, + WM831X_AUX_AUX4 = 3, + WM831X_AUX_AUX3 = 2, + WM831X_AUX_AUX2 = 1, + WM831X_AUX_AUX1 = 0, +}; + +int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input); +int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input); + +#endif diff --git a/include/linux/mfd/wm831x/core.h b/include/linux/mfd/wm831x/core.h index b96c935..d7134df 100644 --- a/include/linux/mfd/wm831x/core.h +++ b/include/linux/mfd/wm831x/core.h @@ -234,6 +234,8 @@ struct wm831x { unsigned int irq_base; int irq_masks[5]; + struct mutex auxadc_lock; + /* The WM831x has a security key blocking access to certain * registers. The mutex is taken by the accessors for locking * and unlocking the security key, locked is used to fail -- cgit v0.10.2 From 63aed85e3535b4603798184cc941e49de386d354 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:55 +0100 Subject: mfd: Conditionally add WM831x backlight subdevice The WM831x backlight driver requires at least the specification of the current sink to use and a maximum current to allow them to function and will actively interfere with other users of the regulators it uses if misconfigured so only register the subdevice for it if this platform data has been supplied. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 42bef1d..bc40ea3 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -1172,6 +1172,12 @@ static struct mfd_cell wm8312_devs[] = { }, }; +static struct mfd_cell backlight_devs[] = { + { + .name = "wm831x-backlight", + }, +}; + /* * Instantiate the generic non-control parts of the device. */ @@ -1325,6 +1331,15 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) goto err_irq; } + if (pdata && pdata->backlight) { + /* Treat errors as non-critical */ + ret = mfd_add_devices(wm831x->dev, -1, backlight_devs, + ARRAY_SIZE(backlight_devs), NULL, 0); + if (ret < 0) + dev_err(wm831x->dev, "Failed to add backlight: %d\n", + ret); + } + if (pdata && pdata->post_init) { ret = pdata->post_init(wm831x); if (ret != 0) { diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h index 571e601..90d8202 100644 --- a/include/linux/mfd/wm831x/pdata.h +++ b/include/linux/mfd/wm831x/pdata.h @@ -18,6 +18,11 @@ struct wm831x; struct regulator_init_data; +struct wm831x_backlight_pdata { + int isink; /** ISINK to use, 1 or 2 */ + int max_uA; /** Maximum current to allow */ +}; + struct wm831x_backup_pdata { int charger_enable; int no_constant_voltage; /** Disable constant voltage charging */ @@ -87,6 +92,7 @@ struct wm831x_pdata { int (*post_init)(struct wm831x *wm831x); int gpio_base; + struct wm831x_backlight_pdata *backlight; struct wm831x_backup_pdata *backup; struct wm831x_battery_pdata *battery; struct wm831x_touch_pdata *touch; -- cgit v0.10.2 From 6704e5171ba9053ba173bcd807c7392d2076bdb4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:56 +0100 Subject: mfd: Add basic WM831x OTP support The WM831x series of devices use OTP (One Time Programmable, a type of PROM) to store system configuration. At run time this data is visible via registers. Currently the only explicitly supported feature is that the unique ID provided by every WM831x device is exported to user space via sysfs. Other configuration data may be read by system-specific code in the pre_init() and post_init() platform data operations. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index bc40ea3..33eaea2 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -23,6 +23,7 @@ #include #include #include +#include enum wm831x_parent { WM8310 = 0, @@ -1340,6 +1341,8 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) ret); } + wm831x_otp_init(wm831x); + if (pdata && pdata->post_init) { ret = pdata->post_init(wm831x); if (ret != 0) { @@ -1360,6 +1363,7 @@ err: static void wm831x_device_exit(struct wm831x *wm831x) { + wm831x_otp_exit(wm831x); mfd_remove_devices(wm831x->dev); wm831x_irq_exit(wm831x); kfree(wm831x); diff --git a/drivers/mfd/wm831x-otp.c b/drivers/mfd/wm831x-otp.c new file mode 100644 index 0000000..f742745 --- /dev/null +++ b/drivers/mfd/wm831x-otp.c @@ -0,0 +1,83 @@ +/* + * wm831x-otp.c -- OTP for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* In bytes */ +#define WM831X_UNIQUE_ID_LEN 16 + +/* Read the unique ID from the chip into id */ +static int wm831x_unique_id_read(struct wm831x *wm831x, char *id) +{ + int i, val; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN / 2; i++) { + val = wm831x_reg_read(wm831x, WM831X_UNIQUE_ID_1 + i); + if (val < 0) + return val; + + id[i * 2] = (val >> 8) & 0xff; + id[(i * 2) + 1] = val & 0xff; + } + + return 0; +} + +static ssize_t wm831x_unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int i, rval; + char id[WM831X_UNIQUE_ID_LEN]; + ssize_t ret = 0; + + rval = wm831x_unique_id_read(wm831x, id); + if (rval < 0) + return 0; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN; i++) + ret += sprintf(&buf[ret], "%02x", buf[i]); + + ret += sprintf(&buf[ret], "\n"); + + return ret; +} + +static DEVICE_ATTR(unique_id, 0444, wm831x_unique_id_show, NULL); + +int wm831x_otp_init(struct wm831x *wm831x) +{ + int ret; + + ret = device_create_file(wm831x->dev, &dev_attr_unique_id); + if (ret != 0) + dev_err(wm831x->dev, "Unique ID attribute not created: %d\n", + ret); + + return ret; +} + +void wm831x_otp_exit(struct wm831x *wm831x) +{ + device_remove_file(wm831x->dev, &dev_attr_unique_id); +} + diff --git a/include/linux/mfd/wm831x/otp.h b/include/linux/mfd/wm831x/otp.h new file mode 100644 index 0000000..ce1f81a --- /dev/null +++ b/include/linux/mfd/wm831x/otp.h @@ -0,0 +1,162 @@ +/* + * include/linux/mfd/wm831x/otp.h -- OTP interface for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_OTP_H__ +#define __MFD_WM831X_OTP_H__ + +int wm831x_otp_init(struct wm831x *wm831x); +void wm831x_otp_exit(struct wm831x *wm831x); + +/* + * R30720 (0x7800) - Unique ID 1 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30721 (0x7801) - Unique ID 2 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30722 (0x7802) - Unique ID 3 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30723 (0x7803) - Unique ID 4 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30724 (0x7804) - Unique ID 5 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30725 (0x7805) - Unique ID 6 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30726 (0x7806) - Unique ID 7 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30727 (0x7807) - Unique ID 8 + */ +#define WM831X_UNIQUE_ID_MASK 0xFFFF /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_SHIFT 0 /* UNIQUE_ID - [15:0] */ +#define WM831X_UNIQUE_ID_WIDTH 16 /* UNIQUE_ID - [15:0] */ + +/* + * R30728 (0x7808) - Factory OTP ID + */ +#define WM831X_OTP_FACT_ID_MASK 0xFFFE /* OTP_FACT_ID - [15:1] */ +#define WM831X_OTP_FACT_ID_SHIFT 1 /* OTP_FACT_ID - [15:1] */ +#define WM831X_OTP_FACT_ID_WIDTH 15 /* OTP_FACT_ID - [15:1] */ +#define WM831X_OTP_FACT_FINAL 0x0001 /* OTP_FACT_FINAL */ +#define WM831X_OTP_FACT_FINAL_MASK 0x0001 /* OTP_FACT_FINAL */ +#define WM831X_OTP_FACT_FINAL_SHIFT 0 /* OTP_FACT_FINAL */ +#define WM831X_OTP_FACT_FINAL_WIDTH 1 /* OTP_FACT_FINAL */ + +/* + * R30729 (0x7809) - Factory OTP 1 + */ +#define WM831X_DC3_TRIM_MASK 0xF000 /* DC3_TRIM - [15:12] */ +#define WM831X_DC3_TRIM_SHIFT 12 /* DC3_TRIM - [15:12] */ +#define WM831X_DC3_TRIM_WIDTH 4 /* DC3_TRIM - [15:12] */ +#define WM831X_DC2_TRIM_MASK 0x0FC0 /* DC2_TRIM - [11:6] */ +#define WM831X_DC2_TRIM_SHIFT 6 /* DC2_TRIM - [11:6] */ +#define WM831X_DC2_TRIM_WIDTH 6 /* DC2_TRIM - [11:6] */ +#define WM831X_DC1_TRIM_MASK 0x003F /* DC1_TRIM - [5:0] */ +#define WM831X_DC1_TRIM_SHIFT 0 /* DC1_TRIM - [5:0] */ +#define WM831X_DC1_TRIM_WIDTH 6 /* DC1_TRIM - [5:0] */ + +/* + * R30730 (0x780A) - Factory OTP 2 + */ +#define WM831X_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */ +#define WM831X_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */ +#define WM831X_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */ + +/* + * R30731 (0x780B) - Factory OTP 3 + */ +#define WM831X_OSC_TRIM_MASK 0x0780 /* OSC_TRIM - [10:7] */ +#define WM831X_OSC_TRIM_SHIFT 7 /* OSC_TRIM - [10:7] */ +#define WM831X_OSC_TRIM_WIDTH 4 /* OSC_TRIM - [10:7] */ +#define WM831X_BG_TRIM_MASK 0x0078 /* BG_TRIM - [6:3] */ +#define WM831X_BG_TRIM_SHIFT 3 /* BG_TRIM - [6:3] */ +#define WM831X_BG_TRIM_WIDTH 4 /* BG_TRIM - [6:3] */ +#define WM831X_LPBG_TRIM_MASK 0x0007 /* LPBG_TRIM - [2:0] */ +#define WM831X_LPBG_TRIM_SHIFT 0 /* LPBG_TRIM - [2:0] */ +#define WM831X_LPBG_TRIM_WIDTH 3 /* LPBG_TRIM - [2:0] */ + +/* + * R30732 (0x780C) - Factory OTP 4 + */ +#define WM831X_CHILD_I2C_ADDR_MASK 0x00FE /* CHILD_I2C_ADDR - [7:1] */ +#define WM831X_CHILD_I2C_ADDR_SHIFT 1 /* CHILD_I2C_ADDR - [7:1] */ +#define WM831X_CHILD_I2C_ADDR_WIDTH 7 /* CHILD_I2C_ADDR - [7:1] */ +#define WM831X_CH_AW 0x0001 /* CH_AW */ +#define WM831X_CH_AW_MASK 0x0001 /* CH_AW */ +#define WM831X_CH_AW_SHIFT 0 /* CH_AW */ +#define WM831X_CH_AW_WIDTH 1 /* CH_AW */ + +/* + * R30733 (0x780D) - Factory OTP 5 + */ +#define WM831X_CHARGE_TRIM_MASK 0x003F /* CHARGE_TRIM - [5:0] */ +#define WM831X_CHARGE_TRIM_SHIFT 0 /* CHARGE_TRIM - [5:0] */ +#define WM831X_CHARGE_TRIM_WIDTH 6 /* CHARGE_TRIM - [5:0] */ + +/* + * R30736 (0x7810) - Customer OTP ID + */ +#define WM831X_OTP_AUTO_PROG 0x8000 /* OTP_AUTO_PROG */ +#define WM831X_OTP_AUTO_PROG_MASK 0x8000 /* OTP_AUTO_PROG */ +#define WM831X_OTP_AUTO_PROG_SHIFT 15 /* OTP_AUTO_PROG */ +#define WM831X_OTP_AUTO_PROG_WIDTH 1 /* OTP_AUTO_PROG */ +#define WM831X_OTP_CUST_ID_MASK 0x7FFE /* OTP_CUST_ID - [14:1] */ +#define WM831X_OTP_CUST_ID_SHIFT 1 /* OTP_CUST_ID - [14:1] */ +#define WM831X_OTP_CUST_ID_WIDTH 14 /* OTP_CUST_ID - [14:1] */ +#define WM831X_OTP_CUST_FINAL 0x0001 /* OTP_CUST_FINAL */ +#define WM831X_OTP_CUST_FINAL_MASK 0x0001 /* OTP_CUST_FINAL */ +#define WM831X_OTP_CUST_FINAL_SHIFT 0 /* OTP_CUST_FINAL */ +#define WM831X_OTP_CUST_FINAL_WIDTH 1 /* OTP_CUST_FINAL */ + +/* + * R30759 (0x7827) - DBE CHECK DATA + */ +#define WM831X_DBE_VALID_DATA_MASK 0xFFFF /* DBE_VALID_DATA - [15:0] */ +#define WM831X_DBE_VALID_DATA_SHIFT 0 /* DBE_VALID_DATA - [15:0] */ +#define WM831X_DBE_VALID_DATA_WIDTH 16 /* DBE_VALID_DATA - [15:0] */ + + +#endif -- cgit v0.10.2 From 698659d5f78606c698781574773f433c60176e40 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:57 +0100 Subject: mfd: Export ISEL values from WM831x core The current settings which can be used with the WM831x current sinks can't easily be mapped between register values and currents at run time without a lookup table since the values scale logarithmically to match the way the human eye interprets brightness. This lookup table is inclided in the core since several drivers need to use it. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 33eaea2..49b7885 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -24,6 +24,70 @@ #include #include #include +#include + +/* Current settings - values are 2*2^(reg_val/4) microamps. These are + * exported since they are used by multiple drivers. + */ +int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL] = { + 2, + 2, + 3, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 13, + 16, + 19, + 23, + 27, + 32, + 38, + 45, + 54, + 64, + 76, + 91, + 108, + 128, + 152, + 181, + 215, + 256, + 304, + 362, + 431, + 512, + 609, + 724, + 861, + 1024, + 1218, + 1448, + 1722, + 2048, + 2435, + 2896, + 3444, + 4096, + 4871, + 5793, + 6889, + 8192, + 9742, + 11585, + 13777, + 16384, + 19484, + 23170, + 27554, +}; +EXPORT_SYMBOL_GPL(wm831x_isinkv_values); enum wm831x_parent { WM8310 = 0, diff --git a/include/linux/mfd/wm831x/regulator.h b/include/linux/mfd/wm831x/regulator.h new file mode 100644 index 0000000..b5d58fb --- /dev/null +++ b/include/linux/mfd/wm831x/regulator.h @@ -0,0 +1,21 @@ +/* + * linux/mfd/wm831x/regulator.h -- Regulator definitons for wm831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_REGULATOR_H__ +#define __MFD_WM831X_REGULATOR_H__ + +#define WM831X_ISINK_MAX_ISEL 56 +extern int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL]; + +#endif -- cgit v0.10.2 From b11062b9c558695cd054f16c697e1db0988e2603 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:45:58 +0100 Subject: mfd: Hook WM831x into build system Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 491ac0f..0273456 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -157,6 +157,16 @@ config MFD_WM8400 the device, additional drivers must be enabled in order to use the functionality of the device. +config MFD_WM831X + tristate "Support Wolfson Microelectronics WM831x PMICs" + select MFD_CORE + depends on I2C + help + Support for the Wolfson Microelecronics WM831x PMICs. This + driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_WM8350 tristate diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 6f8a9a1af..285cb32 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -15,6 +15,8 @@ obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o obj-$(CONFIG_MFD_WM8400) += wm8400-core.o +wm831x-objs := wm831x-core.o wm831x-irq.o wm831x-otp.o +obj-$(CONFIG_MFD_WM831X) += wm831x.o wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o -- cgit v0.10.2 From e4b736f18f338daae141325c818187c4ab3e244c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Jul 2009 14:46:00 +0100 Subject: gpio: Add WM831X GPIO driver Add support for the GPIO pins on the WM831x. No direct support is currently supplied for configuring non-gpiolib functionality such as pull configuration and alternate functions, soft configuration of these will be provided in a future patch. Currently use of these pins as interrupts is not supported due to the ongoing issues with generic irq not support interrupt controllers on interrupt driven buses. Users can directly request the interrupts with the wm831x-specific APIs currently provided if required. Signed-off-by: Mark Brown Acked-by: David Brownell Signed-off-by: Samuel Ortiz diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 96dda81..6b4c484 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -155,6 +155,13 @@ config GPIO_TWL4030 Say yes here to access the GPIO signals of various multi-function power management chips from Texas Instruments. +config GPIO_WM831X + tristate "WM831x GPIOs" + depends on MFD_WM831X + help + Say yes here to access the GPIO signals of WM831x power management + chips from Wolfson Microelectronics. + comment "PCI GPIO expanders:" config GPIO_BT8XX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 9244c6f..ea7c745 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o +obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c new file mode 100644 index 0000000..f9c09a5 --- /dev/null +++ b/drivers/gpio/wm831x-gpio.c @@ -0,0 +1,252 @@ +/* + * wm831x-gpio.c -- gpiolib support for Wolfson WM831x PMICs + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_GPIO_MAX 16 + +struct wm831x_gpio { + struct wm831x *wm831x; + struct gpio_chip gpio_chip; +}; + +static inline struct wm831x_gpio *to_wm831x_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct wm831x_gpio, gpio_chip); +} + +static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + + return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, + WM831X_GPN_DIR | WM831X_GPN_TRI, + WM831X_GPN_DIR); +} + +static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + if (ret < 0) + return ret; + + if (ret & 1 << offset) + return 1; + else + return 0; +} + +static int wm831x_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + + return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, + WM831X_GPN_DIR | WM831X_GPN_TRI, 0); +} + +static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + + wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset, + value << offset); +} + +#ifdef CONFIG_DEBUG_FS +static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip); + struct wm831x *wm831x = wm831x_gpio->wm831x; + int i; + + for (i = 0; i < chip->ngpio; i++) { + int gpio = i + chip->base; + int reg; + const char *label, *pull, *powerdomain; + + /* We report the GPIO even if it's not requested since + * we're also reporting things like alternate + * functions which apply even when the GPIO is not in + * use as a GPIO. + */ + label = gpiochip_is_requested(chip, i); + if (!label) + label = "Unrequested"; + + seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); + + reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i); + if (reg < 0) { + dev_err(wm831x->dev, + "GPIO control %d read failed: %d\n", + gpio, reg); + seq_printf(s, "\n"); + continue; + } + + switch (reg & WM831X_GPN_PULL_MASK) { + case WM831X_GPIO_PULL_NONE: + pull = "nopull"; + break; + case WM831X_GPIO_PULL_DOWN: + pull = "pulldown"; + break; + case WM831X_GPIO_PULL_UP: + pull = "pullup"; + default: + pull = "INVALID PULL"; + break; + } + + switch (i + 1) { + case 1 ... 3: + case 7 ... 9: + if (reg & WM831X_GPN_PWR_DOM) + powerdomain = "VPMIC"; + else + powerdomain = "DBVDD"; + break; + + case 4 ... 6: + case 10 ... 12: + if (reg & WM831X_GPN_PWR_DOM) + powerdomain = "SYSVDD"; + else + powerdomain = "DBVDD"; + break; + + case 13 ... 16: + powerdomain = "TPVDD"; + break; + + default: + BUG(); + break; + } + + seq_printf(s, " %s %s %s %s%s\n" + " %s%s (0x%4x)\n", + reg & WM831X_GPN_DIR ? "in" : "out", + wm831x_gpio_get(chip, i) ? "high" : "low", + pull, + powerdomain, + reg & WM831X_GPN_POL ? " inverted" : "", + reg & WM831X_GPN_OD ? "open-drain" : "CMOS", + reg & WM831X_GPN_TRI ? " tristated" : "", + reg); + } +} +#else +#define wm831x_gpio_dbg_show NULL +#endif + +static struct gpio_chip template_chip = { + .label = "wm831x", + .owner = THIS_MODULE, + .direction_input = wm831x_gpio_direction_in, + .get = wm831x_gpio_get, + .direction_output = wm831x_gpio_direction_out, + .set = wm831x_gpio_set, + .dbg_show = wm831x_gpio_dbg_show, + .can_sleep = 1, +}; + +static int __devinit wm831x_gpio_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + struct wm831x_gpio *wm831x_gpio; + int ret; + + wm831x_gpio = kzalloc(sizeof(*wm831x_gpio), GFP_KERNEL); + if (wm831x_gpio == NULL) + return -ENOMEM; + + wm831x_gpio->wm831x = wm831x; + wm831x_gpio->gpio_chip = template_chip; + wm831x_gpio->gpio_chip.ngpio = WM831X_GPIO_MAX; + wm831x_gpio->gpio_chip.dev = &pdev->dev; + if (pdata && pdata->gpio_base) + wm831x_gpio->gpio_chip.base = pdata->gpio_base; + else + wm831x_gpio->gpio_chip.base = -1; + + ret = gpiochip_add(&wm831x_gpio->gpio_chip); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip, %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, wm831x_gpio); + + return ret; + +err: + kfree(wm831x_gpio); + return ret; +} + +static int __devexit wm831x_gpio_remove(struct platform_device *pdev) +{ + struct wm831x_gpio *wm831x_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&wm831x_gpio->gpio_chip); + if (ret == 0) + kfree(wm831x_gpio); + + return ret; +} + +static struct platform_driver wm831x_gpio_driver = { + .driver.name = "wm831x-gpio", + .driver.owner = THIS_MODULE, + .probe = wm831x_gpio_probe, + .remove = __devexit_p(wm831x_gpio_remove), +}; + +static int __init wm831x_gpio_init(void) +{ + return platform_driver_register(&wm831x_gpio_driver); +} +subsys_initcall(wm831x_gpio_init); + +static void __exit wm831x_gpio_exit(void) +{ + platform_driver_unregister(&wm831x_gpio_driver); +} +module_exit(wm831x_gpio_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("GPIO interface for WM831x PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-gpio"); diff --git a/include/linux/mfd/wm831x/gpio.h b/include/linux/mfd/wm831x/gpio.h new file mode 100644 index 0000000..2835614 --- /dev/null +++ b/include/linux/mfd/wm831x/gpio.h @@ -0,0 +1,55 @@ +/* + * include/linux/mfd/wm831x/gpio.h -- GPIO for WM831x + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __MFD_WM831X_GPIO_H__ +#define __MFD_WM831X_GPIO_H__ + +/* + * R16440-16455 (0x4038-0x4047) - GPIOx Control + */ +#define WM831X_GPN_DIR 0x8000 /* GPN_DIR */ +#define WM831X_GPN_DIR_MASK 0x8000 /* GPN_DIR */ +#define WM831X_GPN_DIR_SHIFT 15 /* GPN_DIR */ +#define WM831X_GPN_DIR_WIDTH 1 /* GPN_DIR */ +#define WM831X_GPN_PULL_MASK 0x6000 /* GPN_PULL - [14:13] */ +#define WM831X_GPN_PULL_SHIFT 13 /* GPN_PULL - [14:13] */ +#define WM831X_GPN_PULL_WIDTH 2 /* GPN_PULL - [14:13] */ +#define WM831X_GPN_INT_MODE 0x1000 /* GPN_INT_MODE */ +#define WM831X_GPN_INT_MODE_MASK 0x1000 /* GPN_INT_MODE */ +#define WM831X_GPN_INT_MODE_SHIFT 12 /* GPN_INT_MODE */ +#define WM831X_GPN_INT_MODE_WIDTH 1 /* GPN_INT_MODE */ +#define WM831X_GPN_PWR_DOM 0x0800 /* GPN_PWR_DOM */ +#define WM831X_GPN_PWR_DOM_MASK 0x0800 /* GPN_PWR_DOM */ +#define WM831X_GPN_PWR_DOM_SHIFT 11 /* GPN_PWR_DOM */ +#define WM831X_GPN_PWR_DOM_WIDTH 1 /* GPN_PWR_DOM */ +#define WM831X_GPN_POL 0x0400 /* GPN_POL */ +#define WM831X_GPN_POL_MASK 0x0400 /* GPN_POL */ +#define WM831X_GPN_POL_SHIFT 10 /* GPN_POL */ +#define WM831X_GPN_POL_WIDTH 1 /* GPN_POL */ +#define WM831X_GPN_OD 0x0200 /* GPN_OD */ +#define WM831X_GPN_OD_MASK 0x0200 /* GPN_OD */ +#define WM831X_GPN_OD_SHIFT 9 /* GPN_OD */ +#define WM831X_GPN_OD_WIDTH 1 /* GPN_OD */ +#define WM831X_GPN_TRI 0x0080 /* GPN_TRI */ +#define WM831X_GPN_TRI_MASK 0x0080 /* GPN_TRI */ +#define WM831X_GPN_TRI_SHIFT 7 /* GPN_TRI */ +#define WM831X_GPN_TRI_WIDTH 1 /* GPN_TRI */ +#define WM831X_GPN_FN_MASK 0x000F /* GPN_FN - [3:0] */ +#define WM831X_GPN_FN_SHIFT 0 /* GPN_FN - [3:0] */ +#define WM831X_GPN_FN_WIDTH 4 /* GPN_FN - [3:0] */ + +#define WM831X_GPIO_PULL_NONE (0 << WM831X_GPN_PULL_SHIFT) +#define WM831X_GPIO_PULL_DOWN (1 << WM831X_GPN_PULL_SHIFT) +#define WM831X_GPIO_PULL_UP (2 << WM831X_GPN_PULL_SHIFT) +#endif -- cgit v0.10.2 From 08bad5a821371548942aa13565831f18fe1875f3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:52:22 +0100 Subject: hwmon: WM831x PMIC hardware monitoring driver This driver adds support for the hardware monitoring features of the WM831x PMICs to the hwmon API. Monitoring is provided for the system voltages supported natively by the WM831x, the chip temperature, the battery temperature and the auxiliary inputs of the WM831x. Currently no alarms are supported, though digital comparators on the WM831x devices would allow these to be provided. Since the auxiliary and battery temperature input scaling depends on the system configuration the value is reported as a voltage to userspace. Signed-off-by: Mark Brown Acked-by: Jean Delvare Signed-off-by: Samuel Ortiz diff --git a/Documentation/hwmon/wm831x b/Documentation/hwmon/wm831x new file mode 100644 index 0000000..24f47d8 --- /dev/null +++ b/Documentation/hwmon/wm831x @@ -0,0 +1,37 @@ +Kernel driver wm831x-hwmon +========================== + +Supported chips: + * Wolfson Microelectronics WM831x PMICs + Prefix: 'wm831x' + Datasheet: + http://www.wolfsonmicro.com/products/WM8310 + http://www.wolfsonmicro.com/products/WM8311 + http://www.wolfsonmicro.com/products/WM8312 + +Authors: Mark Brown + +Description +----------- + +The WM831x series of PMICs include an AUXADC which can be used to +monitor a range of system operating parameters, including the voltages +of the major supplies within the system. Currently the driver provides +reporting of all the input values but does not provide any alarms. + +Voltage Monitoring +------------------ + +Voltages are sampled by a 12 bit ADC. Voltages in milivolts are 1.465 +times the ADC value. + +Temperature Monitoring +---------------------- + +Temperatures are sampled by a 12 bit ADC. Chip and battery temperatures +are available. The chip temperature is calculated as: + + Degrees celsius = (512.18 - data) / 1.0983 + +while the battery temperature calculation will depend on the NTC +thermistor component. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 120085c..83e07e5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -937,6 +937,17 @@ config SENSORS_W83627EHF This driver can also be built as a module. If so, the module will be called w83627ehf. +config SENSORS_WM831X + tristate "WM831x PMICs" + depends on MFD_WM831X + help + If you say yes here you get support for the hardware + monitoring functionality of the Wolfson Microelectronics + WM831x series of PMICs. + + This driver can also be built as a module. If so, the module + will be called wm831x-hwmon. + config SENSORS_WM8350 tristate "Wolfson Microelectronics WM835x" depends on MFD_WM8350 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index ed5f178..c5effd60 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) diff --git a/drivers/hwmon/wm831x-hwmon.c b/drivers/hwmon/wm831x-hwmon.c new file mode 100644 index 0000000..c16e9e7 --- /dev/null +++ b/drivers/hwmon/wm831x-hwmon.c @@ -0,0 +1,226 @@ +/* + * drivers/hwmon/wm831x-hwmon.c - Wolfson Microelectronics WM831x PMIC + * hardware monitoring features. + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct wm831x_hwmon { + struct wm831x *wm831x; + struct device *classdev; +}; + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "wm831x\n"); +} + +static const char *input_names[] = { + [WM831X_AUX_SYSVDD] = "SYSVDD", + [WM831X_AUX_USB] = "USB", + [WM831X_AUX_BKUP_BATT] = "Backup battery", + [WM831X_AUX_BATT] = "Battery", + [WM831X_AUX_WALL] = "WALL", + [WM831X_AUX_CHIP_TEMP] = "PMIC", + [WM831X_AUX_BATT_TEMP] = "Battery", +}; + + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x_hwmon *hwmon = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int ret; + + ret = wm831x_auxadc_read_uv(hwmon->wm831x, channel); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(ret, 1000)); +} + +static ssize_t show_chip_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x_hwmon *hwmon = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int ret; + + ret = wm831x_auxadc_read(hwmon->wm831x, channel); + if (ret < 0) + return ret; + + /* Degrees celsius = (512.18-ret) / 1.0983 */ + ret = 512180 - (ret * 1000); + ret = DIV_ROUND_CLOSEST(ret * 10000, 10983); + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", input_names[channel]); +} + +#define WM831X_VOLTAGE(id, name) \ + static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage, \ + NULL, name) + +#define WM831X_NAMED_VOLTAGE(id, name) \ + WM831X_VOLTAGE(id, name); \ + static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ + NULL, name) + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +WM831X_VOLTAGE(0, WM831X_AUX_AUX1); +WM831X_VOLTAGE(1, WM831X_AUX_AUX2); +WM831X_VOLTAGE(2, WM831X_AUX_AUX3); +WM831X_VOLTAGE(3, WM831X_AUX_AUX4); + +WM831X_NAMED_VOLTAGE(4, WM831X_AUX_SYSVDD); +WM831X_NAMED_VOLTAGE(5, WM831X_AUX_USB); +WM831X_NAMED_VOLTAGE(6, WM831X_AUX_BATT); +WM831X_NAMED_VOLTAGE(7, WM831X_AUX_WALL); +WM831X_NAMED_VOLTAGE(8, WM831X_AUX_BKUP_BATT); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_chip_temp, NULL, + WM831X_AUX_CHIP_TEMP); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, + WM831X_AUX_CHIP_TEMP); +/* Report as a voltage since conversion depends on external components + * and that's what the ABI wants. */ +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_voltage, NULL, + WM831X_AUX_BATT_TEMP); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, + WM831X_AUX_BATT_TEMP); + +static struct attribute *wm831x_attributes[] = { + &dev_attr_name.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + + NULL +}; + +static const struct attribute_group wm831x_attr_group = { + .attrs = wm831x_attributes, +}; + +static int __devinit wm831x_hwmon_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_hwmon *hwmon; + int ret; + + hwmon = kzalloc(sizeof(struct wm831x_hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->wm831x = wm831x; + + ret = sysfs_create_group(&pdev->dev.kobj, &wm831x_attr_group); + if (ret) + goto err; + + hwmon->classdev = hwmon_device_register(&pdev->dev); + if (IS_ERR(hwmon->classdev)) { + ret = PTR_ERR(hwmon->classdev); + goto err_sysfs; + } + + platform_set_drvdata(pdev, hwmon); + + return 0; + +err_sysfs: + sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group); +err: + kfree(hwmon); + return ret; +} + +static int __devexit wm831x_hwmon_remove(struct platform_device *pdev) +{ + struct wm831x_hwmon *hwmon = platform_get_drvdata(pdev); + + hwmon_device_unregister(hwmon->classdev); + sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group); + platform_set_drvdata(pdev, NULL); + kfree(hwmon); + + return 0; +} + +static struct platform_driver wm831x_hwmon_driver = { + .probe = wm831x_hwmon_probe, + .remove = __devexit_p(wm831x_hwmon_remove), + .driver = { + .name = "wm831x-hwmon", + .owner = THIS_MODULE, + }, +}; + +static int __init wm831x_hwmon_init(void) +{ + return platform_driver_register(&wm831x_hwmon_driver); +} +module_init(wm831x_hwmon_init); + +static void __exit wm831x_hwmon_exit(void) +{ + platform_driver_unregister(&wm831x_hwmon_driver); +} +module_exit(wm831x_hwmon_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM831x Hardware Monitoring"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-hwmon"); -- cgit v0.10.2 From 0c73b992dd4c645f050344cb13142c0fd3496824 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 15 Sep 2009 12:07:12 +0200 Subject: input: Add support for the WM831x ON pin The WM831x series of PMICs support control of initial power on through the ON pin on the device with soft control of the pin at other times. Represent this to userspace as KEY_POWER. Signed-off-by: Mark Brown Acked-by: Dmitry Torokhov Signed-off-by: Samuel Ortiz diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index cbe21bc..852941d 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -279,4 +279,14 @@ config INPUT_BFIN_ROTARY To compile this driver as a module, choose M here: the module will be called bfin-rotary. +config INPUT_WM831X_ON + tristate "WM831X ON pin" + depends on MFD_WM831X + help + Support the ON pin of WM831X PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called wm831x_on. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 79c1e9a..c97533f 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -26,4 +26,5 @@ obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o +obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o diff --git a/drivers/input/misc/wm831x-on.c b/drivers/input/misc/wm831x-on.c new file mode 100644 index 0000000..ba4f5dd --- /dev/null +++ b/drivers/input/misc/wm831x-on.c @@ -0,0 +1,163 @@ +/** + * wm831x-on.c - WM831X ON pin driver + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct wm831x_on { + struct input_dev *dev; + struct delayed_work work; + struct wm831x *wm831x; +}; + +/* + * The chip gives us an interrupt when the ON pin is asserted but we + * then need to poll to see when the pin is deasserted. + */ +static void wm831x_poll_on(struct work_struct *work) +{ + struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on, + work.work); + struct wm831x *wm831x = wm831x_on->wm831x; + int poll, ret; + + ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL); + if (ret >= 0) { + poll = !(ret & WM831X_ON_PIN_STS); + + input_report_key(wm831x_on->dev, KEY_POWER, poll); + input_sync(wm831x_on->dev); + } else { + dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret); + poll = 1; + } + + if (poll) + schedule_delayed_work(&wm831x_on->work, 100); +} + +static irqreturn_t wm831x_on_irq(int irq, void *data) +{ + struct wm831x_on *wm831x_on = data; + + schedule_delayed_work(&wm831x_on->work, 0); + + return IRQ_HANDLED; +} + +static int __devinit wm831x_on_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_on *wm831x_on; + int irq = platform_get_irq(pdev, 0); + int ret; + + wm831x_on = kzalloc(sizeof(struct wm831x_on), GFP_KERNEL); + if (!wm831x_on) { + dev_err(&pdev->dev, "Can't allocate data\n"); + return -ENOMEM; + } + + wm831x_on->wm831x = wm831x; + INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on); + + wm831x_on->dev = input_allocate_device(); + if (!wm831x_on->dev) { + dev_err(&pdev->dev, "Can't allocate input dev\n"); + ret = -ENOMEM; + goto err; + } + + wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY); + wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + wm831x_on->dev->name = "wm831x_on"; + wm831x_on->dev->phys = "wm831x_on/input0"; + wm831x_on->dev->dev.parent = &pdev->dev; + + ret = wm831x_request_irq(wm831x, irq, wm831x_on_irq, + IRQF_TRIGGER_RISING, "wm831x_on", wm831x_on); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret); + goto err_input_dev; + } + ret = input_register_device(wm831x_on->dev); + if (ret) { + dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret); + goto err_irq; + } + + platform_set_drvdata(pdev, wm831x_on); + + return 0; + +err_irq: + wm831x_free_irq(wm831x, irq, NULL); +err_input_dev: + input_free_device(wm831x_on->dev); +err: + kfree(wm831x_on); + return ret; +} + +static int __devexit wm831x_on_remove(struct platform_device *pdev) +{ + struct wm831x_on *wm831x_on = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + wm831x_free_irq(wm831x_on->wm831x, irq, wm831x_on); + cancel_delayed_work_sync(&wm831x_on->work); + input_unregister_device(wm831x_on->dev); + kfree(wm831x_on); + + return 0; +} + +static struct platform_driver wm831x_on_driver = { + .probe = wm831x_on_probe, + .remove = __devexit_p(wm831x_on_remove), + .driver = { + .name = "wm831x-on", + .owner = THIS_MODULE, + }, +}; + +static int __init wm831x_on_init(void) +{ + return platform_driver_register(&wm831x_on_driver); +} +module_init(wm831x_on_init); + +static void __exit wm831x_on_exit(void) +{ + platform_driver_unregister(&wm831x_on_driver); +} +module_exit(wm831x_on_exit); + +MODULE_ALIAS("platform:wm831x-on"); +MODULE_DESCRIPTION("WM831x ON pin"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown "); + diff --git a/include/linux/mfd/wm831x/core.h b/include/linux/mfd/wm831x/core.h index d7134df..91eb493 100644 --- a/include/linux/mfd/wm831x/core.h +++ b/include/linux/mfd/wm831x/core.h @@ -216,6 +216,25 @@ #define WM831X_PARENT_ID_SHIFT 0 /* PARENT_ID - [15:0] */ #define WM831X_PARENT_ID_WIDTH 16 /* PARENT_ID - [15:0] */ +/* + * R16389 (0x4005) - ON Pin Control + */ +#define WM831X_ON_PIN_SECACT_MASK 0x0300 /* ON_PIN_SECACT - [9:8] */ +#define WM831X_ON_PIN_SECACT_SHIFT 8 /* ON_PIN_SECACT - [9:8] */ +#define WM831X_ON_PIN_SECACT_WIDTH 2 /* ON_PIN_SECACT - [9:8] */ +#define WM831X_ON_PIN_PRIMACT_MASK 0x0030 /* ON_PIN_PRIMACT - [5:4] */ +#define WM831X_ON_PIN_PRIMACT_SHIFT 4 /* ON_PIN_PRIMACT - [5:4] */ +#define WM831X_ON_PIN_PRIMACT_WIDTH 2 /* ON_PIN_PRIMACT - [5:4] */ +#define WM831X_ON_PIN_STS 0x0008 /* ON_PIN_STS */ +#define WM831X_ON_PIN_STS_MASK 0x0008 /* ON_PIN_STS */ +#define WM831X_ON_PIN_STS_SHIFT 3 /* ON_PIN_STS */ +#define WM831X_ON_PIN_STS_WIDTH 1 /* ON_PIN_STS */ +#define WM831X_ON_PIN_TO_MASK 0x0003 /* ON_PIN_TO - [1:0] */ +#define WM831X_ON_PIN_TO_SHIFT 0 /* ON_PIN_TO - [1:0] */ +#define WM831X_ON_PIN_TO_WIDTH 2 /* ON_PIN_TO - [1:0] */ + +struct regulator_dev; + struct wm831x { struct mutex io_lock; -- cgit v0.10.2 From be721979dd6b335e4ab6f83abb5cc11c33662aa8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 4 Aug 2009 20:09:52 +0200 Subject: regulator: Provide mode to status conversion function This is useful for implementing get_status() in terms of get_mode(). Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 98c3a74..91ba9bf 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1864,6 +1864,30 @@ int regulator_notifier_call_chain(struct regulator_dev *rdev, } EXPORT_SYMBOL_GPL(regulator_notifier_call_chain); +/** + * regulator_mode_to_status - convert a regulator mode into a status + * + * @mode: Mode to convert + * + * Convert a regulator mode into a status. + */ +int regulator_mode_to_status(unsigned int mode) +{ + switch (mode) { + case REGULATOR_MODE_FAST: + return REGULATOR_STATUS_FAST; + case REGULATOR_MODE_NORMAL: + return REGULATOR_STATUS_NORMAL; + case REGULATOR_MODE_IDLE: + return REGULATOR_STATUS_IDLE; + case REGULATOR_STATUS_STANDBY: + return REGULATOR_STATUS_STANDBY; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(regulator_mode_to_status); + /* * To avoid cluttering sysfs (and memory) with useless state, only * create attributes that can be meaningfully displayed. diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 225f733..ce1be70 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -193,6 +193,8 @@ void *rdev_get_drvdata(struct regulator_dev *rdev); struct device *rdev_get_dev(struct regulator_dev *rdev); int rdev_get_id(struct regulator_dev *rdev); +int regulator_mode_to_status(unsigned int); + void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data); #endif -- cgit v0.10.2 From e4ee831f949a7c7746a56bcf1e7ca057d6f69e2a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:21:49 +0100 Subject: regulator: Add WM831x DC-DC buck convertor support The WM831x series of devices all have 3 DC-DC buck convertors. This driver implements software control for these regulators via the regulator API. Use with split hardware/software control of individual regulators is not supported, though regulators not controlled by software may be controlled via the hardware control interfaces. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index da7483e..38ea5dc 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -82,6 +82,13 @@ config REGULATOR_TWL4030 This driver supports the voltage regulators provided by this family of companion chips. +config REGULATOR_WM831X + tristate "Wolfson Microelcronics WM831x PMIC regulators" + depends on MFD_WM831X + help + Support the voltage and current regulators of the WM831x series + of PMIC devices. + config REGULATOR_WM8350 tristate "Wolfson Microelectroncis WM8350 AudioPlus PMIC" depends on MFD_WM8350 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3a9748f..b1d2b82 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c new file mode 100644 index 0000000..fa5126e --- /dev/null +++ b/drivers/regulator/wm831x-dcdc.c @@ -0,0 +1,643 @@ +/* + * wm831x-dcdc.c -- DC-DC buck convertor driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_BUCKV_MAX_SELECTOR 0x68 +#define WM831X_BUCKP_MAX_SELECTOR 0x66 + +#define WM831X_DCDC_MODE_FAST 0 +#define WM831X_DCDC_MODE_NORMAL 1 +#define WM831X_DCDC_MODE_IDLE 2 +#define WM831X_DCDC_MODE_STANDBY 3 + +#define WM831X_DCDC_MAX_NAME 6 + +/* Register offsets in control block */ +#define WM831X_DCDC_CONTROL_1 0 +#define WM831X_DCDC_CONTROL_2 1 +#define WM831X_DCDC_ON_CONFIG 2 +#define WM831X_DCDC_SLEEP_CONTROL 3 + +/* + * Shared + */ + +struct wm831x_dcdc { + char name[WM831X_DCDC_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +static int wm831x_dcdc_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + int reg; + + reg = wm831x_reg_read(wm831x, WM831X_DCDC_ENABLE); + if (reg < 0) + return reg; + + if (reg & mask) + return 1; + else + return 0; +} + +static int wm831x_dcdc_enable(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, mask); +} + +static int wm831x_dcdc_disable(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, 0); +} + +static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev) + +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + val = (val & WM831X_DC1_ON_MODE_MASK) >> WM831X_DC1_ON_MODE_SHIFT; + + switch (val) { + case WM831X_DCDC_MODE_FAST: + return REGULATOR_MODE_FAST; + case WM831X_DCDC_MODE_NORMAL: + return REGULATOR_MODE_NORMAL; + case WM831X_DCDC_MODE_STANDBY: + return REGULATOR_MODE_STANDBY; + case WM831X_DCDC_MODE_IDLE: + return REGULATOR_MODE_IDLE; + default: + BUG(); + } +} + +static int wm831x_dcdc_set_mode_int(struct wm831x *wm831x, int reg, + unsigned int mode) +{ + int val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = WM831X_DCDC_MODE_FAST; + break; + case REGULATOR_MODE_NORMAL: + val = WM831X_DCDC_MODE_NORMAL; + break; + case REGULATOR_MODE_STANDBY: + val = WM831X_DCDC_MODE_STANDBY; + break; + case REGULATOR_MODE_IDLE: + val = WM831X_DCDC_MODE_IDLE; + break; + default: + return -EINVAL; + } + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_MODE_MASK, + val << WM831X_DC1_ON_MODE_SHIFT); +} + +static int wm831x_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* DCDC1 and DCDC2 can additionally detect high voltage/current */ + if (rdev_get_id(rdev) < 2) { + if (ret & (WM831X_DC1_OV_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + if (ret & (WM831X_DC1_HC_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over current\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (!(ret & (1 << rdev_get_id(rdev)))) + return REGULATOR_STATUS_OFF; + + /* TODO: When we handle hardware control modes so we can report the + * current mode. */ + return REGULATOR_STATUS_ON; +} + +static irqreturn_t wm831x_dcdc_uv_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_dcdc_oc_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + +/* + * BUCKV specifics + */ + +static int wm831x_buckv_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector <= 0x8) + return 600000; + if (selector <= WM831X_BUCKV_MAX_SELECTOR) + return 600000 + ((selector - 0x8) * 12500); + return -EINVAL; +} + +static int wm831x_buckv_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 vsel; + + if (min_uV < 600000) + vsel = 0; + else if (min_uV <= 1800000) + vsel = ((min_uV - 600000) / 12500) + 8; + else + return -EINVAL; + + if (wm831x_buckv_list_voltage(rdev, vsel) > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_VSEL_MASK, vsel); +} + +static int wm831x_buckv_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_buckv_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_buckv_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_buckv_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_buckv_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_buckv_list_voltage(rdev, val & WM831X_DC1_ON_VSEL_MASK); +} + +/* Current limit options */ +static u16 wm831x_dcdc_ilim[] = { + 125, 250, 375, 500, 625, 750, 875, 1000 +}; + +static int wm831x_buckv_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int i; + + for (i = 0; i < ARRAY_SIZE(wm831x_dcdc_ilim); i++) { + if (max_uA <= wm831x_dcdc_ilim[i]) + break; + } + if (i == ARRAY_SIZE(wm831x_dcdc_ilim)) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_HC_THR_MASK, i); +} + +static int wm831x_buckv_get_current_limit(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_dcdc_ilim[val & WM831X_DC1_HC_THR_MASK]; +} + +static struct regulator_ops wm831x_buckv_ops = { + .set_voltage = wm831x_buckv_set_voltage, + .get_voltage = wm831x_buckv_get_voltage, + .list_voltage = wm831x_buckv_list_voltage, + .set_suspend_voltage = wm831x_buckv_set_suspend_voltage, + .set_current_limit = wm831x_buckv_set_current_limit, + .get_current_limit = wm831x_buckv_get_current_limit, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +static __devinit int wm831x_buckv_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKV_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckv_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + irq = platform_get_irq_byname(pdev, "HC"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_oc_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request HC IRQ %d: %d\n", + irq, ret); + goto err_uv; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_uv: + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_buckv_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "HC"), dcdc); + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_buckv_driver = { + .probe = wm831x_buckv_probe, + .remove = __devexit_p(wm831x_buckv_remove), + .driver = { + .name = "wm831x-buckv", + }, +}; + +/* + * BUCKP specifics + */ + +static int wm831x_buckp_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector <= WM831X_BUCKP_MAX_SELECTOR) + return 850000 + (selector * 25000); + else + return -EINVAL; +} + +static int wm831x_buckp_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 vsel; + + if (min_uV <= 34000000) + vsel = (min_uV - 850000) / 25000; + else + return -EINVAL; + + if (wm831x_buckp_list_voltage(rdev, vsel) > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_DC3_ON_VSEL_MASK, vsel); +} + +static int wm831x_buckp_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_buckp_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_buckp_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_buckp_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_buckp_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + return wm831x_buckp_list_voltage(rdev, val & WM831X_DC3_ON_VSEL_MASK); +} + +static struct regulator_ops wm831x_buckp_ops = { + .set_voltage = wm831x_buckp_set_voltage, + .get_voltage = wm831x_buckp_get_voltage, + .list_voltage = wm831x_buckp_list_voltage, + .set_suspend_voltage = wm831x_buckp_set_suspend_voltage, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +static __devinit int wm831x_buckp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKP_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckp_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_buckp_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_buckp_driver = { + .probe = wm831x_buckp_probe, + .remove = __devexit_p(wm831x_buckp_remove), + .driver = { + .name = "wm831x-buckp", + }, +}; + +static int __init wm831x_dcdc_init(void) +{ + int ret; + ret = platform_driver_register(&wm831x_buckv_driver); + if (ret != 0) + pr_err("Failed to register WM831x BUCKV driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_buckp_driver); + if (ret != 0) + pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + + return 0; +} +subsys_initcall(wm831x_dcdc_init); + +static void __exit wm831x_dcdc_exit(void) +{ + platform_driver_unregister(&wm831x_buckp_driver); + platform_driver_unregister(&wm831x_buckv_driver); +} +module_exit(wm831x_dcdc_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x DC-DC convertor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-buckv"); +MODULE_ALIAS("platform:wm831x-buckp"); diff --git a/include/linux/mfd/wm831x/regulator.h b/include/linux/mfd/wm831x/regulator.h index b5d58fb..c74d6aa 100644 --- a/include/linux/mfd/wm831x/regulator.h +++ b/include/linux/mfd/wm831x/regulator.h @@ -15,6 +15,577 @@ #ifndef __MFD_WM831X_REGULATOR_H__ #define __MFD_WM831X_REGULATOR_H__ +/* + * R16462 (0x404E) - Current Sink 1 + */ +#define WM831X_CS1_ENA 0x8000 /* CS1_ENA */ +#define WM831X_CS1_ENA_MASK 0x8000 /* CS1_ENA */ +#define WM831X_CS1_ENA_SHIFT 15 /* CS1_ENA */ +#define WM831X_CS1_ENA_WIDTH 1 /* CS1_ENA */ +#define WM831X_CS1_DRIVE 0x4000 /* CS1_DRIVE */ +#define WM831X_CS1_DRIVE_MASK 0x4000 /* CS1_DRIVE */ +#define WM831X_CS1_DRIVE_SHIFT 14 /* CS1_DRIVE */ +#define WM831X_CS1_DRIVE_WIDTH 1 /* CS1_DRIVE */ +#define WM831X_CS1_SLPENA 0x1000 /* CS1_SLPENA */ +#define WM831X_CS1_SLPENA_MASK 0x1000 /* CS1_SLPENA */ +#define WM831X_CS1_SLPENA_SHIFT 12 /* CS1_SLPENA */ +#define WM831X_CS1_SLPENA_WIDTH 1 /* CS1_SLPENA */ +#define WM831X_CS1_OFF_RAMP_MASK 0x0C00 /* CS1_OFF_RAMP - [11:10] */ +#define WM831X_CS1_OFF_RAMP_SHIFT 10 /* CS1_OFF_RAMP - [11:10] */ +#define WM831X_CS1_OFF_RAMP_WIDTH 2 /* CS1_OFF_RAMP - [11:10] */ +#define WM831X_CS1_ON_RAMP_MASK 0x0300 /* CS1_ON_RAMP - [9:8] */ +#define WM831X_CS1_ON_RAMP_SHIFT 8 /* CS1_ON_RAMP - [9:8] */ +#define WM831X_CS1_ON_RAMP_WIDTH 2 /* CS1_ON_RAMP - [9:8] */ +#define WM831X_CS1_ISEL_MASK 0x003F /* CS1_ISEL - [5:0] */ +#define WM831X_CS1_ISEL_SHIFT 0 /* CS1_ISEL - [5:0] */ +#define WM831X_CS1_ISEL_WIDTH 6 /* CS1_ISEL - [5:0] */ + +/* + * R16463 (0x404F) - Current Sink 2 + */ +#define WM831X_CS2_ENA 0x8000 /* CS2_ENA */ +#define WM831X_CS2_ENA_MASK 0x8000 /* CS2_ENA */ +#define WM831X_CS2_ENA_SHIFT 15 /* CS2_ENA */ +#define WM831X_CS2_ENA_WIDTH 1 /* CS2_ENA */ +#define WM831X_CS2_DRIVE 0x4000 /* CS2_DRIVE */ +#define WM831X_CS2_DRIVE_MASK 0x4000 /* CS2_DRIVE */ +#define WM831X_CS2_DRIVE_SHIFT 14 /* CS2_DRIVE */ +#define WM831X_CS2_DRIVE_WIDTH 1 /* CS2_DRIVE */ +#define WM831X_CS2_SLPENA 0x1000 /* CS2_SLPENA */ +#define WM831X_CS2_SLPENA_MASK 0x1000 /* CS2_SLPENA */ +#define WM831X_CS2_SLPENA_SHIFT 12 /* CS2_SLPENA */ +#define WM831X_CS2_SLPENA_WIDTH 1 /* CS2_SLPENA */ +#define WM831X_CS2_OFF_RAMP_MASK 0x0C00 /* CS2_OFF_RAMP - [11:10] */ +#define WM831X_CS2_OFF_RAMP_SHIFT 10 /* CS2_OFF_RAMP - [11:10] */ +#define WM831X_CS2_OFF_RAMP_WIDTH 2 /* CS2_OFF_RAMP - [11:10] */ +#define WM831X_CS2_ON_RAMP_MASK 0x0300 /* CS2_ON_RAMP - [9:8] */ +#define WM831X_CS2_ON_RAMP_SHIFT 8 /* CS2_ON_RAMP - [9:8] */ +#define WM831X_CS2_ON_RAMP_WIDTH 2 /* CS2_ON_RAMP - [9:8] */ +#define WM831X_CS2_ISEL_MASK 0x003F /* CS2_ISEL - [5:0] */ +#define WM831X_CS2_ISEL_SHIFT 0 /* CS2_ISEL - [5:0] */ +#define WM831X_CS2_ISEL_WIDTH 6 /* CS2_ISEL - [5:0] */ + +/* + * R16464 (0x4050) - DCDC Enable + */ +#define WM831X_EPE2_ENA 0x0080 /* EPE2_ENA */ +#define WM831X_EPE2_ENA_MASK 0x0080 /* EPE2_ENA */ +#define WM831X_EPE2_ENA_SHIFT 7 /* EPE2_ENA */ +#define WM831X_EPE2_ENA_WIDTH 1 /* EPE2_ENA */ +#define WM831X_EPE1_ENA 0x0040 /* EPE1_ENA */ +#define WM831X_EPE1_ENA_MASK 0x0040 /* EPE1_ENA */ +#define WM831X_EPE1_ENA_SHIFT 6 /* EPE1_ENA */ +#define WM831X_EPE1_ENA_WIDTH 1 /* EPE1_ENA */ +#define WM831X_DC4_ENA 0x0008 /* DC4_ENA */ +#define WM831X_DC4_ENA_MASK 0x0008 /* DC4_ENA */ +#define WM831X_DC4_ENA_SHIFT 3 /* DC4_ENA */ +#define WM831X_DC4_ENA_WIDTH 1 /* DC4_ENA */ +#define WM831X_DC3_ENA 0x0004 /* DC3_ENA */ +#define WM831X_DC3_ENA_MASK 0x0004 /* DC3_ENA */ +#define WM831X_DC3_ENA_SHIFT 2 /* DC3_ENA */ +#define WM831X_DC3_ENA_WIDTH 1 /* DC3_ENA */ +#define WM831X_DC2_ENA 0x0002 /* DC2_ENA */ +#define WM831X_DC2_ENA_MASK 0x0002 /* DC2_ENA */ +#define WM831X_DC2_ENA_SHIFT 1 /* DC2_ENA */ +#define WM831X_DC2_ENA_WIDTH 1 /* DC2_ENA */ +#define WM831X_DC1_ENA 0x0001 /* DC1_ENA */ +#define WM831X_DC1_ENA_MASK 0x0001 /* DC1_ENA */ +#define WM831X_DC1_ENA_SHIFT 0 /* DC1_ENA */ +#define WM831X_DC1_ENA_WIDTH 1 /* DC1_ENA */ + +/* + * R16465 (0x4051) - LDO Enable + */ +#define WM831X_LDO11_ENA 0x0400 /* LDO11_ENA */ +#define WM831X_LDO11_ENA_MASK 0x0400 /* LDO11_ENA */ +#define WM831X_LDO11_ENA_SHIFT 10 /* LDO11_ENA */ +#define WM831X_LDO11_ENA_WIDTH 1 /* LDO11_ENA */ +#define WM831X_LDO10_ENA 0x0200 /* LDO10_ENA */ +#define WM831X_LDO10_ENA_MASK 0x0200 /* LDO10_ENA */ +#define WM831X_LDO10_ENA_SHIFT 9 /* LDO10_ENA */ +#define WM831X_LDO10_ENA_WIDTH 1 /* LDO10_ENA */ +#define WM831X_LDO9_ENA 0x0100 /* LDO9_ENA */ +#define WM831X_LDO9_ENA_MASK 0x0100 /* LDO9_ENA */ +#define WM831X_LDO9_ENA_SHIFT 8 /* LDO9_ENA */ +#define WM831X_LDO9_ENA_WIDTH 1 /* LDO9_ENA */ +#define WM831X_LDO8_ENA 0x0080 /* LDO8_ENA */ +#define WM831X_LDO8_ENA_MASK 0x0080 /* LDO8_ENA */ +#define WM831X_LDO8_ENA_SHIFT 7 /* LDO8_ENA */ +#define WM831X_LDO8_ENA_WIDTH 1 /* LDO8_ENA */ +#define WM831X_LDO7_ENA 0x0040 /* LDO7_ENA */ +#define WM831X_LDO7_ENA_MASK 0x0040 /* LDO7_ENA */ +#define WM831X_LDO7_ENA_SHIFT 6 /* LDO7_ENA */ +#define WM831X_LDO7_ENA_WIDTH 1 /* LDO7_ENA */ +#define WM831X_LDO6_ENA 0x0020 /* LDO6_ENA */ +#define WM831X_LDO6_ENA_MASK 0x0020 /* LDO6_ENA */ +#define WM831X_LDO6_ENA_SHIFT 5 /* LDO6_ENA */ +#define WM831X_LDO6_ENA_WIDTH 1 /* LDO6_ENA */ +#define WM831X_LDO5_ENA 0x0010 /* LDO5_ENA */ +#define WM831X_LDO5_ENA_MASK 0x0010 /* LDO5_ENA */ +#define WM831X_LDO5_ENA_SHIFT 4 /* LDO5_ENA */ +#define WM831X_LDO5_ENA_WIDTH 1 /* LDO5_ENA */ +#define WM831X_LDO4_ENA 0x0008 /* LDO4_ENA */ +#define WM831X_LDO4_ENA_MASK 0x0008 /* LDO4_ENA */ +#define WM831X_LDO4_ENA_SHIFT 3 /* LDO4_ENA */ +#define WM831X_LDO4_ENA_WIDTH 1 /* LDO4_ENA */ +#define WM831X_LDO3_ENA 0x0004 /* LDO3_ENA */ +#define WM831X_LDO3_ENA_MASK 0x0004 /* LDO3_ENA */ +#define WM831X_LDO3_ENA_SHIFT 2 /* LDO3_ENA */ +#define WM831X_LDO3_ENA_WIDTH 1 /* LDO3_ENA */ +#define WM831X_LDO2_ENA 0x0002 /* LDO2_ENA */ +#define WM831X_LDO2_ENA_MASK 0x0002 /* LDO2_ENA */ +#define WM831X_LDO2_ENA_SHIFT 1 /* LDO2_ENA */ +#define WM831X_LDO2_ENA_WIDTH 1 /* LDO2_ENA */ +#define WM831X_LDO1_ENA 0x0001 /* LDO1_ENA */ +#define WM831X_LDO1_ENA_MASK 0x0001 /* LDO1_ENA */ +#define WM831X_LDO1_ENA_SHIFT 0 /* LDO1_ENA */ +#define WM831X_LDO1_ENA_WIDTH 1 /* LDO1_ENA */ + +/* + * R16466 (0x4052) - DCDC Status + */ +#define WM831X_EPE2_STS 0x0080 /* EPE2_STS */ +#define WM831X_EPE2_STS_MASK 0x0080 /* EPE2_STS */ +#define WM831X_EPE2_STS_SHIFT 7 /* EPE2_STS */ +#define WM831X_EPE2_STS_WIDTH 1 /* EPE2_STS */ +#define WM831X_EPE1_STS 0x0040 /* EPE1_STS */ +#define WM831X_EPE1_STS_MASK 0x0040 /* EPE1_STS */ +#define WM831X_EPE1_STS_SHIFT 6 /* EPE1_STS */ +#define WM831X_EPE1_STS_WIDTH 1 /* EPE1_STS */ +#define WM831X_DC4_STS 0x0008 /* DC4_STS */ +#define WM831X_DC4_STS_MASK 0x0008 /* DC4_STS */ +#define WM831X_DC4_STS_SHIFT 3 /* DC4_STS */ +#define WM831X_DC4_STS_WIDTH 1 /* DC4_STS */ +#define WM831X_DC3_STS 0x0004 /* DC3_STS */ +#define WM831X_DC3_STS_MASK 0x0004 /* DC3_STS */ +#define WM831X_DC3_STS_SHIFT 2 /* DC3_STS */ +#define WM831X_DC3_STS_WIDTH 1 /* DC3_STS */ +#define WM831X_DC2_STS 0x0002 /* DC2_STS */ +#define WM831X_DC2_STS_MASK 0x0002 /* DC2_STS */ +#define WM831X_DC2_STS_SHIFT 1 /* DC2_STS */ +#define WM831X_DC2_STS_WIDTH 1 /* DC2_STS */ +#define WM831X_DC1_STS 0x0001 /* DC1_STS */ +#define WM831X_DC1_STS_MASK 0x0001 /* DC1_STS */ +#define WM831X_DC1_STS_SHIFT 0 /* DC1_STS */ +#define WM831X_DC1_STS_WIDTH 1 /* DC1_STS */ + +/* + * R16467 (0x4053) - LDO Status + */ +#define WM831X_LDO11_STS 0x0400 /* LDO11_STS */ +#define WM831X_LDO11_STS_MASK 0x0400 /* LDO11_STS */ +#define WM831X_LDO11_STS_SHIFT 10 /* LDO11_STS */ +#define WM831X_LDO11_STS_WIDTH 1 /* LDO11_STS */ +#define WM831X_LDO10_STS 0x0200 /* LDO10_STS */ +#define WM831X_LDO10_STS_MASK 0x0200 /* LDO10_STS */ +#define WM831X_LDO10_STS_SHIFT 9 /* LDO10_STS */ +#define WM831X_LDO10_STS_WIDTH 1 /* LDO10_STS */ +#define WM831X_LDO9_STS 0x0100 /* LDO9_STS */ +#define WM831X_LDO9_STS_MASK 0x0100 /* LDO9_STS */ +#define WM831X_LDO9_STS_SHIFT 8 /* LDO9_STS */ +#define WM831X_LDO9_STS_WIDTH 1 /* LDO9_STS */ +#define WM831X_LDO8_STS 0x0080 /* LDO8_STS */ +#define WM831X_LDO8_STS_MASK 0x0080 /* LDO8_STS */ +#define WM831X_LDO8_STS_SHIFT 7 /* LDO8_STS */ +#define WM831X_LDO8_STS_WIDTH 1 /* LDO8_STS */ +#define WM831X_LDO7_STS 0x0040 /* LDO7_STS */ +#define WM831X_LDO7_STS_MASK 0x0040 /* LDO7_STS */ +#define WM831X_LDO7_STS_SHIFT 6 /* LDO7_STS */ +#define WM831X_LDO7_STS_WIDTH 1 /* LDO7_STS */ +#define WM831X_LDO6_STS 0x0020 /* LDO6_STS */ +#define WM831X_LDO6_STS_MASK 0x0020 /* LDO6_STS */ +#define WM831X_LDO6_STS_SHIFT 5 /* LDO6_STS */ +#define WM831X_LDO6_STS_WIDTH 1 /* LDO6_STS */ +#define WM831X_LDO5_STS 0x0010 /* LDO5_STS */ +#define WM831X_LDO5_STS_MASK 0x0010 /* LDO5_STS */ +#define WM831X_LDO5_STS_SHIFT 4 /* LDO5_STS */ +#define WM831X_LDO5_STS_WIDTH 1 /* LDO5_STS */ +#define WM831X_LDO4_STS 0x0008 /* LDO4_STS */ +#define WM831X_LDO4_STS_MASK 0x0008 /* LDO4_STS */ +#define WM831X_LDO4_STS_SHIFT 3 /* LDO4_STS */ +#define WM831X_LDO4_STS_WIDTH 1 /* LDO4_STS */ +#define WM831X_LDO3_STS 0x0004 /* LDO3_STS */ +#define WM831X_LDO3_STS_MASK 0x0004 /* LDO3_STS */ +#define WM831X_LDO3_STS_SHIFT 2 /* LDO3_STS */ +#define WM831X_LDO3_STS_WIDTH 1 /* LDO3_STS */ +#define WM831X_LDO2_STS 0x0002 /* LDO2_STS */ +#define WM831X_LDO2_STS_MASK 0x0002 /* LDO2_STS */ +#define WM831X_LDO2_STS_SHIFT 1 /* LDO2_STS */ +#define WM831X_LDO2_STS_WIDTH 1 /* LDO2_STS */ +#define WM831X_LDO1_STS 0x0001 /* LDO1_STS */ +#define WM831X_LDO1_STS_MASK 0x0001 /* LDO1_STS */ +#define WM831X_LDO1_STS_SHIFT 0 /* LDO1_STS */ +#define WM831X_LDO1_STS_WIDTH 1 /* LDO1_STS */ + +/* + * R16468 (0x4054) - DCDC UV Status + */ +#define WM831X_DC2_OV_STS 0x2000 /* DC2_OV_STS */ +#define WM831X_DC2_OV_STS_MASK 0x2000 /* DC2_OV_STS */ +#define WM831X_DC2_OV_STS_SHIFT 13 /* DC2_OV_STS */ +#define WM831X_DC2_OV_STS_WIDTH 1 /* DC2_OV_STS */ +#define WM831X_DC1_OV_STS 0x1000 /* DC1_OV_STS */ +#define WM831X_DC1_OV_STS_MASK 0x1000 /* DC1_OV_STS */ +#define WM831X_DC1_OV_STS_SHIFT 12 /* DC1_OV_STS */ +#define WM831X_DC1_OV_STS_WIDTH 1 /* DC1_OV_STS */ +#define WM831X_DC2_HC_STS 0x0200 /* DC2_HC_STS */ +#define WM831X_DC2_HC_STS_MASK 0x0200 /* DC2_HC_STS */ +#define WM831X_DC2_HC_STS_SHIFT 9 /* DC2_HC_STS */ +#define WM831X_DC2_HC_STS_WIDTH 1 /* DC2_HC_STS */ +#define WM831X_DC1_HC_STS 0x0100 /* DC1_HC_STS */ +#define WM831X_DC1_HC_STS_MASK 0x0100 /* DC1_HC_STS */ +#define WM831X_DC1_HC_STS_SHIFT 8 /* DC1_HC_STS */ +#define WM831X_DC1_HC_STS_WIDTH 1 /* DC1_HC_STS */ +#define WM831X_DC4_UV_STS 0x0008 /* DC4_UV_STS */ +#define WM831X_DC4_UV_STS_MASK 0x0008 /* DC4_UV_STS */ +#define WM831X_DC4_UV_STS_SHIFT 3 /* DC4_UV_STS */ +#define WM831X_DC4_UV_STS_WIDTH 1 /* DC4_UV_STS */ +#define WM831X_DC3_UV_STS 0x0004 /* DC3_UV_STS */ +#define WM831X_DC3_UV_STS_MASK 0x0004 /* DC3_UV_STS */ +#define WM831X_DC3_UV_STS_SHIFT 2 /* DC3_UV_STS */ +#define WM831X_DC3_UV_STS_WIDTH 1 /* DC3_UV_STS */ +#define WM831X_DC2_UV_STS 0x0002 /* DC2_UV_STS */ +#define WM831X_DC2_UV_STS_MASK 0x0002 /* DC2_UV_STS */ +#define WM831X_DC2_UV_STS_SHIFT 1 /* DC2_UV_STS */ +#define WM831X_DC2_UV_STS_WIDTH 1 /* DC2_UV_STS */ +#define WM831X_DC1_UV_STS 0x0001 /* DC1_UV_STS */ +#define WM831X_DC1_UV_STS_MASK 0x0001 /* DC1_UV_STS */ +#define WM831X_DC1_UV_STS_SHIFT 0 /* DC1_UV_STS */ +#define WM831X_DC1_UV_STS_WIDTH 1 /* DC1_UV_STS */ + +/* + * R16469 (0x4055) - LDO UV Status + */ +#define WM831X_INTLDO_UV_STS 0x8000 /* INTLDO_UV_STS */ +#define WM831X_INTLDO_UV_STS_MASK 0x8000 /* INTLDO_UV_STS */ +#define WM831X_INTLDO_UV_STS_SHIFT 15 /* INTLDO_UV_STS */ +#define WM831X_INTLDO_UV_STS_WIDTH 1 /* INTLDO_UV_STS */ +#define WM831X_LDO10_UV_STS 0x0200 /* LDO10_UV_STS */ +#define WM831X_LDO10_UV_STS_MASK 0x0200 /* LDO10_UV_STS */ +#define WM831X_LDO10_UV_STS_SHIFT 9 /* LDO10_UV_STS */ +#define WM831X_LDO10_UV_STS_WIDTH 1 /* LDO10_UV_STS */ +#define WM831X_LDO9_UV_STS 0x0100 /* LDO9_UV_STS */ +#define WM831X_LDO9_UV_STS_MASK 0x0100 /* LDO9_UV_STS */ +#define WM831X_LDO9_UV_STS_SHIFT 8 /* LDO9_UV_STS */ +#define WM831X_LDO9_UV_STS_WIDTH 1 /* LDO9_UV_STS */ +#define WM831X_LDO8_UV_STS 0x0080 /* LDO8_UV_STS */ +#define WM831X_LDO8_UV_STS_MASK 0x0080 /* LDO8_UV_STS */ +#define WM831X_LDO8_UV_STS_SHIFT 7 /* LDO8_UV_STS */ +#define WM831X_LDO8_UV_STS_WIDTH 1 /* LDO8_UV_STS */ +#define WM831X_LDO7_UV_STS 0x0040 /* LDO7_UV_STS */ +#define WM831X_LDO7_UV_STS_MASK 0x0040 /* LDO7_UV_STS */ +#define WM831X_LDO7_UV_STS_SHIFT 6 /* LDO7_UV_STS */ +#define WM831X_LDO7_UV_STS_WIDTH 1 /* LDO7_UV_STS */ +#define WM831X_LDO6_UV_STS 0x0020 /* LDO6_UV_STS */ +#define WM831X_LDO6_UV_STS_MASK 0x0020 /* LDO6_UV_STS */ +#define WM831X_LDO6_UV_STS_SHIFT 5 /* LDO6_UV_STS */ +#define WM831X_LDO6_UV_STS_WIDTH 1 /* LDO6_UV_STS */ +#define WM831X_LDO5_UV_STS 0x0010 /* LDO5_UV_STS */ +#define WM831X_LDO5_UV_STS_MASK 0x0010 /* LDO5_UV_STS */ +#define WM831X_LDO5_UV_STS_SHIFT 4 /* LDO5_UV_STS */ +#define WM831X_LDO5_UV_STS_WIDTH 1 /* LDO5_UV_STS */ +#define WM831X_LDO4_UV_STS 0x0008 /* LDO4_UV_STS */ +#define WM831X_LDO4_UV_STS_MASK 0x0008 /* LDO4_UV_STS */ +#define WM831X_LDO4_UV_STS_SHIFT 3 /* LDO4_UV_STS */ +#define WM831X_LDO4_UV_STS_WIDTH 1 /* LDO4_UV_STS */ +#define WM831X_LDO3_UV_STS 0x0004 /* LDO3_UV_STS */ +#define WM831X_LDO3_UV_STS_MASK 0x0004 /* LDO3_UV_STS */ +#define WM831X_LDO3_UV_STS_SHIFT 2 /* LDO3_UV_STS */ +#define WM831X_LDO3_UV_STS_WIDTH 1 /* LDO3_UV_STS */ +#define WM831X_LDO2_UV_STS 0x0002 /* LDO2_UV_STS */ +#define WM831X_LDO2_UV_STS_MASK 0x0002 /* LDO2_UV_STS */ +#define WM831X_LDO2_UV_STS_SHIFT 1 /* LDO2_UV_STS */ +#define WM831X_LDO2_UV_STS_WIDTH 1 /* LDO2_UV_STS */ +#define WM831X_LDO1_UV_STS 0x0001 /* LDO1_UV_STS */ +#define WM831X_LDO1_UV_STS_MASK 0x0001 /* LDO1_UV_STS */ +#define WM831X_LDO1_UV_STS_SHIFT 0 /* LDO1_UV_STS */ +#define WM831X_LDO1_UV_STS_WIDTH 1 /* LDO1_UV_STS */ + +/* + * R16470 (0x4056) - DC1 Control 1 + */ +#define WM831X_DC1_RATE_MASK 0xC000 /* DC1_RATE - [15:14] */ +#define WM831X_DC1_RATE_SHIFT 14 /* DC1_RATE - [15:14] */ +#define WM831X_DC1_RATE_WIDTH 2 /* DC1_RATE - [15:14] */ +#define WM831X_DC1_PHASE 0x1000 /* DC1_PHASE */ +#define WM831X_DC1_PHASE_MASK 0x1000 /* DC1_PHASE */ +#define WM831X_DC1_PHASE_SHIFT 12 /* DC1_PHASE */ +#define WM831X_DC1_PHASE_WIDTH 1 /* DC1_PHASE */ +#define WM831X_DC1_FREQ_MASK 0x0300 /* DC1_FREQ - [9:8] */ +#define WM831X_DC1_FREQ_SHIFT 8 /* DC1_FREQ - [9:8] */ +#define WM831X_DC1_FREQ_WIDTH 2 /* DC1_FREQ - [9:8] */ +#define WM831X_DC1_FLT 0x0080 /* DC1_FLT */ +#define WM831X_DC1_FLT_MASK 0x0080 /* DC1_FLT */ +#define WM831X_DC1_FLT_SHIFT 7 /* DC1_FLT */ +#define WM831X_DC1_FLT_WIDTH 1 /* DC1_FLT */ +#define WM831X_DC1_SOFT_START_MASK 0x0030 /* DC1_SOFT_START - [5:4] */ +#define WM831X_DC1_SOFT_START_SHIFT 4 /* DC1_SOFT_START - [5:4] */ +#define WM831X_DC1_SOFT_START_WIDTH 2 /* DC1_SOFT_START - [5:4] */ +#define WM831X_DC1_CAP_MASK 0x0003 /* DC1_CAP - [1:0] */ +#define WM831X_DC1_CAP_SHIFT 0 /* DC1_CAP - [1:0] */ +#define WM831X_DC1_CAP_WIDTH 2 /* DC1_CAP - [1:0] */ + +/* + * R16471 (0x4057) - DC1 Control 2 + */ +#define WM831X_DC1_ERR_ACT_MASK 0xC000 /* DC1_ERR_ACT - [15:14] */ +#define WM831X_DC1_ERR_ACT_SHIFT 14 /* DC1_ERR_ACT - [15:14] */ +#define WM831X_DC1_ERR_ACT_WIDTH 2 /* DC1_ERR_ACT - [15:14] */ +#define WM831X_DC1_HWC_SRC_MASK 0x1800 /* DC1_HWC_SRC - [12:11] */ +#define WM831X_DC1_HWC_SRC_SHIFT 11 /* DC1_HWC_SRC - [12:11] */ +#define WM831X_DC1_HWC_SRC_WIDTH 2 /* DC1_HWC_SRC - [12:11] */ +#define WM831X_DC1_HWC_VSEL 0x0400 /* DC1_HWC_VSEL */ +#define WM831X_DC1_HWC_VSEL_MASK 0x0400 /* DC1_HWC_VSEL */ +#define WM831X_DC1_HWC_VSEL_SHIFT 10 /* DC1_HWC_VSEL */ +#define WM831X_DC1_HWC_VSEL_WIDTH 1 /* DC1_HWC_VSEL */ +#define WM831X_DC1_HWC_MODE_MASK 0x0300 /* DC1_HWC_MODE - [9:8] */ +#define WM831X_DC1_HWC_MODE_SHIFT 8 /* DC1_HWC_MODE - [9:8] */ +#define WM831X_DC1_HWC_MODE_WIDTH 2 /* DC1_HWC_MODE - [9:8] */ +#define WM831X_DC1_HC_THR_MASK 0x0070 /* DC1_HC_THR - [6:4] */ +#define WM831X_DC1_HC_THR_SHIFT 4 /* DC1_HC_THR - [6:4] */ +#define WM831X_DC1_HC_THR_WIDTH 3 /* DC1_HC_THR - [6:4] */ +#define WM831X_DC1_HC_IND_ENA 0x0001 /* DC1_HC_IND_ENA */ +#define WM831X_DC1_HC_IND_ENA_MASK 0x0001 /* DC1_HC_IND_ENA */ +#define WM831X_DC1_HC_IND_ENA_SHIFT 0 /* DC1_HC_IND_ENA */ +#define WM831X_DC1_HC_IND_ENA_WIDTH 1 /* DC1_HC_IND_ENA */ + +/* + * R16472 (0x4058) - DC1 ON Config + */ +#define WM831X_DC1_ON_SLOT_MASK 0xE000 /* DC1_ON_SLOT - [15:13] */ +#define WM831X_DC1_ON_SLOT_SHIFT 13 /* DC1_ON_SLOT - [15:13] */ +#define WM831X_DC1_ON_SLOT_WIDTH 3 /* DC1_ON_SLOT - [15:13] */ +#define WM831X_DC1_ON_MODE_MASK 0x0300 /* DC1_ON_MODE - [9:8] */ +#define WM831X_DC1_ON_MODE_SHIFT 8 /* DC1_ON_MODE - [9:8] */ +#define WM831X_DC1_ON_MODE_WIDTH 2 /* DC1_ON_MODE - [9:8] */ +#define WM831X_DC1_ON_VSEL_MASK 0x007F /* DC1_ON_VSEL - [6:0] */ +#define WM831X_DC1_ON_VSEL_SHIFT 0 /* DC1_ON_VSEL - [6:0] */ +#define WM831X_DC1_ON_VSEL_WIDTH 7 /* DC1_ON_VSEL - [6:0] */ + +/* + * R16473 (0x4059) - DC1 SLEEP Control + */ +#define WM831X_DC1_SLP_SLOT_MASK 0xE000 /* DC1_SLP_SLOT - [15:13] */ +#define WM831X_DC1_SLP_SLOT_SHIFT 13 /* DC1_SLP_SLOT - [15:13] */ +#define WM831X_DC1_SLP_SLOT_WIDTH 3 /* DC1_SLP_SLOT - [15:13] */ +#define WM831X_DC1_SLP_MODE_MASK 0x0300 /* DC1_SLP_MODE - [9:8] */ +#define WM831X_DC1_SLP_MODE_SHIFT 8 /* DC1_SLP_MODE - [9:8] */ +#define WM831X_DC1_SLP_MODE_WIDTH 2 /* DC1_SLP_MODE - [9:8] */ +#define WM831X_DC1_SLP_VSEL_MASK 0x007F /* DC1_SLP_VSEL - [6:0] */ +#define WM831X_DC1_SLP_VSEL_SHIFT 0 /* DC1_SLP_VSEL - [6:0] */ +#define WM831X_DC1_SLP_VSEL_WIDTH 7 /* DC1_SLP_VSEL - [6:0] */ + +/* + * R16474 (0x405A) - DC1 DVS Control + */ +#define WM831X_DC1_DVS_SRC_MASK 0x1800 /* DC1_DVS_SRC - [12:11] */ +#define WM831X_DC1_DVS_SRC_SHIFT 11 /* DC1_DVS_SRC - [12:11] */ +#define WM831X_DC1_DVS_SRC_WIDTH 2 /* DC1_DVS_SRC - [12:11] */ +#define WM831X_DC1_DVS_VSEL_MASK 0x007F /* DC1_DVS_VSEL - [6:0] */ +#define WM831X_DC1_DVS_VSEL_SHIFT 0 /* DC1_DVS_VSEL - [6:0] */ +#define WM831X_DC1_DVS_VSEL_WIDTH 7 /* DC1_DVS_VSEL - [6:0] */ + +/* + * R16475 (0x405B) - DC2 Control 1 + */ +#define WM831X_DC2_RATE_MASK 0xC000 /* DC2_RATE - [15:14] */ +#define WM831X_DC2_RATE_SHIFT 14 /* DC2_RATE - [15:14] */ +#define WM831X_DC2_RATE_WIDTH 2 /* DC2_RATE - [15:14] */ +#define WM831X_DC2_PHASE 0x1000 /* DC2_PHASE */ +#define WM831X_DC2_PHASE_MASK 0x1000 /* DC2_PHASE */ +#define WM831X_DC2_PHASE_SHIFT 12 /* DC2_PHASE */ +#define WM831X_DC2_PHASE_WIDTH 1 /* DC2_PHASE */ +#define WM831X_DC2_FREQ_MASK 0x0300 /* DC2_FREQ - [9:8] */ +#define WM831X_DC2_FREQ_SHIFT 8 /* DC2_FREQ - [9:8] */ +#define WM831X_DC2_FREQ_WIDTH 2 /* DC2_FREQ - [9:8] */ +#define WM831X_DC2_FLT 0x0080 /* DC2_FLT */ +#define WM831X_DC2_FLT_MASK 0x0080 /* DC2_FLT */ +#define WM831X_DC2_FLT_SHIFT 7 /* DC2_FLT */ +#define WM831X_DC2_FLT_WIDTH 1 /* DC2_FLT */ +#define WM831X_DC2_SOFT_START_MASK 0x0030 /* DC2_SOFT_START - [5:4] */ +#define WM831X_DC2_SOFT_START_SHIFT 4 /* DC2_SOFT_START - [5:4] */ +#define WM831X_DC2_SOFT_START_WIDTH 2 /* DC2_SOFT_START - [5:4] */ +#define WM831X_DC2_CAP_MASK 0x0003 /* DC2_CAP - [1:0] */ +#define WM831X_DC2_CAP_SHIFT 0 /* DC2_CAP - [1:0] */ +#define WM831X_DC2_CAP_WIDTH 2 /* DC2_CAP - [1:0] */ + +/* + * R16476 (0x405C) - DC2 Control 2 + */ +#define WM831X_DC2_ERR_ACT_MASK 0xC000 /* DC2_ERR_ACT - [15:14] */ +#define WM831X_DC2_ERR_ACT_SHIFT 14 /* DC2_ERR_ACT - [15:14] */ +#define WM831X_DC2_ERR_ACT_WIDTH 2 /* DC2_ERR_ACT - [15:14] */ +#define WM831X_DC2_HWC_SRC_MASK 0x1800 /* DC2_HWC_SRC - [12:11] */ +#define WM831X_DC2_HWC_SRC_SHIFT 11 /* DC2_HWC_SRC - [12:11] */ +#define WM831X_DC2_HWC_SRC_WIDTH 2 /* DC2_HWC_SRC - [12:11] */ +#define WM831X_DC2_HWC_VSEL 0x0400 /* DC2_HWC_VSEL */ +#define WM831X_DC2_HWC_VSEL_MASK 0x0400 /* DC2_HWC_VSEL */ +#define WM831X_DC2_HWC_VSEL_SHIFT 10 /* DC2_HWC_VSEL */ +#define WM831X_DC2_HWC_VSEL_WIDTH 1 /* DC2_HWC_VSEL */ +#define WM831X_DC2_HWC_MODE_MASK 0x0300 /* DC2_HWC_MODE - [9:8] */ +#define WM831X_DC2_HWC_MODE_SHIFT 8 /* DC2_HWC_MODE - [9:8] */ +#define WM831X_DC2_HWC_MODE_WIDTH 2 /* DC2_HWC_MODE - [9:8] */ +#define WM831X_DC2_HC_THR_MASK 0x0070 /* DC2_HC_THR - [6:4] */ +#define WM831X_DC2_HC_THR_SHIFT 4 /* DC2_HC_THR - [6:4] */ +#define WM831X_DC2_HC_THR_WIDTH 3 /* DC2_HC_THR - [6:4] */ +#define WM831X_DC2_HC_IND_ENA 0x0001 /* DC2_HC_IND_ENA */ +#define WM831X_DC2_HC_IND_ENA_MASK 0x0001 /* DC2_HC_IND_ENA */ +#define WM831X_DC2_HC_IND_ENA_SHIFT 0 /* DC2_HC_IND_ENA */ +#define WM831X_DC2_HC_IND_ENA_WIDTH 1 /* DC2_HC_IND_ENA */ + +/* + * R16477 (0x405D) - DC2 ON Config + */ +#define WM831X_DC2_ON_SLOT_MASK 0xE000 /* DC2_ON_SLOT - [15:13] */ +#define WM831X_DC2_ON_SLOT_SHIFT 13 /* DC2_ON_SLOT - [15:13] */ +#define WM831X_DC2_ON_SLOT_WIDTH 3 /* DC2_ON_SLOT - [15:13] */ +#define WM831X_DC2_ON_MODE_MASK 0x0300 /* DC2_ON_MODE - [9:8] */ +#define WM831X_DC2_ON_MODE_SHIFT 8 /* DC2_ON_MODE - [9:8] */ +#define WM831X_DC2_ON_MODE_WIDTH 2 /* DC2_ON_MODE - [9:8] */ +#define WM831X_DC2_ON_VSEL_MASK 0x007F /* DC2_ON_VSEL - [6:0] */ +#define WM831X_DC2_ON_VSEL_SHIFT 0 /* DC2_ON_VSEL - [6:0] */ +#define WM831X_DC2_ON_VSEL_WIDTH 7 /* DC2_ON_VSEL - [6:0] */ + +/* + * R16478 (0x405E) - DC2 SLEEP Control + */ +#define WM831X_DC2_SLP_SLOT_MASK 0xE000 /* DC2_SLP_SLOT - [15:13] */ +#define WM831X_DC2_SLP_SLOT_SHIFT 13 /* DC2_SLP_SLOT - [15:13] */ +#define WM831X_DC2_SLP_SLOT_WIDTH 3 /* DC2_SLP_SLOT - [15:13] */ +#define WM831X_DC2_SLP_MODE_MASK 0x0300 /* DC2_SLP_MODE - [9:8] */ +#define WM831X_DC2_SLP_MODE_SHIFT 8 /* DC2_SLP_MODE - [9:8] */ +#define WM831X_DC2_SLP_MODE_WIDTH 2 /* DC2_SLP_MODE - [9:8] */ +#define WM831X_DC2_SLP_VSEL_MASK 0x007F /* DC2_SLP_VSEL - [6:0] */ +#define WM831X_DC2_SLP_VSEL_SHIFT 0 /* DC2_SLP_VSEL - [6:0] */ +#define WM831X_DC2_SLP_VSEL_WIDTH 7 /* DC2_SLP_VSEL - [6:0] */ + +/* + * R16479 (0x405F) - DC2 DVS Control + */ +#define WM831X_DC2_DVS_SRC_MASK 0x1800 /* DC2_DVS_SRC - [12:11] */ +#define WM831X_DC2_DVS_SRC_SHIFT 11 /* DC2_DVS_SRC - [12:11] */ +#define WM831X_DC2_DVS_SRC_WIDTH 2 /* DC2_DVS_SRC - [12:11] */ +#define WM831X_DC2_DVS_VSEL_MASK 0x007F /* DC2_DVS_VSEL - [6:0] */ +#define WM831X_DC2_DVS_VSEL_SHIFT 0 /* DC2_DVS_VSEL - [6:0] */ +#define WM831X_DC2_DVS_VSEL_WIDTH 7 /* DC2_DVS_VSEL - [6:0] */ + +/* + * R16480 (0x4060) - DC3 Control 1 + */ +#define WM831X_DC3_PHASE 0x1000 /* DC3_PHASE */ +#define WM831X_DC3_PHASE_MASK 0x1000 /* DC3_PHASE */ +#define WM831X_DC3_PHASE_SHIFT 12 /* DC3_PHASE */ +#define WM831X_DC3_PHASE_WIDTH 1 /* DC3_PHASE */ +#define WM831X_DC3_FLT 0x0080 /* DC3_FLT */ +#define WM831X_DC3_FLT_MASK 0x0080 /* DC3_FLT */ +#define WM831X_DC3_FLT_SHIFT 7 /* DC3_FLT */ +#define WM831X_DC3_FLT_WIDTH 1 /* DC3_FLT */ +#define WM831X_DC3_SOFT_START_MASK 0x0030 /* DC3_SOFT_START - [5:4] */ +#define WM831X_DC3_SOFT_START_SHIFT 4 /* DC3_SOFT_START - [5:4] */ +#define WM831X_DC3_SOFT_START_WIDTH 2 /* DC3_SOFT_START - [5:4] */ +#define WM831X_DC3_STNBY_LIM_MASK 0x000C /* DC3_STNBY_LIM - [3:2] */ +#define WM831X_DC3_STNBY_LIM_SHIFT 2 /* DC3_STNBY_LIM - [3:2] */ +#define WM831X_DC3_STNBY_LIM_WIDTH 2 /* DC3_STNBY_LIM - [3:2] */ +#define WM831X_DC3_CAP_MASK 0x0003 /* DC3_CAP - [1:0] */ +#define WM831X_DC3_CAP_SHIFT 0 /* DC3_CAP - [1:0] */ +#define WM831X_DC3_CAP_WIDTH 2 /* DC3_CAP - [1:0] */ + +/* + * R16481 (0x4061) - DC3 Control 2 + */ +#define WM831X_DC3_ERR_ACT_MASK 0xC000 /* DC3_ERR_ACT - [15:14] */ +#define WM831X_DC3_ERR_ACT_SHIFT 14 /* DC3_ERR_ACT - [15:14] */ +#define WM831X_DC3_ERR_ACT_WIDTH 2 /* DC3_ERR_ACT - [15:14] */ +#define WM831X_DC3_HWC_SRC_MASK 0x1800 /* DC3_HWC_SRC - [12:11] */ +#define WM831X_DC3_HWC_SRC_SHIFT 11 /* DC3_HWC_SRC - [12:11] */ +#define WM831X_DC3_HWC_SRC_WIDTH 2 /* DC3_HWC_SRC - [12:11] */ +#define WM831X_DC3_HWC_VSEL 0x0400 /* DC3_HWC_VSEL */ +#define WM831X_DC3_HWC_VSEL_MASK 0x0400 /* DC3_HWC_VSEL */ +#define WM831X_DC3_HWC_VSEL_SHIFT 10 /* DC3_HWC_VSEL */ +#define WM831X_DC3_HWC_VSEL_WIDTH 1 /* DC3_HWC_VSEL */ +#define WM831X_DC3_HWC_MODE_MASK 0x0300 /* DC3_HWC_MODE - [9:8] */ +#define WM831X_DC3_HWC_MODE_SHIFT 8 /* DC3_HWC_MODE - [9:8] */ +#define WM831X_DC3_HWC_MODE_WIDTH 2 /* DC3_HWC_MODE - [9:8] */ +#define WM831X_DC3_OVP 0x0080 /* DC3_OVP */ +#define WM831X_DC3_OVP_MASK 0x0080 /* DC3_OVP */ +#define WM831X_DC3_OVP_SHIFT 7 /* DC3_OVP */ +#define WM831X_DC3_OVP_WIDTH 1 /* DC3_OVP */ + +/* + * R16482 (0x4062) - DC3 ON Config + */ +#define WM831X_DC3_ON_SLOT_MASK 0xE000 /* DC3_ON_SLOT - [15:13] */ +#define WM831X_DC3_ON_SLOT_SHIFT 13 /* DC3_ON_SLOT - [15:13] */ +#define WM831X_DC3_ON_SLOT_WIDTH 3 /* DC3_ON_SLOT - [15:13] */ +#define WM831X_DC3_ON_MODE_MASK 0x0300 /* DC3_ON_MODE - [9:8] */ +#define WM831X_DC3_ON_MODE_SHIFT 8 /* DC3_ON_MODE - [9:8] */ +#define WM831X_DC3_ON_MODE_WIDTH 2 /* DC3_ON_MODE - [9:8] */ +#define WM831X_DC3_ON_VSEL_MASK 0x007F /* DC3_ON_VSEL - [6:0] */ +#define WM831X_DC3_ON_VSEL_SHIFT 0 /* DC3_ON_VSEL - [6:0] */ +#define WM831X_DC3_ON_VSEL_WIDTH 7 /* DC3_ON_VSEL - [6:0] */ + +/* + * R16483 (0x4063) - DC3 SLEEP Control + */ +#define WM831X_DC3_SLP_SLOT_MASK 0xE000 /* DC3_SLP_SLOT - [15:13] */ +#define WM831X_DC3_SLP_SLOT_SHIFT 13 /* DC3_SLP_SLOT - [15:13] */ +#define WM831X_DC3_SLP_SLOT_WIDTH 3 /* DC3_SLP_SLOT - [15:13] */ +#define WM831X_DC3_SLP_MODE_MASK 0x0300 /* DC3_SLP_MODE - [9:8] */ +#define WM831X_DC3_SLP_MODE_SHIFT 8 /* DC3_SLP_MODE - [9:8] */ +#define WM831X_DC3_SLP_MODE_WIDTH 2 /* DC3_SLP_MODE - [9:8] */ +#define WM831X_DC3_SLP_VSEL_MASK 0x007F /* DC3_SLP_VSEL - [6:0] */ +#define WM831X_DC3_SLP_VSEL_SHIFT 0 /* DC3_SLP_VSEL - [6:0] */ +#define WM831X_DC3_SLP_VSEL_WIDTH 7 /* DC3_SLP_VSEL - [6:0] */ + +/* + * R16484 (0x4064) - DC4 Control + */ +#define WM831X_DC4_ERR_ACT_MASK 0xC000 /* DC4_ERR_ACT - [15:14] */ +#define WM831X_DC4_ERR_ACT_SHIFT 14 /* DC4_ERR_ACT - [15:14] */ +#define WM831X_DC4_ERR_ACT_WIDTH 2 /* DC4_ERR_ACT - [15:14] */ +#define WM831X_DC4_HWC_SRC_MASK 0x1800 /* DC4_HWC_SRC - [12:11] */ +#define WM831X_DC4_HWC_SRC_SHIFT 11 /* DC4_HWC_SRC - [12:11] */ +#define WM831X_DC4_HWC_SRC_WIDTH 2 /* DC4_HWC_SRC - [12:11] */ +#define WM831X_DC4_HWC_MODE 0x0100 /* DC4_HWC_MODE */ +#define WM831X_DC4_HWC_MODE_MASK 0x0100 /* DC4_HWC_MODE */ +#define WM831X_DC4_HWC_MODE_SHIFT 8 /* DC4_HWC_MODE */ +#define WM831X_DC4_HWC_MODE_WIDTH 1 /* DC4_HWC_MODE */ +#define WM831X_DC4_RANGE_MASK 0x000C /* DC4_RANGE - [3:2] */ +#define WM831X_DC4_RANGE_SHIFT 2 /* DC4_RANGE - [3:2] */ +#define WM831X_DC4_RANGE_WIDTH 2 /* DC4_RANGE - [3:2] */ +#define WM831X_DC4_FBSRC 0x0001 /* DC4_FBSRC */ +#define WM831X_DC4_FBSRC_MASK 0x0001 /* DC4_FBSRC */ +#define WM831X_DC4_FBSRC_SHIFT 0 /* DC4_FBSRC */ +#define WM831X_DC4_FBSRC_WIDTH 1 /* DC4_FBSRC */ + +/* + * R16485 (0x4065) - DC4 SLEEP Control + */ +#define WM831X_DC4_SLPENA 0x0100 /* DC4_SLPENA */ +#define WM831X_DC4_SLPENA_MASK 0x0100 /* DC4_SLPENA */ +#define WM831X_DC4_SLPENA_SHIFT 8 /* DC4_SLPENA */ +#define WM831X_DC4_SLPENA_WIDTH 1 /* DC4_SLPENA */ + +/* + * R16526 (0x408E) - Power Good Source 1 + */ +#define WM831X_DC4_OK 0x0008 /* DC4_OK */ +#define WM831X_DC4_OK_MASK 0x0008 /* DC4_OK */ +#define WM831X_DC4_OK_SHIFT 3 /* DC4_OK */ +#define WM831X_DC4_OK_WIDTH 1 /* DC4_OK */ +#define WM831X_DC3_OK 0x0004 /* DC3_OK */ +#define WM831X_DC3_OK_MASK 0x0004 /* DC3_OK */ +#define WM831X_DC3_OK_SHIFT 2 /* DC3_OK */ +#define WM831X_DC3_OK_WIDTH 1 /* DC3_OK */ +#define WM831X_DC2_OK 0x0002 /* DC2_OK */ +#define WM831X_DC2_OK_MASK 0x0002 /* DC2_OK */ +#define WM831X_DC2_OK_SHIFT 1 /* DC2_OK */ +#define WM831X_DC2_OK_WIDTH 1 /* DC2_OK */ +#define WM831X_DC1_OK 0x0001 /* DC1_OK */ +#define WM831X_DC1_OK_MASK 0x0001 /* DC1_OK */ +#define WM831X_DC1_OK_SHIFT 0 /* DC1_OK */ +#define WM831X_DC1_OK_WIDTH 1 /* DC1_OK */ + #define WM831X_ISINK_MAX_ISEL 56 extern int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL]; -- cgit v0.10.2 From d1c6b4fe668b2ae02f490deee86eaab60822a362 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:22:02 +0100 Subject: regulator: Add WM831x LDO support The WM831x series of devices provide three types of LDO: - General purpose LDOs supporting voltages from 0.9-3.3V - High performance analogue LDOs supporting voltages from 1-3.5V - Very low power consumption LDOs intended to support always on functionality. This patch adds support for all three kinds of LDO. Each regulator is probed as an individual platform device with resources used to provide the register map location of the regulator. Mixed hardware and software control of regulators is not current supported. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b1d2b82..a0a635f 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c new file mode 100644 index 0000000..bb61aed --- /dev/null +++ b/drivers/regulator/wm831x-ldo.c @@ -0,0 +1,852 @@ +/* + * wm831x-ldo.c -- LDO driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_LDO_MAX_NAME 6 + +#define WM831X_LDO_CONTROL 0 +#define WM831X_LDO_ON_CONTROL 1 +#define WM831X_LDO_SLEEP_CONTROL 2 + +#define WM831X_ALIVE_LDO_ON_CONTROL 0 +#define WM831X_ALIVE_LDO_SLEEP_CONTROL 1 + +struct wm831x_ldo { + char name[WM831X_LDO_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +/* + * Shared + */ + +static int wm831x_ldo_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int reg; + + reg = wm831x_reg_read(wm831x, WM831X_LDO_ENABLE); + if (reg < 0) + return reg; + + if (reg & mask) + return 1; + else + return 0; +} + +static int wm831x_ldo_enable(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, mask); +} + +static int wm831x_ldo_disable(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + + return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, 0); +} + +static irqreturn_t wm831x_ldo_uv_irq(int irq, void *data) +{ + struct wm831x_ldo *ldo = data; + + regulator_notifier_call_chain(ldo->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +/* + * General purpose LDOs + */ + +#define WM831X_GP_LDO_SELECTOR_LOW 0xe +#define WM831X_GP_LDO_MAX_SELECTOR 0x1f + +static int wm831x_gp_ldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 0.9-1.6V in 50mV steps */ + if (selector <= WM831X_GP_LDO_SELECTOR_LOW) + return 900000 + (selector * 50000); + /* 1.7-3.3V in 50mV steps */ + if (selector <= WM831X_GP_LDO_MAX_SELECTOR) + return 1600000 + ((selector - WM831X_GP_LDO_SELECTOR_LOW) + * 100000); + return -EINVAL; +} + +static int wm831x_gp_ldo_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + if (min_uV < 900000) + vsel = 0; + else if (min_uV < 1700000) + vsel = ((min_uV - 900000) / 50000); + else + vsel = ((min_uV - 1700000) / 100000) + + WM831X_GP_LDO_SELECTOR_LOW + 1; + + ret = wm831x_gp_ldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO1_ON_VSEL_MASK, vsel); +} + +static int wm831x_gp_ldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + + return wm831x_gp_ldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + return wm831x_gp_ldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_gp_ldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO1_ON_VSEL_MASK; + + return wm831x_gp_ldo_list_voltage(rdev, ret); +} + +static unsigned int wm831x_gp_ldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + unsigned int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return 0; + + if (!(ret & WM831X_LDO1_ON_MODE)) + return REGULATOR_MODE_NORMAL; + + ret = wm831x_reg_read(wm831x, ctrl_reg); + if (ret < 0) + return 0; + + if (ret & WM831X_LDO1_LP_MODE) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_IDLE; +} + +static int wm831x_gp_ldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, + WM831X_LDO1_LP_MODE); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + + case REGULATOR_MODE_STANDBY: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_gp_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_gp_ldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static unsigned int wm831x_gp_ldo_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, + int output_uV, int load_uA) +{ + if (load_uA < 20000) + return REGULATOR_MODE_STANDBY; + if (load_uA < 50000) + return REGULATOR_MODE_IDLE; + return REGULATOR_MODE_NORMAL; +} + + +static struct regulator_ops wm831x_gp_ldo_ops = { + .list_voltage = wm831x_gp_ldo_list_voltage, + .get_voltage = wm831x_gp_ldo_get_voltage, + .set_voltage = wm831x_gp_ldo_set_voltage, + .set_suspend_voltage = wm831x_gp_ldo_set_suspend_voltage, + .get_mode = wm831x_gp_ldo_get_mode, + .set_mode = wm831x_gp_ldo_set_mode, + .get_status = wm831x_gp_ldo_get_status, + .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_gp_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_GP_LDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_gp_ldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING, ldo->name, + ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err_regulator: + regulator_unregister(ldo->regulator); +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_gp_ldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + struct wm831x *wm831x = ldo->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo); + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_gp_ldo_driver = { + .probe = wm831x_gp_ldo_probe, + .remove = __devexit_p(wm831x_gp_ldo_remove), + .driver = { + .name = "wm831x-ldo", + }, +}; + +/* + * Analogue LDOs + */ + + +#define WM831X_ALDO_SELECTOR_LOW 0xc +#define WM831X_ALDO_MAX_SELECTOR 0x1f + +static int wm831x_aldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 1-1.6V in 50mV steps */ + if (selector <= WM831X_ALDO_SELECTOR_LOW) + return 1000000 + (selector * 50000); + /* 1.7-3.5V in 50mV steps */ + if (selector <= WM831X_ALDO_MAX_SELECTOR) + return 1600000 + ((selector - WM831X_ALDO_SELECTOR_LOW) + * 100000); + return -EINVAL; +} + +static int wm831x_aldo_set_voltage_int(struct regulator_dev *rdev, int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + if (min_uV < 1000000) + vsel = 0; + else if (min_uV < 1700000) + vsel = ((min_uV - 1000000) / 50000); + else + vsel = ((min_uV - 1700000) / 100000) + + WM831X_ALDO_SELECTOR_LOW + 1; + + ret = wm831x_aldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO7_ON_VSEL_MASK, vsel); +} + +static int wm831x_aldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + + return wm831x_aldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_aldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + return wm831x_aldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_aldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO7_ON_VSEL_MASK; + + return wm831x_aldo_list_voltage(rdev, ret); +} + +static unsigned int wm831x_aldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + unsigned int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return 0; + + if (ret & WM831X_LDO7_ON_MODE) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int wm831x_aldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO7_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO7_ON_MODE, + WM831X_LDO7_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_aldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_aldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static struct regulator_ops wm831x_aldo_ops = { + .list_voltage = wm831x_aldo_list_voltage, + .get_voltage = wm831x_aldo_get_voltage, + .set_voltage = wm831x_aldo_set_voltage, + .set_suspend_voltage = wm831x_aldo_set_suspend_voltage, + .get_mode = wm831x_aldo_get_mode, + .set_mode = wm831x_aldo_set_mode, + .get_status = wm831x_aldo_get_status, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_aldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_ALDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_aldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING, ldo->name, + ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err_regulator: + regulator_unregister(ldo->regulator); +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_aldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + struct wm831x *wm831x = ldo->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo); + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_aldo_driver = { + .probe = wm831x_aldo_probe, + .remove = __devexit_p(wm831x_aldo_remove), + .driver = { + .name = "wm831x-aldo", + }, +}; + +/* + * Alive LDO + */ + +#define WM831X_ALIVE_LDO_MAX_SELECTOR 0xf + +static int wm831x_alive_ldo_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + /* 0.8-1.55V in 50mV steps */ + if (selector <= WM831X_ALIVE_LDO_MAX_SELECTOR) + return 800000 + (selector * 50000); + return -EINVAL; +} + +static int wm831x_alive_ldo_set_voltage_int(struct regulator_dev *rdev, + int reg, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int vsel, ret; + + vsel = (min_uV - 800000) / 50000; + + ret = wm831x_alive_ldo_list_voltage(rdev, vsel); + if (ret < 0) + return ret; + if (ret < min_uV || ret > max_uV) + return -EINVAL; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO11_ON_VSEL_MASK, vsel); +} + +static int wm831x_alive_ldo_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL; + + return wm831x_alive_ldo_set_voltage_int(rdev, reg, min_uV, max_uV); +} + +static int wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + int reg = ldo->base + WM831X_ALIVE_LDO_SLEEP_CONTROL; + + return wm831x_alive_ldo_set_voltage_int(rdev, reg, uV, uV); +} + +static int wm831x_alive_ldo_get_voltage(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, reg); + if (ret < 0) + return ret; + + ret &= WM831X_LDO11_ON_VSEL_MASK; + + return wm831x_alive_ldo_list_voltage(rdev, ret); +} + +static int wm831x_alive_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (ret & mask) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static struct regulator_ops wm831x_alive_ldo_ops = { + .list_voltage = wm831x_alive_ldo_list_voltage, + .get_voltage = wm831x_alive_ldo_get_voltage, + .set_voltage = wm831x_alive_ldo_set_voltage, + .set_suspend_voltage = wm831x_alive_ldo_set_suspend_voltage, + .get_status = wm831x_alive_ldo_get_status, + + .is_enabled = wm831x_ldo_is_enabled, + .enable = wm831x_ldo_enable, + .disable = wm831x_ldo_disable, +}; + +static __devinit int wm831x_alive_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct wm831x_ldo *ldo; + struct resource *res; + int ret; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + if (pdata == NULL || pdata->ldo[id] == NULL) + return -ENODEV; + + ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL); + if (ldo == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_ALIVE_LDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_alive_ldo_ops; + ldo->desc.owner = THIS_MODULE; + + ldo->regulator = regulator_register(&ldo->desc, &pdev->dev, + pdata->ldo[id], ldo); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + kfree(ldo); + return ret; +} + +static __devexit int wm831x_alive_ldo_remove(struct platform_device *pdev) +{ + struct wm831x_ldo *ldo = platform_get_drvdata(pdev); + + regulator_unregister(ldo->regulator); + kfree(ldo); + + return 0; +} + +static struct platform_driver wm831x_alive_ldo_driver = { + .probe = wm831x_alive_ldo_probe, + .remove = __devexit_p(wm831x_alive_ldo_remove), + .driver = { + .name = "wm831x-alive-ldo", + }, +}; + +static int __init wm831x_ldo_init(void) +{ + int ret; + + ret = platform_driver_register(&wm831x_gp_ldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x GP LDO driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_aldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x ALDO driver: %d\n", ret); + + ret = platform_driver_register(&wm831x_alive_ldo_driver); + if (ret != 0) + pr_err("Failed to register WM831x alive LDO driver: %d\n", + ret); + + return 0; +} +subsys_initcall(wm831x_ldo_init); + +static void __exit wm831x_ldo_exit(void) +{ + platform_driver_unregister(&wm831x_alive_ldo_driver); + platform_driver_unregister(&wm831x_aldo_driver); + platform_driver_unregister(&wm831x_gp_ldo_driver); +} +module_exit(wm831x_ldo_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM831x LDO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-ldo"); +MODULE_ALIAS("platform:wm831x-aldo"); +MODULE_ALIAS("platform:wm831x-aliveldo"); diff --git a/include/linux/mfd/wm831x/regulator.h b/include/linux/mfd/wm831x/regulator.h index c74d6aa..f954663 100644 --- a/include/linux/mfd/wm831x/regulator.h +++ b/include/linux/mfd/wm831x/regulator.h @@ -567,6 +567,588 @@ #define WM831X_DC4_SLPENA_WIDTH 1 /* DC4_SLPENA */ /* + * R16488 (0x4068) - LDO1 Control + */ +#define WM831X_LDO1_ERR_ACT_MASK 0xC000 /* LDO1_ERR_ACT - [15:14] */ +#define WM831X_LDO1_ERR_ACT_SHIFT 14 /* LDO1_ERR_ACT - [15:14] */ +#define WM831X_LDO1_ERR_ACT_WIDTH 2 /* LDO1_ERR_ACT - [15:14] */ +#define WM831X_LDO1_HWC_SRC_MASK 0x1800 /* LDO1_HWC_SRC - [12:11] */ +#define WM831X_LDO1_HWC_SRC_SHIFT 11 /* LDO1_HWC_SRC - [12:11] */ +#define WM831X_LDO1_HWC_SRC_WIDTH 2 /* LDO1_HWC_SRC - [12:11] */ +#define WM831X_LDO1_HWC_VSEL 0x0400 /* LDO1_HWC_VSEL */ +#define WM831X_LDO1_HWC_VSEL_MASK 0x0400 /* LDO1_HWC_VSEL */ +#define WM831X_LDO1_HWC_VSEL_SHIFT 10 /* LDO1_HWC_VSEL */ +#define WM831X_LDO1_HWC_VSEL_WIDTH 1 /* LDO1_HWC_VSEL */ +#define WM831X_LDO1_HWC_MODE_MASK 0x0300 /* LDO1_HWC_MODE - [9:8] */ +#define WM831X_LDO1_HWC_MODE_SHIFT 8 /* LDO1_HWC_MODE - [9:8] */ +#define WM831X_LDO1_HWC_MODE_WIDTH 2 /* LDO1_HWC_MODE - [9:8] */ +#define WM831X_LDO1_FLT 0x0080 /* LDO1_FLT */ +#define WM831X_LDO1_FLT_MASK 0x0080 /* LDO1_FLT */ +#define WM831X_LDO1_FLT_SHIFT 7 /* LDO1_FLT */ +#define WM831X_LDO1_FLT_WIDTH 1 /* LDO1_FLT */ +#define WM831X_LDO1_SWI 0x0040 /* LDO1_SWI */ +#define WM831X_LDO1_SWI_MASK 0x0040 /* LDO1_SWI */ +#define WM831X_LDO1_SWI_SHIFT 6 /* LDO1_SWI */ +#define WM831X_LDO1_SWI_WIDTH 1 /* LDO1_SWI */ +#define WM831X_LDO1_LP_MODE 0x0001 /* LDO1_LP_MODE */ +#define WM831X_LDO1_LP_MODE_MASK 0x0001 /* LDO1_LP_MODE */ +#define WM831X_LDO1_LP_MODE_SHIFT 0 /* LDO1_LP_MODE */ +#define WM831X_LDO1_LP_MODE_WIDTH 1 /* LDO1_LP_MODE */ + +/* + * R16489 (0x4069) - LDO1 ON Control + */ +#define WM831X_LDO1_ON_SLOT_MASK 0xE000 /* LDO1_ON_SLOT - [15:13] */ +#define WM831X_LDO1_ON_SLOT_SHIFT 13 /* LDO1_ON_SLOT - [15:13] */ +#define WM831X_LDO1_ON_SLOT_WIDTH 3 /* LDO1_ON_SLOT - [15:13] */ +#define WM831X_LDO1_ON_MODE 0x0100 /* LDO1_ON_MODE */ +#define WM831X_LDO1_ON_MODE_MASK 0x0100 /* LDO1_ON_MODE */ +#define WM831X_LDO1_ON_MODE_SHIFT 8 /* LDO1_ON_MODE */ +#define WM831X_LDO1_ON_MODE_WIDTH 1 /* LDO1_ON_MODE */ +#define WM831X_LDO1_ON_VSEL_MASK 0x001F /* LDO1_ON_VSEL - [4:0] */ +#define WM831X_LDO1_ON_VSEL_SHIFT 0 /* LDO1_ON_VSEL - [4:0] */ +#define WM831X_LDO1_ON_VSEL_WIDTH 5 /* LDO1_ON_VSEL - [4:0] */ + +/* + * R16490 (0x406A) - LDO1 SLEEP Control + */ +#define WM831X_LDO1_SLP_SLOT_MASK 0xE000 /* LDO1_SLP_SLOT - [15:13] */ +#define WM831X_LDO1_SLP_SLOT_SHIFT 13 /* LDO1_SLP_SLOT - [15:13] */ +#define WM831X_LDO1_SLP_SLOT_WIDTH 3 /* LDO1_SLP_SLOT - [15:13] */ +#define WM831X_LDO1_SLP_MODE 0x0100 /* LDO1_SLP_MODE */ +#define WM831X_LDO1_SLP_MODE_MASK 0x0100 /* LDO1_SLP_MODE */ +#define WM831X_LDO1_SLP_MODE_SHIFT 8 /* LDO1_SLP_MODE */ +#define WM831X_LDO1_SLP_MODE_WIDTH 1 /* LDO1_SLP_MODE */ +#define WM831X_LDO1_SLP_VSEL_MASK 0x001F /* LDO1_SLP_VSEL - [4:0] */ +#define WM831X_LDO1_SLP_VSEL_SHIFT 0 /* LDO1_SLP_VSEL - [4:0] */ +#define WM831X_LDO1_SLP_VSEL_WIDTH 5 /* LDO1_SLP_VSEL - [4:0] */ + +/* + * R16491 (0x406B) - LDO2 Control + */ +#define WM831X_LDO2_ERR_ACT_MASK 0xC000 /* LDO2_ERR_ACT - [15:14] */ +#define WM831X_LDO2_ERR_ACT_SHIFT 14 /* LDO2_ERR_ACT - [15:14] */ +#define WM831X_LDO2_ERR_ACT_WIDTH 2 /* LDO2_ERR_ACT - [15:14] */ +#define WM831X_LDO2_HWC_SRC_MASK 0x1800 /* LDO2_HWC_SRC - [12:11] */ +#define WM831X_LDO2_HWC_SRC_SHIFT 11 /* LDO2_HWC_SRC - [12:11] */ +#define WM831X_LDO2_HWC_SRC_WIDTH 2 /* LDO2_HWC_SRC - [12:11] */ +#define WM831X_LDO2_HWC_VSEL 0x0400 /* LDO2_HWC_VSEL */ +#define WM831X_LDO2_HWC_VSEL_MASK 0x0400 /* LDO2_HWC_VSEL */ +#define WM831X_LDO2_HWC_VSEL_SHIFT 10 /* LDO2_HWC_VSEL */ +#define WM831X_LDO2_HWC_VSEL_WIDTH 1 /* LDO2_HWC_VSEL */ +#define WM831X_LDO2_HWC_MODE_MASK 0x0300 /* LDO2_HWC_MODE - [9:8] */ +#define WM831X_LDO2_HWC_MODE_SHIFT 8 /* LDO2_HWC_MODE - [9:8] */ +#define WM831X_LDO2_HWC_MODE_WIDTH 2 /* LDO2_HWC_MODE - [9:8] */ +#define WM831X_LDO2_FLT 0x0080 /* LDO2_FLT */ +#define WM831X_LDO2_FLT_MASK 0x0080 /* LDO2_FLT */ +#define WM831X_LDO2_FLT_SHIFT 7 /* LDO2_FLT */ +#define WM831X_LDO2_FLT_WIDTH 1 /* LDO2_FLT */ +#define WM831X_LDO2_SWI 0x0040 /* LDO2_SWI */ +#define WM831X_LDO2_SWI_MASK 0x0040 /* LDO2_SWI */ +#define WM831X_LDO2_SWI_SHIFT 6 /* LDO2_SWI */ +#define WM831X_LDO2_SWI_WIDTH 1 /* LDO2_SWI */ +#define WM831X_LDO2_LP_MODE 0x0001 /* LDO2_LP_MODE */ +#define WM831X_LDO2_LP_MODE_MASK 0x0001 /* LDO2_LP_MODE */ +#define WM831X_LDO2_LP_MODE_SHIFT 0 /* LDO2_LP_MODE */ +#define WM831X_LDO2_LP_MODE_WIDTH 1 /* LDO2_LP_MODE */ + +/* + * R16492 (0x406C) - LDO2 ON Control + */ +#define WM831X_LDO2_ON_SLOT_MASK 0xE000 /* LDO2_ON_SLOT - [15:13] */ +#define WM831X_LDO2_ON_SLOT_SHIFT 13 /* LDO2_ON_SLOT - [15:13] */ +#define WM831X_LDO2_ON_SLOT_WIDTH 3 /* LDO2_ON_SLOT - [15:13] */ +#define WM831X_LDO2_ON_MODE 0x0100 /* LDO2_ON_MODE */ +#define WM831X_LDO2_ON_MODE_MASK 0x0100 /* LDO2_ON_MODE */ +#define WM831X_LDO2_ON_MODE_SHIFT 8 /* LDO2_ON_MODE */ +#define WM831X_LDO2_ON_MODE_WIDTH 1 /* LDO2_ON_MODE */ +#define WM831X_LDO2_ON_VSEL_MASK 0x001F /* LDO2_ON_VSEL - [4:0] */ +#define WM831X_LDO2_ON_VSEL_SHIFT 0 /* LDO2_ON_VSEL - [4:0] */ +#define WM831X_LDO2_ON_VSEL_WIDTH 5 /* LDO2_ON_VSEL - [4:0] */ + +/* + * R16493 (0x406D) - LDO2 SLEEP Control + */ +#define WM831X_LDO2_SLP_SLOT_MASK 0xE000 /* LDO2_SLP_SLOT - [15:13] */ +#define WM831X_LDO2_SLP_SLOT_SHIFT 13 /* LDO2_SLP_SLOT - [15:13] */ +#define WM831X_LDO2_SLP_SLOT_WIDTH 3 /* LDO2_SLP_SLOT - [15:13] */ +#define WM831X_LDO2_SLP_MODE 0x0100 /* LDO2_SLP_MODE */ +#define WM831X_LDO2_SLP_MODE_MASK 0x0100 /* LDO2_SLP_MODE */ +#define WM831X_LDO2_SLP_MODE_SHIFT 8 /* LDO2_SLP_MODE */ +#define WM831X_LDO2_SLP_MODE_WIDTH 1 /* LDO2_SLP_MODE */ +#define WM831X_LDO2_SLP_VSEL_MASK 0x001F /* LDO2_SLP_VSEL - [4:0] */ +#define WM831X_LDO2_SLP_VSEL_SHIFT 0 /* LDO2_SLP_VSEL - [4:0] */ +#define WM831X_LDO2_SLP_VSEL_WIDTH 5 /* LDO2_SLP_VSEL - [4:0] */ + +/* + * R16494 (0x406E) - LDO3 Control + */ +#define WM831X_LDO3_ERR_ACT_MASK 0xC000 /* LDO3_ERR_ACT - [15:14] */ +#define WM831X_LDO3_ERR_ACT_SHIFT 14 /* LDO3_ERR_ACT - [15:14] */ +#define WM831X_LDO3_ERR_ACT_WIDTH 2 /* LDO3_ERR_ACT - [15:14] */ +#define WM831X_LDO3_HWC_SRC_MASK 0x1800 /* LDO3_HWC_SRC - [12:11] */ +#define WM831X_LDO3_HWC_SRC_SHIFT 11 /* LDO3_HWC_SRC - [12:11] */ +#define WM831X_LDO3_HWC_SRC_WIDTH 2 /* LDO3_HWC_SRC - [12:11] */ +#define WM831X_LDO3_HWC_VSEL 0x0400 /* LDO3_HWC_VSEL */ +#define WM831X_LDO3_HWC_VSEL_MASK 0x0400 /* LDO3_HWC_VSEL */ +#define WM831X_LDO3_HWC_VSEL_SHIFT 10 /* LDO3_HWC_VSEL */ +#define WM831X_LDO3_HWC_VSEL_WIDTH 1 /* LDO3_HWC_VSEL */ +#define WM831X_LDO3_HWC_MODE_MASK 0x0300 /* LDO3_HWC_MODE - [9:8] */ +#define WM831X_LDO3_HWC_MODE_SHIFT 8 /* LDO3_HWC_MODE - [9:8] */ +#define WM831X_LDO3_HWC_MODE_WIDTH 2 /* LDO3_HWC_MODE - [9:8] */ +#define WM831X_LDO3_FLT 0x0080 /* LDO3_FLT */ +#define WM831X_LDO3_FLT_MASK 0x0080 /* LDO3_FLT */ +#define WM831X_LDO3_FLT_SHIFT 7 /* LDO3_FLT */ +#define WM831X_LDO3_FLT_WIDTH 1 /* LDO3_FLT */ +#define WM831X_LDO3_SWI 0x0040 /* LDO3_SWI */ +#define WM831X_LDO3_SWI_MASK 0x0040 /* LDO3_SWI */ +#define WM831X_LDO3_SWI_SHIFT 6 /* LDO3_SWI */ +#define WM831X_LDO3_SWI_WIDTH 1 /* LDO3_SWI */ +#define WM831X_LDO3_LP_MODE 0x0001 /* LDO3_LP_MODE */ +#define WM831X_LDO3_LP_MODE_MASK 0x0001 /* LDO3_LP_MODE */ +#define WM831X_LDO3_LP_MODE_SHIFT 0 /* LDO3_LP_MODE */ +#define WM831X_LDO3_LP_MODE_WIDTH 1 /* LDO3_LP_MODE */ + +/* + * R16495 (0x406F) - LDO3 ON Control + */ +#define WM831X_LDO3_ON_SLOT_MASK 0xE000 /* LDO3_ON_SLOT - [15:13] */ +#define WM831X_LDO3_ON_SLOT_SHIFT 13 /* LDO3_ON_SLOT - [15:13] */ +#define WM831X_LDO3_ON_SLOT_WIDTH 3 /* LDO3_ON_SLOT - [15:13] */ +#define WM831X_LDO3_ON_MODE 0x0100 /* LDO3_ON_MODE */ +#define WM831X_LDO3_ON_MODE_MASK 0x0100 /* LDO3_ON_MODE */ +#define WM831X_LDO3_ON_MODE_SHIFT 8 /* LDO3_ON_MODE */ +#define WM831X_LDO3_ON_MODE_WIDTH 1 /* LDO3_ON_MODE */ +#define WM831X_LDO3_ON_VSEL_MASK 0x001F /* LDO3_ON_VSEL - [4:0] */ +#define WM831X_LDO3_ON_VSEL_SHIFT 0 /* LDO3_ON_VSEL - [4:0] */ +#define WM831X_LDO3_ON_VSEL_WIDTH 5 /* LDO3_ON_VSEL - [4:0] */ + +/* + * R16496 (0x4070) - LDO3 SLEEP Control + */ +#define WM831X_LDO3_SLP_SLOT_MASK 0xE000 /* LDO3_SLP_SLOT - [15:13] */ +#define WM831X_LDO3_SLP_SLOT_SHIFT 13 /* LDO3_SLP_SLOT - [15:13] */ +#define WM831X_LDO3_SLP_SLOT_WIDTH 3 /* LDO3_SLP_SLOT - [15:13] */ +#define WM831X_LDO3_SLP_MODE 0x0100 /* LDO3_SLP_MODE */ +#define WM831X_LDO3_SLP_MODE_MASK 0x0100 /* LDO3_SLP_MODE */ +#define WM831X_LDO3_SLP_MODE_SHIFT 8 /* LDO3_SLP_MODE */ +#define WM831X_LDO3_SLP_MODE_WIDTH 1 /* LDO3_SLP_MODE */ +#define WM831X_LDO3_SLP_VSEL_MASK 0x001F /* LDO3_SLP_VSEL - [4:0] */ +#define WM831X_LDO3_SLP_VSEL_SHIFT 0 /* LDO3_SLP_VSEL - [4:0] */ +#define WM831X_LDO3_SLP_VSEL_WIDTH 5 /* LDO3_SLP_VSEL - [4:0] */ + +/* + * R16497 (0x4071) - LDO4 Control + */ +#define WM831X_LDO4_ERR_ACT_MASK 0xC000 /* LDO4_ERR_ACT - [15:14] */ +#define WM831X_LDO4_ERR_ACT_SHIFT 14 /* LDO4_ERR_ACT - [15:14] */ +#define WM831X_LDO4_ERR_ACT_WIDTH 2 /* LDO4_ERR_ACT - [15:14] */ +#define WM831X_LDO4_HWC_SRC_MASK 0x1800 /* LDO4_HWC_SRC - [12:11] */ +#define WM831X_LDO4_HWC_SRC_SHIFT 11 /* LDO4_HWC_SRC - [12:11] */ +#define WM831X_LDO4_HWC_SRC_WIDTH 2 /* LDO4_HWC_SRC - [12:11] */ +#define WM831X_LDO4_HWC_VSEL 0x0400 /* LDO4_HWC_VSEL */ +#define WM831X_LDO4_HWC_VSEL_MASK 0x0400 /* LDO4_HWC_VSEL */ +#define WM831X_LDO4_HWC_VSEL_SHIFT 10 /* LDO4_HWC_VSEL */ +#define WM831X_LDO4_HWC_VSEL_WIDTH 1 /* LDO4_HWC_VSEL */ +#define WM831X_LDO4_HWC_MODE_MASK 0x0300 /* LDO4_HWC_MODE - [9:8] */ +#define WM831X_LDO4_HWC_MODE_SHIFT 8 /* LDO4_HWC_MODE - [9:8] */ +#define WM831X_LDO4_HWC_MODE_WIDTH 2 /* LDO4_HWC_MODE - [9:8] */ +#define WM831X_LDO4_FLT 0x0080 /* LDO4_FLT */ +#define WM831X_LDO4_FLT_MASK 0x0080 /* LDO4_FLT */ +#define WM831X_LDO4_FLT_SHIFT 7 /* LDO4_FLT */ +#define WM831X_LDO4_FLT_WIDTH 1 /* LDO4_FLT */ +#define WM831X_LDO4_SWI 0x0040 /* LDO4_SWI */ +#define WM831X_LDO4_SWI_MASK 0x0040 /* LDO4_SWI */ +#define WM831X_LDO4_SWI_SHIFT 6 /* LDO4_SWI */ +#define WM831X_LDO4_SWI_WIDTH 1 /* LDO4_SWI */ +#define WM831X_LDO4_LP_MODE 0x0001 /* LDO4_LP_MODE */ +#define WM831X_LDO4_LP_MODE_MASK 0x0001 /* LDO4_LP_MODE */ +#define WM831X_LDO4_LP_MODE_SHIFT 0 /* LDO4_LP_MODE */ +#define WM831X_LDO4_LP_MODE_WIDTH 1 /* LDO4_LP_MODE */ + +/* + * R16498 (0x4072) - LDO4 ON Control + */ +#define WM831X_LDO4_ON_SLOT_MASK 0xE000 /* LDO4_ON_SLOT - [15:13] */ +#define WM831X_LDO4_ON_SLOT_SHIFT 13 /* LDO4_ON_SLOT - [15:13] */ +#define WM831X_LDO4_ON_SLOT_WIDTH 3 /* LDO4_ON_SLOT - [15:13] */ +#define WM831X_LDO4_ON_MODE 0x0100 /* LDO4_ON_MODE */ +#define WM831X_LDO4_ON_MODE_MASK 0x0100 /* LDO4_ON_MODE */ +#define WM831X_LDO4_ON_MODE_SHIFT 8 /* LDO4_ON_MODE */ +#define WM831X_LDO4_ON_MODE_WIDTH 1 /* LDO4_ON_MODE */ +#define WM831X_LDO4_ON_VSEL_MASK 0x001F /* LDO4_ON_VSEL - [4:0] */ +#define WM831X_LDO4_ON_VSEL_SHIFT 0 /* LDO4_ON_VSEL - [4:0] */ +#define WM831X_LDO4_ON_VSEL_WIDTH 5 /* LDO4_ON_VSEL - [4:0] */ + +/* + * R16499 (0x4073) - LDO4 SLEEP Control + */ +#define WM831X_LDO4_SLP_SLOT_MASK 0xE000 /* LDO4_SLP_SLOT - [15:13] */ +#define WM831X_LDO4_SLP_SLOT_SHIFT 13 /* LDO4_SLP_SLOT - [15:13] */ +#define WM831X_LDO4_SLP_SLOT_WIDTH 3 /* LDO4_SLP_SLOT - [15:13] */ +#define WM831X_LDO4_SLP_MODE 0x0100 /* LDO4_SLP_MODE */ +#define WM831X_LDO4_SLP_MODE_MASK 0x0100 /* LDO4_SLP_MODE */ +#define WM831X_LDO4_SLP_MODE_SHIFT 8 /* LDO4_SLP_MODE */ +#define WM831X_LDO4_SLP_MODE_WIDTH 1 /* LDO4_SLP_MODE */ +#define WM831X_LDO4_SLP_VSEL_MASK 0x001F /* LDO4_SLP_VSEL - [4:0] */ +#define WM831X_LDO4_SLP_VSEL_SHIFT 0 /* LDO4_SLP_VSEL - [4:0] */ +#define WM831X_LDO4_SLP_VSEL_WIDTH 5 /* LDO4_SLP_VSEL - [4:0] */ + +/* + * R16500 (0x4074) - LDO5 Control + */ +#define WM831X_LDO5_ERR_ACT_MASK 0xC000 /* LDO5_ERR_ACT - [15:14] */ +#define WM831X_LDO5_ERR_ACT_SHIFT 14 /* LDO5_ERR_ACT - [15:14] */ +#define WM831X_LDO5_ERR_ACT_WIDTH 2 /* LDO5_ERR_ACT - [15:14] */ +#define WM831X_LDO5_HWC_SRC_MASK 0x1800 /* LDO5_HWC_SRC - [12:11] */ +#define WM831X_LDO5_HWC_SRC_SHIFT 11 /* LDO5_HWC_SRC - [12:11] */ +#define WM831X_LDO5_HWC_SRC_WIDTH 2 /* LDO5_HWC_SRC - [12:11] */ +#define WM831X_LDO5_HWC_VSEL 0x0400 /* LDO5_HWC_VSEL */ +#define WM831X_LDO5_HWC_VSEL_MASK 0x0400 /* LDO5_HWC_VSEL */ +#define WM831X_LDO5_HWC_VSEL_SHIFT 10 /* LDO5_HWC_VSEL */ +#define WM831X_LDO5_HWC_VSEL_WIDTH 1 /* LDO5_HWC_VSEL */ +#define WM831X_LDO5_HWC_MODE_MASK 0x0300 /* LDO5_HWC_MODE - [9:8] */ +#define WM831X_LDO5_HWC_MODE_SHIFT 8 /* LDO5_HWC_MODE - [9:8] */ +#define WM831X_LDO5_HWC_MODE_WIDTH 2 /* LDO5_HWC_MODE - [9:8] */ +#define WM831X_LDO5_FLT 0x0080 /* LDO5_FLT */ +#define WM831X_LDO5_FLT_MASK 0x0080 /* LDO5_FLT */ +#define WM831X_LDO5_FLT_SHIFT 7 /* LDO5_FLT */ +#define WM831X_LDO5_FLT_WIDTH 1 /* LDO5_FLT */ +#define WM831X_LDO5_SWI 0x0040 /* LDO5_SWI */ +#define WM831X_LDO5_SWI_MASK 0x0040 /* LDO5_SWI */ +#define WM831X_LDO5_SWI_SHIFT 6 /* LDO5_SWI */ +#define WM831X_LDO5_SWI_WIDTH 1 /* LDO5_SWI */ +#define WM831X_LDO5_LP_MODE 0x0001 /* LDO5_LP_MODE */ +#define WM831X_LDO5_LP_MODE_MASK 0x0001 /* LDO5_LP_MODE */ +#define WM831X_LDO5_LP_MODE_SHIFT 0 /* LDO5_LP_MODE */ +#define WM831X_LDO5_LP_MODE_WIDTH 1 /* LDO5_LP_MODE */ + +/* + * R16501 (0x4075) - LDO5 ON Control + */ +#define WM831X_LDO5_ON_SLOT_MASK 0xE000 /* LDO5_ON_SLOT - [15:13] */ +#define WM831X_LDO5_ON_SLOT_SHIFT 13 /* LDO5_ON_SLOT - [15:13] */ +#define WM831X_LDO5_ON_SLOT_WIDTH 3 /* LDO5_ON_SLOT - [15:13] */ +#define WM831X_LDO5_ON_MODE 0x0100 /* LDO5_ON_MODE */ +#define WM831X_LDO5_ON_MODE_MASK 0x0100 /* LDO5_ON_MODE */ +#define WM831X_LDO5_ON_MODE_SHIFT 8 /* LDO5_ON_MODE */ +#define WM831X_LDO5_ON_MODE_WIDTH 1 /* LDO5_ON_MODE */ +#define WM831X_LDO5_ON_VSEL_MASK 0x001F /* LDO5_ON_VSEL - [4:0] */ +#define WM831X_LDO5_ON_VSEL_SHIFT 0 /* LDO5_ON_VSEL - [4:0] */ +#define WM831X_LDO5_ON_VSEL_WIDTH 5 /* LDO5_ON_VSEL - [4:0] */ + +/* + * R16502 (0x4076) - LDO5 SLEEP Control + */ +#define WM831X_LDO5_SLP_SLOT_MASK 0xE000 /* LDO5_SLP_SLOT - [15:13] */ +#define WM831X_LDO5_SLP_SLOT_SHIFT 13 /* LDO5_SLP_SLOT - [15:13] */ +#define WM831X_LDO5_SLP_SLOT_WIDTH 3 /* LDO5_SLP_SLOT - [15:13] */ +#define WM831X_LDO5_SLP_MODE 0x0100 /* LDO5_SLP_MODE */ +#define WM831X_LDO5_SLP_MODE_MASK 0x0100 /* LDO5_SLP_MODE */ +#define WM831X_LDO5_SLP_MODE_SHIFT 8 /* LDO5_SLP_MODE */ +#define WM831X_LDO5_SLP_MODE_WIDTH 1 /* LDO5_SLP_MODE */ +#define WM831X_LDO5_SLP_VSEL_MASK 0x001F /* LDO5_SLP_VSEL - [4:0] */ +#define WM831X_LDO5_SLP_VSEL_SHIFT 0 /* LDO5_SLP_VSEL - [4:0] */ +#define WM831X_LDO5_SLP_VSEL_WIDTH 5 /* LDO5_SLP_VSEL - [4:0] */ + +/* + * R16503 (0x4077) - LDO6 Control + */ +#define WM831X_LDO6_ERR_ACT_MASK 0xC000 /* LDO6_ERR_ACT - [15:14] */ +#define WM831X_LDO6_ERR_ACT_SHIFT 14 /* LDO6_ERR_ACT - [15:14] */ +#define WM831X_LDO6_ERR_ACT_WIDTH 2 /* LDO6_ERR_ACT - [15:14] */ +#define WM831X_LDO6_HWC_SRC_MASK 0x1800 /* LDO6_HWC_SRC - [12:11] */ +#define WM831X_LDO6_HWC_SRC_SHIFT 11 /* LDO6_HWC_SRC - [12:11] */ +#define WM831X_LDO6_HWC_SRC_WIDTH 2 /* LDO6_HWC_SRC - [12:11] */ +#define WM831X_LDO6_HWC_VSEL 0x0400 /* LDO6_HWC_VSEL */ +#define WM831X_LDO6_HWC_VSEL_MASK 0x0400 /* LDO6_HWC_VSEL */ +#define WM831X_LDO6_HWC_VSEL_SHIFT 10 /* LDO6_HWC_VSEL */ +#define WM831X_LDO6_HWC_VSEL_WIDTH 1 /* LDO6_HWC_VSEL */ +#define WM831X_LDO6_HWC_MODE_MASK 0x0300 /* LDO6_HWC_MODE - [9:8] */ +#define WM831X_LDO6_HWC_MODE_SHIFT 8 /* LDO6_HWC_MODE - [9:8] */ +#define WM831X_LDO6_HWC_MODE_WIDTH 2 /* LDO6_HWC_MODE - [9:8] */ +#define WM831X_LDO6_FLT 0x0080 /* LDO6_FLT */ +#define WM831X_LDO6_FLT_MASK 0x0080 /* LDO6_FLT */ +#define WM831X_LDO6_FLT_SHIFT 7 /* LDO6_FLT */ +#define WM831X_LDO6_FLT_WIDTH 1 /* LDO6_FLT */ +#define WM831X_LDO6_SWI 0x0040 /* LDO6_SWI */ +#define WM831X_LDO6_SWI_MASK 0x0040 /* LDO6_SWI */ +#define WM831X_LDO6_SWI_SHIFT 6 /* LDO6_SWI */ +#define WM831X_LDO6_SWI_WIDTH 1 /* LDO6_SWI */ +#define WM831X_LDO6_LP_MODE 0x0001 /* LDO6_LP_MODE */ +#define WM831X_LDO6_LP_MODE_MASK 0x0001 /* LDO6_LP_MODE */ +#define WM831X_LDO6_LP_MODE_SHIFT 0 /* LDO6_LP_MODE */ +#define WM831X_LDO6_LP_MODE_WIDTH 1 /* LDO6_LP_MODE */ + +/* + * R16504 (0x4078) - LDO6 ON Control + */ +#define WM831X_LDO6_ON_SLOT_MASK 0xE000 /* LDO6_ON_SLOT - [15:13] */ +#define WM831X_LDO6_ON_SLOT_SHIFT 13 /* LDO6_ON_SLOT - [15:13] */ +#define WM831X_LDO6_ON_SLOT_WIDTH 3 /* LDO6_ON_SLOT - [15:13] */ +#define WM831X_LDO6_ON_MODE 0x0100 /* LDO6_ON_MODE */ +#define WM831X_LDO6_ON_MODE_MASK 0x0100 /* LDO6_ON_MODE */ +#define WM831X_LDO6_ON_MODE_SHIFT 8 /* LDO6_ON_MODE */ +#define WM831X_LDO6_ON_MODE_WIDTH 1 /* LDO6_ON_MODE */ +#define WM831X_LDO6_ON_VSEL_MASK 0x001F /* LDO6_ON_VSEL - [4:0] */ +#define WM831X_LDO6_ON_VSEL_SHIFT 0 /* LDO6_ON_VSEL - [4:0] */ +#define WM831X_LDO6_ON_VSEL_WIDTH 5 /* LDO6_ON_VSEL - [4:0] */ + +/* + * R16505 (0x4079) - LDO6 SLEEP Control + */ +#define WM831X_LDO6_SLP_SLOT_MASK 0xE000 /* LDO6_SLP_SLOT - [15:13] */ +#define WM831X_LDO6_SLP_SLOT_SHIFT 13 /* LDO6_SLP_SLOT - [15:13] */ +#define WM831X_LDO6_SLP_SLOT_WIDTH 3 /* LDO6_SLP_SLOT - [15:13] */ +#define WM831X_LDO6_SLP_MODE 0x0100 /* LDO6_SLP_MODE */ +#define WM831X_LDO6_SLP_MODE_MASK 0x0100 /* LDO6_SLP_MODE */ +#define WM831X_LDO6_SLP_MODE_SHIFT 8 /* LDO6_SLP_MODE */ +#define WM831X_LDO6_SLP_MODE_WIDTH 1 /* LDO6_SLP_MODE */ +#define WM831X_LDO6_SLP_VSEL_MASK 0x001F /* LDO6_SLP_VSEL - [4:0] */ +#define WM831X_LDO6_SLP_VSEL_SHIFT 0 /* LDO6_SLP_VSEL - [4:0] */ +#define WM831X_LDO6_SLP_VSEL_WIDTH 5 /* LDO6_SLP_VSEL - [4:0] */ + +/* + * R16506 (0x407A) - LDO7 Control + */ +#define WM831X_LDO7_ERR_ACT_MASK 0xC000 /* LDO7_ERR_ACT - [15:14] */ +#define WM831X_LDO7_ERR_ACT_SHIFT 14 /* LDO7_ERR_ACT - [15:14] */ +#define WM831X_LDO7_ERR_ACT_WIDTH 2 /* LDO7_ERR_ACT - [15:14] */ +#define WM831X_LDO7_HWC_SRC_MASK 0x1800 /* LDO7_HWC_SRC - [12:11] */ +#define WM831X_LDO7_HWC_SRC_SHIFT 11 /* LDO7_HWC_SRC - [12:11] */ +#define WM831X_LDO7_HWC_SRC_WIDTH 2 /* LDO7_HWC_SRC - [12:11] */ +#define WM831X_LDO7_HWC_VSEL 0x0400 /* LDO7_HWC_VSEL */ +#define WM831X_LDO7_HWC_VSEL_MASK 0x0400 /* LDO7_HWC_VSEL */ +#define WM831X_LDO7_HWC_VSEL_SHIFT 10 /* LDO7_HWC_VSEL */ +#define WM831X_LDO7_HWC_VSEL_WIDTH 1 /* LDO7_HWC_VSEL */ +#define WM831X_LDO7_HWC_MODE_MASK 0x0300 /* LDO7_HWC_MODE - [9:8] */ +#define WM831X_LDO7_HWC_MODE_SHIFT 8 /* LDO7_HWC_MODE - [9:8] */ +#define WM831X_LDO7_HWC_MODE_WIDTH 2 /* LDO7_HWC_MODE - [9:8] */ +#define WM831X_LDO7_FLT 0x0080 /* LDO7_FLT */ +#define WM831X_LDO7_FLT_MASK 0x0080 /* LDO7_FLT */ +#define WM831X_LDO7_FLT_SHIFT 7 /* LDO7_FLT */ +#define WM831X_LDO7_FLT_WIDTH 1 /* LDO7_FLT */ +#define WM831X_LDO7_SWI 0x0040 /* LDO7_SWI */ +#define WM831X_LDO7_SWI_MASK 0x0040 /* LDO7_SWI */ +#define WM831X_LDO7_SWI_SHIFT 6 /* LDO7_SWI */ +#define WM831X_LDO7_SWI_WIDTH 1 /* LDO7_SWI */ + +/* + * R16507 (0x407B) - LDO7 ON Control + */ +#define WM831X_LDO7_ON_SLOT_MASK 0xE000 /* LDO7_ON_SLOT - [15:13] */ +#define WM831X_LDO7_ON_SLOT_SHIFT 13 /* LDO7_ON_SLOT - [15:13] */ +#define WM831X_LDO7_ON_SLOT_WIDTH 3 /* LDO7_ON_SLOT - [15:13] */ +#define WM831X_LDO7_ON_MODE 0x0100 /* LDO7_ON_MODE */ +#define WM831X_LDO7_ON_MODE_MASK 0x0100 /* LDO7_ON_MODE */ +#define WM831X_LDO7_ON_MODE_SHIFT 8 /* LDO7_ON_MODE */ +#define WM831X_LDO7_ON_MODE_WIDTH 1 /* LDO7_ON_MODE */ +#define WM831X_LDO7_ON_VSEL_MASK 0x001F /* LDO7_ON_VSEL - [4:0] */ +#define WM831X_LDO7_ON_VSEL_SHIFT 0 /* LDO7_ON_VSEL - [4:0] */ +#define WM831X_LDO7_ON_VSEL_WIDTH 5 /* LDO7_ON_VSEL - [4:0] */ + +/* + * R16508 (0x407C) - LDO7 SLEEP Control + */ +#define WM831X_LDO7_SLP_SLOT_MASK 0xE000 /* LDO7_SLP_SLOT - [15:13] */ +#define WM831X_LDO7_SLP_SLOT_SHIFT 13 /* LDO7_SLP_SLOT - [15:13] */ +#define WM831X_LDO7_SLP_SLOT_WIDTH 3 /* LDO7_SLP_SLOT - [15:13] */ +#define WM831X_LDO7_SLP_MODE 0x0100 /* LDO7_SLP_MODE */ +#define WM831X_LDO7_SLP_MODE_MASK 0x0100 /* LDO7_SLP_MODE */ +#define WM831X_LDO7_SLP_MODE_SHIFT 8 /* LDO7_SLP_MODE */ +#define WM831X_LDO7_SLP_MODE_WIDTH 1 /* LDO7_SLP_MODE */ +#define WM831X_LDO7_SLP_VSEL_MASK 0x001F /* LDO7_SLP_VSEL - [4:0] */ +#define WM831X_LDO7_SLP_VSEL_SHIFT 0 /* LDO7_SLP_VSEL - [4:0] */ +#define WM831X_LDO7_SLP_VSEL_WIDTH 5 /* LDO7_SLP_VSEL - [4:0] */ + +/* + * R16509 (0x407D) - LDO8 Control + */ +#define WM831X_LDO8_ERR_ACT_MASK 0xC000 /* LDO8_ERR_ACT - [15:14] */ +#define WM831X_LDO8_ERR_ACT_SHIFT 14 /* LDO8_ERR_ACT - [15:14] */ +#define WM831X_LDO8_ERR_ACT_WIDTH 2 /* LDO8_ERR_ACT - [15:14] */ +#define WM831X_LDO8_HWC_SRC_MASK 0x1800 /* LDO8_HWC_SRC - [12:11] */ +#define WM831X_LDO8_HWC_SRC_SHIFT 11 /* LDO8_HWC_SRC - [12:11] */ +#define WM831X_LDO8_HWC_SRC_WIDTH 2 /* LDO8_HWC_SRC - [12:11] */ +#define WM831X_LDO8_HWC_VSEL 0x0400 /* LDO8_HWC_VSEL */ +#define WM831X_LDO8_HWC_VSEL_MASK 0x0400 /* LDO8_HWC_VSEL */ +#define WM831X_LDO8_HWC_VSEL_SHIFT 10 /* LDO8_HWC_VSEL */ +#define WM831X_LDO8_HWC_VSEL_WIDTH 1 /* LDO8_HWC_VSEL */ +#define WM831X_LDO8_HWC_MODE_MASK 0x0300 /* LDO8_HWC_MODE - [9:8] */ +#define WM831X_LDO8_HWC_MODE_SHIFT 8 /* LDO8_HWC_MODE - [9:8] */ +#define WM831X_LDO8_HWC_MODE_WIDTH 2 /* LDO8_HWC_MODE - [9:8] */ +#define WM831X_LDO8_FLT 0x0080 /* LDO8_FLT */ +#define WM831X_LDO8_FLT_MASK 0x0080 /* LDO8_FLT */ +#define WM831X_LDO8_FLT_SHIFT 7 /* LDO8_FLT */ +#define WM831X_LDO8_FLT_WIDTH 1 /* LDO8_FLT */ +#define WM831X_LDO8_SWI 0x0040 /* LDO8_SWI */ +#define WM831X_LDO8_SWI_MASK 0x0040 /* LDO8_SWI */ +#define WM831X_LDO8_SWI_SHIFT 6 /* LDO8_SWI */ +#define WM831X_LDO8_SWI_WIDTH 1 /* LDO8_SWI */ + +/* + * R16510 (0x407E) - LDO8 ON Control + */ +#define WM831X_LDO8_ON_SLOT_MASK 0xE000 /* LDO8_ON_SLOT - [15:13] */ +#define WM831X_LDO8_ON_SLOT_SHIFT 13 /* LDO8_ON_SLOT - [15:13] */ +#define WM831X_LDO8_ON_SLOT_WIDTH 3 /* LDO8_ON_SLOT - [15:13] */ +#define WM831X_LDO8_ON_MODE 0x0100 /* LDO8_ON_MODE */ +#define WM831X_LDO8_ON_MODE_MASK 0x0100 /* LDO8_ON_MODE */ +#define WM831X_LDO8_ON_MODE_SHIFT 8 /* LDO8_ON_MODE */ +#define WM831X_LDO8_ON_MODE_WIDTH 1 /* LDO8_ON_MODE */ +#define WM831X_LDO8_ON_VSEL_MASK 0x001F /* LDO8_ON_VSEL - [4:0] */ +#define WM831X_LDO8_ON_VSEL_SHIFT 0 /* LDO8_ON_VSEL - [4:0] */ +#define WM831X_LDO8_ON_VSEL_WIDTH 5 /* LDO8_ON_VSEL - [4:0] */ + +/* + * R16511 (0x407F) - LDO8 SLEEP Control + */ +#define WM831X_LDO8_SLP_SLOT_MASK 0xE000 /* LDO8_SLP_SLOT - [15:13] */ +#define WM831X_LDO8_SLP_SLOT_SHIFT 13 /* LDO8_SLP_SLOT - [15:13] */ +#define WM831X_LDO8_SLP_SLOT_WIDTH 3 /* LDO8_SLP_SLOT - [15:13] */ +#define WM831X_LDO8_SLP_MODE 0x0100 /* LDO8_SLP_MODE */ +#define WM831X_LDO8_SLP_MODE_MASK 0x0100 /* LDO8_SLP_MODE */ +#define WM831X_LDO8_SLP_MODE_SHIFT 8 /* LDO8_SLP_MODE */ +#define WM831X_LDO8_SLP_MODE_WIDTH 1 /* LDO8_SLP_MODE */ +#define WM831X_LDO8_SLP_VSEL_MASK 0x001F /* LDO8_SLP_VSEL - [4:0] */ +#define WM831X_LDO8_SLP_VSEL_SHIFT 0 /* LDO8_SLP_VSEL - [4:0] */ +#define WM831X_LDO8_SLP_VSEL_WIDTH 5 /* LDO8_SLP_VSEL - [4:0] */ + +/* + * R16512 (0x4080) - LDO9 Control + */ +#define WM831X_LDO9_ERR_ACT_MASK 0xC000 /* LDO9_ERR_ACT - [15:14] */ +#define WM831X_LDO9_ERR_ACT_SHIFT 14 /* LDO9_ERR_ACT - [15:14] */ +#define WM831X_LDO9_ERR_ACT_WIDTH 2 /* LDO9_ERR_ACT - [15:14] */ +#define WM831X_LDO9_HWC_SRC_MASK 0x1800 /* LDO9_HWC_SRC - [12:11] */ +#define WM831X_LDO9_HWC_SRC_SHIFT 11 /* LDO9_HWC_SRC - [12:11] */ +#define WM831X_LDO9_HWC_SRC_WIDTH 2 /* LDO9_HWC_SRC - [12:11] */ +#define WM831X_LDO9_HWC_VSEL 0x0400 /* LDO9_HWC_VSEL */ +#define WM831X_LDO9_HWC_VSEL_MASK 0x0400 /* LDO9_HWC_VSEL */ +#define WM831X_LDO9_HWC_VSEL_SHIFT 10 /* LDO9_HWC_VSEL */ +#define WM831X_LDO9_HWC_VSEL_WIDTH 1 /* LDO9_HWC_VSEL */ +#define WM831X_LDO9_HWC_MODE_MASK 0x0300 /* LDO9_HWC_MODE - [9:8] */ +#define WM831X_LDO9_HWC_MODE_SHIFT 8 /* LDO9_HWC_MODE - [9:8] */ +#define WM831X_LDO9_HWC_MODE_WIDTH 2 /* LDO9_HWC_MODE - [9:8] */ +#define WM831X_LDO9_FLT 0x0080 /* LDO9_FLT */ +#define WM831X_LDO9_FLT_MASK 0x0080 /* LDO9_FLT */ +#define WM831X_LDO9_FLT_SHIFT 7 /* LDO9_FLT */ +#define WM831X_LDO9_FLT_WIDTH 1 /* LDO9_FLT */ +#define WM831X_LDO9_SWI 0x0040 /* LDO9_SWI */ +#define WM831X_LDO9_SWI_MASK 0x0040 /* LDO9_SWI */ +#define WM831X_LDO9_SWI_SHIFT 6 /* LDO9_SWI */ +#define WM831X_LDO9_SWI_WIDTH 1 /* LDO9_SWI */ + +/* + * R16513 (0x4081) - LDO9 ON Control + */ +#define WM831X_LDO9_ON_SLOT_MASK 0xE000 /* LDO9_ON_SLOT - [15:13] */ +#define WM831X_LDO9_ON_SLOT_SHIFT 13 /* LDO9_ON_SLOT - [15:13] */ +#define WM831X_LDO9_ON_SLOT_WIDTH 3 /* LDO9_ON_SLOT - [15:13] */ +#define WM831X_LDO9_ON_MODE 0x0100 /* LDO9_ON_MODE */ +#define WM831X_LDO9_ON_MODE_MASK 0x0100 /* LDO9_ON_MODE */ +#define WM831X_LDO9_ON_MODE_SHIFT 8 /* LDO9_ON_MODE */ +#define WM831X_LDO9_ON_MODE_WIDTH 1 /* LDO9_ON_MODE */ +#define WM831X_LDO9_ON_VSEL_MASK 0x001F /* LDO9_ON_VSEL - [4:0] */ +#define WM831X_LDO9_ON_VSEL_SHIFT 0 /* LDO9_ON_VSEL - [4:0] */ +#define WM831X_LDO9_ON_VSEL_WIDTH 5 /* LDO9_ON_VSEL - [4:0] */ + +/* + * R16514 (0x4082) - LDO9 SLEEP Control + */ +#define WM831X_LDO9_SLP_SLOT_MASK 0xE000 /* LDO9_SLP_SLOT - [15:13] */ +#define WM831X_LDO9_SLP_SLOT_SHIFT 13 /* LDO9_SLP_SLOT - [15:13] */ +#define WM831X_LDO9_SLP_SLOT_WIDTH 3 /* LDO9_SLP_SLOT - [15:13] */ +#define WM831X_LDO9_SLP_MODE 0x0100 /* LDO9_SLP_MODE */ +#define WM831X_LDO9_SLP_MODE_MASK 0x0100 /* LDO9_SLP_MODE */ +#define WM831X_LDO9_SLP_MODE_SHIFT 8 /* LDO9_SLP_MODE */ +#define WM831X_LDO9_SLP_MODE_WIDTH 1 /* LDO9_SLP_MODE */ +#define WM831X_LDO9_SLP_VSEL_MASK 0x001F /* LDO9_SLP_VSEL - [4:0] */ +#define WM831X_LDO9_SLP_VSEL_SHIFT 0 /* LDO9_SLP_VSEL - [4:0] */ +#define WM831X_LDO9_SLP_VSEL_WIDTH 5 /* LDO9_SLP_VSEL - [4:0] */ + +/* + * R16515 (0x4083) - LDO10 Control + */ +#define WM831X_LDO10_ERR_ACT_MASK 0xC000 /* LDO10_ERR_ACT - [15:14] */ +#define WM831X_LDO10_ERR_ACT_SHIFT 14 /* LDO10_ERR_ACT - [15:14] */ +#define WM831X_LDO10_ERR_ACT_WIDTH 2 /* LDO10_ERR_ACT - [15:14] */ +#define WM831X_LDO10_HWC_SRC_MASK 0x1800 /* LDO10_HWC_SRC - [12:11] */ +#define WM831X_LDO10_HWC_SRC_SHIFT 11 /* LDO10_HWC_SRC - [12:11] */ +#define WM831X_LDO10_HWC_SRC_WIDTH 2 /* LDO10_HWC_SRC - [12:11] */ +#define WM831X_LDO10_HWC_VSEL 0x0400 /* LDO10_HWC_VSEL */ +#define WM831X_LDO10_HWC_VSEL_MASK 0x0400 /* LDO10_HWC_VSEL */ +#define WM831X_LDO10_HWC_VSEL_SHIFT 10 /* LDO10_HWC_VSEL */ +#define WM831X_LDO10_HWC_VSEL_WIDTH 1 /* LDO10_HWC_VSEL */ +#define WM831X_LDO10_HWC_MODE_MASK 0x0300 /* LDO10_HWC_MODE - [9:8] */ +#define WM831X_LDO10_HWC_MODE_SHIFT 8 /* LDO10_HWC_MODE - [9:8] */ +#define WM831X_LDO10_HWC_MODE_WIDTH 2 /* LDO10_HWC_MODE - [9:8] */ +#define WM831X_LDO10_FLT 0x0080 /* LDO10_FLT */ +#define WM831X_LDO10_FLT_MASK 0x0080 /* LDO10_FLT */ +#define WM831X_LDO10_FLT_SHIFT 7 /* LDO10_FLT */ +#define WM831X_LDO10_FLT_WIDTH 1 /* LDO10_FLT */ +#define WM831X_LDO10_SWI 0x0040 /* LDO10_SWI */ +#define WM831X_LDO10_SWI_MASK 0x0040 /* LDO10_SWI */ +#define WM831X_LDO10_SWI_SHIFT 6 /* LDO10_SWI */ +#define WM831X_LDO10_SWI_WIDTH 1 /* LDO10_SWI */ + +/* + * R16516 (0x4084) - LDO10 ON Control + */ +#define WM831X_LDO10_ON_SLOT_MASK 0xE000 /* LDO10_ON_SLOT - [15:13] */ +#define WM831X_LDO10_ON_SLOT_SHIFT 13 /* LDO10_ON_SLOT - [15:13] */ +#define WM831X_LDO10_ON_SLOT_WIDTH 3 /* LDO10_ON_SLOT - [15:13] */ +#define WM831X_LDO10_ON_MODE 0x0100 /* LDO10_ON_MODE */ +#define WM831X_LDO10_ON_MODE_MASK 0x0100 /* LDO10_ON_MODE */ +#define WM831X_LDO10_ON_MODE_SHIFT 8 /* LDO10_ON_MODE */ +#define WM831X_LDO10_ON_MODE_WIDTH 1 /* LDO10_ON_MODE */ +#define WM831X_LDO10_ON_VSEL_MASK 0x001F /* LDO10_ON_VSEL - [4:0] */ +#define WM831X_LDO10_ON_VSEL_SHIFT 0 /* LDO10_ON_VSEL - [4:0] */ +#define WM831X_LDO10_ON_VSEL_WIDTH 5 /* LDO10_ON_VSEL - [4:0] */ + +/* + * R16517 (0x4085) - LDO10 SLEEP Control + */ +#define WM831X_LDO10_SLP_SLOT_MASK 0xE000 /* LDO10_SLP_SLOT - [15:13] */ +#define WM831X_LDO10_SLP_SLOT_SHIFT 13 /* LDO10_SLP_SLOT - [15:13] */ +#define WM831X_LDO10_SLP_SLOT_WIDTH 3 /* LDO10_SLP_SLOT - [15:13] */ +#define WM831X_LDO10_SLP_MODE 0x0100 /* LDO10_SLP_MODE */ +#define WM831X_LDO10_SLP_MODE_MASK 0x0100 /* LDO10_SLP_MODE */ +#define WM831X_LDO10_SLP_MODE_SHIFT 8 /* LDO10_SLP_MODE */ +#define WM831X_LDO10_SLP_MODE_WIDTH 1 /* LDO10_SLP_MODE */ +#define WM831X_LDO10_SLP_VSEL_MASK 0x001F /* LDO10_SLP_VSEL - [4:0] */ +#define WM831X_LDO10_SLP_VSEL_SHIFT 0 /* LDO10_SLP_VSEL - [4:0] */ +#define WM831X_LDO10_SLP_VSEL_WIDTH 5 /* LDO10_SLP_VSEL - [4:0] */ + +/* + * R16519 (0x4087) - LDO11 ON Control + */ +#define WM831X_LDO11_ON_SLOT_MASK 0xE000 /* LDO11_ON_SLOT - [15:13] */ +#define WM831X_LDO11_ON_SLOT_SHIFT 13 /* LDO11_ON_SLOT - [15:13] */ +#define WM831X_LDO11_ON_SLOT_WIDTH 3 /* LDO11_ON_SLOT - [15:13] */ +#define WM831X_LDO11_OFFENA 0x1000 /* LDO11_OFFENA */ +#define WM831X_LDO11_OFFENA_MASK 0x1000 /* LDO11_OFFENA */ +#define WM831X_LDO11_OFFENA_SHIFT 12 /* LDO11_OFFENA */ +#define WM831X_LDO11_OFFENA_WIDTH 1 /* LDO11_OFFENA */ +#define WM831X_LDO11_VSEL_SRC 0x0080 /* LDO11_VSEL_SRC */ +#define WM831X_LDO11_VSEL_SRC_MASK 0x0080 /* LDO11_VSEL_SRC */ +#define WM831X_LDO11_VSEL_SRC_SHIFT 7 /* LDO11_VSEL_SRC */ +#define WM831X_LDO11_VSEL_SRC_WIDTH 1 /* LDO11_VSEL_SRC */ +#define WM831X_LDO11_ON_VSEL_MASK 0x000F /* LDO11_ON_VSEL - [3:0] */ +#define WM831X_LDO11_ON_VSEL_SHIFT 0 /* LDO11_ON_VSEL - [3:0] */ +#define WM831X_LDO11_ON_VSEL_WIDTH 4 /* LDO11_ON_VSEL - [3:0] */ + +/* + * R16520 (0x4088) - LDO11 SLEEP Control + */ +#define WM831X_LDO11_SLP_SLOT_MASK 0xE000 /* LDO11_SLP_SLOT - [15:13] */ +#define WM831X_LDO11_SLP_SLOT_SHIFT 13 /* LDO11_SLP_SLOT - [15:13] */ +#define WM831X_LDO11_SLP_SLOT_WIDTH 3 /* LDO11_SLP_SLOT - [15:13] */ +#define WM831X_LDO11_SLP_VSEL_MASK 0x000F /* LDO11_SLP_VSEL - [3:0] */ +#define WM831X_LDO11_SLP_VSEL_SHIFT 0 /* LDO11_SLP_VSEL - [3:0] */ +#define WM831X_LDO11_SLP_VSEL_WIDTH 4 /* LDO11_SLP_VSEL - [3:0] */ + +/* * R16526 (0x408E) - Power Good Source 1 */ #define WM831X_DC4_OK 0x0008 /* DC4_OK */ @@ -586,6 +1168,50 @@ #define WM831X_DC1_OK_SHIFT 0 /* DC1_OK */ #define WM831X_DC1_OK_WIDTH 1 /* DC1_OK */ +/* + * R16527 (0x408F) - Power Good Source 2 + */ +#define WM831X_LDO10_OK 0x0200 /* LDO10_OK */ +#define WM831X_LDO10_OK_MASK 0x0200 /* LDO10_OK */ +#define WM831X_LDO10_OK_SHIFT 9 /* LDO10_OK */ +#define WM831X_LDO10_OK_WIDTH 1 /* LDO10_OK */ +#define WM831X_LDO9_OK 0x0100 /* LDO9_OK */ +#define WM831X_LDO9_OK_MASK 0x0100 /* LDO9_OK */ +#define WM831X_LDO9_OK_SHIFT 8 /* LDO9_OK */ +#define WM831X_LDO9_OK_WIDTH 1 /* LDO9_OK */ +#define WM831X_LDO8_OK 0x0080 /* LDO8_OK */ +#define WM831X_LDO8_OK_MASK 0x0080 /* LDO8_OK */ +#define WM831X_LDO8_OK_SHIFT 7 /* LDO8_OK */ +#define WM831X_LDO8_OK_WIDTH 1 /* LDO8_OK */ +#define WM831X_LDO7_OK 0x0040 /* LDO7_OK */ +#define WM831X_LDO7_OK_MASK 0x0040 /* LDO7_OK */ +#define WM831X_LDO7_OK_SHIFT 6 /* LDO7_OK */ +#define WM831X_LDO7_OK_WIDTH 1 /* LDO7_OK */ +#define WM831X_LDO6_OK 0x0020 /* LDO6_OK */ +#define WM831X_LDO6_OK_MASK 0x0020 /* LDO6_OK */ +#define WM831X_LDO6_OK_SHIFT 5 /* LDO6_OK */ +#define WM831X_LDO6_OK_WIDTH 1 /* LDO6_OK */ +#define WM831X_LDO5_OK 0x0010 /* LDO5_OK */ +#define WM831X_LDO5_OK_MASK 0x0010 /* LDO5_OK */ +#define WM831X_LDO5_OK_SHIFT 4 /* LDO5_OK */ +#define WM831X_LDO5_OK_WIDTH 1 /* LDO5_OK */ +#define WM831X_LDO4_OK 0x0008 /* LDO4_OK */ +#define WM831X_LDO4_OK_MASK 0x0008 /* LDO4_OK */ +#define WM831X_LDO4_OK_SHIFT 3 /* LDO4_OK */ +#define WM831X_LDO4_OK_WIDTH 1 /* LDO4_OK */ +#define WM831X_LDO3_OK 0x0004 /* LDO3_OK */ +#define WM831X_LDO3_OK_MASK 0x0004 /* LDO3_OK */ +#define WM831X_LDO3_OK_SHIFT 2 /* LDO3_OK */ +#define WM831X_LDO3_OK_WIDTH 1 /* LDO3_OK */ +#define WM831X_LDO2_OK 0x0002 /* LDO2_OK */ +#define WM831X_LDO2_OK_MASK 0x0002 /* LDO2_OK */ +#define WM831X_LDO2_OK_SHIFT 1 /* LDO2_OK */ +#define WM831X_LDO2_OK_WIDTH 1 /* LDO2_OK */ +#define WM831X_LDO1_OK 0x0001 /* LDO1_OK */ +#define WM831X_LDO1_OK_MASK 0x0001 /* LDO1_OK */ +#define WM831X_LDO1_OK_SHIFT 0 /* LDO1_OK */ +#define WM831X_LDO1_OK_WIDTH 1 /* LDO1_OK */ + #define WM831X_ISINK_MAX_ISEL 56 extern int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL]; -- cgit v0.10.2 From 8267a9ba8299e1e70d54c7666da6aada637de4fc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:22:23 +0100 Subject: regulator: Add WM831x EPE support The WM831x series of PMICs provide two optional outputs for controlling external devices during power sequencing, for example an external regulator. While in essence these are GPIOs the hardware presents them as DCDCs with very little control so provide support via the regulator API in that fashion. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index fa5126e..88a7dba 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -613,6 +613,89 @@ static struct platform_driver wm831x_buckp_driver = { }, }; +/* + * External Power Enable + * + * These aren't actually DCDCs but look like them in hardware so share + * code. + */ + +#define WM831X_EPE_BASE 6 + +static struct regulator_ops wm831x_epe_ops = { + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, + .get_status = wm831x_dcdc_get_status, +}; + +static __devinit int wm831x_epe_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->epe); + struct wm831x_dcdc *dcdc; + int ret; + + dev_dbg(&pdev->dev, "Probing EPE%d\n", id + 1); + + if (pdata == NULL || pdata->epe[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(dcdc->name, sizeof(dcdc->name), "EPE%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id + WM831X_EPE_BASE; /* Offset in DCDC registers */ + dcdc->desc.ops = &wm831x_epe_ops; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->epe[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register EPE%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_epe_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_epe_driver = { + .probe = wm831x_epe_probe, + .remove = __devexit_p(wm831x_epe_remove), + .driver = { + .name = "wm831x-epe", + }, +}; + static int __init wm831x_dcdc_init(void) { int ret; @@ -624,12 +707,17 @@ static int __init wm831x_dcdc_init(void) if (ret != 0) pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + ret = platform_driver_register(&wm831x_epe_driver); + if (ret != 0) + pr_err("Failed to register WM831x EPE driver: %d\n", ret); + return 0; } subsys_initcall(wm831x_dcdc_init); static void __exit wm831x_dcdc_exit(void) { + platform_driver_unregister(&wm831x_epe_driver); platform_driver_unregister(&wm831x_buckp_driver); platform_driver_unregister(&wm831x_buckv_driver); } -- cgit v0.10.2 From 1304850d4c5d2f915bdcb8d547f3ef26c60cc825 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:23:16 +0100 Subject: regulator: Add WM831x DC-DC boost convertor support The WM831x series of PMICs include a single DC-DC boost convertor. This adds basic support for this convertor. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index 88a7dba..2eefc1a 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -614,6 +614,132 @@ static struct platform_driver wm831x_buckp_driver = { }; /* + * DCDC boost convertors + */ + +static int wm831x_boostp_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (ret & (1 << rdev_get_id(rdev))) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static struct regulator_ops wm831x_boostp_ops = { + .get_status = wm831x_boostp_get_status, + + .is_enabled = wm831x_dcdc_is_enabled, + .enable = wm831x_dcdc_enable, + .disable = wm831x_dcdc_disable, +}; + +static __devinit int wm831x_boostp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (dcdc == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.ops = &wm831x_boostp_ops; + dcdc->desc.owner = THIS_MODULE; + + dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev, + pdata->dcdc[id], dcdc); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq_byname(pdev, "UV"); + ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING, dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err_regulator: + regulator_unregister(dcdc->regulator); +err: + kfree(dcdc); + return ret; +} + +static __devexit int wm831x_boostp_remove(struct platform_device *pdev) +{ + struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); + struct wm831x *wm831x = dcdc->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc); + regulator_unregister(dcdc->regulator); + kfree(dcdc); + + return 0; +} + +static struct platform_driver wm831x_boostp_driver = { + .probe = wm831x_boostp_probe, + .remove = __devexit_p(wm831x_boostp_remove), + .driver = { + .name = "wm831x-boostp", + }, +}; + +/* * External Power Enable * * These aren't actually DCDCs but look like them in hardware so share @@ -707,6 +833,10 @@ static int __init wm831x_dcdc_init(void) if (ret != 0) pr_err("Failed to register WM831x BUCKP driver: %d\n", ret); + ret = platform_driver_register(&wm831x_boostp_driver); + if (ret != 0) + pr_err("Failed to register WM831x BOOST driver: %d\n", ret); + ret = platform_driver_register(&wm831x_epe_driver); if (ret != 0) pr_err("Failed to register WM831x EPE driver: %d\n", ret); @@ -718,6 +848,7 @@ subsys_initcall(wm831x_dcdc_init); static void __exit wm831x_dcdc_exit(void) { platform_driver_unregister(&wm831x_epe_driver); + platform_driver_unregister(&wm831x_boostp_driver); platform_driver_unregister(&wm831x_buckp_driver); platform_driver_unregister(&wm831x_buckv_driver); } -- cgit v0.10.2 From d4d6b722e780f005f0d4e43a43909fa51cc33a11 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jul 2009 15:23:46 +0100 Subject: regulator: Add WM831x ISINK support The WM831x series of PMICs provide two constant current sinks designed to drive strings of serially connected LEDs for applications such as backlights. This driver adds support for those regulators. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index a0a635f..3a4cdf5 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o diff --git a/drivers/regulator/wm831x-isink.c b/drivers/regulator/wm831x-isink.c new file mode 100644 index 0000000..1d8d987 --- /dev/null +++ b/drivers/regulator/wm831x-isink.c @@ -0,0 +1,260 @@ +/* + * wm831x-isink.c -- Current sink driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM831X_ISINK_MAX_NAME 7 + +struct wm831x_isink { + char name[WM831X_ISINK_MAX_NAME]; + struct regulator_desc desc; + int reg; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +static int wm831x_isink_enable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + /* We have a two stage enable: first start the ISINK... */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, + WM831X_CS1_ENA); + if (ret != 0) + return ret; + + /* ...then enable drive */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, + WM831X_CS1_DRIVE); + if (ret != 0) + wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + + return ret; + +} + +static int wm831x_isink_disable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + if (ret < 0) + return ret; + + return ret; + +} + +static int wm831x_isink_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + if ((ret & (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) == + (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) + return 1; + else + return 0; +} + +static int wm831x_isink_set_current(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(wm831x_isinkv_values); i++) { + int val = wm831x_isinkv_values[i]; + if (min_uA >= val && val <= max_uA) { + ret = wm831x_set_bits(wm831x, isink->reg, + WM831X_CS1_ISEL_MASK, i); + return ret; + } + } + + return -EINVAL; +} + +static int wm831x_isink_get_current(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + ret &= WM831X_CS1_ISEL_MASK; + if (ret > WM831X_ISINK_MAX_ISEL) + ret = WM831X_ISINK_MAX_ISEL; + + return wm831x_isinkv_values[ret]; +} + +static struct regulator_ops wm831x_isink_ops = { + .is_enabled = wm831x_isink_is_enabled, + .enable = wm831x_isink_enable, + .disable = wm831x_isink_disable, + .set_current_limit = wm831x_isink_set_current, + .get_current_limit = wm831x_isink_get_current, +}; + +static irqreturn_t wm831x_isink_irq(int irq, void *data) +{ + struct wm831x_isink *isink = data; + + regulator_notifier_call_chain(isink->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + + +static __devinit int wm831x_isink_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = wm831x->dev->platform_data; + struct wm831x_isink *isink; + int id = pdev->id % ARRAY_SIZE(pdata->isink); + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing ISINK%d\n", id + 1); + + if (pdata == NULL || pdata->isink[id] == NULL) + return -ENODEV; + + isink = kzalloc(sizeof(struct wm831x_isink), GFP_KERNEL); + if (isink == NULL) { + dev_err(&pdev->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource\n"); + ret = -EINVAL; + goto err; + } + isink->reg = res->start; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(isink->name, sizeof(isink->name), "ISINK%d", id + 1); + isink->desc.name = isink->name; + isink->desc.id = id; + isink->desc.ops = &wm831x_isink_ops; + isink->desc.type = REGULATOR_CURRENT; + isink->desc.owner = THIS_MODULE; + + isink->regulator = regulator_register(&isink->desc, &pdev->dev, + pdata->isink[id], isink); + if (IS_ERR(isink->regulator)) { + ret = PTR_ERR(isink->regulator); + dev_err(wm831x->dev, "Failed to register ISINK%d: %d\n", + id + 1, ret); + goto err; + } + + irq = platform_get_irq(pdev, 0); + ret = wm831x_request_irq(wm831x, irq, wm831x_isink_irq, + IRQF_TRIGGER_RISING, isink->name, + isink); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request ISINK IRQ %d: %d\n", + irq, ret); + goto err_regulator; + } + + platform_set_drvdata(pdev, isink); + + return 0; + +err_regulator: + regulator_unregister(isink->regulator); +err: + kfree(isink); + return ret; +} + +static __devexit int wm831x_isink_remove(struct platform_device *pdev) +{ + struct wm831x_isink *isink = platform_get_drvdata(pdev); + struct wm831x *wm831x = isink->wm831x; + + wm831x_free_irq(wm831x, platform_get_irq(pdev, 0), isink); + + regulator_unregister(isink->regulator); + kfree(isink); + + return 0; +} + +static struct platform_driver wm831x_isink_driver = { + .probe = wm831x_isink_probe, + .remove = __devexit_p(wm831x_isink_remove), + .driver = { + .name = "wm831x-isink", + }, +}; + +static int __init wm831x_isink_init(void) +{ + int ret; + ret = platform_driver_register(&wm831x_isink_driver); + if (ret != 0) + pr_err("Failed to register WM831x ISINK driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_isink_init); + +static void __exit wm831x_isink_exit(void) +{ + platform_driver_unregister(&wm831x_isink_driver); +} +module_exit(wm831x_isink_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x current sink driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-isink"); -- cgit v0.10.2 From e397e7ed50e3fb573aa5de183ae308dc7bf20b9e Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Thu, 6 Aug 2009 16:08:52 -0700 Subject: regulator: register pcap earlier Register pcap-regulator earlier so it can be used with cpufreq Signed-off-by: Daniel Ribeiro Acked-by: Mark Brown Signed-off-by: Andrew Morton Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c index fdb9094..137b455 100644 --- a/drivers/regulator/pcap-regulator.c +++ b/drivers/regulator/pcap-regulator.c @@ -310,7 +310,7 @@ static void __exit pcap_regulator_exit(void) platform_driver_unregister(&pcap_regulator_driver); } -module_init(pcap_regulator_init); +subsys_initcall(pcap_regulator_init); module_exit(pcap_regulator_exit); MODULE_AUTHOR("Daniel Ribeiro "); -- cgit v0.10.2 From 0387e107d6043c810915bf552c3fee367f536f3a Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Fri, 7 Aug 2009 22:54:56 +0200 Subject: input: PCAP2 based touchscreen driver Touchscreen driver for the PCAP2 multi function device used in Motorola EZX smartphones. Signed-off-by: Daniel Ribeiro Signed-off-by: Stefan Schmidt Signed-off-by: Antonio Ospite Acked-by: Dmitry Torokhov Signed-off-by: Samuel Ortiz diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 87a1ae6..ab02d72 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -510,4 +510,13 @@ config TOUCHSCREEN_W90X900 To compile this driver as a module, choose M here: the module will be called w90p910_ts. +config TOUCHSCREEN_PCAP + tristate "Motorola PCAP touchscreen" + depends on EZX_PCAP + help + Say Y here if you have a Motorola EZX telephone and + want to enable support for the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called pcap_ts. endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 3e1c5e0..4599bf7 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -40,3 +40,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o +obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o diff --git a/drivers/input/touchscreen/pcap_ts.c b/drivers/input/touchscreen/pcap_ts.c new file mode 100644 index 0000000..67fcd33 --- /dev/null +++ b/drivers/input/touchscreen/pcap_ts.c @@ -0,0 +1,271 @@ +/* + * Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform. + * + * Copyright (C) 2006 Harald Welte + * Copyright (C) 2009 Daniel Ribeiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pcap_ts { + struct pcap_chip *pcap; + struct input_dev *input; + struct delayed_work work; + u16 x, y; + u16 pressure; + u8 read_state; +}; + +#define SAMPLE_DELAY 20 /* msecs */ + +#define X_AXIS_MIN 0 +#define X_AXIS_MAX 1023 +#define Y_AXIS_MAX X_AXIS_MAX +#define Y_AXIS_MIN X_AXIS_MIN +#define PRESSURE_MAX X_AXIS_MAX +#define PRESSURE_MIN X_AXIS_MIN + +static void pcap_ts_read_xy(void *data, u16 res[2]) +{ + struct pcap_ts *pcap_ts = data; + + switch (pcap_ts->read_state) { + case PCAP_ADC_TS_M_PRESSURE: + /* pressure reading is unreliable */ + if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX) + pcap_ts->pressure = res[0]; + pcap_ts->read_state = PCAP_ADC_TS_M_XY; + schedule_delayed_work(&pcap_ts->work, 0); + break; + case PCAP_ADC_TS_M_XY: + pcap_ts->y = res[0]; + pcap_ts->x = res[1]; + if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX || + pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) { + /* pen has been released */ + input_report_abs(pcap_ts->input, ABS_PRESSURE, 0); + input_report_key(pcap_ts->input, BTN_TOUCH, 0); + + pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY; + schedule_delayed_work(&pcap_ts->work, 0); + } else { + /* pen is touching the screen */ + input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x); + input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y); + input_report_key(pcap_ts->input, BTN_TOUCH, 1); + input_report_abs(pcap_ts->input, ABS_PRESSURE, + pcap_ts->pressure); + + /* switch back to pressure read mode */ + pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE; + schedule_delayed_work(&pcap_ts->work, + msecs_to_jiffies(SAMPLE_DELAY)); + } + input_sync(pcap_ts->input); + break; + default: + dev_warn(&pcap_ts->input->dev, + "pcap_ts: Warning, unhandled read_state %d\n", + pcap_ts->read_state); + break; + } +} + +static void pcap_ts_work(struct work_struct *work) +{ + struct delayed_work *dw = container_of(work, struct delayed_work, work); + struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work); + u8 ch[2]; + + pcap_set_ts_bits(pcap_ts->pcap, + pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); + + if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) + return; + + /* start adc conversion */ + ch[0] = PCAP_ADC_CH_TS_X1; + ch[1] = PCAP_ADC_CH_TS_Y1; + pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch, + pcap_ts_read_xy, pcap_ts); +} + +static irqreturn_t pcap_ts_event_touch(int pirq, void *data) +{ + struct pcap_ts *pcap_ts = data; + + if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) { + pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE; + schedule_delayed_work(&pcap_ts->work, 0); + } + return IRQ_HANDLED; +} + +static int pcap_ts_open(struct input_dev *dev) +{ + struct pcap_ts *pcap_ts = input_get_drvdata(dev); + + pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY; + schedule_delayed_work(&pcap_ts->work, 0); + + return 0; +} + +static void pcap_ts_close(struct input_dev *dev) +{ + struct pcap_ts *pcap_ts = input_get_drvdata(dev); + + cancel_delayed_work_sync(&pcap_ts->work); + + pcap_ts->read_state = PCAP_ADC_TS_M_NONTS; + pcap_set_ts_bits(pcap_ts->pcap, + pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); +} + +static int __devinit pcap_ts_probe(struct platform_device *pdev) +{ + struct input_dev *input_dev; + struct pcap_ts *pcap_ts; + int err = -ENOMEM; + + pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL); + if (!pcap_ts) + return err; + + pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, pcap_ts); + + input_dev = input_allocate_device(); + if (!input_dev) + goto fail; + + INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work); + + pcap_ts->read_state = PCAP_ADC_TS_M_NONTS; + pcap_set_ts_bits(pcap_ts->pcap, + pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); + + pcap_ts->input = input_dev; + input_set_drvdata(input_dev, pcap_ts); + + input_dev->name = "pcap-touchscreen"; + input_dev->phys = "pcap_ts/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0002; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + input_dev->open = pcap_ts_open; + input_dev->close = pcap_ts_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, + PRESSURE_MAX, 0, 0); + + err = input_register_device(pcap_ts->input); + if (err) + goto fail_allocate; + + err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), + pcap_ts_event_touch, 0, "Touch Screen", pcap_ts); + if (err) + goto fail_register; + + return 0; + +fail_register: + input_unregister_device(input_dev); + goto fail; +fail_allocate: + input_free_device(input_dev); +fail: + kfree(pcap_ts); + + return err; +} + +static int __devexit pcap_ts_remove(struct platform_device *pdev) +{ + struct pcap_ts *pcap_ts = platform_get_drvdata(pdev); + + free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts); + cancel_delayed_work_sync(&pcap_ts->work); + + input_unregister_device(pcap_ts->input); + + kfree(pcap_ts); + + return 0; +} + +#ifdef CONFIG_PM +static int pcap_ts_suspend(struct device *dev) +{ + struct pcap_ts *pcap_ts = dev_get_drvdata(dev); + + pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR); + return 0; +} + +static int pcap_ts_resume(struct device *dev) +{ + struct pcap_ts *pcap_ts = dev_get_drvdata(dev); + + pcap_set_ts_bits(pcap_ts->pcap, + pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); + return 0; +} + +static struct dev_pm_ops pcap_ts_pm_ops = { + .suspend = pcap_ts_suspend, + .resume = pcap_ts_resume, +}; +#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops) +#else +#define PCAP_TS_PM_OPS NULL +#endif + +static struct platform_driver pcap_ts_driver = { + .probe = pcap_ts_probe, + .remove = __devexit_p(pcap_ts_remove), + .driver = { + .name = "pcap-ts", + .owner = THIS_MODULE, + .pm = PCAP_TS_PM_OPS, + }, +}; + +static int __init pcap_ts_init(void) +{ + return platform_driver_register(&pcap_ts_driver); +} + +static void __exit pcap_ts_exit(void) +{ + platform_driver_unregister(&pcap_ts_driver); +} + +module_init(pcap_ts_init); +module_exit(pcap_ts_exit); + +MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver"); +MODULE_AUTHOR("Daniel Ribeiro / Harald Welte"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcap_ts"); -- cgit v0.10.2 From d0a821324819a2908b886ae8b2f33fc7824ff83f Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Mon, 10 Aug 2009 20:27:48 +0200 Subject: input: PCAP2 misc input driver This is a driver for misc input events for the PCAP2 PMIC, it handles the Power key and the Headphone button. Signed-off-by: Daniel Ribeiro Signed-off-by: Ilya Petrov Signed-off-by: Antonio Ospite Acked-by: Dmitry Torokhov Signed-off-by: Samuel Ortiz diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 852941d..1a50be3 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -289,4 +289,14 @@ config INPUT_WM831X_ON To compile this driver as a module, choose M here: the module will be called wm831x_on. +config INPUT_PCAP + tristate "Motorola EZX PCAP misc input events" + depends on EZX_PCAP + help + Say Y here if you want to use Power key and Headphone button + on Motorola EZX phones. + + To compile this driver as a module, choose M here: the + module will be called pcap_keys. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index c97533f..bf4db62 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o @@ -28,3 +29,4 @@ obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o + diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c new file mode 100644 index 0000000..7ea9693 --- /dev/null +++ b/drivers/input/misc/pcap_keys.c @@ -0,0 +1,144 @@ +/* + * Input driver for PCAP events: + * * Power key + * * Headphone button + * + * Copyright (c) 2008,2009 Ilya Petrov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +struct pcap_keys { + struct pcap_chip *pcap; + struct input_dev *input; +}; + +/* PCAP2 interrupts us on keypress */ +static irqreturn_t pcap_keys_handler(int irq, void *_pcap_keys) +{ + struct pcap_keys *pcap_keys = _pcap_keys; + int pirq = irq_to_pcap(pcap_keys->pcap, irq); + u32 pstat; + + ezx_pcap_read(pcap_keys->pcap, PCAP_REG_PSTAT, &pstat); + pstat &= 1 << pirq; + + switch (pirq) { + case PCAP_IRQ_ONOFF: + input_report_key(pcap_keys->input, KEY_POWER, !pstat); + break; + case PCAP_IRQ_MIC: + input_report_key(pcap_keys->input, KEY_HP, !pstat); + break; + } + + input_sync(pcap_keys->input); + + return IRQ_HANDLED; +} + +static int __devinit pcap_keys_probe(struct platform_device *pdev) +{ + int err = -ENOMEM; + struct pcap_keys *pcap_keys; + struct input_dev *input_dev; + + pcap_keys = kmalloc(sizeof(struct pcap_keys), GFP_KERNEL); + if (!pcap_keys) + return err; + + pcap_keys->pcap = dev_get_drvdata(pdev->dev.parent); + + input_dev = input_allocate_device(); + if (!input_dev) + goto fail; + + pcap_keys->input = input_dev; + + platform_set_drvdata(pdev, pcap_keys); + input_dev->name = pdev->name; + input_dev->phys = "pcap-keys/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_POWER, input_dev->keybit); + __set_bit(KEY_HP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) + goto fail_allocate; + + err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), + pcap_keys_handler, 0, "Power key", pcap_keys); + if (err) + goto fail_register; + + err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), + pcap_keys_handler, 0, "Headphone button", pcap_keys); + if (err) + goto fail_pwrkey; + + return 0; + +fail_pwrkey: + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys); +fail_register: + input_unregister_device(input_dev); + goto fail; +fail_allocate: + input_free_device(input_dev); +fail: + kfree(pcap_keys); + return err; +} + +static int __devexit pcap_keys_remove(struct platform_device *pdev) +{ + struct pcap_keys *pcap_keys = platform_get_drvdata(pdev); + + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys); + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), pcap_keys); + + input_unregister_device(pcap_keys->input); + kfree(pcap_keys); + + return 0; +} + +static struct platform_driver pcap_keys_device_driver = { + .probe = pcap_keys_probe, + .remove = __devexit_p(pcap_keys_remove), + .driver = { + .name = "pcap-keys", + .owner = THIS_MODULE, + } +}; + +static int __init pcap_keys_init(void) +{ + return platform_driver_register(&pcap_keys_device_driver); +}; + +static void __exit pcap_keys_exit(void) +{ + platform_driver_unregister(&pcap_keys_device_driver); +}; + +module_init(pcap_keys_init); +module_exit(pcap_keys_exit); + +MODULE_DESCRIPTION("Motorola PCAP2 input events driver"); +MODULE_AUTHOR("Ilya Petrov "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcap_keys"); -- cgit v0.10.2 From 70fde5cbd421773f0b9d684933ecb441efe89c84 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 7 Aug 2009 23:18:41 +0200 Subject: regulator: get pcap data from the parent device Right now the pcap core driver passes a reference to its pcap data abusing the subdrivers platform drvdata, this is not good. Get the reference directly from the parent device. Signed-off-by: Antonio Ospite Acked-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c index 137b455..33d7d89 100644 --- a/drivers/regulator/pcap-regulator.c +++ b/drivers/regulator/pcap-regulator.c @@ -271,7 +271,7 @@ static struct regulator_desc pcap_regulators[] = { static int __devinit pcap_regulator_probe(struct platform_device *pdev) { struct regulator_dev *rdev; - void *pcap = platform_get_drvdata(pdev); + void *pcap = dev_get_drvdata(pdev->dev.parent); rdev = regulator_register(&pcap_regulators[pdev->id], &pdev->dev, pdev->dev.platform_data, pcap); -- cgit v0.10.2 From 35c86bf66d9d0ebc3f32f8c56251197b3921394e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 27 Aug 2009 19:59:05 +0200 Subject: rtc: Add support for RTCs on Wolfson WM831x devices The WM831x series of PMICs contain RTC functionality. The hardware provides a 32 bit counter incrementing at 1Hz together with a per tick interrupt and an alarm value. For simplicity the driver chooses to define the epoch for the counter as the Unix epoch - if required platform data can be used in future to customise this. When powered on from a completely cold state the RTC reports that it has not been configured - when this happens an error is returned when attempting to read the RTC in order to avoid use of values we know to be invalid. The hardware also provides security features which mean that it can ignore attempts to set the RTC time in certain circumstances, most notably if the RTC is written to too often. These errors are detected by verifying the written RTC value. Signed-off-by: Mark Brown Acked-by: Alessandro Zummo Signed-off-by: Samuel Ortiz diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 81adbdb..b136299 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -518,6 +518,16 @@ config RTC_DRV_V3020 This driver can also be built as a module. If so, the module will be called rtc-v3020. +config RTC_DRV_WM831X + tristate "Wolfson Microelectronics WM831x RTC" + depends on MFD_WM831X + help + If you say yes here you will get support for the RTC subsystem + of the Wolfson Microelectronics WM831X series PMICs. + + This driver can also be built as a module. If so, the module + will be called "rtc-wm831x". + config RTC_DRV_WM8350 tristate "Wolfson Microelectronics WM8350 RTC" depends on MFD_WM8350 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 3c0f2b2..f3e5735 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl4030.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o +obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o diff --git a/drivers/rtc/rtc-wm831x.c b/drivers/rtc/rtc-wm831x.c new file mode 100644 index 0000000..79795cd --- /dev/null +++ b/drivers/rtc/rtc-wm831x.c @@ -0,0 +1,523 @@ +/* + * Real Time Clock driver for Wolfson Microelectronics WM831x + * + * Copyright (C) 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * R16416 (0x4020) - RTC Write Counter + */ +#define WM831X_RTC_WR_CNT_MASK 0xFFFF /* RTC_WR_CNT - [15:0] */ +#define WM831X_RTC_WR_CNT_SHIFT 0 /* RTC_WR_CNT - [15:0] */ +#define WM831X_RTC_WR_CNT_WIDTH 16 /* RTC_WR_CNT - [15:0] */ + +/* + * R16417 (0x4021) - RTC Time 1 + */ +#define WM831X_RTC_TIME_MASK 0xFFFF /* RTC_TIME - [15:0] */ +#define WM831X_RTC_TIME_SHIFT 0 /* RTC_TIME - [15:0] */ +#define WM831X_RTC_TIME_WIDTH 16 /* RTC_TIME - [15:0] */ + +/* + * R16418 (0x4022) - RTC Time 2 + */ +#define WM831X_RTC_TIME_MASK 0xFFFF /* RTC_TIME - [15:0] */ +#define WM831X_RTC_TIME_SHIFT 0 /* RTC_TIME - [15:0] */ +#define WM831X_RTC_TIME_WIDTH 16 /* RTC_TIME - [15:0] */ + +/* + * R16419 (0x4023) - RTC Alarm 1 + */ +#define WM831X_RTC_ALM_MASK 0xFFFF /* RTC_ALM - [15:0] */ +#define WM831X_RTC_ALM_SHIFT 0 /* RTC_ALM - [15:0] */ +#define WM831X_RTC_ALM_WIDTH 16 /* RTC_ALM - [15:0] */ + +/* + * R16420 (0x4024) - RTC Alarm 2 + */ +#define WM831X_RTC_ALM_MASK 0xFFFF /* RTC_ALM - [15:0] */ +#define WM831X_RTC_ALM_SHIFT 0 /* RTC_ALM - [15:0] */ +#define WM831X_RTC_ALM_WIDTH 16 /* RTC_ALM - [15:0] */ + +/* + * R16421 (0x4025) - RTC Control + */ +#define WM831X_RTC_VALID 0x8000 /* RTC_VALID */ +#define WM831X_RTC_VALID_MASK 0x8000 /* RTC_VALID */ +#define WM831X_RTC_VALID_SHIFT 15 /* RTC_VALID */ +#define WM831X_RTC_VALID_WIDTH 1 /* RTC_VALID */ +#define WM831X_RTC_SYNC_BUSY 0x4000 /* RTC_SYNC_BUSY */ +#define WM831X_RTC_SYNC_BUSY_MASK 0x4000 /* RTC_SYNC_BUSY */ +#define WM831X_RTC_SYNC_BUSY_SHIFT 14 /* RTC_SYNC_BUSY */ +#define WM831X_RTC_SYNC_BUSY_WIDTH 1 /* RTC_SYNC_BUSY */ +#define WM831X_RTC_ALM_ENA 0x0400 /* RTC_ALM_ENA */ +#define WM831X_RTC_ALM_ENA_MASK 0x0400 /* RTC_ALM_ENA */ +#define WM831X_RTC_ALM_ENA_SHIFT 10 /* RTC_ALM_ENA */ +#define WM831X_RTC_ALM_ENA_WIDTH 1 /* RTC_ALM_ENA */ +#define WM831X_RTC_PINT_FREQ_MASK 0x0070 /* RTC_PINT_FREQ - [6:4] */ +#define WM831X_RTC_PINT_FREQ_SHIFT 4 /* RTC_PINT_FREQ - [6:4] */ +#define WM831X_RTC_PINT_FREQ_WIDTH 3 /* RTC_PINT_FREQ - [6:4] */ + +/* + * R16422 (0x4026) - RTC Trim + */ +#define WM831X_RTC_TRIM_MASK 0x03FF /* RTC_TRIM - [9:0] */ +#define WM831X_RTC_TRIM_SHIFT 0 /* RTC_TRIM - [9:0] */ +#define WM831X_RTC_TRIM_WIDTH 10 /* RTC_TRIM - [9:0] */ + +#define WM831X_SET_TIME_RETRIES 5 +#define WM831X_GET_TIME_RETRIES 5 + +struct wm831x_rtc { + struct wm831x *wm831x; + struct rtc_device *rtc; + unsigned int alarm_enabled:1; +}; + +/* + * Read current time and date in RTC + */ +static int wm831x_rtc_readtime(struct device *dev, struct rtc_time *tm) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + struct wm831x *wm831x = wm831x_rtc->wm831x; + u16 time1[2], time2[2]; + int ret; + int count = 0; + + /* Has the RTC been programmed? */ + ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL); + if (ret < 0) { + dev_err(dev, "Failed to read RTC control: %d\n", ret); + return ret; + } + if (!(ret & WM831X_RTC_VALID)) { + dev_dbg(dev, "RTC not yet configured\n"); + return -EINVAL; + } + + /* Read twice to make sure we don't read a corrupt, partially + * incremented, value. + */ + do { + ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1, + 2, time1); + if (ret != 0) + continue; + + ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1, + 2, time2); + if (ret != 0) + continue; + + if (memcmp(time1, time2, sizeof(time1)) == 0) { + u32 time = (time1[0] << 16) | time1[1]; + + rtc_time_to_tm(time, tm); + return rtc_valid_tm(tm); + } + + } while (++count < WM831X_GET_TIME_RETRIES); + + dev_err(dev, "Timed out reading current time\n"); + + return -EIO; +} + +/* + * Set current time and date in RTC + */ +static int wm831x_rtc_set_mmss(struct device *dev, unsigned long time) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + struct wm831x *wm831x = wm831x_rtc->wm831x; + struct rtc_time new_tm; + unsigned long new_time; + int ret; + int count = 0; + + ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_1, + (time >> 16) & 0xffff); + if (ret < 0) { + dev_err(dev, "Failed to write TIME_1: %d\n", ret); + return ret; + } + + ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_2, time & 0xffff); + if (ret < 0) { + dev_err(dev, "Failed to write TIME_2: %d\n", ret); + return ret; + } + + /* Wait for the update to complete - should happen first time + * round but be conservative. + */ + do { + msleep(1); + + ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL); + if (ret < 0) + ret = WM831X_RTC_SYNC_BUSY; + } while (!(ret & WM831X_RTC_SYNC_BUSY) && + ++count < WM831X_SET_TIME_RETRIES); + + if (ret & WM831X_RTC_SYNC_BUSY) { + dev_err(dev, "Timed out writing RTC update\n"); + return -EIO; + } + + /* Check that the update was accepted; security features may + * have caused the update to be ignored. + */ + ret = wm831x_rtc_readtime(dev, &new_tm); + if (ret < 0) + return ret; + + ret = rtc_tm_to_time(&new_tm, &new_time); + if (ret < 0) { + dev_err(dev, "Failed to convert time: %d\n", ret); + return ret; + } + + /* Allow a second of change in case of tick */ + if (new_time - time > 1) { + dev_err(dev, "RTC update not permitted by hardware\n"); + return -EPERM; + } + + return 0; +} + +/* + * Read alarm time and date in RTC + */ +static int wm831x_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + int ret; + u16 data[2]; + u32 time; + + ret = wm831x_bulk_read(wm831x_rtc->wm831x, WM831X_RTC_ALARM_1, + 2, data); + if (ret != 0) { + dev_err(dev, "Failed to read alarm time: %d\n", ret); + return ret; + } + + time = (data[0] << 16) | data[1]; + + rtc_time_to_tm(time, &alrm->time); + + ret = wm831x_reg_read(wm831x_rtc->wm831x, WM831X_RTC_CONTROL); + if (ret < 0) { + dev_err(dev, "Failed to read RTC control: %d\n", ret); + return ret; + } + + if (ret & WM831X_RTC_ALM_ENA) + alrm->enabled = 1; + else + alrm->enabled = 0; + + return 0; +} + +static int wm831x_rtc_stop_alarm(struct wm831x_rtc *wm831x_rtc) +{ + wm831x_rtc->alarm_enabled = 0; + + return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL, + WM831X_RTC_ALM_ENA, 0); +} + +static int wm831x_rtc_start_alarm(struct wm831x_rtc *wm831x_rtc) +{ + wm831x_rtc->alarm_enabled = 1; + + return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL, + WM831X_RTC_ALM_ENA, WM831X_RTC_ALM_ENA); +} + +static int wm831x_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + struct wm831x *wm831x = wm831x_rtc->wm831x; + int ret; + unsigned long time; + + ret = rtc_tm_to_time(&alrm->time, &time); + if (ret < 0) { + dev_err(dev, "Failed to convert time: %d\n", ret); + return ret; + } + + ret = wm831x_rtc_stop_alarm(wm831x_rtc); + if (ret < 0) { + dev_err(dev, "Failed to stop alarm: %d\n", ret); + return ret; + } + + ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_1, + (time >> 16) & 0xffff); + if (ret < 0) { + dev_err(dev, "Failed to write ALARM_1: %d\n", ret); + return ret; + } + + ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_2, time & 0xffff); + if (ret < 0) { + dev_err(dev, "Failed to write ALARM_2: %d\n", ret); + return ret; + } + + if (alrm->enabled) { + ret = wm831x_rtc_start_alarm(wm831x_rtc); + if (ret < 0) { + dev_err(dev, "Failed to start alarm: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int wm831x_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + + if (enabled) + return wm831x_rtc_start_alarm(wm831x_rtc); + else + return wm831x_rtc_stop_alarm(wm831x_rtc); +} + +static int wm831x_rtc_update_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev); + int val; + + if (enabled) + val = 1 << WM831X_RTC_PINT_FREQ_SHIFT; + else + val = 0; + + return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL, + WM831X_RTC_PINT_FREQ_MASK, val); +} + +static irqreturn_t wm831x_alm_irq(int irq, void *data) +{ + struct wm831x_rtc *wm831x_rtc = data; + + rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_per_irq(int irq, void *data) +{ + struct wm831x_rtc *wm831x_rtc = data; + + rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_UF); + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops wm831x_rtc_ops = { + .read_time = wm831x_rtc_readtime, + .set_mmss = wm831x_rtc_set_mmss, + .read_alarm = wm831x_rtc_readalarm, + .set_alarm = wm831x_rtc_setalarm, + .alarm_irq_enable = wm831x_rtc_alarm_irq_enable, + .update_irq_enable = wm831x_rtc_update_irq_enable, +}; + +#ifdef CONFIG_PM +/* Turn off the alarm if it should not be a wake source. */ +static int wm831x_rtc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev); + int ret, enable; + + if (wm831x_rtc->alarm_enabled && device_may_wakeup(&pdev->dev)) + enable = WM831X_RTC_ALM_ENA; + else + enable = 0; + + ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL, + WM831X_RTC_ALM_ENA, enable); + if (ret != 0) + dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret); + + return 0; +} + +/* Enable the alarm if it should be enabled (in case it was disabled to + * prevent use as a wake source). + */ +static int wm831x_rtc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev); + int ret; + + if (wm831x_rtc->alarm_enabled) { + ret = wm831x_rtc_start_alarm(wm831x_rtc); + if (ret != 0) + dev_err(&pdev->dev, + "Failed to restart RTC alarm: %d\n", ret); + } + + return 0; +} + +/* Unconditionally disable the alarm */ +static int wm831x_rtc_freeze(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev); + int ret; + + ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL, + WM831X_RTC_ALM_ENA, 0); + if (ret != 0) + dev_err(&pdev->dev, "Failed to stop RTC alarm: %d\n", ret); + + return 0; +} +#else +#define wm831x_rtc_suspend NULL +#define wm831x_rtc_resume NULL +#define wm831x_rtc_freeze NULL +#endif + +static int wm831x_rtc_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_rtc *wm831x_rtc; + int per_irq = platform_get_irq_byname(pdev, "PER"); + int alm_irq = platform_get_irq_byname(pdev, "ALM"); + int ret = 0; + + wm831x_rtc = kzalloc(sizeof(*wm831x_rtc), GFP_KERNEL); + if (wm831x_rtc == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, wm831x_rtc); + wm831x_rtc->wm831x = wm831x; + + ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read RTC control: %d\n", ret); + goto err; + } + if (ret & WM831X_RTC_ALM_ENA) + wm831x_rtc->alarm_enabled = 1; + + device_init_wakeup(&pdev->dev, 1); + + wm831x_rtc->rtc = rtc_device_register("wm831x", &pdev->dev, + &wm831x_rtc_ops, THIS_MODULE); + if (IS_ERR(wm831x_rtc->rtc)) { + ret = PTR_ERR(wm831x_rtc->rtc); + goto err; + } + + ret = wm831x_request_irq(wm831x, per_irq, wm831x_per_irq, + IRQF_TRIGGER_RISING, "wm831x_rtc_per", + wm831x_rtc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request periodic IRQ %d: %d\n", + per_irq, ret); + } + + ret = wm831x_request_irq(wm831x, alm_irq, wm831x_alm_irq, + IRQF_TRIGGER_RISING, "wm831x_rtc_alm", + wm831x_rtc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n", + alm_irq, ret); + } + + return 0; + +err: + kfree(wm831x_rtc); + return ret; +} + +static int __devexit wm831x_rtc_remove(struct platform_device *pdev) +{ + struct wm831x_rtc *wm831x_rtc = platform_get_drvdata(pdev); + int per_irq = platform_get_irq_byname(pdev, "PER"); + int alm_irq = platform_get_irq_byname(pdev, "ALM"); + + wm831x_free_irq(wm831x_rtc->wm831x, alm_irq, wm831x_rtc); + wm831x_free_irq(wm831x_rtc->wm831x, per_irq, wm831x_rtc); + rtc_device_unregister(wm831x_rtc->rtc); + kfree(wm831x_rtc); + + return 0; +} + +static struct dev_pm_ops wm831x_rtc_pm_ops = { + .suspend = wm831x_rtc_suspend, + .resume = wm831x_rtc_resume, + + .freeze = wm831x_rtc_freeze, + .thaw = wm831x_rtc_resume, + .restore = wm831x_rtc_resume, + + .poweroff = wm831x_rtc_suspend, +}; + +static struct platform_driver wm831x_rtc_driver = { + .probe = wm831x_rtc_probe, + .remove = __devexit_p(wm831x_rtc_remove), + .driver = { + .name = "wm831x-rtc", + .pm = &wm831x_rtc_pm_ops, + }, +}; + +static int __init wm831x_rtc_init(void) +{ + return platform_driver_register(&wm831x_rtc_driver); +} +module_init(wm831x_rtc_init); + +static void __exit wm831x_rtc_exit(void) +{ + platform_driver_unregister(&wm831x_rtc_driver); +} +module_exit(wm831x_rtc_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("RTC driver for the WM831x series PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-rtc"); -- cgit v0.10.2 From 956f25a6778a2510d52973ab8a3ac2e03e2c3704 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 13 Aug 2009 11:49:23 +0200 Subject: mfd: AB3100 accessor function cleanups This adds the _interruptible suffix to the AB3100 accessor functions on par with mutex_lock_interruptible() that's used for blocking simultaneous calls to the AB3100 acessor functions. Since these accesses are slow on a 100kHz I2C bus and may line up waiting for the mutex, we need to handle interruption by system shutdown or kill signals and may just as well denote that in the function names. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 8ff10cb..ffe4b64 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -77,7 +77,7 @@ u8 ab3100_get_chip_type(struct ab3100 *ab3100) } EXPORT_SYMBOL(ab3100_get_chip_type); -int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval) +int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) { u8 regandval[2] = {reg, regval}; int err; @@ -109,7 +109,8 @@ int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval) mutex_unlock(&ab3100->access_mutex); return 0; } -EXPORT_SYMBOL(ab3100_set_register); +EXPORT_SYMBOL(ab3100_set_register_interruptible); + /* * The test registers exist at an I2C bus address up one @@ -118,7 +119,7 @@ EXPORT_SYMBOL(ab3100_set_register); * anyway. It's currently only used from this file so declare * it static and do not export. */ -static int ab3100_set_test_register(struct ab3100 *ab3100, +static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) { u8 regandval[2] = {reg, regval}; @@ -148,7 +149,8 @@ static int ab3100_set_test_register(struct ab3100 *ab3100, return err; } -int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval) + +int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval) { int err; @@ -202,9 +204,10 @@ int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval) mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register); +EXPORT_SYMBOL(ab3100_get_register_interruptible); + -int ab3100_get_register_page(struct ab3100 *ab3100, +int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, u8 first_reg, u8 *regvals, u8 numregs) { int err; @@ -258,9 +261,10 @@ int ab3100_get_register_page(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_get_register_page); +EXPORT_SYMBOL(ab3100_get_register_page_interruptible); -int ab3100_mask_and_set_register(struct ab3100 *ab3100, + +int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 andmask, u8 ormask) { u8 regandval[2] = {reg, 0}; @@ -328,7 +332,8 @@ int ab3100_mask_and_set_register(struct ab3100 *ab3100, mutex_unlock(&ab3100->access_mutex); return err; } -EXPORT_SYMBOL(ab3100_mask_and_set_register); +EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible); + /* * Register a simple callback for handling any AB3100 events. @@ -371,7 +376,7 @@ static void ab3100_work(struct work_struct *work) u32 fatevent; int err; - err = ab3100_get_register_page(ab3100, AB3100_EVENTA1, + err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, event_regs, 3); if (err) goto err_event_wq; @@ -435,7 +440,7 @@ static int ab3100_registers_print(struct seq_file *s, void *p) seq_printf(s, "AB3100 registers:\n"); for (reg = 0; reg < 0xff; reg++) { - ab3100_get_register(ab3100, reg, &value); + ab3100_get_register_interruptible(ab3100, reg, &value); seq_printf(s, "[0x%x]: 0x%x\n", reg, value); } return 0; @@ -515,7 +520,7 @@ static ssize_t ab3100_get_set_reg(struct file *file, u8 reg = (u8) user_reg; u8 regvalue; - ab3100_get_register(ab3100, reg, ®value); + ab3100_get_register_interruptible(ab3100, reg, ®value); dev_info(ab3100->dev, "debug read AB3100 reg[0x%02x]: 0x%02x\n", @@ -547,8 +552,8 @@ static ssize_t ab3100_get_set_reg(struct file *file, return -EINVAL; value = (u8) user_value; - ab3100_set_register(ab3100, reg, value); - ab3100_get_register(ab3100, reg, ®value); + ab3100_set_register_interruptible(ab3100, reg, value); + ab3100_get_register_interruptible(ab3100, reg, ®value); dev_info(ab3100->dev, "debug write reg[0x%02x] with 0x%02x, " @@ -696,7 +701,7 @@ static int __init ab3100_setup(struct ab3100 *ab3100) int i; for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) { - err = ab3100_set_register(ab3100, + err = ab3100_set_register_interruptible(ab3100, ab3100_init_settings[i].abreg, ab3100_init_settings[i].setting); if (err) @@ -705,14 +710,14 @@ static int __init ab3100_setup(struct ab3100 *ab3100) /* * Special trick to make the AB3100 use the 32kHz clock (RTC) - * bit 3 in test registe 0x02 is a special, undocumented test + * bit 3 in test register 0x02 is a special, undocumented test * register bit that only exist in AB3100 P1E */ if (ab3100->chip_id == 0xc4) { dev_warn(ab3100->dev, "AB3100 P1E variant detected, " "forcing chip to 32KHz\n"); - err = ab3100_set_test_register(ab3100, 0x02, 0x08); + err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08); } exit_no_setup: @@ -852,8 +857,8 @@ static int __init ab3100_probe(struct i2c_client *client, i2c_set_clientdata(client, ab3100); /* Read chip ID register */ - err = ab3100_get_register(ab3100, AB3100_CID, - &ab3100->chip_id); + err = ab3100_get_register_interruptible(ab3100, AB3100_CID, + &ab3100->chip_id); if (err) { dev_err(&client->dev, "could not communicate with the AB3100 analog " diff --git a/include/linux/mfd/ab3100.h b/include/linux/mfd/ab3100.h index 7a3f316..56343b8 100644 --- a/include/linux/mfd/ab3100.h +++ b/include/linux/mfd/ab3100.h @@ -86,11 +86,11 @@ struct ab3100 { bool startup_events_read; }; -int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval); -int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval); -int ab3100_get_register_page(struct ab3100 *ab3100, +int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval); +int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval); +int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, u8 first_reg, u8 *regvals, u8 numregs); -int ab3100_mask_and_set_register(struct ab3100 *ab3100, +int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 andmask, u8 ormask); u8 ab3100_get_chip_type(struct ab3100 *ab3100); int ab3100_event_register(struct ab3100 *ab3100, -- cgit v0.10.2 From 7cdc2b98cec4c9b5bd563adf9eec90e7a7e12234 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 13 Aug 2009 11:49:38 +0200 Subject: mfd: AB3100 propagate error This makes ab3100_set_register_interruptible() propagate the error code from suboperations properly so it can be handles properly. (A special case comes from signal interruption.) Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index ffe4b64..377ec2b 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -107,7 +107,7 @@ int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval) err = 0; } mutex_unlock(&ab3100->access_mutex); - return 0; + return err; } EXPORT_SYMBOL(ab3100_set_register_interruptible); -- cgit v0.10.2 From ce290b0e865ae19f0ae49968def0a2edcb4e6a65 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 13 Aug 2009 11:49:49 +0200 Subject: mfd: AB3100 alter default setting This alters the default setting for AB3100_IMRB1 from 0xff to 0xbf. These registers are used for the yet unimplemented ADC and this new setting will deactivate ADC Trigger 1. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 377ec2b..bba534b 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -667,7 +667,7 @@ ab3100_init_settings[] = { .setting = 0x01 }, { .abreg = AB3100_IMRB1, - .setting = 0xFF + .setting = 0xBF }, { .abreg = AB3100_IMRB2, .setting = 0xFF -- cgit v0.10.2 From 0ad651c94c7a1f3706f63dc0174e681315e7dc81 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 13 Aug 2009 11:50:01 +0200 Subject: mfd: AB3100 disable irq nosync This will make the worker fire interrupt disable the AB3100 IRQ without sync which resolves a race since the interrupt obviously cannot wait for itself to complete while being handled. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index bba534b..1d8ac1a 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -422,7 +422,7 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data) * stuff and we will re-enable the interrupts once th * worker has finished. */ - disable_irq(ab3100->i2c_client->irq); + disable_irq_nosync(irq); schedule_work(&ab3100->work); return IRQ_HANDLED; } -- cgit v0.10.2 From 8238addcc52c94c59b10c3c1e9850d3a7921f825 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 19 Aug 2009 01:40:28 +0200 Subject: mfd: Add Freescale MC13783 driver This driver provides the core Freescale MC13783 support. It registers the client platform_devices and provides access to the A/D converter. Signed-off-by: Sascha Hauer Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0273456..a4f3dff 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -238,6 +238,16 @@ config MFD_PCF50633 facilities, and registers devices for the various functions so that function-specific drivers can bind to them. +config MFD_MC13783 + tristate "Support Freescale MC13783" + depends on SPI_MASTER + select MFD_CORE + help + Support for the Freescale (Atlas) MC13783 PMIC and audio CODEC. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config PCF50633_ADC tristate "Support for NXP PCF50633 ADC" depends on MFD_PCF50633 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 285cb32..7fec04f 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -26,6 +26,8 @@ obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o +obj-$(CONFIG_MFD_MC13783) += mc13783-core.o + obj-$(CONFIG_MFD_CORE) += mfd-core.o obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c new file mode 100644 index 0000000..e354d29 --- /dev/null +++ b/drivers/mfd/mc13783-core.c @@ -0,0 +1,427 @@ +/* + * Copyright 2009 Pengutronix, Sascha Hauer + * + * This code is in parts based on wm8350-core.c and pcf50633-core.c + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MC13783_MAX_REG_NUM 0x3f +#define MC13783_FRAME_MASK 0x00ffffff +#define MC13783_MAX_REG_NUM 0x3f +#define MC13783_REG_NUM_SHIFT 0x19 +#define MC13783_WRITE_BIT_SHIFT 31 + +static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if (spi_sync(spi, &m) != 0 || m.status != 0) + return -EINVAL; + return len - m.actual_length; +} + +static int mc13783_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MC13783_MAX_REG_NUM) + return -EINVAL; + + frame |= reg_num << MC13783_REG_NUM_SHIFT; + + ret = spi_rw(mc13783->spi_device, (u8 *)&frame, 4); + + *reg_val = frame & MC13783_FRAME_MASK; + + return ret; +} + +static int mc13783_write(struct mc13783 *mc13783, int reg_num, u32 reg_val) +{ + unsigned int frame = 0; + + if (reg_num > MC13783_MAX_REG_NUM) + return -EINVAL; + + frame |= (1 << MC13783_WRITE_BIT_SHIFT); + frame |= reg_num << MC13783_REG_NUM_SHIFT; + frame |= reg_val & MC13783_FRAME_MASK; + + return spi_rw(mc13783->spi_device, (u8 *)&frame, 4); +} + +int mc13783_reg_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val) +{ + int ret; + + mutex_lock(&mc13783->io_lock); + ret = mc13783_read(mc13783, reg_num, reg_val); + mutex_unlock(&mc13783->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mc13783_reg_read); + +int mc13783_reg_write(struct mc13783 *mc13783, int reg_num, u32 reg_val) +{ + int ret; + + mutex_lock(&mc13783->io_lock); + ret = mc13783_write(mc13783, reg_num, reg_val); + mutex_unlock(&mc13783->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mc13783_reg_write); + +/** + * mc13783_set_bits - Bitmask write + * + * @mc13783: Pointer to mc13783 control structure + * @reg: Register to access + * @mask: Mask of bits to change + * @val: Value to set for masked bits + */ +int mc13783_set_bits(struct mc13783 *mc13783, int reg, u32 mask, u32 val) +{ + u32 tmp; + int ret; + + mutex_lock(&mc13783->io_lock); + + ret = mc13783_read(mc13783, reg, &tmp); + tmp = (tmp & ~mask) | val; + if (ret == 0) + ret = mc13783_write(mc13783, reg, tmp); + + mutex_unlock(&mc13783->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mc13783_set_bits); + +int mc13783_register_irq(struct mc13783 *mc13783, int irq, + void (*handler) (int, void *), void *data) +{ + if (irq < 0 || irq > MC13783_NUM_IRQ || !handler) + return -EINVAL; + + if (WARN_ON(mc13783->irq_handler[irq].handler)) + return -EBUSY; + + mutex_lock(&mc13783->io_lock); + mc13783->irq_handler[irq].handler = handler; + mc13783->irq_handler[irq].data = data; + mutex_unlock(&mc13783->io_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(mc13783_register_irq); + +int mc13783_free_irq(struct mc13783 *mc13783, int irq) +{ + if (irq < 0 || irq > MC13783_NUM_IRQ) + return -EINVAL; + + mutex_lock(&mc13783->io_lock); + mc13783->irq_handler[irq].handler = NULL; + mutex_unlock(&mc13783->io_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(mc13783_free_irq); + +static void mc13783_irq_work(struct work_struct *work) +{ + struct mc13783 *mc13783 = container_of(work, struct mc13783, work); + int i; + unsigned int adc_sts; + + /* check if the adc has finished any completion */ + mc13783_reg_read(mc13783, MC13783_REG_INTERRUPT_STATUS_0, &adc_sts); + mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0, + adc_sts & MC13783_INT_STAT_ADCDONEI); + + if (adc_sts & MC13783_INT_STAT_ADCDONEI) + complete_all(&mc13783->adc_done); + + for (i = 0; i < MC13783_NUM_IRQ; i++) + if (mc13783->irq_handler[i].handler) + mc13783->irq_handler[i].handler(i, + mc13783->irq_handler[i].data); + enable_irq(mc13783->irq); +} + +static irqreturn_t mc13783_interrupt(int irq, void *dev_id) +{ + struct mc13783 *mc13783 = dev_id; + + disable_irq_nosync(irq); + + schedule_work(&mc13783->work); + return IRQ_HANDLED; +} + +/* set adc to ts interrupt mode, which generates touchscreen wakeup interrupt */ +static inline void mc13783_adc_set_ts_irq_mode(struct mc13783 *mc13783) +{ + unsigned int reg_adc0, reg_adc1; + + reg_adc0 = MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE + | MC13783_ADC0_TSMOD0; + reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN; + + mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0); + mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1); +} + +int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode, + unsigned int channel, unsigned int *sample) +{ + unsigned int reg_adc0, reg_adc1; + int i; + + mutex_lock(&mc13783->adc_conv_lock); + + /* set up auto incrementing anyway to make quick read */ + reg_adc0 = MC13783_ADC0_ADINC1 | MC13783_ADC0_ADINC2; + /* enable the adc, ignore external triggering and set ASC to trigger + * conversion */ + reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN + | MC13783_ADC1_ASC; + + /* setup channel number */ + if (channel > 7) + reg_adc1 |= MC13783_ADC1_ADSEL; + + switch (mode) { + case MC13783_ADC_MODE_TS: + /* enables touch screen reference mode and set touchscreen mode + * to position mode */ + reg_adc0 |= MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE + | MC13783_ADC0_TSMOD0 | MC13783_ADC0_TSMOD1; + reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT; + break; + case MC13783_ADC_MODE_SINGLE_CHAN: + reg_adc1 |= (channel & 0x7) << MC13783_ADC1_CHAN0_SHIFT; + reg_adc1 |= MC13783_ADC1_RAND; + break; + case MC13783_ADC_MODE_MULT_CHAN: + reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT; + break; + default: + return -EINVAL; + } + + mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0); + mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1); + + wait_for_completion_interruptible(&mc13783->adc_done); + + for (i = 0; i < 4; i++) + mc13783_reg_read(mc13783, MC13783_REG_ADC_2, &sample[i]); + + if (mc13783->ts_active) + mc13783_adc_set_ts_irq_mode(mc13783); + + mutex_unlock(&mc13783->adc_conv_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion); + +void mc13783_adc_set_ts_status(struct mc13783 *mc13783, unsigned int status) +{ + mc13783->ts_active = status; +} +EXPORT_SYMBOL_GPL(mc13783_adc_set_ts_status); + +static int mc13783_check_revision(struct mc13783 *mc13783) +{ + u32 rev_id, rev1, rev2, finid, icid; + + mc13783_read(mc13783, MC13783_REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) + rev1 = 3; + + if (rev1 == 0 || icid != 2) { + dev_err(mc13783->dev, "No MC13783 detected.\n"); + return -ENODEV; + } + + mc13783->revision = ((rev1 * 10) + rev2); + dev_info(mc13783->dev, "MC13783 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); + + return 0; +} + +/* + * Register a client device. This is non-fatal since there is no need to + * fail the entire device init due to a single platform device failing. + */ +static void mc13783_client_dev_register(struct mc13783 *mc13783, + const char *name) +{ + struct mfd_cell cell = {}; + + cell.name = name; + + mfd_add_devices(mc13783->dev, -1, &cell, 1, NULL, 0); +} + +static int __devinit mc13783_probe(struct spi_device *spi) +{ + struct mc13783 *mc13783; + struct mc13783_platform_data *pdata = spi->dev.platform_data; + int ret; + + mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL); + if (!mc13783) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, mc13783); + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + spi->bits_per_word = 32; + spi_setup(spi); + + mc13783->spi_device = spi; + mc13783->dev = &spi->dev; + mc13783->irq = spi->irq; + + INIT_WORK(&mc13783->work, mc13783_irq_work); + mutex_init(&mc13783->io_lock); + mutex_init(&mc13783->adc_conv_lock); + init_completion(&mc13783->adc_done); + + if (pdata) { + mc13783->flags = pdata->flags; + mc13783->regulators = pdata->regulators; + mc13783->num_regulators = pdata->num_regulators; + } + + if (mc13783_check_revision(mc13783)) { + ret = -ENODEV; + goto err_out; + } + + /* clear and mask all interrupts */ + mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0, 0x00ffffff); + mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_0, 0x00ffffff); + mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_1, 0x00ffffff); + mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_1, 0x00ffffff); + + /* unmask adcdone interrupts */ + mc13783_set_bits(mc13783, MC13783_REG_INTERRUPT_MASK_0, + MC13783_INT_MASK_ADCDONEM, 0); + + ret = request_irq(mc13783->irq, mc13783_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_HIGH, "mc13783", + mc13783); + if (ret) + goto err_out; + + if (mc13783->flags & MC13783_USE_CODEC) + mc13783_client_dev_register(mc13783, "mc13783-codec"); + if (mc13783->flags & MC13783_USE_ADC) + mc13783_client_dev_register(mc13783, "mc13783-adc"); + if (mc13783->flags & MC13783_USE_RTC) + mc13783_client_dev_register(mc13783, "mc13783-rtc"); + if (mc13783->flags & MC13783_USE_REGULATOR) + mc13783_client_dev_register(mc13783, "mc13783-regulator"); + if (mc13783->flags & MC13783_USE_TOUCHSCREEN) + mc13783_client_dev_register(mc13783, "mc13783-ts"); + + return 0; + +err_out: + kfree(mc13783); + return ret; +} + +static int __devexit mc13783_remove(struct spi_device *spi) +{ + struct mc13783 *mc13783; + + mc13783 = dev_get_drvdata(&spi->dev); + + free_irq(mc13783->irq, mc13783); + + mfd_remove_devices(&spi->dev); + + return 0; +} + +static struct spi_driver pmic_driver = { + .driver = { + .name = "mc13783", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mc13783_probe, + .remove = __devexit_p(mc13783_remove), +}; + +static int __init pmic_init(void) +{ + return spi_register_driver(&pmic_driver); +} +subsys_initcall(pmic_init); + +static void __exit pmic_exit(void) +{ + spi_unregister_driver(&pmic_driver); +} +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for Freescale MC13783 PMIC"); +MODULE_AUTHOR("Sascha Hauer "); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/mfd/mc13783-private.h b/include/linux/mfd/mc13783-private.h new file mode 100644 index 0000000..47e698c --- /dev/null +++ b/include/linux/mfd/mc13783-private.h @@ -0,0 +1,396 @@ +/* + * Copyright 2009 Pengutronix, Sascha Hauer + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_MFD_MC13783_PRIV_H +#define __LINUX_MFD_MC13783_PRIV_H + +#include +#include +#include +#include + +struct mc13783_irq { + void (*handler)(int, void *); + void *data; +}; + +#define MC13783_NUM_IRQ 2 +#define MC13783_IRQ_TS 0 +#define MC13783_IRQ_REGULATOR 1 + +#define MC13783_ADC_MODE_TS 1 +#define MC13783_ADC_MODE_SINGLE_CHAN 2 +#define MC13783_ADC_MODE_MULT_CHAN 3 + +struct mc13783 { + int revision; + struct device *dev; + struct spi_device *spi_device; + + int (*read_dev)(void *data, char reg, int count, u32 *dst); + int (*write_dev)(void *data, char reg, int count, const u32 *src); + + struct mutex io_lock; + void *io_data; + int irq; + unsigned int flags; + + struct mc13783_irq irq_handler[MC13783_NUM_IRQ]; + struct work_struct work; + struct completion adc_done; + unsigned int ts_active; + struct mutex adc_conv_lock; + + struct mc13783_regulator_init_data *regulators; + int num_regulators; +}; + +int mc13783_reg_read(struct mc13783 *, int reg_num, u32 *); +int mc13783_reg_write(struct mc13783 *, int, u32); +int mc13783_set_bits(struct mc13783 *, int, u32, u32); +int mc13783_free_irq(struct mc13783 *mc13783, int irq); +int mc13783_register_irq(struct mc13783 *mc13783, int irq, + void (*handler) (int, void *), void *data); + +#define MC13783_REG_INTERRUPT_STATUS_0 0 +#define MC13783_REG_INTERRUPT_MASK_0 1 +#define MC13783_REG_INTERRUPT_SENSE_0 2 +#define MC13783_REG_INTERRUPT_STATUS_1 3 +#define MC13783_REG_INTERRUPT_MASK_1 4 +#define MC13783_REG_INTERRUPT_SENSE_1 5 +#define MC13783_REG_POWER_UP_MODE_SENSE 6 +#define MC13783_REG_REVISION 7 +#define MC13783_REG_SEMAPHORE 8 +#define MC13783_REG_ARBITRATION_PERIPHERAL_AUDIO 9 +#define MC13783_REG_ARBITRATION_SWITCHERS 10 +#define MC13783_REG_ARBITRATION_REGULATORS_0 11 +#define MC13783_REG_ARBITRATION_REGULATORS_1 12 +#define MC13783_REG_POWER_CONTROL_0 13 +#define MC13783_REG_POWER_CONTROL_1 14 +#define MC13783_REG_POWER_CONTROL_2 15 +#define MC13783_REG_REGEN_ASSIGNMENT 16 +#define MC13783_REG_CONTROL_SPARE 17 +#define MC13783_REG_MEMORY_A 18 +#define MC13783_REG_MEMORY_B 19 +#define MC13783_REG_RTC_TIME 20 +#define MC13783_REG_RTC_ALARM 21 +#define MC13783_REG_RTC_DAY 22 +#define MC13783_REG_RTC_DAY_ALARM 23 +#define MC13783_REG_SWITCHERS_0 24 +#define MC13783_REG_SWITCHERS_1 25 +#define MC13783_REG_SWITCHERS_2 26 +#define MC13783_REG_SWITCHERS_3 27 +#define MC13783_REG_SWITCHERS_4 28 +#define MC13783_REG_SWITCHERS_5 29 +#define MC13783_REG_REGULATOR_SETTING_0 30 +#define MC13783_REG_REGULATOR_SETTING_1 31 +#define MC13783_REG_REGULATOR_MODE_0 32 +#define MC13783_REG_REGULATOR_MODE_1 33 +#define MC13783_REG_POWER_MISCELLANEOUS 34 +#define MC13783_REG_POWER_SPARE 35 +#define MC13783_REG_AUDIO_RX_0 36 +#define MC13783_REG_AUDIO_RX_1 37 +#define MC13783_REG_AUDIO_TX 38 +#define MC13783_REG_AUDIO_SSI_NETWORK 39 +#define MC13783_REG_AUDIO_CODEC 40 +#define MC13783_REG_AUDIO_STEREO_DAC 41 +#define MC13783_REG_AUDIO_SPARE 42 +#define MC13783_REG_ADC_0 43 +#define MC13783_REG_ADC_1 44 +#define MC13783_REG_ADC_2 45 +#define MC13783_REG_ADC_3 46 +#define MC13783_REG_ADC_4 47 +#define MC13783_REG_CHARGER 48 +#define MC13783_REG_USB 49 +#define MC13783_REG_CHARGE_USB_SPARE 50 +#define MC13783_REG_LED_CONTROL_0 51 +#define MC13783_REG_LED_CONTROL_1 52 +#define MC13783_REG_LED_CONTROL_2 53 +#define MC13783_REG_LED_CONTROL_3 54 +#define MC13783_REG_LED_CONTROL_4 55 +#define MC13783_REG_LED_CONTROL_5 56 +#define MC13783_REG_SPARE 57 +#define MC13783_REG_TRIM_0 58 +#define MC13783_REG_TRIM_1 59 +#define MC13783_REG_TEST_0 60 +#define MC13783_REG_TEST_1 61 +#define MC13783_REG_TEST_2 62 +#define MC13783_REG_TEST_3 63 +#define MC13783_REG_NB 64 + + +/* + * Interrupt Status + */ +#define MC13783_INT_STAT_ADCDONEI (1 << 0) +#define MC13783_INT_STAT_ADCBISDONEI (1 << 1) +#define MC13783_INT_STAT_TSI (1 << 2) +#define MC13783_INT_STAT_WHIGHI (1 << 3) +#define MC13783_INT_STAT_WLOWI (1 << 4) +#define MC13783_INT_STAT_CHGDETI (1 << 6) +#define MC13783_INT_STAT_CHGOVI (1 << 7) +#define MC13783_INT_STAT_CHGREVI (1 << 8) +#define MC13783_INT_STAT_CHGSHORTI (1 << 9) +#define MC13783_INT_STAT_CCCVI (1 << 10) +#define MC13783_INT_STAT_CHGCURRI (1 << 11) +#define MC13783_INT_STAT_BPONI (1 << 12) +#define MC13783_INT_STAT_LOBATLI (1 << 13) +#define MC13783_INT_STAT_LOBATHI (1 << 14) +#define MC13783_INT_STAT_UDPI (1 << 15) +#define MC13783_INT_STAT_USBI (1 << 16) +#define MC13783_INT_STAT_IDI (1 << 19) +#define MC13783_INT_STAT_Unused (1 << 20) +#define MC13783_INT_STAT_SE1I (1 << 21) +#define MC13783_INT_STAT_CKDETI (1 << 22) +#define MC13783_INT_STAT_UDMI (1 << 23) + +/* + * Interrupt Mask + */ +#define MC13783_INT_MASK_ADCDONEM (1 << 0) +#define MC13783_INT_MASK_ADCBISDONEM (1 << 1) +#define MC13783_INT_MASK_TSM (1 << 2) +#define MC13783_INT_MASK_WHIGHM (1 << 3) +#define MC13783_INT_MASK_WLOWM (1 << 4) +#define MC13783_INT_MASK_CHGDETM (1 << 6) +#define MC13783_INT_MASK_CHGOVM (1 << 7) +#define MC13783_INT_MASK_CHGREVM (1 << 8) +#define MC13783_INT_MASK_CHGSHORTM (1 << 9) +#define MC13783_INT_MASK_CCCVM (1 << 10) +#define MC13783_INT_MASK_CHGCURRM (1 << 11) +#define MC13783_INT_MASK_BPONM (1 << 12) +#define MC13783_INT_MASK_LOBATLM (1 << 13) +#define MC13783_INT_MASK_LOBATHM (1 << 14) +#define MC13783_INT_MASK_UDPM (1 << 15) +#define MC13783_INT_MASK_USBM (1 << 16) +#define MC13783_INT_MASK_IDM (1 << 19) +#define MC13783_INT_MASK_SE1M (1 << 21) +#define MC13783_INT_MASK_CKDETM (1 << 22) + +/* + * Reg Regulator Mode 0 + */ +#define MC13783_REGCTRL_VAUDIO_EN (1 << 0) +#define MC13783_REGCTRL_VAUDIO_STBY (1 << 1) +#define MC13783_REGCTRL_VAUDIO_MODE (1 << 2) +#define MC13783_REGCTRL_VIOHI_EN (1 << 3) +#define MC13783_REGCTRL_VIOHI_STBY (1 << 4) +#define MC13783_REGCTRL_VIOHI_MODE (1 << 5) +#define MC13783_REGCTRL_VIOLO_EN (1 << 6) +#define MC13783_REGCTRL_VIOLO_STBY (1 << 7) +#define MC13783_REGCTRL_VIOLO_MODE (1 << 8) +#define MC13783_REGCTRL_VDIG_EN (1 << 9) +#define MC13783_REGCTRL_VDIG_STBY (1 << 10) +#define MC13783_REGCTRL_VDIG_MODE (1 << 11) +#define MC13783_REGCTRL_VGEN_EN (1 << 12) +#define MC13783_REGCTRL_VGEN_STBY (1 << 13) +#define MC13783_REGCTRL_VGEN_MODE (1 << 14) +#define MC13783_REGCTRL_VRFDIG_EN (1 << 15) +#define MC13783_REGCTRL_VRFDIG_STBY (1 << 16) +#define MC13783_REGCTRL_VRFDIG_MODE (1 << 17) +#define MC13783_REGCTRL_VRFREF_EN (1 << 18) +#define MC13783_REGCTRL_VRFREF_STBY (1 << 19) +#define MC13783_REGCTRL_VRFREF_MODE (1 << 20) +#define MC13783_REGCTRL_VRFCP_EN (1 << 21) +#define MC13783_REGCTRL_VRFCP_STBY (1 << 22) +#define MC13783_REGCTRL_VRFCP_MODE (1 << 23) + +/* + * Reg Regulator Mode 1 + */ +#define MC13783_REGCTRL_VSIM_EN (1 << 0) +#define MC13783_REGCTRL_VSIM_STBY (1 << 1) +#define MC13783_REGCTRL_VSIM_MODE (1 << 2) +#define MC13783_REGCTRL_VESIM_EN (1 << 3) +#define MC13783_REGCTRL_VESIM_STBY (1 << 4) +#define MC13783_REGCTRL_VESIM_MODE (1 << 5) +#define MC13783_REGCTRL_VCAM_EN (1 << 6) +#define MC13783_REGCTRL_VCAM_STBY (1 << 7) +#define MC13783_REGCTRL_VCAM_MODE (1 << 8) +#define MC13783_REGCTRL_VRFBG_EN (1 << 9) +#define MC13783_REGCTRL_VRFBG_STBY (1 << 10) +#define MC13783_REGCTRL_VVIB_EN (1 << 11) +#define MC13783_REGCTRL_VRF1_EN (1 << 12) +#define MC13783_REGCTRL_VRF1_STBY (1 << 13) +#define MC13783_REGCTRL_VRF1_MODE (1 << 14) +#define MC13783_REGCTRL_VRF2_EN (1 << 15) +#define MC13783_REGCTRL_VRF2_STBY (1 << 16) +#define MC13783_REGCTRL_VRF2_MODE (1 << 17) +#define MC13783_REGCTRL_VMMC1_EN (1 << 18) +#define MC13783_REGCTRL_VMMC1_STBY (1 << 19) +#define MC13783_REGCTRL_VMMC1_MODE (1 << 20) +#define MC13783_REGCTRL_VMMC2_EN (1 << 21) +#define MC13783_REGCTRL_VMMC2_STBY (1 << 22) +#define MC13783_REGCTRL_VMMC2_MODE (1 << 23) + +/* + * Reg Regulator Misc. + */ +#define MC13783_REGCTRL_GPO1_EN (1 << 6) +#define MC13783_REGCTRL_GPO2_EN (1 << 8) +#define MC13783_REGCTRL_GPO3_EN (1 << 10) +#define MC13783_REGCTRL_GPO4_EN (1 << 12) +#define MC13783_REGCTRL_VIBPINCTRL (1 << 14) + +/* + * Reg Switcher 4 + */ +#define MC13783_SWCTRL_SW1A_MODE (1 << 0) +#define MC13783_SWCTRL_SW1A_STBY_MODE (1 << 2) +#define MC13783_SWCTRL_SW1A_DVS_SPEED (1 << 6) +#define MC13783_SWCTRL_SW1A_PANIC_MODE (1 << 8) +#define MC13783_SWCTRL_SW1A_SOFTSTART (1 << 9) +#define MC13783_SWCTRL_SW1B_MODE (1 << 10) +#define MC13783_SWCTRL_SW1B_STBY_MODE (1 << 12) +#define MC13783_SWCTRL_SW1B_DVS_SPEED (1 << 14) +#define MC13783_SWCTRL_SW1B_PANIC_MODE (1 << 16) +#define MC13783_SWCTRL_SW1B_SOFTSTART (1 << 17) +#define MC13783_SWCTRL_PLL_EN (1 << 18) +#define MC13783_SWCTRL_PLL_FACTOR (1 << 19) + +/* + * Reg Switcher 5 + */ +#define MC13783_SWCTRL_SW2A_MODE (1 << 0) +#define MC13783_SWCTRL_SW2A_STBY_MODE (1 << 2) +#define MC13783_SWCTRL_SW2A_DVS_SPEED (1 << 6) +#define MC13783_SWCTRL_SW2A_PANIC_MODE (1 << 8) +#define MC13783_SWCTRL_SW2A_SOFTSTART (1 << 9) +#define MC13783_SWCTRL_SW2B_MODE (1 << 10) +#define MC13783_SWCTRL_SW2B_STBY_MODE (1 << 12) +#define MC13783_SWCTRL_SW2B_DVS_SPEED (1 << 14) +#define MC13783_SWCTRL_SW2B_PANIC_MODE (1 << 16) +#define MC13783_SWCTRL_SW2B_SOFTSTART (1 << 17) +#define MC13783_SWSET_SW3 (1 << 18) +#define MC13783_SWCTRL_SW3_EN (1 << 20) +#define MC13783_SWCTRL_SW3_STBY (1 << 21) +#define MC13783_SWCTRL_SW3_MODE (1 << 22) + +/* + * ADC/Touch + */ +#define MC13783_ADC0_LICELLCON (1 << 0) +#define MC13783_ADC0_CHRGICON (1 << 1) +#define MC13783_ADC0_BATICON (1 << 2) +#define MC13783_ADC0_RTHEN (1 << 3) +#define MC13783_ADC0_DTHEN (1 << 4) +#define MC13783_ADC0_UIDEN (1 << 5) +#define MC13783_ADC0_ADOUTEN (1 << 6) +#define MC13783_ADC0_ADOUTPER (1 << 7) +#define MC13783_ADC0_ADREFEN (1 << 10) +#define MC13783_ADC0_ADREFMODE (1 << 11) +#define MC13783_ADC0_TSMOD0 (1 << 12) +#define MC13783_ADC0_TSMOD1 (1 << 13) +#define MC13783_ADC0_TSMOD2 (1 << 14) +#define MC13783_ADC0_CHRGRAWDIV (1 << 15) +#define MC13783_ADC0_ADINC1 (1 << 16) +#define MC13783_ADC0_ADINC2 (1 << 17) +#define MC13783_ADC0_WCOMP (1 << 18) +#define MC13783_ADC0_ADCBIS0 (1 << 23) + +#define MC13783_ADC1_ADEN (1 << 0) +#define MC13783_ADC1_RAND (1 << 1) +#define MC13783_ADC1_ADSEL (1 << 3) +#define MC13783_ADC1_TRIGMASK (1 << 4) +#define MC13783_ADC1_ADA10 (1 << 5) +#define MC13783_ADC1_ADA11 (1 << 6) +#define MC13783_ADC1_ADA12 (1 << 7) +#define MC13783_ADC1_ADA20 (1 << 8) +#define MC13783_ADC1_ADA21 (1 << 9) +#define MC13783_ADC1_ADA22 (1 << 10) +#define MC13783_ADC1_ATO0 (1 << 11) +#define MC13783_ADC1_ATO1 (1 << 12) +#define MC13783_ADC1_ATO2 (1 << 13) +#define MC13783_ADC1_ATO3 (1 << 14) +#define MC13783_ADC1_ATO4 (1 << 15) +#define MC13783_ADC1_ATO5 (1 << 16) +#define MC13783_ADC1_ATO6 (1 << 17) +#define MC13783_ADC1_ATO7 (1 << 18) +#define MC13783_ADC1_ATOX (1 << 19) +#define MC13783_ADC1_ASC (1 << 20) +#define MC13783_ADC1_ADTRIGIGN (1 << 21) +#define MC13783_ADC1_ADONESHOT (1 << 22) +#define MC13783_ADC1_ADCBIS1 (1 << 23) + +#define MC13783_ADC1_CHAN0_SHIFT 5 +#define MC13783_ADC1_CHAN1_SHIFT 8 + +#define MC13783_ADC2_ADD10 (1 << 2) +#define MC13783_ADC2_ADD11 (1 << 3) +#define MC13783_ADC2_ADD12 (1 << 4) +#define MC13783_ADC2_ADD13 (1 << 5) +#define MC13783_ADC2_ADD14 (1 << 6) +#define MC13783_ADC2_ADD15 (1 << 7) +#define MC13783_ADC2_ADD16 (1 << 8) +#define MC13783_ADC2_ADD17 (1 << 9) +#define MC13783_ADC2_ADD18 (1 << 10) +#define MC13783_ADC2_ADD19 (1 << 11) +#define MC13783_ADC2_ADD20 (1 << 14) +#define MC13783_ADC2_ADD21 (1 << 15) +#define MC13783_ADC2_ADD22 (1 << 16) +#define MC13783_ADC2_ADD23 (1 << 17) +#define MC13783_ADC2_ADD24 (1 << 18) +#define MC13783_ADC2_ADD25 (1 << 19) +#define MC13783_ADC2_ADD26 (1 << 20) +#define MC13783_ADC2_ADD27 (1 << 21) +#define MC13783_ADC2_ADD28 (1 << 22) +#define MC13783_ADC2_ADD29 (1 << 23) + +#define MC13783_ADC3_WHIGH0 (1 << 0) +#define MC13783_ADC3_WHIGH1 (1 << 1) +#define MC13783_ADC3_WHIGH2 (1 << 2) +#define MC13783_ADC3_WHIGH3 (1 << 3) +#define MC13783_ADC3_WHIGH4 (1 << 4) +#define MC13783_ADC3_WHIGH5 (1 << 5) +#define MC13783_ADC3_ICID0 (1 << 6) +#define MC13783_ADC3_ICID1 (1 << 7) +#define MC13783_ADC3_ICID2 (1 << 8) +#define MC13783_ADC3_WLOW0 (1 << 9) +#define MC13783_ADC3_WLOW1 (1 << 10) +#define MC13783_ADC3_WLOW2 (1 << 11) +#define MC13783_ADC3_WLOW3 (1 << 12) +#define MC13783_ADC3_WLOW4 (1 << 13) +#define MC13783_ADC3_WLOW5 (1 << 14) +#define MC13783_ADC3_ADCBIS2 (1 << 23) + +#define MC13783_ADC4_ADDBIS10 (1 << 2) +#define MC13783_ADC4_ADDBIS11 (1 << 3) +#define MC13783_ADC4_ADDBIS12 (1 << 4) +#define MC13783_ADC4_ADDBIS13 (1 << 5) +#define MC13783_ADC4_ADDBIS14 (1 << 6) +#define MC13783_ADC4_ADDBIS15 (1 << 7) +#define MC13783_ADC4_ADDBIS16 (1 << 8) +#define MC13783_ADC4_ADDBIS17 (1 << 9) +#define MC13783_ADC4_ADDBIS18 (1 << 10) +#define MC13783_ADC4_ADDBIS19 (1 << 11) +#define MC13783_ADC4_ADDBIS20 (1 << 14) +#define MC13783_ADC4_ADDBIS21 (1 << 15) +#define MC13783_ADC4_ADDBIS22 (1 << 16) +#define MC13783_ADC4_ADDBIS23 (1 << 17) +#define MC13783_ADC4_ADDBIS24 (1 << 18) +#define MC13783_ADC4_ADDBIS25 (1 << 19) +#define MC13783_ADC4_ADDBIS26 (1 << 20) +#define MC13783_ADC4_ADDBIS27 (1 << 21) +#define MC13783_ADC4_ADDBIS28 (1 << 22) +#define MC13783_ADC4_ADDBIS29 (1 << 23) + +#endif /* __LINUX_MFD_MC13783_PRIV_H */ + diff --git a/include/linux/mfd/mc13783.h b/include/linux/mfd/mc13783.h new file mode 100644 index 0000000..b3a2a72 --- /dev/null +++ b/include/linux/mfd/mc13783.h @@ -0,0 +1,84 @@ +/* + * Copyright 2009 Pengutronix, Sascha Hauer + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __INCLUDE_LINUX_MFD_MC13783_H +#define __INCLUDE_LINUX_MFD_MC13783_H + +struct mc13783; +struct regulator_init_data; + +struct mc13783_regulator_init_data { + int id; + struct regulator_init_data *init_data; +}; + +struct mc13783_platform_data { + struct mc13783_regulator_init_data *regulators; + int num_regulators; + unsigned int flags; +}; + +/* mc13783_platform_data flags */ +#define MC13783_USE_TOUCHSCREEN (1 << 0) +#define MC13783_USE_CODEC (1 << 1) +#define MC13783_USE_ADC (1 << 2) +#define MC13783_USE_RTC (1 << 3) +#define MC13783_USE_REGULATOR (1 << 4) + +int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode, + unsigned int channel, unsigned int *sample); + +void mc13783_adc_set_ts_status(struct mc13783 *mc13783, unsigned int status); + +#define MC13783_SW_SW1A 0 +#define MC13783_SW_SW1B 1 +#define MC13783_SW_SW2A 2 +#define MC13783_SW_SW2B 3 +#define MC13783_SW_SW3 4 +#define MC13783_SW_PLL 5 +#define MC13783_REGU_VAUDIO 6 +#define MC13783_REGU_VIOHI 7 +#define MC13783_REGU_VIOLO 8 +#define MC13783_REGU_VDIG 9 +#define MC13783_REGU_VGEN 10 +#define MC13783_REGU_VRFDIG 11 +#define MC13783_REGU_VRFREF 12 +#define MC13783_REGU_VRFCP 13 +#define MC13783_REGU_VSIM 14 +#define MC13783_REGU_VESIM 15 +#define MC13783_REGU_VCAM 16 +#define MC13783_REGU_VRFBG 17 +#define MC13783_REGU_VVIB 18 +#define MC13783_REGU_VRF1 19 +#define MC13783_REGU_VRF2 20 +#define MC13783_REGU_VMMC1 21 +#define MC13783_REGU_VMMC2 22 +#define MC13783_REGU_GPO1 23 +#define MC13783_REGU_GPO2 24 +#define MC13783_REGU_GPO3 25 +#define MC13783_REGU_GPO4 26 +#define MC13783_REGU_V1 27 +#define MC13783_REGU_V2 28 +#define MC13783_REGU_V3 29 +#define MC13783_REGU_V4 30 + +#endif /* __INCLUDE_LINUX_MFD_MC13783_H */ + -- cgit v0.10.2 From 295c08bc69a5dd8cef69ceaeaaf551a17f50c34b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 19 Aug 2009 01:43:50 +0200 Subject: regulator: Add Freescale MC13783 driver This driver provides basic support for the voltage regulators integrated into the Freescale MC13783 PMIC. It is currently only possible to enable/disable outputs, not to actually set the voltage. Signed-off-by: Sascha Hauer Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 38ea5dc..c505714 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -131,4 +131,12 @@ config REGULATOR_PCAP This driver provides support for the voltage regulators of the PCAP2 PMIC. +config REGULATOR_MC13783 + tristate "Support regulators on Freescale MC13783 PMIC" + depends on MFD_MC13783 + help + Say y here to support the regulators found on the Freescale MC13783 + PMIC. + endif + diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3a4cdf5..5034830 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -20,5 +20,6 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o +obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/mc13783.c b/drivers/regulator/mc13783.c new file mode 100644 index 0000000..710211f --- /dev/null +++ b/drivers/regulator/mc13783.c @@ -0,0 +1,410 @@ +/* + * Regulator Driver for Freescale MC13783 PMIC + * + * Copyright (C) 2008 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mc13783_regulator { + struct regulator_desc desc; + int reg; + int enable_bit; +}; + +static struct regulator_ops mc13783_regulator_ops; + +static struct mc13783_regulator mc13783_regulators[] = { + [MC13783_SW_SW3] = { + .desc = { + .name = "SW_SW3", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_SW_SW3, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_SWITCHERS_5, + .enable_bit = MC13783_SWCTRL_SW3_EN, + }, + [MC13783_SW_PLL] = { + .desc = { + .name = "SW_PLL", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_SW_PLL, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_SWITCHERS_4, + .enable_bit = MC13783_SWCTRL_PLL_EN, + }, + [MC13783_REGU_VAUDIO] = { + .desc = { + .name = "REGU_VAUDIO", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VAUDIO, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VAUDIO_EN, + }, + [MC13783_REGU_VIOHI] = { + .desc = { + .name = "REGU_VIOHI", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VIOHI, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VIOHI_EN, + }, + [MC13783_REGU_VIOLO] = { + .desc = { + .name = "REGU_VIOLO", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VIOLO, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VIOLO_EN, + }, + [MC13783_REGU_VDIG] = { + .desc = { + .name = "REGU_VDIG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VDIG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VDIG_EN, + }, + [MC13783_REGU_VGEN] = { + .desc = { + .name = "REGU_VGEN", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VGEN, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VGEN_EN, + }, + [MC13783_REGU_VRFDIG] = { + .desc = { + .name = "REGU_VRFDIG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFDIG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFDIG_EN, + }, + [MC13783_REGU_VRFREF] = { + .desc = { + .name = "REGU_VRFREF", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFREF, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFREF_EN, + }, + [MC13783_REGU_VRFCP] = { + .desc = { + .name = "REGU_VRFCP", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFCP, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_0, + .enable_bit = MC13783_REGCTRL_VRFCP_EN, + }, + [MC13783_REGU_VSIM] = { + .desc = { + .name = "REGU_VSIM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VSIM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VSIM_EN, + }, + [MC13783_REGU_VESIM] = { + .desc = { + .name = "REGU_VESIM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VESIM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VESIM_EN, + }, + [MC13783_REGU_VCAM] = { + .desc = { + .name = "REGU_VCAM", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VCAM, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VCAM_EN, + }, + [MC13783_REGU_VRFBG] = { + .desc = { + .name = "REGU_VRFBG", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRFBG, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRFBG_EN, + }, + [MC13783_REGU_VVIB] = { + .desc = { + .name = "REGU_VVIB", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VVIB, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VVIB_EN, + }, + [MC13783_REGU_VRF1] = { + .desc = { + .name = "REGU_VRF1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRF1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRF1_EN, + }, + [MC13783_REGU_VRF2] = { + .desc = { + .name = "REGU_VRF2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VRF2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VRF2_EN, + }, + [MC13783_REGU_VMMC1] = { + .desc = { + .name = "REGU_VMMC1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VMMC1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VMMC1_EN, + }, + [MC13783_REGU_VMMC2] = { + .desc = { + .name = "REGU_VMMC2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_VMMC2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_REGULATOR_MODE_1, + .enable_bit = MC13783_REGCTRL_VMMC2_EN, + }, + [MC13783_REGU_GPO1] = { + .desc = { + .name = "REGU_GPO1", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO1, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO1_EN, + }, + [MC13783_REGU_GPO2] = { + .desc = { + .name = "REGU_GPO2", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO2, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO2_EN, + }, + [MC13783_REGU_GPO3] = { + .desc = { + .name = "REGU_GPO3", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO3, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO3_EN, + }, + [MC13783_REGU_GPO4] = { + .desc = { + .name = "REGU_GPO4", + .ops = &mc13783_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = MC13783_REGU_GPO4, + .owner = THIS_MODULE, + }, + .reg = MC13783_REG_POWER_MISCELLANEOUS, + .enable_bit = MC13783_REGCTRL_GPO4_EN, + }, +}; + +struct mc13783_priv { + struct regulator_desc desc[ARRAY_SIZE(mc13783_regulators)]; + struct mc13783 *mc13783; + struct regulator_dev *regulators[0]; +}; + +static int mc13783_enable(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg, + mc13783_regulators[id].enable_bit, + mc13783_regulators[id].enable_bit); +} + +static int mc13783_disable(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg, + mc13783_regulators[id].enable_bit, 0); +} + +static int mc13783_is_enabled(struct regulator_dev *rdev) +{ + struct mc13783_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + unsigned int val; + + ret = mc13783_reg_read(priv->mc13783, mc13783_regulators[id].reg, &val); + if (ret) + return ret; + + return (val & mc13783_regulators[id].enable_bit) != 0; +} + +static struct regulator_ops mc13783_regulator_ops = { + .enable = mc13783_enable, + .disable = mc13783_disable, + .is_enabled = mc13783_is_enabled, +}; + +static int __devinit mc13783_regulator_probe(struct platform_device *pdev) +{ + struct mc13783_priv *priv; + struct mc13783 *mc13783 = dev_get_drvdata(pdev->dev.parent); + struct mc13783_regulator_init_data *init_data; + int i, ret; + + dev_dbg(&pdev->dev, "mc13783_regulator_probe id %d\n", pdev->id); + + priv = kzalloc(sizeof(*priv) + mc13783->num_regulators * sizeof(void *), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mc13783 = mc13783; + + for (i = 0; i < mc13783->num_regulators; i++) { + init_data = &mc13783->regulators[i]; + priv->regulators[i] = regulator_register( + &mc13783_regulators[init_data->id].desc, + &pdev->dev, init_data->init_data, priv); + + if (IS_ERR(priv->regulators[i])) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + mc13783_regulators[i].desc.name); + ret = PTR_ERR(priv->regulators[i]); + goto err; + } + } + + platform_set_drvdata(pdev, priv); + + return 0; +err: + while (--i >= 0) + regulator_unregister(priv->regulators[i]); + + kfree(priv); + + return ret; +} + +static int __devexit mc13783_regulator_remove(struct platform_device *pdev) +{ + struct mc13783_priv *priv = platform_get_drvdata(pdev); + struct mc13783 *mc13783 = priv->mc13783; + int i; + + for (i = 0; i < mc13783->num_regulators; i++) + regulator_unregister(priv->regulators[i]); + + return 0; +} + +static struct platform_driver mc13783_regulator_driver = { + .driver = { + .name = "mc13783-regulator", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(mc13783_regulator_remove), +}; + +static int __init mc13783_regulator_init(void) +{ + return platform_driver_probe(&mc13783_regulator_driver, + mc13783_regulator_probe); +} +subsys_initcall(mc13783_regulator_init); + +static void __exit mc13783_regulator_exit(void) +{ + platform_driver_unregister(&mc13783_regulator_driver); +} +module_exit(mc13783_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sascha Hauer Date: Tue, 18 Aug 2009 22:52:26 +0200 Subject: mfd: AB3100 OTP readout This adds the ability to read out OTP (One-Time Programmable) registers in the AB3100 MFD ASIC. It's a simple sysfs file you can cat to prompt. The OTP registers of the AB3100 are used to store various device-unique information such as customer ID, product flags and the 3GPP standard IMEI (International Mobile Equipment Indentity) number. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a4f3dff..4fd4f91 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -276,6 +276,15 @@ config AB3100_CORE LEDs, vibrator, system power and temperature, power management and ALSA sound. +config AB3100_OTP + tristate "ST-Ericsson AB3100 OTP functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + Select this to enable the AB3100 Mixed Signal IC OTP (one-time + programmable memory) support. This exposes a sysfs file to read + out OTP values. + config EZX_PCAP bool "PCAP Support" depends on GENERIC_HARDIRQS && SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 7fec04f..6a5d8cb 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -48,3 +48,4 @@ obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o obj-$(CONFIG_AB3100_CORE) += ab3100-core.o +obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c new file mode 100644 index 0000000..0499b20 --- /dev/null +++ b/drivers/mfd/ab3100-otp.c @@ -0,0 +1,268 @@ +/* + * drivers/mfd/ab3100_otp.c + * + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Driver to read out OTP from the AB3100 Mixed-signal circuit + * Author: Linus Walleij + */ + +#include +#include +#include +#include +#include +#include + +/* The OTP registers */ +#define AB3100_OTP0 0xb0 +#define AB3100_OTP1 0xb1 +#define AB3100_OTP2 0xb2 +#define AB3100_OTP3 0xb3 +#define AB3100_OTP4 0xb4 +#define AB3100_OTP5 0xb5 +#define AB3100_OTP6 0xb6 +#define AB3100_OTP7 0xb7 +#define AB3100_OTPP 0xbf + +/** + * struct ab3100_otp + * @dev containing device + * @ab3100 a pointer to the parent ab3100 device struct + * @locked whether the OTP is locked, after locking, no more bits + * can be changed but before locking it is still possible + * to change bits from 1->0. + * @freq clocking frequency for the OTP, this frequency is either + * 32768Hz or 1MHz/30 + * @paf product activation flag, indicates whether this is a real + * product (paf true) or a lab board etc (paf false) + * @imeich if this is set it is possible to override the + * IMEI number found in the tac, fac and svn fields with + * (secured) software + * @cid customer ID + * @tac type allocation code of the IMEI + * @fac final assembly code of the IMEI + * @svn software version number of the IMEI + * @debugfs a debugfs file used when dumping to file + */ +struct ab3100_otp { + struct device *dev; + struct ab3100 *ab3100; + bool locked; + u32 freq; + bool paf; + bool imeich; + u16 cid:14; + u32 tac:20; + u8 fac; + u32 svn:20; + struct dentry *debugfs; +}; + +static int __init ab3100_otp_read(struct ab3100_otp *otp) +{ + struct ab3100 *ab = otp->ab3100; + u8 otpval[8]; + u8 otpp; + int err; + + err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp); + if (err) { + dev_err(otp->dev, "unable to read OTPP register\n"); + return err; + } + + err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0, + otpval, 8); + if (err) { + dev_err(otp->dev, "unable to read OTP register page\n"); + return err; + } + + /* Cache OTP properties, they never change by nature */ + otp->locked = (otpp & 0x80); + otp->freq = (otpp & 0x40) ? 32768 : 34100; + otp->paf = (otpval[1] & 0x80); + otp->imeich = (otpval[1] & 0x40); + otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; + otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; + otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); + otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); + return 0; +} + +/* + * This is a simple debugfs human-readable file that dumps out + * the contents of the OTP. + */ +#ifdef CONFIG_DEBUGFS +static int show_otp(struct seq_file *s, void *v) +{ + struct ab3100_otp *otp = s->private; + int err; + + seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); + seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); + seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); + seq_printf(s, "IMEI is %s\n", otp->imeich ? + "CHANGEABLE" : "NOT CHANGEABLE"); + seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); + seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); + return 0; +} + +static int ab3100_otp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_otp_show, inode->i_private); +} + +static const struct file_operations ab3100_otp_operations = { + .open = ab3100_otp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, + NULL, otp, + &ab3100_otp_operations); + if (!otp->debugfs) { + dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); + return err; + } +} + +static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ + debugfs_remove_file(otp->debugfs); +} +#else +/* Compile this out if debugfs not selected */ +static inline int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + return 0; +} + +static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ +} +#endif + +#define SHOW_AB3100_ATTR(name) \ +static ssize_t ab3100_otp_##name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{\ + struct ab3100_otp *otp = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", otp->name); \ +} + +SHOW_AB3100_ATTR(locked) +SHOW_AB3100_ATTR(freq) +SHOW_AB3100_ATTR(paf) +SHOW_AB3100_ATTR(imeich) +SHOW_AB3100_ATTR(cid) +SHOW_AB3100_ATTR(fac) +SHOW_AB3100_ATTR(tac) +SHOW_AB3100_ATTR(svn) + +static struct device_attribute ab3100_otp_attrs[] = { + __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), + __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), + __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), + __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), + __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), + __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), + __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), + __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), +}; + +static int __init ab3100_otp_probe(struct platform_device *pdev) +{ + struct ab3100_otp *otp; + int err = 0; + int i; + + otp = kzalloc(sizeof(struct ab3100_otp), GFP_KERNEL); + if (!otp) { + dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n"); + return -ENOMEM; + } + otp->dev = &pdev->dev; + + /* Replace platform data coming in with a local struct */ + otp->ab3100 = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, otp); + + err = ab3100_otp_read(otp); + if (err) + return err; + + dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); + + /* sysfs entries */ + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { + err = device_create_file(&pdev->dev, + &ab3100_otp_attrs[i]); + if (err) + goto out_no_sysfs; + } + + /* debugfs entries */ + err = ab3100_otp_init_debugfs(&pdev->dev, otp); + if (err) + goto out_no_debugfs; + + return 0; + +out_no_sysfs: + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); +out_no_debugfs: + kfree(otp); + return err; +} + +static int __exit ab3100_otp_remove(struct platform_device *pdev) +{ + struct ab3100_otp *otp = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); + ab3100_otp_exit_debugfs(otp); + kfree(otp); + return 0; +} + +static struct platform_driver ab3100_otp_driver = { + .driver = { + .name = "ab3100-otp", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab3100_otp_remove), +}; + +static int __init ab3100_otp_init(void) +{ + return platform_driver_probe(&ab3100_otp_driver, + ab3100_otp_probe); +} + +static void __exit ab3100_otp_exit(void) +{ + platform_driver_unregister(&ab3100_otp_driver); +} + +module_init(ab3100_otp_init); +module_exit(ab3100_otp_exit); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From ebf0bd366ed8161e6fbc919705d878ccbfd51624 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 31 Aug 2009 18:32:18 +0200 Subject: mfd: Add support for TWL4030/5030 dynamic power switching The TWL4030/5030 family of multifunction devices allows board-specific control of the the various regulators, clock and reset lines through 'scripts' that are loaded into its memory. This allows for Dynamic Power Switching (DPS). Implement board-independent core support for DPS that is then used by board-specific code to load custom DPS scripts. Signed-off-by: Amit Kucheria Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 4fd4f91..570be13 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -108,6 +108,19 @@ config TWL4030_CORE high speed USB OTG transceiver, an audio codec (on most versions) and many other features. +config TWL4030_POWER + bool "Support power resources on TWL4030 family chips" + depends on TWL4030_CORE && ARM + help + Say yes here if you want to use the power resources on the + TWL4030 family chips. Most of these resources are regulators, + which have a separate driver; some are control signals, such + as clock request handshaking. + + This driver uses board-specific data to initialize the resources + and load scripts controling which resources are switched off/on + or reset when a sleep, wakeup or warm reset event occurs. + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 6a5d8cb..f3b277b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o +obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o obj-$(CONFIG_MFD_MC13783) += mc13783-core.o diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index 1fd2620..e424cf6 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -89,6 +89,12 @@ #define twl_has_madc() false #endif +#ifdef CONFIG_TWL4030_POWER +#define twl_has_power() true +#else +#define twl_has_power() false +#endif + #if defined(CONFIG_RTC_DRV_TWL4030) || defined(CONFIG_RTC_DRV_TWL4030_MODULE) #define twl_has_rtc() true #else @@ -801,6 +807,10 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id) /* setup clock framework */ clocks_init(&client->dev); + /* load power event scripts */ + if (twl_has_power() && pdata->power) + twl4030_power_init(pdata->power); + /* Maybe init the T2 Interrupt subsystem */ if (client->irq && pdata->irq_base diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c new file mode 100644 index 0000000..e7688b0 --- /dev/null +++ b/drivers/mfd/twl4030-power.c @@ -0,0 +1,466 @@ +/* + * linux/drivers/i2c/chips/twl4030-power.c + * + * Handle TWL4030 Power initialization + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver + * Several fixes by Amit Kucheria + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include + +static u8 twl4030_start_script_address = 0x2b; + +#define PWR_P1_SW_EVENTS 0x10 +#define PWR_DEVOFF (1<<0) + +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) + +/* resource - hfclk */ +#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) + +/* PM events */ +#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) +#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) +#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) +#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) +#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) +#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) + +#define LVL_WAKEUP 0x08 + +#define ENABLE_WARMRESET (1<<4) + +#define END_OF_SCRIPT 0x3f + +#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) +#define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56) +#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) +#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) +#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) +#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) + +#define R_PROTECT_KEY 0x0E +#define KEY_1 0xC0 +#define KEY_2 0x0C + +/* resource configuration registers */ + +#define DEVGROUP_OFFSET 0 +#define TYPE_OFFSET 1 + +/* Bit positions */ +#define DEVGROUP_SHIFT 5 +#define DEVGROUP_MASK (7 << DEVGROUP_SHIFT) +#define TYPE_SHIFT 0 +#define TYPE_MASK (7 << TYPE_SHIFT) +#define TYPE2_SHIFT 3 +#define TYPE2_MASK (3 << TYPE2_SHIFT) + +static u8 res_config_addrs[] = { + [RES_VAUX1] = 0x17, + [RES_VAUX2] = 0x1b, + [RES_VAUX3] = 0x1f, + [RES_VAUX4] = 0x23, + [RES_VMMC1] = 0x27, + [RES_VMMC2] = 0x2b, + [RES_VPLL1] = 0x2f, + [RES_VPLL2] = 0x33, + [RES_VSIM] = 0x37, + [RES_VDAC] = 0x3b, + [RES_VINTANA1] = 0x3f, + [RES_VINTANA2] = 0x43, + [RES_VINTDIG] = 0x47, + [RES_VIO] = 0x4b, + [RES_VDD1] = 0x55, + [RES_VDD2] = 0x63, + [RES_VUSB_1V5] = 0x71, + [RES_VUSB_1V8] = 0x74, + [RES_VUSB_3V1] = 0x77, + [RES_VUSBCP] = 0x7a, + [RES_REGEN] = 0x7f, + [RES_NRES_PWRON] = 0x82, + [RES_CLKEN] = 0x85, + [RES_SYSEN] = 0x88, + [RES_HFCLKOUT] = 0x8b, + [RES_32KCLKOUT] = 0x8e, + [RES_RESET] = 0x91, + [RES_Main_Ref] = 0x94, +}; + +static int __init twl4030_write_script_byte(u8 address, u8 byte) +{ + int err; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_MEMORY_ADDRESS); + if (err) + goto out; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, byte, + R_MEMORY_DATA); +out: + return err; +} + +static int __init twl4030_write_script_ins(u8 address, u16 pmb_message, + u8 delay, u8 next) +{ + int err; + + address *= 4; + err = twl4030_write_script_byte(address++, pmb_message >> 8); + if (err) + goto out; + err = twl4030_write_script_byte(address++, pmb_message & 0xff); + if (err) + goto out; + err = twl4030_write_script_byte(address++, delay); + if (err) + goto out; + err = twl4030_write_script_byte(address++, next); +out: + return err; +} + +static int __init twl4030_write_script(u8 address, struct twl4030_ins *script, + int len) +{ + int err; + + for (; len; len--, address++, script++) { + if (len == 1) { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + END_OF_SCRIPT); + if (err) + break; + } else { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + address + 1); + if (err) + break; + } + } + return err; +} + +static int __init twl4030_config_wakeup3_sequence(u8 address) +{ + int err; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P3 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A3); + if (err) + goto out; + + /* P3 LVL_WAKEUP should be on LEVEL */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P3_SW_EVENTS); + if (err) + goto out; + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 wakeup sequence for P3 config error\n"); + return err; +} + +static int __init twl4030_config_wakeup12_sequence(u8 address) +{ + int err = 0; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_S2A12); + if (err) + goto out; + + /* P1/P2 LVL_WAKEUP should be on LEVEL */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P1_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_P2_SW_EVENTS); + if (err) + goto out; + + data |= LVL_WAKEUP; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data, + R_P2_SW_EVENTS); + if (err) + goto out; + + if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + /* Disabling AC charger effect on sleep-active transitions */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data, + R_CFG_P1_TRANSITION); + if (err) + goto out; + data &= ~(1<<1); + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data , + R_CFG_P1_TRANSITION); + if (err) + goto out; + } + +out: + if (err) + pr_err("TWL4030 wakeup sequence for P1 and P2" \ + "config error\n"); + return err; +} + +static int __init twl4030_config_sleep_sequence(u8 address) +{ + int err; + + /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_A2S); + + if (err) + pr_err("TWL4030 sleep sequence config error\n"); + + return err; +} + +static int __init twl4030_config_warmreset_sequence(u8 address) +{ + int err; + u8 rd_data; + + /* Set WARM RESET SEQ address for P1 */ + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address, + R_SEQ_ADD_WARM); + if (err) + goto out; + + /* P1/P2/P3 enable WARMRESET */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P2_SW_EVENTS); + if (err) + goto out; + + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data, + R_P3_SW_EVENTS); + if (err) + goto out; + + rd_data |= ENABLE_WARMRESET; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data, + R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 warmreset seq config error\n"); + return err; +} + +static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig) +{ + int rconfig_addr; + int err; + u8 type; + u8 grp; + + if (rconfig->resource > TOTAL_RESOURCES) { + pr_err("TWL4030 Resource %d does not exist\n", + rconfig->resource); + return -EINVAL; + } + + rconfig_addr = res_config_addrs[rconfig->resource]; + + /* Set resource group */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &grp, + rconfig_addr + DEVGROUP_OFFSET); + if (err) { + pr_err("TWL4030 Resource %d group could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->devgroup >= 0) { + grp &= ~DEVGROUP_MASK; + grp |= rconfig->devgroup << DEVGROUP_SHIFT; + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + grp, rconfig_addr + DEVGROUP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program devgroup\n"); + return err; + } + } + + /* Set resource types */ + err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &type, + rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d type could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->type >= 0) { + type &= ~TYPE_MASK; + type |= rconfig->type << TYPE_SHIFT; + } + + if (rconfig->type2 >= 0) { + type &= ~TYPE2_MASK; + type |= rconfig->type2 << TYPE2_SHIFT; + } + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, + type, rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program resource type\n"); + return err; + } + + return 0; +} + +static int __init load_twl4030_script(struct twl4030_script *tscript, + u8 address) +{ + int err; + + /* Make sure the script isn't going beyond last valid address (0x3f) */ + if ((address + tscript->size) > END_OF_SCRIPT) { + pr_err("TWL4030 scripts too big error\n"); + return -EINVAL; + } + + err = twl4030_write_script(address, tscript->script, tscript->size); + if (err) + goto out; + + if (tscript->flags & TWL4030_WRST_SCRIPT) { + err = twl4030_config_warmreset_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) { + err = twl4030_config_wakeup12_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl4030_config_wakeup3_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_SLEEP_SCRIPT) + err = twl4030_config_sleep_sequence(address); +out: + return err; +} + +void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts) +{ + int err = 0; + int i; + struct twl4030_resconfig *resconfig; + u8 address = twl4030_start_script_address; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_1, + R_PROTECT_KEY); + if (err) + goto unlock; + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_2, + R_PROTECT_KEY); + if (err) + goto unlock; + + for (i = 0; i < twl4030_scripts->num; i++) { + err = load_twl4030_script(twl4030_scripts->scripts[i], address); + if (err) + goto load; + address += twl4030_scripts->scripts[i]->size; + } + + resconfig = twl4030_scripts->resource_config; + if (resconfig) { + while (resconfig->resource) { + err = twl4030_configure_resource(resconfig); + if (err) + goto resource; + resconfig++; + + } + } + + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY); + if (err) + pr_err("TWL4030 Unable to relock registers\n"); + return; + +unlock: + if (err) + pr_err("TWL4030 Unable to unlock registers\n"); + return; +load: + if (err) + pr_err("TWL4030 failed to load scripts\n"); + return; +resource: + if (err) + pr_err("TWL4030 failed to configure resource\n"); + return; +} diff --git a/include/linux/i2c/twl4030.h b/include/linux/i2c/twl4030.h index 3fd21d7..2d02dfd 100644 --- a/include/linux/i2c/twl4030.h +++ b/include/linux/i2c/twl4030.h @@ -223,19 +223,28 @@ int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes); /* Power bus message definitions */ -#define DEV_GRP_NULL 0x0 -#define DEV_GRP_P1 0x1 -#define DEV_GRP_P2 0x2 -#define DEV_GRP_P3 0x4 +/* The TWL4030/5030 splits its power-management resources (the various + * regulators, clock and reset lines) into 3 processor groups - P1, P2 and + * P3. These groups can then be configured to transition between sleep, wait-on + * and active states by sending messages to the power bus. See Section 5.4.2 + * Power Resources of TWL4030 TRM + */ -#define RES_GRP_RES 0x0 -#define RES_GRP_PP 0x1 -#define RES_GRP_RC 0x2 +/* Processor groups */ +#define DEV_GRP_NULL 0x0 +#define DEV_GRP_P1 0x1 /* P1: all OMAP devices */ +#define DEV_GRP_P2 0x2 /* P2: all Modem devices */ +#define DEV_GRP_P3 0x4 /* P3: all peripheral devices */ + +/* Resource groups */ +#define RES_GRP_RES 0x0 /* Reserved */ +#define RES_GRP_PP 0x1 /* Power providers */ +#define RES_GRP_RC 0x2 /* Reset and control */ #define RES_GRP_PP_RC 0x3 -#define RES_GRP_PR 0x4 +#define RES_GRP_PR 0x4 /* Power references */ #define RES_GRP_PP_PR 0x5 #define RES_GRP_RC_PR 0x6 -#define RES_GRP_ALL 0x7 +#define RES_GRP_ALL 0x7 /* All resource groups */ #define RES_TYPE2_R0 0x0 @@ -246,6 +255,41 @@ int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes); #define RES_STATE_SLEEP 0x8 #define RES_STATE_OFF 0x0 +/* Power resources */ + +/* Power providers */ +#define RES_VAUX1 1 +#define RES_VAUX2 2 +#define RES_VAUX3 3 +#define RES_VAUX4 4 +#define RES_VMMC1 5 +#define RES_VMMC2 6 +#define RES_VPLL1 7 +#define RES_VPLL2 8 +#define RES_VSIM 9 +#define RES_VDAC 10 +#define RES_VINTANA1 11 +#define RES_VINTANA2 12 +#define RES_VINTDIG 13 +#define RES_VIO 14 +#define RES_VDD1 15 +#define RES_VDD2 16 +#define RES_VUSB_1V5 17 +#define RES_VUSB_1V8 18 +#define RES_VUSB_3V1 19 +#define RES_VUSBCP 20 +#define RES_REGEN 21 +/* Reset and control */ +#define RES_NRES_PWRON 22 +#define RES_CLKEN 23 +#define RES_SYSEN 24 +#define RES_HFCLKOUT 25 +#define RES_32KCLKOUT 26 +#define RES_RESET 27 +/* Power Reference */ +#define RES_Main_Ref 28 + +#define TOTAL_RESOURCES 28 /* * Power Bus Message Format ... these can be sent individually by Linux, * but are usually part of downloaded scripts that are run when various @@ -327,6 +371,36 @@ struct twl4030_usb_data { enum twl4030_usb_mode usb_mode; }; +struct twl4030_ins { + u16 pmb_message; + u8 delay; +}; + +struct twl4030_script { + struct twl4030_ins *script; + unsigned size; + u8 flags; +#define TWL4030_WRST_SCRIPT (1<<0) +#define TWL4030_WAKEUP12_SCRIPT (1<<1) +#define TWL4030_WAKEUP3_SCRIPT (1<<2) +#define TWL4030_SLEEP_SCRIPT (1<<3) +}; + +struct twl4030_resconfig { + u8 resource; + u8 devgroup; /* Processor group that Power resource belongs to */ + u8 type; /* Power resource addressed, 6 / broadcast message */ + u8 type2; /* Power resource addressed, 3 / broadcast message */ +}; + +struct twl4030_power_data { + struct twl4030_script **scripts; + unsigned num; + struct twl4030_resconfig *resource_config; +}; + +extern void twl4030_power_init(struct twl4030_power_data *triton2_scripts); + struct twl4030_platform_data { unsigned irq_base, irq_end; struct twl4030_bci_platform_data *bci; @@ -334,6 +408,7 @@ struct twl4030_platform_data { struct twl4030_madc_platform_data *madc; struct twl4030_keypad_data *keypad; struct twl4030_usb_data *usb; + struct twl4030_power_data *power; /* LDO regulators */ struct regulator_init_data *vdac; @@ -364,7 +439,6 @@ int twl4030_sih_setup(int module); #define TWL4030_VAUX3_DEV_GRP 0x1F #define TWL4030_VAUX3_DEDICATED 0x22 - #if defined(CONFIG_TWL4030_BCI_BATTERY) || \ defined(CONFIG_TWL4030_BCI_BATTERY_MODULE) extern int twl4030charger_usb_en(int enable); -- cgit v0.10.2 From 75a7456539224c5c5c254130afdb18bd7eb2286f Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 17 Aug 2009 17:01:56 +0300 Subject: mfd: Print warning for twl4030 out-of-order script loading When the sleep script is loaded before the wakeup script, there is a chance that the system might go to sleep before the wakeup script loading is completed. This will lead to a system that does not wakeup and has been observed to cause non-booting boards. Various options were considered to solve this problem, including modification of the core twl4030 power code to be smart enough to reorder the loading of the scripts. But it felt too over-engineered. Hence this patch just warns the DPS script developer so that they may be reordered in the board-code itself. Signed-off-by: Amit Kucheria Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index e7688b0..5284c7c 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -379,6 +379,7 @@ static int __init load_twl4030_script(struct twl4030_script *tscript, u8 address) { int err; + static int order; /* Make sure the script isn't going beyond last valid address (0x3f) */ if ((address + tscript->size) > END_OF_SCRIPT) { @@ -399,6 +400,7 @@ static int __init load_twl4030_script(struct twl4030_script *tscript, err = twl4030_config_wakeup12_sequence(address); if (err) goto out; + order = 1; } if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { err = twl4030_config_wakeup3_sequence(address); @@ -406,6 +408,10 @@ static int __init load_twl4030_script(struct twl4030_script *tscript, goto out; } if (tscript->flags & TWL4030_SLEEP_SCRIPT) + if (order) + pr_warning("TWL4030: Bad order of scripts (sleep "\ + "script before wakeup) Leads to boot"\ + "failure on some boards\n"); err = twl4030_config_sleep_sequence(address); out: return err; -- cgit v0.10.2 From 9312fffbad5661a0ad52b5ca5a04875c763a35fa Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Thu, 27 Aug 2009 20:27:57 +0200 Subject: mfd: OMAP: Board-specifc twl4030 DPS scripts for RX51 board Add support for Dynamic Power Switching (DPS) for the RX51 board. These scripts are still a work-in-progress. I'll keep sending patches to update the scripts as they are optimised. Signed-off-by: Amit Kucheria Acked-by: Tony Lindgren Signed-off-by: Samuel Ortiz diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index 56d931a..e70baa7 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -1,5 +1,5 @@ /* - * linux/arch/arm/mach-omap2/board-rx51-flash.c + * linux/arch/arm/mach-omap2/board-rx51-peripherals.c * * Copyright (C) 2008-2009 Nokia * @@ -282,7 +282,124 @@ static struct twl4030_usb_data rx51_usb_data = { .usb_mode = T2_USB_MODE_ULPI, }; -static struct twl4030_platform_data rx51_twldata = { +static struct twl4030_ins sleep_on_seq[] __initdata = { +/* + * Turn off VDD1 and VDD2. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_OFF), 4}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_OFF), 2}, +/* + * And also turn off the OMAP3 PLLs and the sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_OFF), 3}, + {MSG_SINGULAR(DEV_GRP_P1, 0x17, RES_STATE_OFF), 3}, +}; + +static struct twl4030_script sleep_on_script __initdata = { + .script = sleep_on_seq, + .size = ARRAY_SIZE(sleep_on_seq), + .flags = TWL4030_SLEEP_SCRIPT, +}; + +static struct twl4030_ins wakeup_seq[] __initdata = { +/* + * Reenable the OMAP3 PLLs. + * Wakeup VDD1 and VDD2. + * Reenable sysclk output. + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x7, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0xf, RES_STATE_ACTIVE), 0x30}, + {MSG_SINGULAR(DEV_GRP_P1, 0x10, RES_STATE_ACTIVE), 0x37}, + {MSG_SINGULAR(DEV_GRP_P1, 0x19, RES_STATE_ACTIVE), 3}, +}; + +static struct twl4030_script wakeup_script __initdata = { + .script = wakeup_seq, + .size = ARRAY_SIZE(wakeup_seq), + .flags = TWL4030_WAKEUP12_SCRIPT, +}; + +static struct twl4030_ins wakeup_p3_seq[] __initdata = { +/* + * Wakeup VDD1 (dummy to be able to insert a delay) + * Enable CLKEN + */ + {MSG_SINGULAR(DEV_GRP_P1, 0x17, RES_STATE_ACTIVE), 3}, +}; + +static struct twl4030_script wakeup_p3_script __initdata = { + .script = wakeup_p3_seq, + .size = ARRAY_SIZE(wakeup_p3_seq), + .flags = TWL4030_WAKEUP3_SCRIPT, +}; + +static struct twl4030_ins wrst_seq[] __initdata = { +/* + * Reset twl4030. + * Reset VDD1 regulator. + * Reset VDD2 regulator. + * Reset VPLL1 regulator. + * Enable sysclk output. + * Reenable twl4030. + */ + {MSG_SINGULAR(DEV_GRP_NULL, RES_RESET, RES_STATE_OFF), 2}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_ALL, 0, 1, RES_STATE_ACTIVE), + 0x13}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_PP, 0, 2, RES_STATE_WRST), 0x13}, + {MSG_BROADCAST(DEV_GRP_NULL, RES_GRP_PP, 0, 3, RES_STATE_OFF), 0x13}, + {MSG_SINGULAR(DEV_GRP_NULL, RES_VDD1, RES_STATE_WRST), 0x13}, + {MSG_SINGULAR(DEV_GRP_NULL, RES_VDD2, RES_STATE_WRST), 0x13}, + {MSG_SINGULAR(DEV_GRP_NULL, RES_VPLL1, RES_STATE_WRST), 0x35}, + {MSG_SINGULAR(DEV_GRP_P1, RES_HFCLKOUT, RES_STATE_ACTIVE), 2}, + {MSG_SINGULAR(DEV_GRP_NULL, RES_RESET, RES_STATE_ACTIVE), 2}, +}; + +static struct twl4030_script wrst_script __initdata = { + .script = wrst_seq, + .size = ARRAY_SIZE(wrst_seq), + .flags = TWL4030_WRST_SCRIPT, +}; + +static struct twl4030_script *twl4030_scripts[] __initdata = { + /* wakeup12 script should be loaded before sleep script, otherwise a + board might hit retention before loading of wakeup script is + completed. This can cause boot failures depending on timing issues. + */ + &wakeup_script, + &sleep_on_script, + &wakeup_p3_script, + &wrst_script, +}; + +static struct twl4030_resconfig twl4030_rconfig[] __initdata = { + { .resource = RES_VINTANA1, .devgroup = -1, .type = -1, .type2 = 1 }, + { .resource = RES_VINTANA2, .devgroup = -1, .type = -1, .type2 = 1 }, + { .resource = RES_VINTDIG, .devgroup = -1, .type = -1, .type2 = 1 }, + { .resource = RES_VMMC1, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VMMC2, .devgroup = DEV_GRP_NULL, .type = -1, + .type2 = 3}, + { .resource = RES_VAUX1, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VAUX2, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VAUX3, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VAUX4, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VPLL2, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VDAC, .devgroup = -1, .type = -1, .type2 = 3}, + { .resource = RES_VSIM, .devgroup = DEV_GRP_NULL, .type = -1, + .type2 = 3}, + { .resource = RES_CLKEN, .devgroup = DEV_GRP_P3, .type = -1, + .type2 = 1 }, + { 0, 0}, +}; + +static struct twl4030_power_data rx51_t2scripts_data __initdata = { + .scripts = twl4030_scripts, + .num = ARRAY_SIZE(twl4030_scripts), + .resource_config = twl4030_rconfig, +}; + + + +static struct twl4030_platform_data rx51_twldata __initdata = { .irq_base = TWL4030_IRQ_BASE, .irq_end = TWL4030_IRQ_END, @@ -291,6 +408,7 @@ static struct twl4030_platform_data rx51_twldata = { .keypad = &rx51_kp_data, .madc = &rx51_madc_data, .usb = &rx51_usb_data, + .power = &rx51_t2scripts_data, .vaux1 = &rx51_vaux1, .vaux2 = &rx51_vaux2, -- cgit v0.10.2 From 8aba721b23917bc6d374ad42bf80bde5058710e2 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 27 Aug 2009 20:49:08 +0200 Subject: mfd: Fix ab3100-otp build failure ab3100.h should include linux/workqueue.h for otp to build properly. Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index 1d8ac1a..a848df7 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/include/linux/mfd/ab3100.h b/include/linux/mfd/ab3100.h index 56343b8..3ead5cf 100644 --- a/include/linux/mfd/ab3100.h +++ b/include/linux/mfd/ab3100.h @@ -6,6 +6,7 @@ */ #include +#include #ifndef MFD_AB3100_H #define MFD_AB3100_H -- cgit v0.10.2 From bd207cfb0011389d55827b3f3181c60e8c3c7148 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sun, 30 Aug 2009 23:49:04 +0200 Subject: rtc: AB3100 RTC support This adds support for the RTC found inside the AB3100 Mixed Signal chip. The symbols used for communicating with the chip is found in the mfd/ab3100-core.c driver that also provides the platform device. Signed-off-by: Linus Walleij Acked-by: Alessandro Zummo Signed-off-by: Samuel Ortiz diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b136299..73771b0 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -545,6 +545,15 @@ config RTC_DRV_PCF50633 If you say yes here you get support for the RTC subsystem of the NXP PCF50633 used in embedded systems. +config RTC_DRV_AB3100 + tristate "ST-Ericsson AB3100 RTC" + depends on AB3100_CORE + default y if AB3100_CORE + help + Select this to enable the ST-Ericsson AB3100 Mixed Signal IC RTC + support. This chip contains a battery- and capacitor-backed RTC. + + comment "on-CPU RTC drivers" config RTC_DRV_OMAP diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index f3e5735..5e152ff 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -17,6 +17,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o # Keep the list ordered. +obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o diff --git a/drivers/rtc/rtc-ab3100.c b/drivers/rtc/rtc-ab3100.c new file mode 100644 index 0000000..4704aac --- /dev/null +++ b/drivers/rtc/rtc-ab3100.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * RTC clock driver for the AB3100 Analog Baseband Chip + * Author: Linus Walleij + */ +#include +#include +#include +#include +#include +#include + +/* Clock rate in Hz */ +#define AB3100_RTC_CLOCK_RATE 32768 + +/* + * The AB3100 RTC registers. These are the same for + * AB3000 and AB3100. + * Control register: + * Bit 0: RTC Monitor cleared=0, active=1, if you set it + * to 1 it remains active until RTC power is lost. + * Bit 1: 32 kHz Oscillator, 0 = on, 1 = bypass + * Bit 2: Alarm on, 0 = off, 1 = on + * Bit 3: 32 kHz buffer disabling, 0 = enabled, 1 = disabled + */ +#define AB3100_RTC 0x53 +/* default setting, buffer disabled, alarm on */ +#define RTC_SETTING 0x30 +/* Alarm when AL0-AL3 == TI0-TI3 */ +#define AB3100_AL0 0x56 +#define AB3100_AL1 0x57 +#define AB3100_AL2 0x58 +#define AB3100_AL3 0x59 +/* This 48-bit register that counts up at 32768 Hz */ +#define AB3100_TI0 0x5a +#define AB3100_TI1 0x5b +#define AB3100_TI2 0x5c +#define AB3100_TI3 0x5d +#define AB3100_TI4 0x5e +#define AB3100_TI5 0x5f + +/* + * RTC clock functions and device struct declaration + */ +static int ab3100_rtc_set_mmss(struct device *dev, unsigned long secs) +{ + struct ab3100 *ab3100_data = dev_get_drvdata(dev); + u8 regs[] = {AB3100_TI0, AB3100_TI1, AB3100_TI2, + AB3100_TI3, AB3100_TI4, AB3100_TI5}; + unsigned char buf[6]; + u64 fat_time = (u64) secs * AB3100_RTC_CLOCK_RATE * 2; + int err = 0; + int i; + + buf[0] = (fat_time) & 0xFF; + buf[1] = (fat_time >> 8) & 0xFF; + buf[2] = (fat_time >> 16) & 0xFF; + buf[3] = (fat_time >> 24) & 0xFF; + buf[4] = (fat_time >> 32) & 0xFF; + buf[5] = (fat_time >> 40) & 0xFF; + + for (i = 0; i < 6; i++) { + err = ab3100_set_register_interruptible(ab3100_data, + regs[i], buf[i]); + if (err) + return err; + } + + /* Set the flag to mark that the clock is now set */ + return ab3100_mask_and_set_register_interruptible(ab3100_data, + AB3100_RTC, + 0xFE, 0x01); + +} + +static int ab3100_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct ab3100 *ab3100_data = dev_get_drvdata(dev); + unsigned long time; + u8 rtcval; + int err; + + err = ab3100_get_register_interruptible(ab3100_data, + AB3100_RTC, &rtcval); + if (err) + return err; + + if (!(rtcval & 0x01)) { + dev_info(dev, "clock not set (lost power)"); + return -EINVAL; + } else { + u64 fat_time; + u8 buf[6]; + + /* Read out time registers */ + err = ab3100_get_register_page_interruptible(ab3100_data, + AB3100_TI0, + buf, 6); + if (err != 0) + return err; + + fat_time = ((u64) buf[5] << 40) | ((u64) buf[4] << 32) | + ((u64) buf[3] << 24) | ((u64) buf[2] << 16) | + ((u64) buf[1] << 8) | (u64) buf[0]; + time = (unsigned long) (fat_time / + (u64) (AB3100_RTC_CLOCK_RATE * 2)); + } + + rtc_time_to_tm(time, tm); + + return rtc_valid_tm(tm); +} + +static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct ab3100 *ab3100_data = dev_get_drvdata(dev); + unsigned long time; + u64 fat_time; + u8 buf[6]; + u8 rtcval; + int err; + + /* Figure out if alarm is enabled or not */ + err = ab3100_get_register_interruptible(ab3100_data, + AB3100_RTC, &rtcval); + if (err) + return err; + if (rtcval & 0x04) + alarm->enabled = 1; + else + alarm->enabled = 0; + /* No idea how this could be represented */ + alarm->pending = 0; + /* Read out alarm registers, only 4 bytes */ + err = ab3100_get_register_page_interruptible(ab3100_data, + AB3100_AL0, buf, 4); + if (err) + return err; + fat_time = ((u64) buf[3] << 40) | ((u64) buf[2] << 32) | + ((u64) buf[1] << 24) | ((u64) buf[0] << 16); + time = (unsigned long) (fat_time / (u64) (AB3100_RTC_CLOCK_RATE * 2)); + + rtc_time_to_tm(time, &alarm->time); + + return rtc_valid_tm(&alarm->time); +} + +static int ab3100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct ab3100 *ab3100_data = dev_get_drvdata(dev); + u8 regs[] = {AB3100_AL0, AB3100_AL1, AB3100_AL2, AB3100_AL3}; + unsigned char buf[4]; + unsigned long secs; + u64 fat_time; + int err; + int i; + + rtc_tm_to_time(&alarm->time, &secs); + fat_time = (u64) secs * AB3100_RTC_CLOCK_RATE * 2; + buf[0] = (fat_time >> 16) & 0xFF; + buf[1] = (fat_time >> 24) & 0xFF; + buf[2] = (fat_time >> 32) & 0xFF; + buf[3] = (fat_time >> 40) & 0xFF; + + /* Set the alarm */ + for (i = 0; i < 4; i++) { + err = ab3100_set_register_interruptible(ab3100_data, + regs[i], buf[i]); + if (err) + return err; + } + /* Then enable the alarm */ + return ab3100_mask_and_set_register_interruptible(ab3100_data, + AB3100_RTC, ~(1 << 2), + alarm->enabled << 2); +} + +static int ab3100_rtc_irq_enable(struct device *dev, unsigned int enabled) +{ + struct ab3100 *ab3100_data = dev_get_drvdata(dev); + + /* + * It's not possible to enable/disable the alarm IRQ for this RTC. + * It does not actually trigger any IRQ: instead its only function is + * to power up the system, if it wasn't on. This will manifest as + * a "power up cause" in the AB3100 power driver (battery charging etc) + * and need to be handled there instead. + */ + if (enabled) + return ab3100_mask_and_set_register_interruptible(ab3100_data, + AB3100_RTC, ~(1 << 2), + 1 << 2); + else + return ab3100_mask_and_set_register_interruptible(ab3100_data, + AB3100_RTC, ~(1 << 2), + 0); +} + +static const struct rtc_class_ops ab3100_rtc_ops = { + .read_time = ab3100_rtc_read_time, + .set_mmss = ab3100_rtc_set_mmss, + .read_alarm = ab3100_rtc_read_alarm, + .set_alarm = ab3100_rtc_set_alarm, + .alarm_irq_enable = ab3100_rtc_irq_enable, +}; + +static int __init ab3100_rtc_probe(struct platform_device *pdev) +{ + int err; + u8 regval; + struct rtc_device *rtc; + struct ab3100 *ab3100_data = platform_get_drvdata(pdev); + + /* The first RTC register needs special treatment */ + err = ab3100_get_register_interruptible(ab3100_data, + AB3100_RTC, ®val); + if (err) { + dev_err(&pdev->dev, "unable to read RTC register\n"); + return -ENODEV; + } + + if ((regval & 0xFE) != RTC_SETTING) { + dev_warn(&pdev->dev, "not default value in RTC reg 0x%x\n", + regval); + } + + if ((regval & 1) == 0) { + /* + * Set bit to detect power loss. + * This bit remains until RTC power is lost. + */ + regval = 1 | RTC_SETTING; + err = ab3100_set_register_interruptible(ab3100_data, + AB3100_RTC, regval); + /* Ignore any error on this write */ + } + + rtc = rtc_device_register("ab3100-rtc", &pdev->dev, &ab3100_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + err = PTR_ERR(rtc); + return err; + } + + return 0; +} + +static int __exit ab3100_rtc_remove(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + + rtc_device_unregister(rtc); + return 0; +} + +static struct platform_driver ab3100_rtc_driver = { + .driver = { + .name = "ab3100-rtc", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab3100_rtc_remove), +}; + +static int __init ab3100_rtc_init(void) +{ + return platform_driver_probe(&ab3100_rtc_driver, + ab3100_rtc_probe); +} + +static void __exit ab3100_rtc_exit(void) +{ + platform_driver_unregister(&ab3100_rtc_driver); +} + +module_init(ab3100_rtc_init); +module_exit(ab3100_rtc_exit); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("AB3100 RTC Driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From d619bc143e311a738113dbbe7792bd032403939f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 9 Sep 2009 11:31:00 +0200 Subject: regulator: AB3100 support This adds support for the regulators found in the AB3100 Mixed-Signal IC. It further also defines platform data for the ST-Ericsson U300 platform and extends the AB3100 MFD driver so that platform/board data with regulation constraints and an init function can be passed down all the way from the board to the regulators. Signed-off-by: Linus Walleij Acked-by: Mark Brown Signed-off-by: Liam Girdwood Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index a848df7..c533f86 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -837,6 +837,8 @@ static int __init ab3100_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ab3100 *ab3100; + struct ab3100_platform_data *ab3100_plf_data = + client->dev.platform_data; int err; int i; @@ -920,6 +922,8 @@ static int __init ab3100_probe(struct i2c_client *client, for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) { ab3100_platform_devs[i]->dev.parent = &client->dev; + ab3100_platform_devs[i]->dev.platform_data = + ab3100_plf_data; platform_set_drvdata(ab3100_platform_devs[i], ab3100); } diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c505714..2dc42bb 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -138,5 +138,14 @@ config REGULATOR_MC13783 Say y here to support the regulators found on the Freescale MC13783 PMIC. +config REGULATOR_AB3100 + tristate "ST-Ericsson AB3100 Regulator functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + These regulators correspond to functionality in the + AB3100 analog baseband dealing with power regulators + for the system. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 5034830..768b331 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -21,5 +21,6 @@ obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o +obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c new file mode 100644 index 0000000..156979d --- /dev/null +++ b/drivers/regulator/ab3100.c @@ -0,0 +1,694 @@ +/* + * drivers/regulator/ab3100.c + * + * Copyright (C) 2008-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Low-level control of the AB3100 IC Low Dropout (LDO) + * regulators, external regulator and buck converter + * Author: Mattias Wallin + * Author: Linus Walleij + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* LDO registers and some handy masking definitions for AB3100 */ +#define AB3100_LDO_A 0x40 +#define AB3100_LDO_C 0x41 +#define AB3100_LDO_D 0x42 +#define AB3100_LDO_E 0x43 +#define AB3100_LDO_E_SLEEP 0x44 +#define AB3100_LDO_F 0x45 +#define AB3100_LDO_G 0x46 +#define AB3100_LDO_H 0x47 +#define AB3100_LDO_H_SLEEP_MODE 0 +#define AB3100_LDO_H_SLEEP_EN 2 +#define AB3100_LDO_ON 4 +#define AB3100_LDO_H_VSEL_AC 5 +#define AB3100_LDO_K 0x48 +#define AB3100_LDO_EXT 0x49 +#define AB3100_BUCK 0x4A +#define AB3100_BUCK_SLEEP 0x4B +#define AB3100_REG_ON_MASK 0x10 + +/** + * struct ab3100_regulator + * A struct passed around the individual regulator functions + * @platform_device: platform device holding this regulator + * @ab3100: handle to the AB3100 parent chip + * @plfdata: AB3100 platform data passed in at probe time + * @regreg: regulator register number in the AB3100 + * @fixed_voltage: a fixed voltage for this regulator, if this + * 0 the voltages array is used instead. + * @typ_voltages: an array of available typical voltages for + * this regulator + * @voltages_len: length of the array of available voltages + */ +struct ab3100_regulator { + struct regulator_dev *rdev; + struct ab3100 *ab3100; + struct ab3100_platform_data *plfdata; + u8 regreg; + int fixed_voltage; + int const *typ_voltages; + u8 voltages_len; +}; + +/* The order in which registers are initialized */ +static const u8 ab3100_reg_init_order[AB3100_NUM_REGULATORS+2] = { + AB3100_LDO_A, + AB3100_LDO_C, + AB3100_LDO_E, + AB3100_LDO_E_SLEEP, + AB3100_LDO_F, + AB3100_LDO_G, + AB3100_LDO_H, + AB3100_LDO_K, + AB3100_LDO_EXT, + AB3100_BUCK, + AB3100_BUCK_SLEEP, + AB3100_LDO_D, +}; + +/* Preset (hardware defined) voltages for these regulators */ +#define LDO_A_VOLTAGE 2750000 +#define LDO_C_VOLTAGE 2650000 +#define LDO_D_VOLTAGE 2650000 + +static const int const ldo_e_buck_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 900000, +}; + +static const int const ldo_f_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 2500000, + 2650000, +}; + +static const int const ldo_g_typ_voltages[] = { + 2850000, + 2750000, + 1800000, + 1500000, +}; + +static const int const ldo_h_typ_voltages[] = { + 2750000, + 1800000, + 1500000, + 1200000, +}; + +static const int const ldo_k_typ_voltages[] = { + 2750000, + 1800000, +}; + + +/* The regulator devices */ +static struct ab3100_regulator +ab3100_regulators[AB3100_NUM_REGULATORS] = { + { + .regreg = AB3100_LDO_A, + .fixed_voltage = LDO_A_VOLTAGE, + }, + { + .regreg = AB3100_LDO_C, + .fixed_voltage = LDO_C_VOLTAGE, + }, + { + .regreg = AB3100_LDO_D, + .fixed_voltage = LDO_D_VOLTAGE, + }, + { + .regreg = AB3100_LDO_E, + .typ_voltages = ldo_e_buck_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages), + }, + { + .regreg = AB3100_LDO_F, + .typ_voltages = ldo_f_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_f_typ_voltages), + }, + { + .regreg = AB3100_LDO_G, + .typ_voltages = ldo_g_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_g_typ_voltages), + }, + { + .regreg = AB3100_LDO_H, + .typ_voltages = ldo_h_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_h_typ_voltages), + }, + { + .regreg = AB3100_LDO_K, + .typ_voltages = ldo_k_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_k_typ_voltages), + }, + { + .regreg = AB3100_LDO_EXT, + /* No voltages for the external regulator */ + }, + { + .regreg = AB3100_BUCK, + .typ_voltages = ldo_e_buck_typ_voltages, + .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages), + }, +}; + +/* + * General functions for enable, disable and is_enabled used for + * LDO: A,C,E,F,G,H,K,EXT and BUCK + */ +static int ab3100_enable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int err; + u8 regval; + + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_warn(®->dev, "failed to get regid %d value\n", + abreg->regreg); + return err; + } + + /* The regulator is already on, no reason to go further */ + if (regval & AB3100_REG_ON_MASK) + return 0; + + regval |= AB3100_REG_ON_MASK; + + err = ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + regval); + if (err) { + dev_warn(®->dev, "failed to set regid %d value\n", + abreg->regreg); + return err; + } + + /* Per-regulator power on delay from spec */ + switch (abreg->regreg) { + case AB3100_LDO_A: /* Fallthrough */ + case AB3100_LDO_C: /* Fallthrough */ + case AB3100_LDO_D: /* Fallthrough */ + case AB3100_LDO_E: /* Fallthrough */ + case AB3100_LDO_H: /* Fallthrough */ + case AB3100_LDO_K: + udelay(200); + break; + case AB3100_LDO_F: + udelay(600); + break; + case AB3100_LDO_G: + udelay(400); + break; + case AB3100_BUCK: + mdelay(1); + break; + default: + break; + } + + return 0; +} + +static int ab3100_disable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int err; + u8 regval; + + /* + * LDO D is a special regulator. When it is disabled, the entire + * system is shut down. So this is handled specially. + */ + if (abreg->regreg == AB3100_LDO_D) { + int i; + + dev_info(®->dev, "disabling LDO D - shut down system\n"); + /* + * Set regulators to default values, ignore any errors, + * we're going DOWN + */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + (void) ab3100_set_register_interruptible(abreg->ab3100, + ab3100_reg_init_order[i], + abreg->plfdata->reg_initvals[i]); + } + + /* Setting LDO D to 0x00 cuts the power to the SoC */ + return ab3100_set_register_interruptible(abreg->ab3100, + AB3100_LDO_D, 0x00U); + + } + + /* + * All other regulators are handled here + */ + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + regval &= ~AB3100_REG_ON_MASK; + return ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg, + regval); +} + +static int ab3100_is_enabled_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + + err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + + return regval & AB3100_REG_ON_MASK; +} + +static int ab3100_list_voltage_regulator(struct regulator_dev *reg, + unsigned selector) +{ + struct ab3100_regulator *abreg = reg->reg_data; + + if (selector > abreg->voltages_len) + return -EINVAL; + return abreg->typ_voltages[selector]; +} + +static int ab3100_get_voltage_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + + /* Return the voltage for fixed regulators immediately */ + if (abreg->fixed_voltage) + return abreg->fixed_voltage; + + /* + * For variable types, read out setting and index into + * supplied voltage list. + */ + err = ab3100_get_register_interruptible(abreg->ab3100, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator value in register %02x\n", + abreg->regreg); + return err; + } + + /* The 3 highest bits index voltages */ + regval &= 0xE0; + regval >>= 5; + + if (regval > abreg->voltages_len) { + dev_err(®->dev, + "regulator register %02x contains an illegal voltage setting\n", + abreg->regreg); + return -EINVAL; + } + + return abreg->typ_voltages[regval]; +} + +static int ab3100_get_best_voltage_index(struct regulator_dev *reg, + int min_uV, int max_uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + int i; + int bestmatch; + int bestindex; + + /* + * Locate the minimum voltage fitting the criteria on + * this regulator. The switchable voltages are not + * in strict falling order so we need to check them + * all for the best match. + */ + bestmatch = INT_MAX; + bestindex = -1; + for (i = 0; i < abreg->voltages_len; i++) { + if (abreg->typ_voltages[i] <= max_uV && + abreg->typ_voltages[i] >= min_uV && + abreg->typ_voltages[i] < bestmatch) { + bestmatch = abreg->typ_voltages[i]; + bestindex = i; + } + } + + if (bestindex < 0) { + dev_warn(®->dev, "requested %d<=x<=%d uV, out of range!\n", + min_uV, max_uV); + return -EINVAL; + } + return bestindex; +} + +static int ab3100_set_voltage_regulator(struct regulator_dev *reg, + int min_uV, int max_uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + int bestindex; + + bestindex = ab3100_get_best_voltage_index(reg, min_uV, max_uV); + if (bestindex < 0) + return bestindex; + + err = ab3100_get_register_interruptible(abreg->ab3100, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + abreg->regreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (bestindex << 5); + + err = ab3100_set_register_interruptible(abreg->ab3100, + abreg->regreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, + int uV) +{ + struct ab3100_regulator *abreg = reg->reg_data; + u8 regval; + int err; + int bestindex; + u8 targetreg; + + if (abreg->regreg == AB3100_LDO_E) + targetreg = AB3100_LDO_E_SLEEP; + else if (abreg->regreg == AB3100_BUCK) + targetreg = AB3100_BUCK_SLEEP; + else + return -EINVAL; + + /* LDO E and BUCK have special suspend voltages you can set */ + bestindex = ab3100_get_best_voltage_index(reg, uV, uV); + + err = ab3100_get_register_interruptible(abreg->ab3100, + targetreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + targetreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (bestindex << 5); + + err = ab3100_set_register_interruptible(abreg->ab3100, + targetreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +/* + * The external regulator can just define a fixed voltage. + */ +static int ab3100_get_voltage_regulator_external(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = reg->reg_data; + + return abreg->plfdata->external_voltage; +} + +static struct regulator_ops regulator_ops_fixed = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, +}; + +static struct regulator_ops regulator_ops_variable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage = ab3100_set_voltage_regulator, + .list_voltage = ab3100_list_voltage_regulator, +}; + +static struct regulator_ops regulator_ops_variable_sleepable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage = ab3100_set_voltage_regulator, + .set_suspend_voltage = ab3100_set_suspend_voltage_regulator, + .list_voltage = ab3100_list_voltage_regulator, +}; + +/* + * LDO EXT is an external regulator so it is really + * not possible to set any voltage locally here, AB3100 + * is an on/off switch plain an simple. The external + * voltage is defined in the board set-up if any. + */ +static struct regulator_ops regulator_ops_external = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator_external, +}; + +static struct regulator_desc +ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { + { + .name = "LDO_A", + .id = AB3100_LDO_A, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_C", + .id = AB3100_LDO_C, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_D", + .id = AB3100_LDO_D, + .ops = ®ulator_ops_fixed, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_E", + .id = AB3100_LDO_E, + .ops = ®ulator_ops_variable_sleepable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_F", + .id = AB3100_LDO_F, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_G", + .id = AB3100_LDO_G, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_H", + .id = AB3100_LDO_H, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_K", + .id = AB3100_LDO_K, + .ops = ®ulator_ops_variable, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "LDO_EXT", + .id = AB3100_LDO_EXT, + .ops = ®ulator_ops_external, + .type = REGULATOR_VOLTAGE, + }, + { + .name = "BUCK", + .id = AB3100_BUCK, + .ops = ®ulator_ops_variable_sleepable, + .type = REGULATOR_VOLTAGE, + }, +}; + +/* + * NOTE: the following functions are regulators pluralis - it is the + * binding to the AB3100 core driver and the parent platform device + * for all the different regulators. + */ + +static int __init ab3100_regulators_probe(struct platform_device *pdev) +{ + struct ab3100_platform_data *plfdata = pdev->dev.platform_data; + struct ab3100 *ab3100 = platform_get_drvdata(pdev); + int err = 0; + u8 data; + int i; + + /* Check chip state */ + err = ab3100_get_register_interruptible(ab3100, + AB3100_LDO_D, &data); + if (err) { + dev_err(&pdev->dev, "could not read initial status of LDO_D\n"); + return err; + } + if (data & 0x10) + dev_notice(&pdev->dev, + "chip is already in active mode (Warm start)\n"); + else + dev_notice(&pdev->dev, + "chip is in inactive mode (Cold start)\n"); + + /* Set up regulators */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + err = ab3100_set_register_interruptible(ab3100, + ab3100_reg_init_order[i], + plfdata->reg_initvals[i]); + if (err) { + dev_err(&pdev->dev, "regulator initialization failed with error %d\n", + err); + return err; + } + } + + if (err) { + dev_err(&pdev->dev, + "LDO D regulator initialization failed with error %d\n", + err); + return err; + } + + /* Register the regulators */ + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct ab3100_regulator *reg = &ab3100_regulators[i]; + struct regulator_dev *rdev; + + /* + * Initialize per-regulator struct. + * Inherit platform data, this comes down from the + * i2c boarddata, from the machine. So if you want to + * see what it looks like for a certain machine, go + * into the machine I2C setup. + */ + reg->ab3100 = ab3100; + reg->plfdata = plfdata; + + /* + * Register the regulator, pass around + * the ab3100_regulator struct + */ + rdev = regulator_register(&ab3100_regulator_desc[i], + &pdev->dev, + &plfdata->reg_constraints[i], + reg); + + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, + "%s: failed to register regulator %s err %d\n", + __func__, ab3100_regulator_desc[i].name, + err); + i--; + /* remove the already registered regulators */ + while (i > 0) { + regulator_unregister(ab3100_regulators[i].rdev); + i--; + } + return err; + } + + /* Then set a pointer back to the registered regulator */ + reg->rdev = rdev; + } + + return 0; +} + +static int __exit ab3100_regulators_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct ab3100_regulator *reg = &ab3100_regulators[i]; + + regulator_unregister(reg->rdev); + } + return 0; +} + +static struct platform_driver ab3100_regulators_driver = { + .driver = { + .name = "ab3100-regulators", + .owner = THIS_MODULE, + }, + .probe = ab3100_regulators_probe, + .remove = __exit_p(ab3100_regulators_remove), +}; + +static __init int ab3100_regulators_init(void) +{ + return platform_driver_register(&ab3100_regulators_driver); +} + +static __exit void ab3100_regulators_exit(void) +{ + platform_driver_register(&ab3100_regulators_driver); +} + +subsys_initcall(ab3100_regulators_init); +module_exit(ab3100_regulators_exit); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("AB3100 Regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ab3100-regulators"); diff --git a/include/linux/mfd/ab3100.h b/include/linux/mfd/ab3100.h index 3ead5cf..e9aa4c9 100644 --- a/include/linux/mfd/ab3100.h +++ b/include/linux/mfd/ab3100.h @@ -7,6 +7,7 @@ #include #include +#include #ifndef MFD_AB3100_H #define MFD_AB3100_H @@ -57,6 +58,14 @@ #define AB3100_STR_BATT_REMOVAL (0x40) #define AB3100_STR_VBUS (0x80) +/* + * AB3100 contains 8 regulators, one external regulator controller + * and a buck converter, further the LDO E and buck converter can + * have separate settings if they are in sleep mode, this is + * modeled as a separate regulator. + */ +#define AB3100_NUM_REGULATORS 10 + /** * struct ab3100 * @access_mutex: lock out concurrent accesses to the AB3100 registers @@ -87,6 +96,25 @@ struct ab3100 { bool startup_events_read; }; +/** + * struct ab3100_platform_data + * Data supplied to initialize board connections to the AB3100 + * @reg_constraints: regulator constraints for target board + * the order of these constraints are: LDO A, C, D, E, + * F, G, H, K, EXT and BUCK. + * @reg_initvals: initial values for the regulator registers + * plus two sleep settings for LDO E and the BUCK converter. + * exactly AB3100_NUM_REGULATORS+2 values must be sent in. + * Order: LDO A, C, E, E sleep, F, G, H, K, EXT, BUCK, + * BUCK sleep, LDO D. (LDO D need to be initialized last.) + * @external_voltage: voltage level of the external regulator. + */ +struct ab3100_platform_data { + struct regulator_init_data reg_constraints[AB3100_NUM_REGULATORS]; + u8 reg_initvals[AB3100_NUM_REGULATORS+2]; + int external_voltage; +}; + int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval); int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval); int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, -- cgit v0.10.2 From c82693317e4a34b2b3ed4220c6fca3e99a75b045 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 15 Sep 2009 13:06:02 +0200 Subject: mfd: Fix twl4030-power warnings KEY_1 and KEY_2 definitions conflicts with include/linux/input.h Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 5284c7c..d423e0c 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -64,8 +64,8 @@ static u8 twl4030_start_script_address = 0x2b; #define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) #define R_PROTECT_KEY 0x0E -#define KEY_1 0xC0 -#define KEY_2 0x0C +#define R_KEY_1 0xC0 +#define R_KEY_2 0x0C /* resource configuration registers */ @@ -424,12 +424,12 @@ void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts) struct twl4030_resconfig *resconfig; u8 address = twl4030_start_script_address; - err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_1, + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1, R_PROTECT_KEY); if (err) goto unlock; - err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, KEY_2, + err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2, R_PROTECT_KEY); if (err) goto unlock; -- cgit v0.10.2 From 3860e6c4b93d6a1c2428f0f45c77e083197da2d4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 9 Sep 2009 14:46:43 +0100 Subject: mfd: Update MAINTAINERS patterns for WM831x The WM831x PMICs added a backlight driver and a new include directory. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/MAINTAINERS b/MAINTAINERS index fba5e81..9d635f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5681,7 +5681,9 @@ F: drivers/mfd/wm8*.c F: drivers/power/wm83*.c F: drivers/rtc/rtc-wm83*.c F: drivers/regulator/wm8*.c +F: drivers/video/backlight/wm83*_bl.c F: drivers/watchdog/wm83*_wdt.c +F: include/linux/mfd/wm831x/ F: include/linux/mfd/wm8350/ F: include/linux/mfd/wm8400/ F: sound/soc/codecs/wm8350.c -- cgit v0.10.2 From 37bce07077b0c335d8747f1ddb27ad585434a47e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 16 Sep 2009 19:07:32 +0100 Subject: mfd: Convert WM8350 to use request_threaded_irq() Instead of hand rolling our own variant. Signed-off-by: Mark Brown Signed-off-by: Samuel Ortiz diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index 9d662a5..ba27c9d 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -353,15 +353,15 @@ static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq) } /* - * wm8350_irq_worker actually handles the interrupts. Since all + * This is a threaded IRQ handler so can access I2C/SPI. Since all * interrupts are clear on read the IRQ line will be reasserted and * the physical IRQ will be handled again if another interrupt is * asserted while we run - in the normal course of events this is a * rare occurrence so we save I2C/SPI reads. */ -static void wm8350_irq_worker(struct work_struct *work) +static irqreturn_t wm8350_irq(int irq, void *data) { - struct wm8350 *wm8350 = container_of(work, struct wm8350, irq_work); + struct wm8350 *wm8350 = data; u16 level_one, status1, status2, comp; /* TODO: Use block reads to improve performance? */ @@ -552,16 +552,6 @@ static void wm8350_irq_worker(struct work_struct *work) } } - enable_irq(wm8350->chip_irq); -} - -static irqreturn_t wm8350_irq(int irq, void *data) -{ - struct wm8350 *wm8350 = data; - - disable_irq_nosync(irq); - schedule_work(&wm8350->irq_work); - return IRQ_HANDLED; } @@ -1428,9 +1418,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, mutex_init(&wm8350->auxadc_mutex); mutex_init(&wm8350->irq_mutex); - INIT_WORK(&wm8350->irq_work, wm8350_irq_worker); if (irq) { - int flags = 0; + int flags = IRQF_ONESHOT; if (pdata && pdata->irq_high) { flags |= IRQF_TRIGGER_HIGH; @@ -1444,8 +1433,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, WM8350_IRQ_POL); } - ret = request_irq(irq, wm8350_irq, flags, - "wm8350", wm8350); + ret = request_threaded_irq(irq, NULL, wm8350_irq, flags, + "wm8350", wm8350); if (ret != 0) { dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret); @@ -1505,7 +1494,6 @@ void wm8350_device_exit(struct wm8350 *wm8350) platform_device_unregister(wm8350->codec.pdev); free_irq(wm8350->chip_irq, wm8350); - flush_work(&wm8350->irq_work); kfree(wm8350->reg_cache); } EXPORT_SYMBOL_GPL(wm8350_device_exit); diff --git a/include/linux/mfd/wm8350/core.h b/include/linux/mfd/wm8350/core.h index 969b0b5..1d595de 100644 --- a/include/linux/mfd/wm8350/core.h +++ b/include/linux/mfd/wm8350/core.h @@ -626,7 +626,6 @@ struct wm8350 { struct mutex auxadc_mutex; /* Interrupt handling */ - struct work_struct irq_work; struct mutex irq_mutex; /* IRQ table mutex */ struct wm8350_irq irq[WM8350_NUM_IRQ]; int chip_irq; -- cgit v0.10.2 From 75f2ba8f0006440e720e47ae14c917e07c452d72 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 17 Sep 2009 09:17:33 +0200 Subject: regulator: Voltage count for AB3100 This sets the number of voltages for the AB3100 regulators so that they play well with the voltage listing functions and can be used properly with the MMC regulator integration glue for example. Signed-off-by: Linus Walleij Signed-off-by: Samuel Ortiz diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c index 156979d..49aeee8 100644 --- a/drivers/regulator/ab3100.c +++ b/drivers/regulator/ab3100.c @@ -521,30 +521,35 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { .name = "LDO_E", .id = AB3100_LDO_E, .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_F", .id = AB3100_LDO_F, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_f_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_G", .id = AB3100_LDO_G, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_g_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_H", .id = AB3100_LDO_H, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_h_typ_voltages), .type = REGULATOR_VOLTAGE, }, { .name = "LDO_K", .id = AB3100_LDO_K, .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_k_typ_voltages), .type = REGULATOR_VOLTAGE, }, { @@ -557,6 +562,7 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { .name = "BUCK", .id = AB3100_BUCK, .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), .type = REGULATOR_VOLTAGE, }, }; -- cgit v0.10.2