Index: linux-2.6.21.3-moko/drivers/i2c/chips/Kconfig =================================================================== --- linux-2.6.21.3-moko.orig/drivers/i2c/chips/Kconfig +++ linux-2.6.21.3-moko/drivers/i2c/chips/Kconfig @@ -46,6 +46,15 @@ This driver can also be built as a module. If so, the module will be called pcf50606. +config SENSORS_PCF50633 + tristate "Philips PCF50633" + depends on I2C + help + If you say yes here you get support for Philips PCF50633 + PMU (Power Management Unit) chips. + + This driver can also be built as a module. If so, the module + will be called pcf50633. config SENSORS_PCF8574 tristate "Philips PCF8574 and PCF8574A" Index: linux-2.6.21.3-moko/drivers/i2c/chips/Makefile =================================================================== --- linux-2.6.21.3-moko.orig/drivers/i2c/chips/Makefile +++ linux-2.6.21.3-moko/drivers/i2c/chips/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_M41T00) += m41t00.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF50606) += pcf50606.o +obj-$(CONFIG_SENSORS_PCF50633) += pcf50633.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o Index: linux-2.6.21.3-moko/drivers/i2c/chips/pcf50633.c =================================================================== --- /dev/null +++ linux-2.6.21.3-moko/drivers/i2c/chips/pcf50633.c @@ -0,0 +1,1691 @@ +/* Philips PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2007 by OpenMoko, Inc. + * Author: Harald Welte + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * This driver is a monster ;) It provides the following features + * - voltage control for a dozen different voltage domains + * - charging control for main and backup battery + * - rtc / alarm + * - adc driver (hw_sensors like) + * - backlight + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pcf50633.h" + +#if 1 +#define DEBUGP(x, args ...) printk("%s: " x, __FUNCTION__, ## args) +#define DEBUGPC(x, args ...) printk(x, ## args) +#else +#define DEBUGP(x, args ...) +#define DEBUGPC(x, args ...) +#endif + +/*********************************************************************** + * Static data / structures + ***********************************************************************/ + +static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD_1(pcf50633); + +#define PCF50633_F_CHG_FAST 0x00000001 /* Charger Fast allowed */ +#define PCF50633_F_CHG_PRESENT 0x00000002 /* Charger present */ +#define PCF50633_F_CHG_FOK 0x00000004 /* Fast OK for battery */ +#define PCF50633_F_CHG_ERR 0x00000008 /* Charger Error */ +#define PCF50633_F_CHG_PROT 0x00000010 /* Charger Protection */ +#define PCF50633_F_CHG_READY 0x00000020 /* Charging completed */ +#define PCF50633_F_CHG_MASK 0x000000fc + +#define PCF50633_F_PWR_PRESSED 0x00000100 +#define PCF50633_F_RTC_SECOND 0x00000200 +#define PCF50633_F_USB_PRESENT 0x00000400 + +enum close_state { + CLOSE_STATE_NOT, + CLOSE_STATE_ALLOW = 0x2342, +}; + +struct pcf50633_data { + struct i2c_client client; + struct pcf50633_platform_data *pdata; + struct backlight_device *backlight; + struct mutex lock; + unsigned int flags; + unsigned int working; + struct work_struct work; + struct rtc_device *rtc; + struct input_dev *input_dev; + int allow_close; + int onkey_seconds; + int irq; +#ifdef CONFIG_PM + struct { + u_int8_t int1m, int2m, int3m, int4m, int5m; + u_int8_t ooctim2; + u_int8_t autoout, autoena, automxc; + u_int8_t down1out, down1mxc; + u_int8_t down2out, down2ena; + u_int8_t memldoout, memldoena; + u_int8_t ledout, ledena, leddim; + struct { + u_int8_t out; + u_int8_t ena; + } ldo[__NUM_PCF50633_REGS]; + } standby_regs; +#endif +}; + +static struct i2c_driver pcf50633_driver; + +struct pcf50633_data *pcf50633_global; +EXPORT_SYMBOL(pcf50633_global); + +static struct platform_device *pcf50633_pdev; + +/* This is a mitsubishi TN11-3H103J T,B NTC Thermistor -10..79 centigrade */ +static const u_int16_t ntc_table_tn11_3h103j[] = { + /* -10 */ + 40260, 38560, 36950, 35410, 33950, 32550, 31220, 29960, 28750, 27590, + 26490, 25440, 24440, 23480, 22560, 21680, 20830, 20020, 19240, 18500, + 17780, 17710, 16440, 15810, 15210, 14630, 14070, 13540, 13030, 12540, + 12070, 11620, 11190, 10780, 10380, 10000, 9635, 9286, 8950, 8629, + 8320, 8024, 7740, 7467, 7205, 6954, 6713, 6481, 6258, 6044, + 5839, 5641, 5451, 5269, 5093, 4924, 4762, 4605, 4455, 4310, + 4171, 4037, 3908, 3784, 3664, 3549, 3438, 3313, 3227, 3128, + 3032, 2939, 2850, 2763, 2680, 2600, 2522, 2448, 2375, 2306, + 2239, 2174, 2111, 2050, 1922, 1935, 1881, 1828, 1776, 1727, +}; + + +/*********************************************************************** + * Low-Level routines + ***********************************************************************/ + +static inline int __reg_write(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val) +{ + return i2c_smbus_write_byte_data(&pcf->client, reg, val); +} + +static int reg_write(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val) +{ + int ret; + + mutex_lock(&pcf->lock); + ret = __reg_write(pcf, reg, val); + mutex_unlock(&pcf->lock); + + return ret; +} + +static inline int32_t __reg_read(struct pcf50633_data *pcf, u_int8_t reg) +{ + int32_t ret; + + ret = i2c_smbus_read_byte_data(&pcf->client, reg); + + return ret; +} + +static u_int8_t reg_read(struct pcf50633_data *pcf, u_int8_t reg) +{ + int32_t ret; + + mutex_lock(&pcf->lock); + ret = __reg_read(pcf, reg); + mutex_unlock(&pcf->lock); + + return ret & 0xff; +} + +static int reg_set_bit_mask(struct pcf50633_data *pcf, + u_int8_t reg, u_int8_t mask, u_int8_t val) +{ + int ret; + u_int8_t tmp; + + val &= mask; + + mutex_lock(&pcf->lock); + + tmp = __reg_read(pcf, reg); + tmp &= ~mask; + tmp |= val; + ret = __reg_write(pcf, reg, tmp); + + mutex_unlock(&pcf->lock); + + return ret; +} + +static int reg_clear_bits(struct pcf50633_data *pcf, u_int8_t reg, u_int8_t val) +{ + int ret; + u_int8_t tmp; + + mutex_lock(&pcf->lock); + + tmp = __reg_read(pcf, reg); + tmp &= ~val; + ret = __reg_write(pcf, reg, tmp); + + mutex_unlock(&pcf->lock); + + return ret; +} + +/* synchronously read one ADC channel (busy-wait for result to be complete) */ +static u_int16_t adc_read(struct pcf50633_data *pcf, int channel, int avg, + u_int16_t *data2) +{ + u_int8_t adcs3, adcs2, adcs1; + u_int16_t ret; + + DEBUGP("entering (pcf=%p, channel=%u, data2=%p)\n", + pcf, channel, data2); + + channel &= PCF50633_ADCC1_ADCMUX_MASK; + + mutex_lock(&pcf->lock); + + /* start ADC conversion of selected channel */ + __reg_write(pcf, PCF50633_REG_ADCC1, channel | avg | + PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT); + + do { + adcs3 = __reg_read(pcf, PCF50633_REG_ADCS3); + } while (!(adcs3 & PCF50633_ADCS3_ADCRDY)); + + adcs1 = __reg_read(pcf, PCF50633_REG_ADCS1); + ret = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK); + + if (data2) { + adcs2 = __reg_read(pcf, PCF50633_REG_ADCS2); + *data2 = (adcs2 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT2L_MASK) + >> PCF50633_ADCS3_ADCDAT2L_SHIFT; + } + + mutex_unlock(&pcf->lock); + + DEBUGP("returning %u %u\n", ret, data2 ? *data2 : 0); + + return ret; +} + +/*********************************************************************** + * Voltage / ADC + ***********************************************************************/ + +static u_int8_t auto_voltage(unsigned int millivolts) +{ + if (millivolts < 1800) + return 0; + if (millivolts > 3800) + return 0xff; + + millivolts -= 625; + return millivolts/25; +} + +static unsigned int auto_2voltage(u_int8_t bits) +{ + if (bits < 0x2f) + return 0; + return 625 + (bits * 25); +} + +static u_int8_t down_voltage(unsigned int millivolts) +{ + if (millivolts < 625) + return 0; + else if (millivolts > 3000) + return 0xff; + + millivolts -= 625; + return millivolts/25; +} + +static unsigned int down_2voltage(u_int8_t bits) +{ + return 625 + (bits*25); +} + +static u_int8_t ldo_voltage(unsigned int millivolts) +{ + if (millivolts < 900) + return 0; + else if (millivolts > 3600) + return 0x1f; + + millivolts -= 900; + return millivolts/100; +} + +static unsigned int ldo_2voltage(u_int8_t bits) +{ + bits &= 0x1f; + return 900 + (bits * 100); +} + +static const u_int8_t regulator_registers[__NUM_PCF50633_REGULATORS] = { + [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT, + [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT, + [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT, + [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT, + [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT, + [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT, + [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT, + [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT, + [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT, + [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT, + [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, +}; + +int pcf50633_onoff_set(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg, int on) +{ + u_int8_t addr; + + if (reg >= __NUM_PCF50633_REGULATORS) + return -EINVAL; + + /* the *ENA register is always one after the *OUT register */ + addr = regulator_registers[reg] + 1; + + if (on == 0) + reg_set_bit_mask(pcf, addr, PCF50633_REGULATOR_ON, 0); + else + reg_set_bit_mask(pcf, addr, PCF50633_REGULATOR_ON, + PCF50633_REGULATOR_ON); + + return 0; +} +EXPORT_SYMBOL(pcf50633_onoff_set); + +int pcf50633_onoff_get(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg) +{ + u_int8_t val, addr; + + if (reg >= __NUM_PCF50633_REGULATORS) + return -EINVAL; + + /* the *ENA register is always one after the *OUT register */ + addr = regulator_registers[reg] + 1; + val = reg_read(pcf, addr) & PCF50633_REGULATOR_ON; + + return val; +} +EXPORT_SYMBOL(pcf50633_onoff_get); + +int pcf50633_voltage_set(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg, + unsigned int millivolts) +{ + u_int8_t volt_bits; + u_int8_t regnr; + + DEBUGP("pcf=%p, reg=%d, mvolts=%d\n", pcf, reg, millivolts); + + if (reg >= __NUM_PCF50633_REGULATORS) + return -EINVAL; + + regnr = regulator_registers[reg]; + + if (millivolts > pcf->pdata->rails[reg].voltage.max) + return -EINVAL; + + switch (reg) { + case PCF50633_REGULATOR_AUTO: + volt_bits = auto_voltage(millivolts); + break; + case PCF50633_REGULATOR_DOWN1: + volt_bits = down_voltage(millivolts); + break; + case PCF50633_REGULATOR_DOWN2: + volt_bits = down_voltage(millivolts); + break; + case PCF50633_REGULATOR_LDO1: + case PCF50633_REGULATOR_LDO2: + case PCF50633_REGULATOR_LDO3: + case PCF50633_REGULATOR_LDO4: + case PCF50633_REGULATOR_LDO5: + case PCF50633_REGULATOR_LDO6: + case PCF50633_REGULATOR_HCLDO: + volt_bits = ldo_voltage(millivolts); + DEBUGP("ldo_voltage(0x%x)=%u\n", millivolts, volt_bits); + break; + default: + return -EINVAL; + } + + return reg_write(pcf, regnr, volt_bits); +} +EXPORT_SYMBOL(pcf50633_voltage_set); + +unsigned int pcf50633_voltage_get(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg) +{ + u_int8_t volt_bits; + u_int8_t regnr; + unsigned int rc = 0; + + if (reg >= __NUM_PCF50633_REGULATORS) + return -EINVAL; + + regnr = regulator_registers[reg]; + volt_bits = reg_read(pcf, regnr); + + switch (reg) { + case PCF50633_REGULATOR_AUTO: + rc = auto_2voltage(volt_bits); + break; + case PCF50633_REGULATOR_DOWN1: + rc = down_2voltage(volt_bits); + break; + case PCF50633_REGULATOR_DOWN2: + rc = down_2voltage(volt_bits); + break; + case PCF50633_REGULATOR_LDO1: + case PCF50633_REGULATOR_LDO2: + case PCF50633_REGULATOR_LDO3: + case PCF50633_REGULATOR_LDO4: + case PCF50633_REGULATOR_LDO5: + case PCF50633_REGULATOR_LDO6: + case PCF50633_REGULATOR_HCLDO: + rc = ldo_2voltage(volt_bits); + break; + default: + return -EINVAL; + } + + return rc; +} +EXPORT_SYMBOL(pcf50633_voltage_get); + +/* go into 'STANDBY' mode, i.e. power off the main CPU and peripherals */ +void pcf50633_go_standby(void) +{ + reg_write(pcf50633_global, PCF50633_REG_OOCSHDWN, + PCF50633_OOCSHDWN_GOSTDBY); +} +EXPORT_SYMBOL(pcf50633_go_standby); + +void pcf50633_gpo0_set(struct pcf50633_data *pcf, int on) +{ + u_int8_t val; + + if (on) + val = PCF50633_GPOCFG_GPOSEL_1; + else + val = PCF50633_GPOCFG_GPOSEL_0; + + reg_set_bit_mask(pcf, PCF50633_REG_GPOCFG, 0x0f, val); +} +EXPORT_SYMBOL(pcf50633_gpo0_set); + +int pcf50633_gpo0_get(struct pcf50633_data *pcf) +{ + u_int8_t reg = reg_read(pcf, PCF50633_REG_GPOCFG) & 0x0f; + + if (reg == PCF50633_GPOCFG_GPOSEL_1 || + reg == (PCF50633_GPOCFG_GPOSEL_0|PCF50633_GPOCFG_GPOSEL_INVERSE)) + return 1; + + return 0; +} +EXPORT_SYMBOL(pcf50633_gpo0_get); + +static void pcf50633_work(struct work_struct *work) +{ + struct pcf50633_data *pcf = + container_of(work, struct pcf50633_data, work); + u_int8_t int1, int2, int3, int4, int5; + + pcf->working = 1; + + /* FIXME: read in one i2c transaction */ + int1 = __reg_read(pcf, PCF50633_REG_INT1); + int2 = __reg_read(pcf, PCF50633_REG_INT2); + int3 = __reg_read(pcf, PCF50633_REG_INT3); + int4 = __reg_read(pcf, PCF50633_REG_INT4); + int5 = __reg_read(pcf, PCF50633_REG_INT5); + + DEBUGP("INT1=0x%02x INT2=0x%02x INT3=0x%02x INT4=0x%02x INT5=0x%02x\n", + int1, int2, int3, int4, int5); + + if (int1 & PCF50633_INT1_ADPINS) { + /* Charger inserted */ + DEBUGPC("ADPINS "); + input_report_key(pcf->input_dev, KEY_BATTERY, 1); + apm_queue_event(APM_POWER_STATUS_CHANGE); + pcf->flags |= PCF50633_F_CHG_PRESENT; + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_INSERT); + /* FIXME: signal this to userspace */ + //kobject_uevent( ,KOBJ_ADD); + } + if (int1 & PCF50633_INT1_ADPREM) { + /* Charger removed */ + DEBUGPC("ADPREM "); + input_report_key(pcf->input_dev, KEY_BATTERY, 0); + apm_queue_event(APM_POWER_STATUS_CHANGE); + pcf->flags &= ~PCF50633_F_CHG_PRESENT; + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_REMOVE); + /* FIXME: signal this to userspace */ + //kobject_uevent( ,KOBJ_ADD); + } + if (int1 & PCF50633_INT1_USBINS) { + DEBUGPC("USBINS "); + input_report_key(pcf->input_dev, KEY_POWER2, 1); + apm_queue_event(APM_POWER_STATUS_CHANGE); + pcf->flags |= PCF50633_F_USB_PRESENT; + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_USB_INSERT); + + } + if (int1 & PCF50633_INT1_USBREM) { + DEBUGPC("USBREM "); + input_report_key(pcf->input_dev, KEY_POWER2, 0); + apm_queue_event(APM_POWER_STATUS_CHANGE); + pcf->flags &= ~PCF50633_F_USB_PRESENT; + if (pcf->pdata->cb) + pcf->pdata->cb(&pcf->client.dev, + PCF50633_FEAT_MBC, PMU_EVT_USB_REMOVE); + } + if (int1 & PCF50633_INT1_ALARM) { + DEBUGPC("ALARM "); + if (pcf->pdata->used_features & PCF50633_FEAT_RTC) + rtc_update_irq(&pcf->rtc->class_dev, 1, + RTC_AF | RTC_IRQF); + } + if (int1 & PCF50633_INT1_SECOND) { + DEBUGPC("SECOND "); + if (pcf->flags & PCF50633_F_RTC_SECOND) + rtc_update_irq(&pcf->rtc->class_dev, 1, + RTC_PF | RTC_IRQF); + + if (pcf->onkey_seconds >= 0 && + pcf->flags & PCF50633_F_PWR_PRESSED) { + DEBUGP("ONKEY_SECONDS(%u, OOCSTAT=0x%02x) ", + pcf->onkey_seconds, + reg_read(pcf, PCF50633_REG_OOCSTAT)); + pcf->onkey_seconds++; + if (pcf->onkey_seconds >= + pcf->pdata->onkey_seconds_required) { + /* Ask init to do 'ctrlaltdel' */ + DEBUGPC("SIGINT(init) "); + kill_proc(1, SIGINT, 1); + /* FIXME: what if userspace doesn't shut down? */ + } + } + } + + if (int2 & PCF50633_INT2_ONKEYF) { + /* ONKEY falling edge (start of button press) */ + DEBUGPC("ONKEYF "); + pcf->flags |= PCF50633_F_PWR_PRESSED; + input_report_key(pcf->input_dev, KEY_POWER, 1); + } + if (int2 & PCF50633_INT2_ONKEYR) { + /* ONKEY rising edge (end of button press) */ + DEBUGPC("ONKEYR "); + pcf->flags &= ~PCF50633_F_PWR_PRESSED; + pcf->onkey_seconds = -1; + input_report_key(pcf->input_dev, KEY_POWER, 0); + /* disable SECOND interrupt in case RTC didn't + * request it */ + if (!(pcf->flags & PCF50633_F_RTC_SECOND)) + reg_set_bit_mask(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, + PCF50633_INT1_SECOND); + } + /* FIXME: we don't use EXTON1/2/3. thats why we skip it */ + + if (int3 & PCF50633_INT3_BATFULL) { + DEBUGPC("BATFULL "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_CHGHALT) { + DEBUGPC("CHGHALT "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_THLIMON) { + DEBUGPC("THLIMON "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_THLIMOFF) { + DEBUGPC("THLIMOFF "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_USBLIMON) { + DEBUGPC("USBLIMON "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_USBLIMOFF) { + DEBUGPC("USBLIMOFF "); + /* FIXME: signal this to userspace */ + } + if (int3 & PCF50633_INT3_ADCRDY) { + /* ADC result ready */ + DEBUGPC("ADCRDY "); + } + if (int3 & PCF50633_INT3_ONKEY1S) { + /* ONKEY pressed for more than 1 second */ + pcf->onkey_seconds = 0; + DEBUGPC("ONKEY1S "); + /* Tell PMU we are taking care of this */ + reg_set_bit_mask(pcf, PCF50633_REG_OOCSHDWN, + PCF50633_OOCSHDWN_TOTRST, + PCF50633_OOCSHDWN_TOTRST); + /* enable SECOND interrupt (hz tick) */ + reg_clear_bits(pcf, PCF50633_REG_INT1M, PCF50633_INT1_SECOND); + } + + if (int4 & (PCF50633_INT4_LOWBAT|PCF50633_INT4_LOWSYS)) { + /* Really low battery voltage, we have 8 seconds left */ + DEBUGPC("LOWBAT "); + apm_queue_event(APM_LOW_BATTERY); + DEBUGPC("SIGPWR(init) "); + kill_proc(1, SIGPWR, 1); + /* Tell PMU we are taking care of this */ + reg_set_bit_mask(pcf, PCF50633_REG_OOCSHDWN, + PCF50633_OOCSHDWN_TOTRST, + PCF50633_OOCSHDWN_TOTRST); + } + if (int4 & PCF50633_INT4_HIGHTMP) { + /* High temperature */ + DEBUGPC("HIGHTMP "); + apm_queue_event(APM_CRITICAL_SUSPEND); + } + if (int4 & (PCF50633_INT4_AUTOPWRFAIL|PCF50633_INT4_DWN1PWRFAIL| + PCF50633_INT4_DWN2PWRFAIL|PCF50633_INT4_LEDPWRFAIL| + PCF50633_INT4_LEDOVP)) { + /* Some regulator failed */ + DEBUGPC("REGULATOR_FAIL "); + /* FIXME: deal with this */ + } + + DEBUGPC("\n"); + + pcf->working = 0; + input_sync(pcf->input_dev); + put_device(&pcf->client.dev); + + enable_irq(pcf->irq); +} + +static void pcf50633_schedule_work(struct pcf50633_data *pcf) +{ + int status; + + get_device(&pcf->client.dev); + status = schedule_work(&pcf->work); + if (!status && !pcf->working) + dev_dbg(&pcf->client.dev, "work item may be lost\n"); +} + + +static irqreturn_t pcf50633_irq(int irq, void *_pcf) +{ + struct pcf50633_data *pcf = _pcf; + + DEBUGP("entering(irq=%u, pcf=%p): scheduling work\n", + irq, _pcf); + pcf50633_schedule_work(pcf); + + /* Disable any further interrupts until we have processed + * the current one */ + disable_irq(irq); + + return IRQ_HANDLED; +} + +static u_int16_t adc_to_batt_millivolts(u_int16_t adc) +{ + u_int16_t mvolts; + + mvolts = (adc * 6000) / 1024; + + return mvolts; +} + +#define BATTVOLT_SCALE_START 2800 +#define BATTVOLT_SCALE_END 4200 +#define BATTVOLT_SCALE_DIVIDER ((BATTVOLT_SCALE_END - BATTVOLT_SCALE_START)/100) + +static u_int8_t battvolt_scale(u_int16_t battvolt) +{ + /* FIXME: this linear scale is completely bogus */ + u_int16_t battvolt_relative = battvolt - BATTVOLT_SCALE_START; + unsigned int percent = battvolt_relative / BATTVOLT_SCALE_DIVIDER; + + return percent; +} + +u_int16_t pcf50633_battvolt(struct pcf50633_data *pcf) +{ + u_int16_t adc; + adc = adc_read(pcf, PCF50633_ADCC1_MUX_BATSNS_RES, 0, NULL); + + return adc_to_batt_millivolts(adc); +} +EXPORT_SYMBOL(pcf50633_battvolt); + +static ssize_t show_battvolt(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + + return sprintf(buf, "%u\n", pcf50633_battvolt(pcf)); +} +static DEVICE_ATTR(battvolt, S_IRUGO | S_IWUSR, show_battvolt, NULL); + +static int reg_id_by_name(const char *name) +{ + int reg_id; + + if (!strcmp(name, "voltage_auto")) + reg_id = PCF50633_REGULATOR_AUTO; + else if (!strcmp(name, "voltage_down1")) + reg_id = PCF50633_REGULATOR_DOWN1; + else if (!strcmp(name, "voltage_down2")) + reg_id = PCF50633_REGULATOR_DOWN2; + else if (!strcmp(name, "voltage_memldo")) + reg_id = PCF50633_REGULATOR_MEMLDO; + else if (!strcmp(name, "voltage_ldo1")) + reg_id = PCF50633_REGULATOR_LDO1; + else if (!strcmp(name, "voltage_ldo2")) + reg_id = PCF50633_REGULATOR_LDO2; + else if (!strcmp(name, "voltage_ldo3")) + reg_id = PCF50633_REGULATOR_LDO3; + else if (!strcmp(name, "voltage_ldo4")) + reg_id = PCF50633_REGULATOR_LDO4; + else if (!strcmp(name, "voltage_ldo5")) + reg_id = PCF50633_REGULATOR_LDO5; + else if (!strcmp(name, "voltage_ldo6")) + reg_id = PCF50633_REGULATOR_LDO6; + else if (!strcmp(name, "voltage_hcldo")) + reg_id = PCF50633_REGULATOR_HCLDO; + else + reg_id = -1; + + return reg_id; +} + +static ssize_t show_vreg(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + unsigned int reg_id; + + reg_id = reg_id_by_name(attr->attr.name); + if (reg_id < 0) + return 0; + + if (pcf50633_onoff_get(pcf, reg_id) > 0) + return sprintf(buf, "%u\n", pcf50633_voltage_get(pcf, reg_id)); + else + return strlcpy(buf, "0\n", PAGE_SIZE); +} + +static ssize_t set_vreg(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + unsigned long mvolts = simple_strtoul(buf, NULL, 10); + unsigned int reg_id; + + reg_id = reg_id_by_name(attr->attr.name); + if (reg_id < 0) + return -EIO; + + DEBUGP("attempting to set %s(%d) to %lu mvolts\n", attr->attr.name, + reg_id, mvolts); + + if (mvolts == 0) { + pcf50633_onoff_set(pcf, reg_id, 0); + } else { + if (pcf50633_voltage_set(pcf, reg_id, mvolts) < 0) { + dev_warn(dev, "refusing to set %s(%d) to %lu mvolts " + "(max=%u)\n", attr->attr.name, reg_id, mvolts, + pcf->pdata->rails[reg_id].voltage.max); + return -EINVAL; + } + pcf50633_onoff_set(pcf, reg_id, 1); + } + + return count; +} + +static DEVICE_ATTR(voltage_auto, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_down1, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_down2, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_memldo, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo1, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo2, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo3, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo4, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo5, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_ldo6, S_IRUGO | S_IWUSR, show_vreg, set_vreg); +static DEVICE_ATTR(voltage_hcldo, S_IRUGO | S_IWUSR, show_vreg, set_vreg); + +/*********************************************************************** + * Charger Control + ***********************************************************************/ + +/* Enable/disable fast charging (500mA in the GTA02) */ +void pcf50633_charge_fast(struct pcf50633_data *pcf, int on) +{ +#if 0 + if (!(pcf->pdata->used_features & PCF50606_FEAT_MBC)) + return; + + if (on) { + /* We can allow PCF to automatically charge + * using Ifast */ + pcf->flags |= PCF50606_F_CHG_FAST; + reg_set_bit_mask(pcf, PCF50606_REG_MBCC1, + PCF50606_MBCC1_AUTOFST, + PCF50606_MBCC1_AUTOFST); + } else { + pcf->flags &= ~PCF50606_F_CHG_FAST; + /* disable automatic fast-charge */ + reg_clear_bits(pcf, PCF50606_REG_MBCC1, + PCF50606_MBCC1_AUTOFST); + /* switch to idle mode to abort existing charge + * process */ + reg_set_bit_mask(pcf, PCF50606_REG_MBCC1, + PCF50606_MBCC1_CHGMOD_MASK, + PCF50606_MBCC1_CHGMOD_IDLE); + } +#endif +} +EXPORT_SYMBOL(pcf50633_charge_fast); + +#define ONE 1000000 +static inline u_int16_t adc_to_rntc(struct pcf50633_data *pcf, u_int16_t adc) +{ + u_int32_t r_batt = (adc * pcf->pdata->r_fix_batt) / (1023 - adc); + u_int16_t r_ntc; + + /* The battery NTC has a parallell 10kOhms resistor */ + r_ntc = ONE / ((ONE/r_batt) - (ONE/pcf->pdata->r_fix_batt_par)); + + return r_ntc; +} + +static inline int16_t rntc_to_temp(u_int16_t rntc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ntc_table_tn11_3h103j); i++) { + if (rntc > ntc_table_tn11_3h103j[i]) + return i - 10; + } + return 2342; +} + +static ssize_t show_battemp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); +#if 0 + u_int16_t adc; + + adc = adc_read(pcf, PCF50633_ADCC1_MUX_BATTEMP, 0, NULL); + + return sprintf(buf, "%d\n", rntc_to_temp(adc_to_rntc(pcf, adc))); +#endif + return sprintf(buf, "\n"); +} +static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL); + +static inline u_int16_t adc_to_chg_milliamps(struct pcf50633_data *pcf, + u_int16_t adc_adcin1, + u_int16_t adc_batvolt) +{ + u_int32_t res = ((adc_adcin1 - adc_batvolt) * 6000); + return res / (pcf->pdata->r_sense_milli * 1024 / 1000); +} + +static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); +#if 0 + u_int16_t adc_batvolt, adc_adcin1; + u_int16_t ma; + + adc_batvolt = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_ADCIN1, + &adc_adcin1); + ma = adc_to_chg_milliamps(pcf, adc_adcin1, adc_batvolt); + + return sprintf(buf, "%u\n", ma); +#endif + return sprintf(buf, "\n"); +} +static DEVICE_ATTR(chgcur, S_IRUGO | S_IWUSR, show_chgcur, NULL); + +static const char *chgmode_names[] = { + [PCF50633_MBCS2_MBC_PLAY] = "play-only", + [PCF50633_MBCS2_MBC_USB_PRE] = "pre", + [PCF50633_MBCS2_MBC_ADP_PRE] = "pre", + [PCF50633_MBCS2_MBC_USB_PRE_WAIT] = "pre-wait", + [PCF50633_MBCS2_MBC_ADP_PRE_WAIT] = "pre-wait", + [PCF50633_MBCS2_MBC_USB_FAST] = "fast", + [PCF50633_MBCS2_MBC_ADP_FAST] = "fast", + [PCF50633_MBCS2_MBC_USB_FAST_WAIT] = "fast-wait", + [PCF50633_MBCS2_MBC_ADP_FAST_WAIT] = "fast-wait", + [PCF50633_MBCS2_MBC_ADP_FAST_WAIT] = "bat-full", +}; + +static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + u_int8_t mbcs2 = reg_read(pcf, PCF50633_REG_MBCS2); + u_int8_t chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + return sprintf(buf, "%s\n", chgmode_names[chgmod]); +} + +static ssize_t set_chgmode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); +#if 0 + u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1); + + mbcc1 &= ~PCF50606_MBCC1_CHGMOD_MASK; + + if (!strcmp(buf, "qualification")) + mbcc1 |= PCF50606_MBCC1_CHGMOD_QUAL; + else if (!strcmp(buf, "pre")) + mbcc1 |= PCF50606_MBCC1_CHGMOD_PRE; + else if (!strcmp(buf, "trickle")) + mbcc1 |= PCF50606_MBCC1_CHGMOD_TRICKLE; + else if (!strcmp(buf, "fast_cccv")) + mbcc1 |= PCF50606_MBCC1_CHGMOD_FAST_CCCV; + /* We don't allow the other fast modes for security reasons */ + else if (!strcmp(buf, "idle")) + mbcc1 |= PCF50606_MBCC1_CHGMOD_IDLE; + else + return -EINVAL; + + reg_write(pcf, PCF50606_REG_MBCC1, mbcc1); +#endif + return count; +} + +static DEVICE_ATTR(chgmode, S_IRUGO | S_IWUSR, show_chgmode, set_chgmode); + +static const char *chgstate_names[] = { + [PCF50633_F_CHG_FAST] = "fast_enabled", + [PCF50633_F_CHG_PRESENT] = "present", + [PCF50633_F_CHG_FOK] = "fast_ok", + [PCF50633_F_CHG_ERR] = "error", + [PCF50633_F_CHG_PROT] = "protection", + [PCF50633_F_CHG_READY] = "ready", +}; + +static ssize_t show_chgstate(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); +#if 0 + char *b = buf; + int i; + + for (i = 0; i < 32; i++) + if (pcf->flags & (1 << i) && i < ARRAY_SIZE(chgstate_names)) + b += sprintf(b, "%s ", chgstate_names[i]); + + if (b > buf) + b += sprintf(b, "\n"); + + return b - buf; +#endif + return 0; +} +static DEVICE_ATTR(chgstate, S_IRUGO | S_IWUSR, show_chgstate, NULL); + +/*********************************************************************** + * APM emulation + ***********************************************************************/ + +extern void (*apm_get_power_status)(struct apm_power_info *); + +static void pcf50633_get_power_status(struct apm_power_info *info) +{ + struct pcf50633_data *pcf = pcf50633_global; + u_int8_t chgmod = reg_read(pcf, PCF50633_REG_MBCS2) & + PCF50633_MBCS2_MBC_MASK; + u_int16_t battvolt = 0; /* FIXME */ + //u_int16_t battvolt = pcf50606_battvolt(pcf); + + if (reg_read(pcf, PCF50633_REG_MBCS1) & + (PCF50633_MBCS1_USBPRES|PCF50633_MBCS1_ADAPTPRES)) + info->ac_line_status = APM_AC_ONLINE; + else + info->ac_line_status = APM_AC_OFFLINE; + + switch (chgmod) { + case PCF50633_MBCS2_MBC_USB_PRE: + case PCF50633_MBCS2_MBC_USB_PRE_WAIT: + case PCF50633_MBCS2_MBC_USB_FAST_WAIT: + case PCF50633_MBCS2_MBC_ADP_PRE: + case PCF50633_MBCS2_MBC_ADP_PRE_WAIT: + case PCF50633_MBCS2_MBC_ADP_FAST_WAIT: + case PCF50633_MBCS2_MBC_BAT_FULL: + case PCF50633_MBCS2_MBC_HALT: + info->battery_life = battvolt_scale(battvolt); + break; + case PCF50633_MBCS2_MBC_USB_FAST: + case PCF50633_MBCS2_MBC_ADP_FAST: + info->battery_status = APM_BATTERY_STATUS_CHARGING; + info->battery_flag = APM_BATTERY_FLAG_CHARGING; + default: + break; + } +} + +/*********************************************************************** + * RTC + ***********************************************************************/ + +struct pcf50633_time { + u_int8_t sec; + u_int8_t min; + u_int8_t hour; + u_int8_t wkday; + u_int8_t day; + u_int8_t month; + u_int8_t year; +}; + +static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50633_time *pcf) +{ + rtc->tm_sec = BCD2BIN(pcf->sec); + rtc->tm_min = BCD2BIN(pcf->min); + rtc->tm_hour = BCD2BIN(pcf->hour); + rtc->tm_wday = BCD2BIN(pcf->wkday); + rtc->tm_mday = BCD2BIN(pcf->day); + rtc->tm_mon = BCD2BIN(pcf->month); + rtc->tm_year = BCD2BIN(pcf->year) + 100; +} + +static void rtc2pcf_time(struct pcf50633_time *pcf, struct rtc_time *rtc) +{ + pcf->sec = BIN2BCD(rtc->tm_sec); + pcf->min = BIN2BCD(rtc->tm_min); + pcf->hour = BIN2BCD(rtc->tm_hour); + pcf->wkday = BIN2BCD(rtc->tm_wday); + pcf->day = BIN2BCD(rtc->tm_mday); + pcf->month = BIN2BCD(rtc->tm_mon); + pcf->year = BIN2BCD(rtc->tm_year - 100); +} + +static int pcf50633_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + switch (cmd) { + case RTC_PIE_OFF: + /* disable periodic interrupt (hz tick) */ + pcf->flags &= ~PCF50633_F_RTC_SECOND; + reg_set_bit_mask(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, PCF50633_INT1_SECOND); + return 0; + case RTC_PIE_ON: + /* ensable periodic interrupt (hz tick) */ + pcf->flags |= PCF50633_F_RTC_SECOND; + reg_clear_bits(pcf, PCF50633_REG_INT1M, PCF50633_INT1_SECOND); + return 0; + } + return -ENOIOCTLCMD; +} + +static int pcf50633_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + struct pcf50633_time pcf_tm; + + mutex_lock(&pcf->lock); + pcf_tm.sec = __reg_read(pcf, PCF50633_REG_RTCSC); + pcf_tm.min = __reg_read(pcf, PCF50633_REG_RTCMN); + pcf_tm.hour = __reg_read(pcf, PCF50633_REG_RTCHR); + pcf_tm.wkday = __reg_read(pcf, PCF50633_REG_RTCWD); + pcf_tm.day = __reg_read(pcf, PCF50633_REG_RTCDT); + pcf_tm.month = __reg_read(pcf, PCF50633_REG_RTCMT); + pcf_tm.year = __reg_read(pcf, PCF50633_REG_RTCYR); + mutex_unlock(&pcf->lock); + + DEBUGP("PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n", + pcf_tm.day, pcf_tm.month, pcf_tm.year, + pcf_tm.hour, pcf_tm.min, pcf_tm.sec); + + pcf2rtc_time(tm, &pcf_tm); + + DEBUGP("RTC_TIME: %u.%u.%u %u:%u:%u\n", + tm->tm_mday, tm->tm_mon, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + return 0; +} + +static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + struct pcf50633_time pcf_tm; + + DEBUGP("RTC_TIME: %u.%u.%u %u:%u:%u\n", + tm->tm_mday, tm->tm_mon, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + rtc2pcf_time(&pcf_tm, tm); + DEBUGP("PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n", + pcf_tm.day, pcf_tm.month, pcf_tm.year, + pcf_tm.hour, pcf_tm.min, pcf_tm.sec); + + mutex_lock(&pcf->lock); + /* FIXME: disable second interrupt */ + __reg_write(pcf, PCF50633_REG_RTCSC, pcf_tm.sec); + __reg_write(pcf, PCF50633_REG_RTCMN, pcf_tm.min); + __reg_write(pcf, PCF50633_REG_RTCHR, pcf_tm.hour); + __reg_write(pcf, PCF50633_REG_RTCWD, pcf_tm.wkday); + __reg_write(pcf, PCF50633_REG_RTCDT, pcf_tm.day); + __reg_write(pcf, PCF50633_REG_RTCMT, pcf_tm.month); + __reg_write(pcf, PCF50633_REG_RTCYR, pcf_tm.year); + /* FIXME: re-enable second interrupt */ + mutex_unlock(&pcf->lock); + + return 0; +} + +static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + struct pcf50633_time pcf_tm; + + mutex_lock(&pcf->lock); + pcf_tm.sec = __reg_read(pcf, PCF50633_REG_RTCSCA); + pcf_tm.min = __reg_read(pcf, PCF50633_REG_RTCMNA); + pcf_tm.hour = __reg_read(pcf, PCF50633_REG_RTCHRA); + pcf_tm.wkday = __reg_read(pcf, PCF50633_REG_RTCWDA); + pcf_tm.day = __reg_read(pcf, PCF50633_REG_RTCDTA); + pcf_tm.month = __reg_read(pcf, PCF50633_REG_RTCMTA); + pcf_tm.year = __reg_read(pcf, PCF50633_REG_RTCYRA); + mutex_unlock(&pcf->lock); + + pcf2rtc_time(&alrm->time, &pcf_tm); + + return 0; +} + +static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + struct pcf50633_time pcf_tm; + u_int8_t irqmask; + + rtc2pcf_time(&pcf_tm, &alrm->time); + + mutex_lock(&pcf->lock); + + /* disable alarm interrupt */ + irqmask = __reg_read(pcf, PCF50633_REG_INT1M); + irqmask |= PCF50633_INT1_ALARM; + __reg_write(pcf, PCF50633_REG_INT1M, irqmask); + + __reg_write(pcf, PCF50633_REG_RTCSCA, pcf_tm.sec); + __reg_write(pcf, PCF50633_REG_RTCMNA, pcf_tm.min); + __reg_write(pcf, PCF50633_REG_RTCHRA, pcf_tm.hour); + __reg_write(pcf, PCF50633_REG_RTCWDA, pcf_tm.wkday); + __reg_write(pcf, PCF50633_REG_RTCDTA, pcf_tm.day); + __reg_write(pcf, PCF50633_REG_RTCMTA, pcf_tm.month); + __reg_write(pcf, PCF50633_REG_RTCYRA, pcf_tm.year); + + if (alrm->enabled) { + /* (re-)enaable alarm interrupt */ + irqmask = __reg_read(pcf, PCF50633_REG_INT1M); + irqmask &= ~PCF50633_INT1_ALARM; + __reg_write(pcf, PCF50633_REG_INT1M, irqmask); + } + + mutex_unlock(&pcf->lock); + + /* FIXME */ + return 0; +} + +static struct rtc_class_ops pcf50633_rtc_ops = { + .ioctl = pcf50633_rtc_ioctl, + .read_time = pcf50633_rtc_read_time, + .set_time = pcf50633_rtc_set_time, + .read_alarm = pcf50633_rtc_read_alarm, + .set_alarm = pcf50633_rtc_set_alarm, +}; + +/*********************************************************************** + * Backlight device + ***********************************************************************/ + +static int pcf50633bl_get_intensity(struct backlight_device *bd) +{ + struct pcf50633_data *pcf = class_get_devdata(&bd->class_dev); + int intensity = reg_read(pcf, PCF50633_REG_LEDOUT); + + return intensity & 0x3f; +} + +static int pcf50633bl_set_intensity(struct backlight_device *bd) +{ + struct pcf50633_data *pcf = class_get_devdata(&bd->class_dev); + int intensity = bd->props.brightness; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + return reg_set_bit_mask(pcf, PCF50633_REG_LEDOUT, 0x3f, + intensity); +} + +static struct backlight_ops pcf50633bl_ops = { + .get_brightness = pcf50633bl_get_intensity, + .update_status = pcf50633bl_set_intensity, +}; + +/*********************************************************************** + * Driver initialization + ***********************************************************************/ + +#ifdef CONFIG_MACH_NEO1973_GTA02 +/* We currently place those platform devices here to make sure the device + * suspend/resume order is correct */ +static struct platform_device gta01_pm_gps_dev = { + .name ="gta01-pm-gps", +}; + +static struct platform_device gta01_pm_bt_dev = { + .name ="gta01-pm-bt", +}; +#endif + +static struct attribute *pcf_sysfs_entries[17] = { + &dev_attr_voltage_auto.attr, + &dev_attr_voltage_down1.attr, + &dev_attr_voltage_down2.attr, + &dev_attr_voltage_memldo.attr, + &dev_attr_voltage_ldo1.attr, + &dev_attr_voltage_ldo2.attr, + &dev_attr_voltage_ldo3.attr, + &dev_attr_voltage_ldo4.attr, + &dev_attr_voltage_ldo5.attr, + &dev_attr_voltage_ldo6.attr, + &dev_attr_voltage_hcldo.attr, + NULL +}; + +static struct attribute_group pcf_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcf_sysfs_entries, +}; + +static void populate_sysfs_group(struct pcf50633_data *pcf) +{ + int i = 0; + struct attribute **attr; + + for (attr = pcf_sysfs_entries; *attr; attr++) + i++; + + if (pcf->pdata->used_features & PCF50633_FEAT_MBC) { + pcf_sysfs_entries[i++] = &dev_attr_chgstate.attr; + pcf_sysfs_entries[i++] = &dev_attr_chgmode.attr; + } + + if (pcf->pdata->used_features & PCF50633_FEAT_CHGCUR) + pcf_sysfs_entries[i++] = &dev_attr_chgcur.attr; + + if (pcf->pdata->used_features & PCF50633_FEAT_BATVOLT) + pcf_sysfs_entries[i++] = &dev_attr_battvolt.attr; + + if (pcf->pdata->used_features & PCF50633_FEAT_BATTEMP) + pcf_sysfs_entries[i++] = &dev_attr_battemp.attr; + +} + +static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *new_client; + struct pcf50633_data *data; + int err = 0; + int irq; + + DEBUGP("entering\n"); + if (!pcf50633_pdev) { + printk(KERN_ERR "pcf50633: driver needs a platform_device!\n"); + return -EIO; + } + + irq = platform_get_irq(pcf50633_pdev, 0); + if (irq < 0) { + dev_err(&pcf50633_pdev->dev, "no irq in platform resources!\n"); + return -EIO; + } + + /* At the moment, we only support one PCF50633 in a system */ + if (pcf50633_global) { + dev_err(&pcf50633_pdev->dev, + "currently only one chip supported\n"); + return -EBUSY; + } + + if (!(data = kzalloc(sizeof(*data), GFP_KERNEL))) + return -ENOMEM; + + mutex_init(&data->lock); + INIT_WORK(&data->work, pcf50633_work); + data->irq = irq; + data->working = 0; + data->onkey_seconds = -1; + data->pdata = pcf50633_pdev->dev.platform_data; + + new_client = &data->client; + i2c_set_clientdata(new_client, data); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &pcf50633_driver; + new_client->flags = 0; + strlcpy(new_client->name, "pcf50633", I2C_NAME_SIZE); + + /* now we try to detect the chip */ + + /* register with i2c core */ + if ((err = i2c_attach_client(new_client))) { + dev_err(&new_client->dev, + "error during i2c_attach_client()\n"); + goto exit_free; + } + + pcf50633_global = data; + + populate_sysfs_group(data); + + err = sysfs_create_group(&new_client->dev.kobj, &pcf_attr_group); + if (err) { + dev_err(&new_client->dev, "error creating sysfs group\n"); + goto exit_detach; + } + + /* create virtual charger 'device' */ + + /* register power off handler with core power management */ + pm_power_off = &pcf50633_go_standby; + + /* configure interrupt mask */ + reg_write(data, PCF50633_REG_INT1M, PCF50633_INT1_SECOND); + reg_write(data, PCF50633_REG_INT2M, 0x00); + reg_write(data, PCF50633_REG_INT3M, 0x00); + reg_write(data, PCF50633_REG_INT4M, 0x00); + reg_write(data, PCF50633_REG_INT5M, 0x00); + + err = request_irq(irq, pcf50633_irq, SA_INTERRUPT, + "pcf50633", data); + if (err < 0) + goto exit_sysfs; + + set_irq_type(irq, IRQT_FALLING); + + if (enable_irq_wake(irq) < 0) + dev_err(&new_client->dev, "IRQ %u cannot be enabled as wake-up" + "source in this hardware revision!", irq); + + if (data->pdata->used_features & PCF50633_FEAT_RTC) { + data->rtc = rtc_device_register("pcf50633", &new_client->dev, + &pcf50633_rtc_ops, THIS_MODULE); + if (IS_ERR(data->rtc)) { + err = PTR_ERR(data->rtc); + goto exit_irq; + } + } + + if (data->pdata->used_features & PCF50633_FEAT_PWM_BL) { + data->backlight = backlight_device_register("pcf50633-bl", + &new_client->dev, + data, + &pcf50633bl_ops); + if (!data->backlight) + goto exit_rtc; + /* FIXME: are we sure we want default == off? */ + data->backlight->props.max_brightness = 16; + data->backlight->props.power = FB_BLANK_UNBLANK; + data->backlight->props.brightness = 0; + backlight_update_status(data->backlight); + } + + data->input_dev = input_allocate_device(); + if (!data->input_dev) + goto exit_pwm; + + data->input_dev->name = "FIC Neo1973 PMU events"; + data->input_dev->phys = "FIXME"; + data->input_dev->id.bustype = BUS_I2C; + data->input_dev->cdev.dev = &new_client->dev; + + data->input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR); + set_bit(KEY_POWER, data->input_dev->keybit); + set_bit(KEY_POWER2, data->input_dev->keybit); + set_bit(KEY_BATTERY, data->input_dev->keybit); + + input_register_device(data->input_dev); + + apm_get_power_status = pcf50633_get_power_status; + +#ifdef CONFIG_MACH_NEO1973_GTA02 + if (machine_is_neo1973_gta02()) { + gta01_pm_gps_dev.dev.parent = &new_client->dev; + switch (system_rev) { + case GTA02v1_SYSTEM_REV: + gta01_pm_bt_dev.dev.parent = &new_client->dev; + platform_device_register(>a01_pm_bt_dev); + break; + } + platform_device_register(>a01_pm_gps_dev); + } +#endif + + return 0; +exit_pwm: + if (data->pdata->used_features & PCF50633_FEAT_PWM_BL) + backlight_device_unregister(data->backlight); +exit_rtc: + if (data->pdata->used_features & PCF50633_FEAT_RTC) + rtc_device_unregister(pcf50633_global->rtc); +exit_irq: + free_irq(pcf50633_global->irq, pcf50633_global); +exit_sysfs: + pm_power_off = NULL; + sysfs_remove_group(&new_client->dev.kobj, &pcf_attr_group); +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(data); + pcf50633_global = NULL; + return err; +} + +static int pcf50633_attach_adapter(struct i2c_adapter *adapter) +{ + DEBUGP("entering, calling i2c_probe\n"); + return i2c_probe(adapter, &addr_data, &pcf50633_detect); +} + +static int pcf50633_detach_client(struct i2c_client *client) +{ + struct pcf50633_data *pcf = i2c_get_clientdata(client); + + DEBUGP("entering\n"); + + apm_get_power_status = NULL; + input_unregister_device(pcf->input_dev); + + if (pcf->pdata->used_features & PCF50633_FEAT_PWM_BL) + backlight_device_unregister(pcf->backlight); + + if (pcf->pdata->used_features & PCF50633_FEAT_RTC) + rtc_device_unregister(pcf->rtc); + + free_irq(pcf->irq, pcf); + + sysfs_remove_group(&client->dev.kobj, &pcf_attr_group); + + pm_power_off = NULL; + + kfree(pcf); + + return 0; +} + +#ifdef CONFIG_PM +#define INT1M_RESUMERS (PCF50633_INT1_ADPINS | \ + PCF50633_INT1_ADPREM | \ + PCF50633_INT1_USBINS | \ + PCF50633_INT1_USBREM | \ + PCF50633_INT1_ALARM) +#define INT2M_RESUMERS (PCF50633_INT2_ONKEYF) +#define INT3M_RESUMERS (PCF50633_INT3_BATFULL | \ + PCF50633_INT3_CHGHALT | \ + PCF50633_INT3_THLIMON | \ + PCF50633_INT3_THLIMOFF | \ + PCF50633_INT3_USBLIMON | \ + PCF50633_INT3_USBLIMOFF | \ + PCF50633_INT3_ONKEY1S) +#define INT4M_RESUMERS (PCF50633_INT4_LOWSYS | \ + PCF50633_INT4_LOWBAT | \ + PCF50633_INT4_HIGHTMP) +#define INT5M_RESUMERS (0) + +static int pcf50633_suspend(struct device *dev, pm_message_t state) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + int i; + + /* The general idea is to power down all unused power supplies, + * and then mask all PCF50606 interrup sources but EXTONR, ONKEYF + * and ALARM */ + + mutex_lock(&pcf->lock); + + /* Save all registers that don't "survive" standby state */ + pcf->standby_regs.ooctim2 = __reg_read(pcf, PCF50633_REG_OOCTIM2); + pcf->standby_regs.autoout = __reg_read(pcf, PCF50633_REG_AUTOOUT); + pcf->standby_regs.autoena = __reg_read(pcf, PCF50633_REG_AUTOENA); + pcf->standby_regs.automxc = __reg_read(pcf, PCF50633_REG_AUTOMXC); + pcf->standby_regs.down1out = __reg_read(pcf, PCF50633_REG_DOWN1OUT); + pcf->standby_regs.down1mxc = __reg_read(pcf, PCF50633_REG_DOWN1MXC); + pcf->standby_regs.down2out = __reg_read(pcf, PCF50633_REG_DOWN2OUT); + pcf->standby_regs.down2ena = __reg_read(pcf, PCF50633_REG_DOWN2ENA); + pcf->standby_regs.memldoout = __reg_read(pcf, PCF50633_REG_MEMLDOOUT); + pcf->standby_regs.memldoena = __reg_read(pcf, PCF50633_REG_MEMLDOENA); + pcf->standby_regs.ledout = __reg_read(pcf, PCF50633_REG_LEDOUT); + pcf->standby_regs.ledena = __reg_read(pcf, PCF50633_REG_LEDENA); + pcf->standby_regs.leddim = __reg_read(pcf, PCF50633_REG_LEDDIM); + /* FIXME: one big read? */ + for (i = 0; i < 7; i++) { + u_int8_t reg_out = PCF50633_REG_LDO1OUT + 2*i; + pcf->standby_regs.ldo[i].out = __reg_read(pcf, reg_out); + pcf->standby_regs.ldo[i].ena = __reg_read(pcf, reg_out+1); + } + + /* switch off power supplies that are not needed during suspend */ + for (i = 0; i < __NUM_PCF50633_REGULATORS; i++) { + if (!(pcf->pdata->rails[i].flags & PMU_VRAIL_F_SUSPEND_ON)) { + u_int8_t tmp; + + DEBUGP("disabling pcf50633 regulator %u\n", i); + /* we cannot use pcf50633_onoff_set() because we're + * already under the mutex */ + tmp = __reg_read(pcf, regulator_registers[i]+1); + tmp &= 0xfe; + __reg_write(pcf, regulator_registers[i]+1, tmp); + } + } + + pcf->standby_regs.int1m = __reg_read(pcf, PCF50633_REG_INT1M); + pcf->standby_regs.int2m = __reg_read(pcf, PCF50633_REG_INT2M); + pcf->standby_regs.int3m = __reg_read(pcf, PCF50633_REG_INT3M); + pcf->standby_regs.int4m = __reg_read(pcf, PCF50633_REG_INT4M); + pcf->standby_regs.int5m = __reg_read(pcf, PCF50633_REG_INT5M); + __reg_write(pcf, PCF50633_REG_INT1M, ~INT1M_RESUMERS & 0xff); + __reg_write(pcf, PCF50633_REG_INT2M, ~INT2M_RESUMERS & 0xff); + __reg_write(pcf, PCF50633_REG_INT3M, ~INT3M_RESUMERS & 0xff); + __reg_write(pcf, PCF50633_REG_INT4M, ~INT4M_RESUMERS & 0xff); + __reg_write(pcf, PCF50633_REG_INT5M, ~INT5M_RESUMERS & 0xff); + + mutex_unlock(&pcf->lock); + + return 0; +} + +static int pcf50633_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633_data *pcf = i2c_get_clientdata(client); + int i; + + mutex_lock(&pcf->lock); + + /* Resume all saved registers that don't "survive" standby state */ + __reg_write(pcf, PCF50633_REG_INT1M, pcf->standby_regs.int1m); + __reg_write(pcf, PCF50633_REG_INT2M, pcf->standby_regs.int2m); + __reg_write(pcf, PCF50633_REG_INT3M, pcf->standby_regs.int3m); + __reg_write(pcf, PCF50633_REG_INT4M, pcf->standby_regs.int4m); + __reg_write(pcf, PCF50633_REG_INT5M, pcf->standby_regs.int5m); + + __reg_write(pcf, PCF50633_REG_OOCTIM2, pcf->standby_regs.ooctim2); + __reg_write(pcf, PCF50633_REG_AUTOOUT, pcf->standby_regs.autoout); + __reg_write(pcf, PCF50633_REG_AUTOMXC, pcf->standby_regs.automxc); + __reg_write(pcf, PCF50633_REG_DOWN1OUT, pcf->standby_regs.down1out); + __reg_write(pcf, PCF50633_REG_DOWN1MXC, pcf->standby_regs.down1mxc); + __reg_write(pcf, PCF50633_REG_DOWN2OUT, pcf->standby_regs.down2out); + __reg_write(pcf, PCF50633_REG_DOWN2ENA, pcf->standby_regs.down2ena); + __reg_write(pcf, PCF50633_REG_MEMLDOOUT, pcf->standby_regs.memldoout); + __reg_write(pcf, PCF50633_REG_MEMLDOENA, pcf->standby_regs.memldoena); + __reg_write(pcf, PCF50633_REG_LEDOUT, pcf->standby_regs.ledout); + __reg_write(pcf, PCF50633_REG_LEDENA, pcf->standby_regs.ledena); + __reg_write(pcf, PCF50633_REG_LEDDIM, pcf->standby_regs.leddim); + /* FIXME: one big read? */ + for (i = 0; i < 7; i++) { + u_int8_t reg_out = PCF50633_REG_LDO1OUT + 2*i; + __reg_write(pcf, reg_out, pcf->standby_regs.ldo[i].out); + __reg_write(pcf, reg_out+1, pcf->standby_regs.ldo[i].ena); + } + + mutex_unlock(&pcf->lock); + + return 0; +} +#else +#define pcf50633_suspend NULL +#define pcf50633_resume NULL +#endif + +static struct i2c_driver pcf50633_driver = { + .driver = { + .name = "pcf50633", + .suspend= &pcf50633_suspend, + .resume = &pcf50633_resume, + }, + .id = I2C_DRIVERID_PCF50633, + .attach_adapter = &pcf50633_attach_adapter, + .detach_client = &pcf50633_detach_client, +}; + +/* platform driver, since i2c devices don't have platform_data */ +static int __init pcf50633_plat_probe(struct platform_device *pdev) +{ + struct pcf50633_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -ENODEV; + + pcf50633_pdev = pdev; + + return 0; +} + +static int pcf50633_plat_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver pcf50633_plat_driver = { + .probe = pcf50633_plat_probe, + .remove = pcf50633_plat_remove, + .driver = { + .owner = THIS_MODULE, + .name = "pcf50633", + }, +}; + +static int __init pcf50633_init(void) +{ + int rc; + + if (!(rc = platform_driver_register(&pcf50633_plat_driver))) + rc = i2c_add_driver(&pcf50633_driver); + + return rc; +} + +static void pcf50633_exit(void) +{ + i2c_del_driver(&pcf50633_driver); + platform_driver_unregister(&pcf50633_plat_driver); +} + +MODULE_AUTHOR("Harald Welte "); +MODULE_LICENSE("GPL"); + +module_init(pcf50633_init); +module_exit(pcf50633_exit); Index: linux-2.6.21.3-moko/include/linux/pcf50633.h =================================================================== --- /dev/null +++ linux-2.6.21.3-moko/include/linux/pcf50633.h @@ -0,0 +1,103 @@ +#ifndef _LINUX_PCF50633_H +#define _LINUX_PCF50633_H + +/* public in-kernel pcf50633 api */ +enum pcf50633_regulator_id { + PCF50633_REGULATOR_AUTO, + PCF50633_REGULATOR_DOWN1, + PCF50633_REGULATOR_DOWN2, + PCF50633_REGULATOR_MEMLDO, + PCF50633_REGULATOR_LDO1, + PCF50633_REGULATOR_LDO2, + PCF50633_REGULATOR_LDO3, + PCF50633_REGULATOR_LDO4, + PCF50633_REGULATOR_LDO5, + PCF50633_REGULATOR_LDO6, + PCF50633_REGULATOR_HCLDO, + __NUM_PCF50633_REGULATORS +}; + +struct pcf50633_data; +extern struct pcf50633_data *pcf50633_global; + +extern void +pcf50633_go_standby(void); + +extern void +pcf50633_gpo0_set(struct pcf50633_data *pcf, int on); + +extern int +pcf50633_gpo0_get(struct pcf50633_data *pcf); + +extern int +pcf50633_voltage_set(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg, + unsigned int millivolts); +extern unsigned int +pcf50633_voltage_get(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg); +extern int +pcf50633_onoff_get(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg); + +extern int +pcf50633_onoff_set(struct pcf50633_data *pcf, + enum pcf50633_regulator_id reg, int on); + +extern void +pcf50633_charge_fast(struct pcf50633_data *pcf, int on); + +/* FIXME: sharded with pcf50606 */ +#define PMU_VRAIL_F_SUSPEND_ON 0x00000001 /* Remains on during suspend */ +#define PMU_VRAIL_F_UNUSED 0x00000002 /* This rail is not used */ +struct pmu_voltage_rail { + char *name; + unsigned int flags; + struct { + unsigned int init; + unsigned int max; + } voltage; +}; + +enum pmu_event { + PMU_EVT_NONE, + PMU_EVT_INSERT, + PMU_EVT_REMOVE, + PMU_EVT_USB_INSERT, + PMU_EVT_USB_REMOVE, + __NUM_PMU_EVTS +}; + +typedef int pmu_cb(struct device *dev, unsigned int feature, + enum pmu_event event); + +#define PCF50633_FEAT_EXTON 0x00000001 /* not yet supported */ +#define PCF50633_FEAT_MBC 0x00000002 +#define PCF50633_FEAT_BBC 0x00000004 /* not yet supported */ +#define PCF50633_FEAT_RTC 0x00000040 +#define PCF50633_FEAT_CHGCUR 0x00000100 +#define PCF50633_FEAT_BATVOLT 0x00000200 +#define PCF50633_FEAT_BATTEMP 0x00000400 +#define PCF50633_FEAT_PWM_BL 0x00000800 + +struct pcf50633_platform_data { + /* general */ + unsigned int used_features; + unsigned int onkey_seconds_required; + + /* voltage regulator related */ + struct pmu_voltage_rail rails[__NUM_PCF50633_REGULATORS]; + unsigned int used_regulators; + + /* charger related */ + unsigned int r_fix_batt; + unsigned int r_fix_batt_par; + unsigned int r_sense_milli; + + struct { + u_int8_t mbcc3; /* charger voltage / current */ + } charger; + pmu_cb *cb; +}; + +#endif /* _PCF50633_H */ Index: linux-2.6.21.3-moko/include/linux/i2c-id.h =================================================================== --- linux-2.6.21.3-moko.orig/include/linux/i2c-id.h +++ linux-2.6.21.3-moko/include/linux/i2c-id.h @@ -161,6 +161,7 @@ #define I2C_DRIVERID_OV7670 1048 /* Omnivision 7670 camera */ #define I2C_DRIVERID_PCF50606 1049 #define I2C_DRIVERID_TSL256X 1050 +#define I2C_DRIVERID_PCF50633 1051 /* * ---- Adapter types ---------------------------------------------------- Index: linux-2.6.21.3-moko/drivers/i2c/chips/pcf50633.h =================================================================== --- /dev/null +++ linux-2.6.21.3-moko/drivers/i2c/chips/pcf50633.h @@ -0,0 +1,402 @@ +#ifndef _PCF50633_H +#define _PCF50633_H + +/* Philips PCF50633 Power Managemnt Unit (PMU) driver + * (C) 2006-2007 by OpenMoko, Inc. + * Author: Harald Welte + * + */ + +enum pfc50633_regs { + PCF50633_REG_VERSION = 0x00, + PCF50633_REG_VARIANT = 0x01, + PCF50633_REG_INT1 = 0x02, /* Interrupt Status */ + PCF50633_REG_INT2 = 0x03, /* Interrupt Status */ + PCF50633_REG_INT3 = 0x04, /* Interrupt Status */ + PCF50633_REG_INT4 = 0x05, /* Interrupt Status */ + PCF50633_REG_INT5 = 0x06, /* Interrupt Status */ + PCF50633_REG_INT1M = 0x07, /* Interrupt Mask */ + PCF50633_REG_INT2M = 0x08, /* Interrupt Mask */ + PCF50633_REG_INT3M = 0x09, /* Interrupt Mask */ + PCF50633_REG_INT4M = 0x0a, /* Interrupt Mask */ + PCF50633_REG_INT5M = 0x0b, /* Interrupt Mask */ + PCF50633_REG_OOCSHDWN = 0x0c, + PCF50633_REG_OOCWAKE = 0x0d, + PCF50633_REG_OOCTIM1 = 0x0e, + PCF50633_REG_OOCTIM2 = 0x0f, + PCF50633_REG_OOCMODE = 0x10, + PCF50633_REG_OOCCTL = 0x11, + PCF50633_REG_OOCSTAT = 0x12, + PCF50633_REG_GPIOCTL = 0x13, + PCF50633_REG_GPIO1CFG = 0x14, + PCF50633_REG_GPIO2CFG = 0x15, + PCF50633_REG_GPIO3CFG = 0x16, + PCF50633_REG_GPOCFG = 0x17, + PCF50633_REG_BVMCTL = 0x18, + PCF50633_REG_SVMCTL = 0x19, + PCF50633_REG_AUTOOUT = 0x1a, + PCF50633_REG_AUTOENA = 0x1b, + PCF50633_REG_AUTOCTL = 0x1c, + PCF50633_REG_AUTOMXC = 0x1d, + PCF50633_REG_DOWN1OUT = 0x1e, + PCF50633_REG_DOWN1ENA = 0x1f, + PCF50633_REG_DOWN1CTL = 0x20, + PCF50633_REG_DOWN1MXC = 0x21, + PCF50633_REG_DOWN2OUT = 0x22, + PCF50633_REG_DOWN2ENA = 0x23, + PCF50633_REG_DOWN2CTL = 0x24, + PCF50633_REG_DOWN2MXC = 0x25, + PCF50633_REG_MEMLDOOUT = 0x26, + PCF50633_REG_MEMLDOENA = 0x27, + PCF50633_REG_LEDOUT = 0x28, + PCF50633_REG_LEDENA = 0x29, + PCF50633_REG_LEDCTL = 0x2a, + PCF50633_REG_LEDDIM = 0x2b, + /* reserved */ + PCF50633_REG_LDO1OUT = 0x2d, + PCF50633_REG_LDO1ENA = 0x2e, + PCF50633_REG_LDO2OUT = 0x2f, + PCF50633_REG_LDO2ENA = 0x30, + PCF50633_REG_LDO3OUT = 0x31, + PCF50633_REG_LDO3ENA = 0x32, + PCF50633_REG_LDO4OUT = 0x33, + PCF50633_REG_LDO4ENA = 0x34, + PCF50633_REG_LDO5OUT = 0x35, + PCF50633_REG_LDO5ENA = 0x36, + PCF50633_REG_LDO6OUT = 0x37, + PCF50633_REG_LDO6ENA = 0x38, + PCF50633_REG_HCLDOOUT = 0x39, + PCF50633_REG_HCLDOENA = 0x3a, + PCF50633_REG_STBYCTL1 = 0x3b, + PCF50633_REG_STBYCTL2 = 0x3c, + PCF50633_REG_DEBPF1 = 0x3d, + PCF50633_REG_DEBPF2 = 0x3e, + PCF50633_REG_DEBPF3 = 0x3f, + PCF50633_REG_HCLDOOVL = 0x40, + PCF50633_REG_DCDCSTAT = 0x41, + PCF50633_REG_LDOSTAT = 0x42, + PCF50633_REG_MBCC1 = 0x43, + PCF50633_REG_MBCC2 = 0x44, + PCF50633_REG_MBCC3 = 0x45, + PCF50633_REG_MBCC4 = 0x46, + PCF50633_REG_MBCC5 = 0x47, + PCF50633_REG_MBCC6 = 0x48, + PCF50633_REG_MBCC7 = 0x49, + PCF50633_REG_MBCC8 = 0x4a, + PCF50633_REG_MBCS1 = 0x4b, + PCF50633_REG_MBCS2 = 0x4c, + PCF50633_REG_MBCS3 = 0x4d, + PCF50633_REG_BBCCTL = 0x4e, + PCF50633_REG_ALMGAIN = 0x4f, + PCF50633_REG_ALMDATA = 0x50, + /* reserved */ + PCF50633_REG_ADCC3 = 0x52, + PCF50633_REG_ADCC2 = 0x53, + PCF50633_REG_ADCC1 = 0x54, + PCF50633_REG_ADCS1 = 0x55, + PCF50633_REG_ADCS2 = 0x56, + PCF50633_REG_ADCS3 = 0x57, + /* reserved */ + PCF50633_REG_RTCSC = 0x59, /* Second */ + PCF50633_REG_RTCMN = 0x5a, /* Minute */ + PCF50633_REG_RTCHR = 0x5b, /* Hour */ + PCF50633_REG_RTCWD = 0x5c, /* Weekday */ + PCF50633_REG_RTCDT = 0x5d, /* Day */ + PCF50633_REG_RTCMT = 0x5e, /* Month */ + PCF50633_REG_RTCYR = 0x5f, /* Year */ + PCF50633_REG_RTCSCA = 0x60, /* Alarm Second */ + PCF50633_REG_RTCMNA = 0x61, /* Alarm Minute */ + PCF50633_REG_RTCHRA = 0x62, /* Alarm Hour */ + PCF50633_REG_RTCWDA = 0x63, /* Alarm Weekday */ + PCF50633_REG_RTCDTA = 0x64, /* Alarm Day */ + PCF50633_REG_RTCMTA = 0x65, /* Alarm Month */ + PCF50633_REG_RTCYRA = 0x66, /* Alarm Year */ + + PCF50633_REG_MEMBYTE0 = 0x67, + PCF50633_REG_MEMBYTE1 = 0x68, + PCF50633_REG_MEMBYTE2 = 0x69, + PCF50633_REG_MEMBYTE3 = 0x6a, + PCF50633_REG_MEMBYTE4 = 0x6b, + PCF50633_REG_MEMBYTE5 = 0x6c, + PCF50633_REG_MEMBYTE6 = 0x6d, + PCF50633_REG_MEMBYTE7 = 0x6e, + /* reserved */ + PCF50633_REG_DCDCPFM = 0x84, + __NUM_PCF50633_REGS +}; + +enum pcf50633_reg_int1 { + PCF50633_INT1_ADPINS = 0x01, /* Adapter inserted */ + PCF50633_INT1_ADPREM = 0x02, /* Adapter removed */ + PCF50633_INT1_USBINS = 0x04, /* USB inserted */ + PCF50633_INT1_USBREM = 0x08, /* USB removed */ + /* reserved */ + PCF50633_INT1_ALARM = 0x40, /* RTC alarm time is reached */ + PCF50633_INT1_SECOND = 0x80, /* RTC periodic second interrupt */ +}; + +enum pcf50633_reg_int2 { + PCF50633_INT2_ONKEYR = 0x01, /* ONKEY rising edge */ + PCF50633_INT2_ONKEYF = 0x02, /* ONKEY falling edge */ + PCF50633_INT2_EXTON1R = 0x04, /* EXTON1 rising edge */ + PCF50633_INT2_EXTON1F = 0x08, /* EXTON1 falling edge */ + PCF50633_INT2_EXTON2R = 0x10, /* EXTON2 rising edge */ + PCF50633_INT2_EXTON2F = 0x20, /* EXTON2 falling edge */ + PCF50633_INT2_EXTON3R = 0x40, /* EXTON3 rising edge */ + PCF50633_INT2_EXTON3F = 0x80, /* EXTON3 falling edge */ +}; + +enum pcf50633_reg_int3 { + PCF50633_INT3_BATFULL = 0x01, /* Battery full */ + PCF50633_INT3_CHGHALT = 0x02, /* Charger halt */ + PCF50633_INT3_THLIMON = 0x04, + PCF50633_INT3_THLIMOFF = 0x08, + PCF50633_INT3_USBLIMON = 0x10, + PCF50633_INT3_USBLIMOFF = 0x20, + PCF50633_INT3_ADCRDY = 0x40, /* ADC conversion finished */ + PCF50633_INT3_ONKEY1S = 0x80, /* ONKEY pressed 1 second */ +}; + +enum pcf50633_reg_int4 { + PCF50633_INT4_LOWSYS = 0x01, + PCF50633_INT4_LOWBAT = 0x02, + PCF50633_INT4_HIGHTMP = 0x04, + PCF50633_INT4_AUTOPWRFAIL = 0x08, + PCF50633_INT4_DWN1PWRFAIL = 0x10, + PCF50633_INT4_DWN2PWRFAIL = 0x20, + PCF50633_INT4_LEDPWRFAIL = 0x40, + PCF50633_INT4_LEDOVP = 0x80, +}; + +enum pcf50633_reg_int5 { + PCF50633_INT5_LDO1PWRFAIL = 0x01, + PCF50633_INT5_LDO2PWRFAIL = 0x02, + PCF50633_INT5_LDO3PWRFAIL = 0x04, + PCF50633_INT5_LDO4PWRFAIL = 0x08, + PCF50633_INT5_LDO5PWRFAIL = 0x10, + PCF50633_INT5_LDO6PWRFAIL = 0x20, + PCF50633_INT5_HCLDOPWRFAIL = 0x40, + PCF50633_INT5_HCLDOOVL = 0x80, +}; + +enum pcf50633_reg_oocshdwn { + PCF50633_OOCSHDWN_GOSTDBY = 0x01, + PCF50633_OOCSHDWN_TOTRST = 0x04, + PCF50633_OOCSHDWN_COLDBOOT = 0x08, +}; + +enum pcf50633_reg_oocwake { + PCF50633_OOCWAKE_ONKEY = 0x01, + PCF50633_OOCWAKE_EXTON1 = 0x02, + PCF50633_OOCWAKE_EXTON2 = 0x04, + PCF50633_OOCWAKE_EXTON3 = 0x08, + PCF50633_OOCWAKE_RTC = 0x10, + /* reserved */ + PCF50633_OOCWAKE_USB = 0x40, + PCF50633_OOCWAKE_ADP = 0x80, +}; + +enum pcf50633_reg_mbcc1 { + PCF50633_MBCC1_CHGENA = 0x01, /* Charger enable */ + PCF50633_MBCC1_AUTOSTOP = 0x02, + PCF50633_MBCC1_AUTORES = 0x04, /* automatic resume */ + PCF50633_MBCC1_RESUME = 0x08, /* explicit resume cmd */ + PCF50633_MBCC1_RESTART = 0x10, /* restart charging */ + PCF50633_MBCC1_PREWDTIME_60M = 0x20, /* max. precharging time */ + PCF50633_MBCC1_WDTIME_1H = 0x00, + PCF50633_MBCC1_WDTIME_2H = 0x40, + PCF50633_MBCC1_WDTIME_4H = 0x80, + PCF50633_MBCC1_WDTIME_6H = 0xc0, +}; +#define PCF50633_MBCC1_WDTIME_MASK 0xc0 + +enum pcf50633_reg_mbcc2 { + PCF50633_MBCC2_VBATCOND_2V7 = 0x00, + PCF50633_MBCC2_VBATCOND_2V85 = 0x01, + PCF50633_MBCC2_VBATCOND_3V0 = 0x02, + PCF50633_MBCC2_VBATCOND_3V15 = 0x03, + PCF50633_MBCC2_VMAX_4V = 0x00, + PCF50633_MBCC2_VMAX_4V20 = 0x28, + PCF50633_MBCC2_VRESDEBTIME_64S = 0x80, /* debounce time (32/64sec) */ +}; +#define PCF50633_MBCC2_VBATCOND_MASK 0x03 +#define PCF50633_MBCC2_VMAX_MASK 0x3c + +enum pcf50633_reg_adcc1 { + PCF50633_ADCC1_ADCSTART = 0x01, + PCF50633_ADCC1_RES_10BIT = 0x02, + PCF50633_ADCC1_AVERAGE_NO = 0x00, + PCF50633_ADCC1_AVERAGE_4 = 0x04, + PCF50633_ADCC1_AVERAGE_8 = 0x08, + PCF50633_ADCC1_AVERAGE_16 = 0x0c, + + PCF50633_ADCC1_MUX_BATSNS_RES = 0x00, + PCF50633_ADCC1_MUX_BATSNS_SUBTR = 0x10, + PCF50633_ADCC1_MUX_ADCIN2_RES = 0x20, + PCF50633_ADCC1_MUX_ADCIN2_SUBTR = 0x30, + PCF50633_ADCC1_MUX_BATTEMP = 0x60, + PCF50633_ADCC1_MUX_ADCIN1 = 0x70, +}; +#define PCF50633_ADCC1_AVERAGE_MASK 0x0c +#define PCF50633_ADCC1_ADCMUX_MASK 0xf0 + +enum pcf50633_reg_adcc2 { + PCF50633_ADCC2_RATIO_NONE = 0x00, + PCF50633_ADCC2_RATIO_BATTEMP = 0x01, + PCF50633_ADCC2_RATIO_ADCIN1 = 0x02, + PCF50633_ADCC2_RATIO_BOTH = 0x03, + PCF50633_ADCC2_RATIOSETTL_100US = 0x04, +}; +#define PCF50633_ADCC2_RATIO_MASK 0x03 + +enum pcf50633_reg_adcc3 { + PCF50633_ADCC3_ACCSW_EN = 0x01, + PCF50633_ADCC3_NTCSW_EN = 0x04, + PCF50633_ADCC3_RES_DIV_TWO = 0x10, + PCF50633_ADCC3_RES_DIV_THREE = 0x00, +}; + +enum pcf50633_reg_adcs3 { + PCF50633_ADCS3_REF_NTCSW = 0x00, + PCF50633_ADCS3_REF_ACCSW = 0x10, + PCF50633_ADCS3_REF_2V0 = 0x20, + PCF50633_ADCS3_REF_VISA = 0x30, + PCF50633_ADCS3_REF_2V0_2 = 0x70, + PCF50633_ADCS3_ADCRDY = 0x80, +}; +#define PCF50633_ADCS3_ADCDAT1L_MASK 0x03 +#define PCF50633_ADCS3_ADCDAT2L_MASK 0x0c +#define PCF50633_ADCS3_ADCDAT2L_SHIFT 2 +#define PCF50633_ASCS3_REF_MASK 0x70 + +enum pcf50633_regulator_enable { + PCF50633_REGULATOR_ON = 0x01, + PCF50633_REGULATOR_ON_GPIO1 = 0x02, + PCF50633_REGULATOR_ON_GPIO2 = 0x04, + PCF50633_REGULATOR_ON_GPIO3 = 0x08, +}; +#define PCF50633_REGULATOR_ON_MASK 0x0f + +enum pcf50633_regulator_phase { + PCF50633_REGULATOR_ACTPH1 = 0x00, + PCF50633_REGULATOR_ACTPH2 = 0x10, + PCF50633_REGULATOR_ACTPH3 = 0x20, + PCF50633_REGULATOR_ACTPH4 = 0x30, +}; +#define PCF50633_REGULATOR_ACTPH_MASK 0x30 + +enum pcf50633_reg_gpocfg { + PCF50633_GPOCFG_GPOSEL_0 = 0x00, + PCF50633_GPOCFG_GPOSEL_LED_NFET = 0x01, + PCF50633_GPOCFG_GPOSEL_SYSxOK = 0x02, + PCF50633_GPOCFG_GPOSEL_CLK32K = 0x03, + PCF50633_GPOCFG_GPOSEL_ADAPUSB = 0x04, + PCF50633_GPOCFG_GPOSEL_USBxOK = 0x05, + PCF50633_GPOCFG_GPOSEL_ACTPH4 = 0x06, + PCF50633_GPOCFG_GPOSEL_1 = 0x07, + PCF50633_GPOCFG_GPOSEL_INVERSE = 0x08, +}; +#define PCF50633_GPOCFG_GPOSEL_MASK 0x07 + +#if 0 +enum pcf50633_reg_mbcc1 { + PCF50633_MBCC1_CHGENA = 0x01, + PCF50633_MBCC1_AUTOSTOP = 0x02, + PCF50633_MBCC1_AUTORES = 0x04, + PCF50633_MBCC1_RESUME = 0x08, + PCF50633_MBCC1_RESTART = 0x10, + PCF50633_MBCC1_PREWDTIME_30MIN = 0x00, + PCF50633_MBCC1_PREWDTIME_60MIN = 0x20, + PCF50633_MBCC1_WDTIME_2HRS = 0x40, + PCF50633_MBCC1_WDTIME_4HRS = 0x80, + PCF50633_MBCC1_WDTIME_6HRS = 0xc0, +}; + +enum pcf50633_reg_mbcc2 { + PCF50633_MBCC2_VBATCOND_2V7 = 0x00, + PCF50633_MBCC2_VBATCOND_2V85 = 0x01, + PCF50633_MBCC2_VBATCOND_3V0 = 0x02, + PCF50633_MBCC2_VBATCOND_3V15 = 0x03, + PCF50633_MBCC2_VRESDEBTIME_64S = 0x80, +}; +#define PCF50633_MBCC2_VMAX_MASK 0x3c +#endif + +enum pcf50633_reg_mbcc7 { + PCF50633_MBCC7_USB_100mA = 0x00, + PCF50633_MBCC7_USB_500mA = 0x01, + PCF50633_MBCC7_USB_1000mA = 0x02, + PCF50633_MBCC7_USB_SUSPEND = 0x03, + PCF50633_MBCC7_BATTEMP_EN = 0x04, + PCF50633_MBCC7_BATSYSIMAX_1A6 = 0x00, + PCF50633_MBCC7_BATSYSIMAX_1A8 = 0x40, + PCF50633_MBCC7_BATSYSIMAX_2A0 = 0x80, + PCF50633_MBCC7_BATSYSIMAX_2A2 = 0xc0, +}; +#define PCF56033_MBCC7_USB_MASK 0x03 + +enum pcf50633_reg_mbcc8 { + PCF50633_MBCC8_USBENASUS = 0x10, +}; + +enum pcf50633_reg_mbcs1 { + PCF50633_MBCS1_USBPRES = 0x01, + PCF50633_MBCS1_USBOK = 0x02, + PCF50633_MBCS1_ADAPTPRES = 0x04, + PCF50633_MBCS1_ADAPTOK = 0x08, + PCF50633_MBCS1_TBAT_OK = 0x00, + PCF50633_MBCS1_TBAT_ABOVE = 0x10, + PCF50633_MBCS1_TBAT_BELOW = 0x20, + PCF50633_MBCS1_TBAT_UNDEF = 0x30, + PCF50633_MBCS1_PREWDTEXP = 0x40, + PCF50633_MBCS1_WDTEXP = 0x80, +}; + +enum pcf50633_reg_mbcs2_mbcmod { + PCF50633_MBCS2_MBC_PLAY = 0x00, + PCF50633_MBCS2_MBC_USB_PRE = 0x01, + PCF50633_MBCS2_MBC_USB_PRE_WAIT = 0x02, + PCF50633_MBCS2_MBC_USB_FAST = 0x03, + PCF50633_MBCS2_MBC_USB_FAST_WAIT= 0x04, + PCF50633_MBCS2_MBC_USB_SUSPEND = 0x05, + PCF50633_MBCS2_MBC_ADP_PRE = 0x06, + PCF50633_MBCS2_MBC_ADP_PRE_WAIT = 0x07, + PCF50633_MBCS2_MBC_ADP_FAST = 0x08, + PCF50633_MBCS2_MBC_ADP_FAST_WAIT= 0x09, + PCF50633_MBCS2_MBC_BAT_FULL = 0x0a, + PCF50633_MBCS2_MBC_HALT = 0x0b, +}; +#define PCF50633_MBCS2_MBC_MASK 0x0f +enum pcf50633_reg_mbcs2_chgstat { + PCF50633_MBCS2_CHGS_NONE = 0x00, + PCF50633_MBCS2_CHGS_ADAPTER = 0x10, + PCF50633_MBCS2_CHGS_USB = 0x20, + PCF50633_MBCS2_CHGS_BOTH = 0x30, +}; +#define PCF50633_MBCS2_RESSTAT_AUTO 0x40 + +enum pcf50633_reg_mbcs3 { + PCF50633_MBCS3_USBLIM_PLAY = 0x01, + PCF50633_MBCS3_USBLIM_CGH = 0x02, + PCF50633_MBCS3_TLIM_PLAY = 0x04, + PCF50633_MBCS3_TLIM_CHG = 0x08, + PCF50633_MBCS3_ILIM = 0x10, /* 1: Ibat > Icutoff */ + PCF50633_MBCS3_VLIM = 0x20, /* 1: Vbat == Vmax */ + PCF50633_MBCS3_VBATSTAT = 0x40, /* 1: Vbat > Vbatcond */ + PCF50633_MBCS3_VRES = 0x80, /* 1: Vbat > Vth(RES) */ +}; + +/* this is to be provided by the board implementation */ +extern const u_int8_t pcf50633_initial_regs[__NUM_PCF50633_REGS]; + +void pcf50633_reg_write(u_int8_t reg, u_int8_t val); + +u_int8_t pcf50633_reg_read(u_int8_t reg); + +void pcf50633_reg_set_bit_mask(u_int8_t reg, u_int8_t mask, u_int8_t val); +void pcf50633_reg_clear_bits(u_int8_t reg, u_int8_t bits); + +void pcf50633_charge_autofast(int on); + +#endif /* _PCF50606_H */ +