mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-25 04:01:52 +02:00
a95f9f92e2
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@13619 3c298f89-4303-0410-b956-a3cf2f4a3e73
2444 lines
70 KiB
Diff
Executable File
2444 lines
70 KiB
Diff
Executable File
From cb7ab7ce6ad690be5f71bde73a9369d3640985e8 Mon Sep 17 00:00:00 2001
|
|
From: mokopatches <mokopatches@openmoko.org>
|
|
Date: Wed, 16 Jul 2008 14:44:11 +0100
|
|
Subject: [PATCH] gta01-pcf50606.patch
|
|
This is a NXP PCF50606 power management unit driver.
|
|
|
|
The PCF50606 is used in the FIC/OpenMoko Neo1973 GTA01 GSM phone.
|
|
|
|
Signed-off-by: Harald Welte <laforge@openmoko.org>
|
|
---
|
|
drivers/i2c/chips/Kconfig | 11 +
|
|
drivers/i2c/chips/Makefile | 1 +
|
|
drivers/i2c/chips/pcf50606.c | 1945 ++++++++++++++++++++++++++++++++++++++++++
|
|
drivers/i2c/chips/pcf50606.h | 302 +++++++
|
|
include/linux/i2c-id.h | 1 +
|
|
include/linux/pcf50606.h | 108 +++
|
|
6 files changed, 2368 insertions(+), 0 deletions(-)
|
|
create mode 100644 drivers/i2c/chips/pcf50606.c
|
|
create mode 100644 drivers/i2c/chips/pcf50606.h
|
|
create mode 100644 include/linux/pcf50606.h
|
|
|
|
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
|
|
index 2da2edf..e8e64aa 100644
|
|
--- a/drivers/i2c/chips/Kconfig
|
|
+++ b/drivers/i2c/chips/Kconfig
|
|
@@ -25,6 +25,17 @@ config SENSORS_EEPROM
|
|
This driver can also be built as a module. If so, the module
|
|
will be called eeprom.
|
|
|
|
+config SENSORS_PCF50606
|
|
+ tristate "Philips/NXP PCF50606"
|
|
+ depends on I2C
|
|
+ help
|
|
+ If you say yes here you get support for Philips/NXP PCF50606
|
|
+ PMU (Power Management Unit) chips.
|
|
+
|
|
+ This driver can also be built as a module. If so, the module
|
|
+ will be called pcf50606.
|
|
+
|
|
+
|
|
config SENSORS_PCF8574
|
|
tristate "Philips PCF8574 and PCF8574A"
|
|
depends on EXPERIMENTAL
|
|
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
|
|
index e47aca0..60d3d5a 100644
|
|
--- a/drivers/i2c/chips/Makefile
|
|
+++ b/drivers/i2c/chips/Makefile
|
|
@@ -13,6 +13,7 @@ obj-$(CONFIG_DS1682) += ds1682.o
|
|
obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o
|
|
obj-$(CONFIG_SENSORS_MAX6875) += max6875.o
|
|
obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o
|
|
+obj-$(CONFIG_SENSORS_PCF50606) += pcf50606.o
|
|
obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
|
|
obj-$(CONFIG_PCF8575) += pcf8575.o
|
|
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
|
|
diff --git a/drivers/i2c/chips/pcf50606.c b/drivers/i2c/chips/pcf50606.c
|
|
new file mode 100644
|
|
index 0000000..6626c68
|
|
--- /dev/null
|
|
+++ b/drivers/i2c/chips/pcf50606.c
|
|
@@ -0,0 +1,1945 @@
|
|
+/* Philips/NXP PCF50606 Power Management Unit (PMU) driver
|
|
+ *
|
|
+ * (C) 2006-2007 by OpenMoko, Inc.
|
|
+ * Authors: Harald Welte <laforge@openmoko.org>,
|
|
+ * Matt Hsu <matt@openmoko.org>
|
|
+ * 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
|
|
+ * - watchdog
|
|
+ * - adc driver (hw_sensors like)
|
|
+ * - pwm driver
|
|
+ * - backlight
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/rtc.h>
|
|
+#include <linux/bcd.h>
|
|
+#include <linux/watchdog.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/fb.h>
|
|
+#include <linux/backlight.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pcf50606.h>
|
|
+#include <linux/apm-emulation.h>
|
|
+
|
|
+#include <asm/mach-types.h>
|
|
+#include <asm/arch/gta01.h>
|
|
+
|
|
+#include "pcf50606.h"
|
|
+
|
|
+/* we use dev_dbg() throughout the code, but sometimes don't want to
|
|
+ * write an entire line of debug related information. This DEBUGPC
|
|
+ * macro is a continuation for dev_dbg() */
|
|
+#ifdef DEBUG
|
|
+#define DEBUGPC(x, args ...) printk(x, ## args)
|
|
+#else
|
|
+#define DEBUGPC(x, args ...)
|
|
+#endif
|
|
+
|
|
+/***********************************************************************
|
|
+ * Static data / structures
|
|
+ ***********************************************************************/
|
|
+
|
|
+static unsigned short normal_i2c[] = { 0x08, I2C_CLIENT_END };
|
|
+
|
|
+I2C_CLIENT_INSMOD_1(pcf50606);
|
|
+
|
|
+#define PCF50606_F_CHG_FAST 0x00000001 /* Charger Fast allowed */
|
|
+#define PCF50606_F_CHG_PRESENT 0x00000002 /* Charger present */
|
|
+#define PCF50606_F_CHG_FOK 0x00000004 /* Fast OK for battery */
|
|
+#define PCF50606_F_CHG_ERR 0x00000008 /* Charger Error */
|
|
+#define PCF50606_F_CHG_PROT 0x00000010 /* Charger Protection */
|
|
+#define PCF50606_F_CHG_READY 0x00000020 /* Charging completed */
|
|
+#define PCF50606_F_CHG_MASK 0x000000fc
|
|
+
|
|
+#define PCF50606_F_PWR_PRESSED 0x00000100
|
|
+#define PCF50606_F_RTC_SECOND 0x00000200
|
|
+
|
|
+enum close_state {
|
|
+ CLOSE_STATE_NOT,
|
|
+ CLOSE_STATE_ALLOW = 0x2342,
|
|
+};
|
|
+
|
|
+struct pcf50606_data {
|
|
+ struct i2c_client client;
|
|
+ struct pcf50606_platform_data *pdata;
|
|
+ struct backlight_device *backlight;
|
|
+ struct mutex lock;
|
|
+ unsigned int flags;
|
|
+ unsigned int working;
|
|
+ struct mutex working_lock;
|
|
+ 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 dcdc1, dcdc2;
|
|
+ u_int8_t dcdec1;
|
|
+ u_int8_t dcudc1;
|
|
+ u_int8_t ioregc;
|
|
+ u_int8_t d1regc1;
|
|
+ u_int8_t d2regc1;
|
|
+ u_int8_t d3regc1;
|
|
+ u_int8_t lpregc1;
|
|
+ u_int8_t adcc1, adcc2;
|
|
+ u_int8_t pwmc1;
|
|
+ u_int8_t int1m, int2m, int3m;
|
|
+ } standby_regs;
|
|
+#endif
|
|
+};
|
|
+
|
|
+static struct i2c_driver pcf50606_driver;
|
|
+
|
|
+/* This is an ugly construct on how to access the (currently single/global)
|
|
+ * pcf50606 handle from other code in the kernel. I didn't really come up with
|
|
+ * a more decent method of dynamically resolving this */
|
|
+struct pcf50606_data *pcf50606_global;
|
|
+EXPORT_SYMBOL_GPL(pcf50606_global);
|
|
+
|
|
+static struct platform_device *pcf50606_pdev;
|
|
+
|
|
+/* This is a 10k, B=3370 NTC Thermistor -10..79 centigrade */
|
|
+/* Table entries are offset by +0.5C so a properly rounded value is generated */
|
|
+static const u_int16_t ntc_table_10k_3370B[] = {
|
|
+ /* -10 */
|
|
+ 43888, 41819, 39862, 38010, 36257, 34596, 33024, 31534, 30121, 28781,
|
|
+ 27510, 26304, 25159, 24071, 23038, 22056, 21122, 20234, 19390, 18586,
|
|
+ 17821, 17093, 16399, 15738, 15107, 14506, 13933, 13387, 12865, 12367,
|
|
+ 11891, 11437, 11003, 10588, 10192, 9813, 9450, 9103, 8771, 8453,
|
|
+ 8149, 7857, 7578, 7310, 7054, 6808, 6572, 6346, 6129, 5920,
|
|
+ 5720, 5528, 5344, 5167, 4996, 4833, 4675, 4524, 4379, 4239,
|
|
+ 4104, 3975, 3850, 3730, 3614, 3503, 3396, 3292, 3193, 3097,
|
|
+ 3004, 2915, 2829, 2745, 2665, 2588, 2513, 2441, 2371, 2304,
|
|
+ 2239, 2176, 2116, 2057, 2000, 1945, 1892, 1841, 1791, 1743,
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+ * Low-Level routines
|
|
+ ***********************************************************************/
|
|
+
|
|
+static inline int __reg_write(struct pcf50606_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 pcf50606_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 pcf50606_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 pcf50606_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 pcf50606_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 pcf50606_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 pcf50606_data *pcf, int channel,
|
|
+ u_int16_t *data2)
|
|
+{
|
|
+ u_int8_t adcs2, adcs1;
|
|
+ u_int16_t ret;
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "entering (pcf=%p, channel=%u, data2=%p)\n",
|
|
+ pcf, channel, data2);
|
|
+
|
|
+ channel &= PCF50606_ADCC2_ADCMUX_MASK;
|
|
+
|
|
+ mutex_lock(&pcf->lock);
|
|
+
|
|
+ /* start ADC conversion of selected channel */
|
|
+ __reg_write(pcf, PCF50606_REG_ADCC2, channel |
|
|
+ PCF50606_ADCC2_ADCSTART | PCF50606_ADCC2_RES_10BIT);
|
|
+
|
|
+ do {
|
|
+ adcs2 = __reg_read(pcf, PCF50606_REG_ADCS2);
|
|
+ } while (!(adcs2 & PCF50606_ADCS2_ADCRDY));
|
|
+
|
|
+ adcs1 = __reg_read(pcf, PCF50606_REG_ADCS1);
|
|
+ ret = (adcs1 << 2) | (adcs2 & 0x03);
|
|
+
|
|
+ if (data2) {
|
|
+ adcs1 = __reg_read(pcf, PCF50606_REG_ADCS3);
|
|
+ *data2 = (adcs1 << 2) | ((adcs2 & 0x0c) >> 2);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "returning %u %u\n", ret,
|
|
+ data2 ? *data2 : 0);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+ * Voltage / ADC
|
|
+ ***********************************************************************/
|
|
+
|
|
+static u_int8_t dcudc_voltage(unsigned int millivolts)
|
|
+{
|
|
+ if (millivolts < 900)
|
|
+ return 0;
|
|
+ if (millivolts > 5500)
|
|
+ return 0x1f;
|
|
+ if (millivolts <= 3300) {
|
|
+ millivolts -= 900;
|
|
+ return millivolts/300;
|
|
+ }
|
|
+ if (millivolts < 4000)
|
|
+ return 0x0f;
|
|
+ else {
|
|
+ millivolts -= 4000;
|
|
+ return millivolts/100;
|
|
+ }
|
|
+}
|
|
+
|
|
+static unsigned int dcudc_2voltage(u_int8_t bits)
|
|
+{
|
|
+ bits &= 0x1f;
|
|
+ if (bits < 0x08)
|
|
+ return 900 + bits * 300;
|
|
+ else if (bits < 0x10)
|
|
+ return 3300;
|
|
+ else
|
|
+ return 4000 + bits * 100;
|
|
+}
|
|
+
|
|
+static u_int8_t dcdec_voltage(unsigned int millivolts)
|
|
+{
|
|
+ if (millivolts < 900)
|
|
+ return 0;
|
|
+ else if (millivolts > 3300)
|
|
+ return 0x0f;
|
|
+
|
|
+ millivolts -= 900;
|
|
+ return millivolts/300;
|
|
+}
|
|
+
|
|
+static unsigned int dcdec_2voltage(u_int8_t bits)
|
|
+{
|
|
+ bits &= 0x0f;
|
|
+ return 900 + bits*300;
|
|
+}
|
|
+
|
|
+static u_int8_t dcdc_voltage(unsigned int millivolts)
|
|
+{
|
|
+ if (millivolts < 900)
|
|
+ return 0;
|
|
+ else if (millivolts > 3600)
|
|
+ return 0x1f;
|
|
+
|
|
+ if (millivolts < 1500) {
|
|
+ millivolts -= 900;
|
|
+ return millivolts/25;
|
|
+ } else {
|
|
+ millivolts -= 1500;
|
|
+ return 0x18 + millivolts/300;
|
|
+ }
|
|
+}
|
|
+
|
|
+static unsigned int dcdc_2voltage(u_int8_t bits)
|
|
+{
|
|
+ bits &= 0x1f;
|
|
+ if ((bits & 0x18) == 0x18)
|
|
+ return 1500 + ((bits & 0x7) * 300);
|
|
+ else
|
|
+ return 900 + (bits * 25);
|
|
+}
|
|
+
|
|
+static u_int8_t dx_voltage(unsigned int millivolts)
|
|
+{
|
|
+ if (millivolts < 900)
|
|
+ return 0;
|
|
+ else if (millivolts > 3300)
|
|
+ return 0x18;
|
|
+
|
|
+ millivolts -= 900;
|
|
+ return millivolts/100;
|
|
+}
|
|
+
|
|
+static unsigned int dx_2voltage(u_int8_t bits)
|
|
+{
|
|
+ bits &= 0x1f;
|
|
+ return 900 + (bits * 100);
|
|
+}
|
|
+
|
|
+static const u_int8_t regulator_registers[__NUM_PCF50606_REGULATORS] = {
|
|
+ [PCF50606_REGULATOR_DCD] = PCF50606_REG_DCDC1,
|
|
+ [PCF50606_REGULATOR_DCDE] = PCF50606_REG_DCDEC1,
|
|
+ [PCF50606_REGULATOR_DCUD] = PCF50606_REG_DCUDC1,
|
|
+ [PCF50606_REGULATOR_D1REG] = PCF50606_REG_D1REGC1,
|
|
+ [PCF50606_REGULATOR_D2REG] = PCF50606_REG_D2REGC1,
|
|
+ [PCF50606_REGULATOR_D3REG] = PCF50606_REG_D3REGC1,
|
|
+ [PCF50606_REGULATOR_LPREG] = PCF50606_REG_LPREGC1,
|
|
+ [PCF50606_REGULATOR_IOREG] = PCF50606_REG_IOREGC,
|
|
+};
|
|
+
|
|
+int pcf50606_onoff_set(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg, int on)
|
|
+{
|
|
+ u_int8_t addr;
|
|
+
|
|
+ if (reg >= __NUM_PCF50606_REGULATORS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* IOREG cannot be powered off since it powers the PMU I2C */
|
|
+ if (reg == PCF50606_REGULATOR_IOREG)
|
|
+ return -EIO;
|
|
+
|
|
+ addr = regulator_registers[reg];
|
|
+
|
|
+ if (on == 0)
|
|
+ reg_set_bit_mask(pcf, addr, 0xe0, 0x00);
|
|
+ else
|
|
+ reg_set_bit_mask(pcf, addr, 0xe0, 0xe0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_onoff_set);
|
|
+
|
|
+int pcf50606_onoff_get(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg)
|
|
+{
|
|
+ u_int8_t val, addr;
|
|
+
|
|
+ if (reg >= __NUM_PCF50606_REGULATORS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ addr = regulator_registers[reg];
|
|
+ val = (reg_read(pcf, addr) & 0xe0) >> 5;
|
|
+
|
|
+ /* PWREN1 = 1, PWREN2 = 1, see table 16 of datasheet */
|
|
+ switch (val) {
|
|
+ case 0:
|
|
+ case 5:
|
|
+ return 0;
|
|
+ default:
|
|
+ return 1;
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_onoff_get);
|
|
+
|
|
+int pcf50606_voltage_set(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg,
|
|
+ unsigned int millivolts)
|
|
+{
|
|
+ u_int8_t volt_bits;
|
|
+ u_int8_t regnr;
|
|
+ int rc;
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "pcf=%p, reg=%d, mvolts=%d\n", pcf, reg,
|
|
+ millivolts);
|
|
+
|
|
+ if (reg >= __NUM_PCF50606_REGULATORS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (millivolts > pcf->pdata->rails[reg].voltage.max)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (reg) {
|
|
+ case PCF50606_REGULATOR_DCD:
|
|
+ volt_bits = dcdc_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, PCF50606_REG_DCDC1, 0x1f,
|
|
+ volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_DCDE:
|
|
+ volt_bits = dcdec_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, PCF50606_REG_DCDEC1, 0x0f,
|
|
+ volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_DCUD:
|
|
+ volt_bits = dcudc_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, PCF50606_REG_DCUDC1, 0x1f,
|
|
+ volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_D1REG:
|
|
+ case PCF50606_REGULATOR_D2REG:
|
|
+ case PCF50606_REGULATOR_D3REG:
|
|
+ regnr = PCF50606_REG_D1REGC1 + (reg - PCF50606_REGULATOR_D1REG);
|
|
+ volt_bits = dx_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, regnr, 0x1f, volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_LPREG:
|
|
+ volt_bits = dx_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, PCF50606_REG_LPREGC1, 0x1f,
|
|
+ volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_IOREG:
|
|
+ if (millivolts < 1800)
|
|
+ return -EINVAL;
|
|
+ volt_bits = dx_voltage(millivolts);
|
|
+ rc = reg_set_bit_mask(pcf, PCF50606_REG_IOREGC, 0x1f,
|
|
+ volt_bits);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_voltage_set);
|
|
+
|
|
+unsigned int pcf50606_voltage_get(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg)
|
|
+{
|
|
+ u_int8_t volt_bits;
|
|
+ u_int8_t regnr;
|
|
+ unsigned int rc = 0;
|
|
+
|
|
+ if (reg >= __NUM_PCF50606_REGULATORS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (reg) {
|
|
+ case PCF50606_REGULATOR_DCD:
|
|
+ volt_bits = reg_read(pcf, PCF50606_REG_DCDC1) & 0x1f;
|
|
+ rc = dcdc_2voltage(volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_DCDE:
|
|
+ volt_bits = reg_read(pcf, PCF50606_REG_DCDEC1) & 0x0f;
|
|
+ rc = dcdec_2voltage(volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_DCUD:
|
|
+ volt_bits = reg_read(pcf, PCF50606_REG_DCUDC1) & 0x1f;
|
|
+ rc = dcudc_2voltage(volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_D1REG:
|
|
+ case PCF50606_REGULATOR_D2REG:
|
|
+ case PCF50606_REGULATOR_D3REG:
|
|
+ regnr = PCF50606_REG_D1REGC1 + (reg - PCF50606_REGULATOR_D1REG);
|
|
+ volt_bits = reg_read(pcf, regnr) & 0x1f;
|
|
+ if (volt_bits > 0x18)
|
|
+ volt_bits = 0x18;
|
|
+ rc = dx_2voltage(volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_LPREG:
|
|
+ volt_bits = reg_read(pcf, PCF50606_REG_LPREGC1) & 0x1f;
|
|
+ if (volt_bits > 0x18)
|
|
+ volt_bits = 0x18;
|
|
+ rc = dx_2voltage(volt_bits);
|
|
+ break;
|
|
+ case PCF50606_REGULATOR_IOREG:
|
|
+ volt_bits = reg_read(pcf, PCF50606_REG_IOREGC) & 0x1f;
|
|
+ if (volt_bits > 0x18)
|
|
+ volt_bits = 0x18;
|
|
+ rc = dx_2voltage(volt_bits);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_voltage_get);
|
|
+
|
|
+/* go into 'STANDBY' mode, i.e. power off the main CPU and peripherals */
|
|
+void pcf50606_go_standby(void)
|
|
+{
|
|
+ reg_write(pcf50606_global, PCF50606_REG_OOCC1,
|
|
+ PCF50606_OOCC1_GOSTDBY);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_go_standby);
|
|
+
|
|
+void pcf50606_gpo0_set(struct pcf50606_data *pcf, int on)
|
|
+{
|
|
+ u_int8_t val;
|
|
+
|
|
+ if (on)
|
|
+ val = 0x07;
|
|
+ else
|
|
+ val = 0x0f;
|
|
+
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_GPOC1, 0x0f, val);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_gpo0_set);
|
|
+
|
|
+int pcf50606_gpo0_get(struct pcf50606_data *pcf)
|
|
+{
|
|
+ u_int8_t reg = reg_read(pcf, PCF50606_REG_GPOC1) & 0x0f;
|
|
+
|
|
+ if (reg == 0x07 || reg == 0x08)
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_gpo0_get);
|
|
+
|
|
+static void pcf50606_work(struct work_struct *work)
|
|
+{
|
|
+ struct pcf50606_data *pcf =
|
|
+ container_of(work, struct pcf50606_data, work);
|
|
+ u_int8_t pcfirq[3];
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&pcf->working_lock);
|
|
+ pcf->working = 1;
|
|
+ /*
|
|
+ * p35 pcf50606 datasheet rev 2.2:
|
|
+ * ''The system controller shall read all interrupt registers in
|
|
+ * one I2C read action''
|
|
+ * because if you don't INT# gets stuck asserted forever after a
|
|
+ * while
|
|
+ */
|
|
+ ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50606_REG_INT1, 3,
|
|
+ pcfirq);
|
|
+ if (ret != 3)
|
|
+ DEBUGPC("Oh crap PMU IRQ register read failed %d\n", ret);
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x:",
|
|
+ pcfirq[0], pcfirq[1], pcfirq[2]);
|
|
+
|
|
+ if (pcfirq[0] & PCF50606_INT1_ONKEYF) {
|
|
+ /* ONKEY falling edge (start of button press) */
|
|
+ DEBUGPC("ONKEYF ");
|
|
+ pcf->flags |= PCF50606_F_PWR_PRESSED;
|
|
+ input_report_key(pcf->input_dev, KEY_POWER, 1);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_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, PCF50606_REG_OOCC1,
|
|
+ PCF50606_OOCC1_TOTRST,
|
|
+ PCF50606_OOCC1_TOTRST);
|
|
+ /* enable SECOND interrupt (hz tick) */
|
|
+ reg_clear_bits(pcf, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_ONKEYR) {
|
|
+ /* ONKEY rising edge (end of button press) */
|
|
+ DEBUGPC("ONKEYR ");
|
|
+ pcf->flags &= ~PCF50606_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 & PCF50606_F_RTC_SECOND))
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_INT1M,
|
|
+ PCF50606_INT1_SECOND,
|
|
+ PCF50606_INT1_SECOND);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_EXTONR) {
|
|
+ DEBUGPC("EXTONR ");
|
|
+ input_report_key(pcf->input_dev, KEY_POWER2, 1);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_EXTONF) {
|
|
+ DEBUGPC("EXTONF ");
|
|
+ input_report_key(pcf->input_dev, KEY_POWER2, 0);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_SECOND) {
|
|
+ DEBUGPC("SECOND ");
|
|
+ if (pcf->flags & PCF50606_F_RTC_SECOND)
|
|
+ rtc_update_irq(pcf->rtc, 1,
|
|
+ RTC_PF | RTC_IRQF);
|
|
+
|
|
+ if (pcf->onkey_seconds >= 0 &&
|
|
+ pcf->flags & PCF50606_F_PWR_PRESSED) {
|
|
+ DEBUGPC("ONKEY_SECONDS(%u, OOCC1=0x%02x) ",
|
|
+ pcf->onkey_seconds,
|
|
+ reg_read(pcf, PCF50606_REG_OOCC1));
|
|
+ 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 to do if userspace doesn't
|
|
+ * shut down? Do we want to force it? */
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50606_INT1_ALARM) {
|
|
+ DEBUGPC("ALARM ");
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_RTC)
|
|
+ rtc_update_irq(pcf->rtc, 1,
|
|
+ RTC_AF | RTC_IRQF);
|
|
+ }
|
|
+
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGINS) {
|
|
+ /* Charger inserted */
|
|
+ DEBUGPC("CHGINS ");
|
|
+ input_report_key(pcf->input_dev, KEY_BATTERY, 1);
|
|
+ apm_queue_event(APM_POWER_STATUS_CHANGE);
|
|
+ pcf->flags |= PCF50606_F_CHG_PRESENT;
|
|
+ if (pcf->pdata->cb)
|
|
+ pcf->pdata->cb(&pcf->client.dev,
|
|
+ PCF50606_FEAT_MBC, PMU_EVT_INSERT);
|
|
+ /* FIXME: how to signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGRM) {
|
|
+ /* Charger removed */
|
|
+ DEBUGPC("CHGRM ");
|
|
+ input_report_key(pcf->input_dev, KEY_BATTERY, 0);
|
|
+ apm_queue_event(APM_POWER_STATUS_CHANGE);
|
|
+ pcf->flags &= ~(PCF50606_F_CHG_MASK|PCF50606_F_CHG_PRESENT);
|
|
+ if (pcf->pdata->cb)
|
|
+ pcf->pdata->cb(&pcf->client.dev,
|
|
+ PCF50606_FEAT_MBC, PMU_EVT_INSERT);
|
|
+ /* FIXME: how signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGFOK) {
|
|
+ /* Battery ready for fast charging */
|
|
+ DEBUGPC("CHGFOK ");
|
|
+ pcf->flags |= PCF50606_F_CHG_FOK;
|
|
+ /* FIXME: how to signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGERR) {
|
|
+ /* Error in charge mode */
|
|
+ DEBUGPC("CHGERR ");
|
|
+ pcf->flags |= PCF50606_F_CHG_ERR;
|
|
+ pcf->flags &= ~(PCF50606_F_CHG_FOK|PCF50606_F_CHG_READY);
|
|
+ /* FIXME: how to signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGFRDY) {
|
|
+ /* Fast charge completed */
|
|
+ DEBUGPC("CHGFRDY ");
|
|
+ pcf->flags |= PCF50606_F_CHG_READY;
|
|
+ pcf->flags &= ~PCF50606_F_CHG_FOK;
|
|
+ /* FIXME: how to signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGPROT) {
|
|
+ /* Charging protection interrupt */
|
|
+ DEBUGPC("CHGPROT ");
|
|
+ pcf->flags &= ~(PCF50606_F_CHG_FOK|PCF50606_F_CHG_READY);
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGWD10S) {
|
|
+ /* Charger watchdog will expire in 10 seconds */
|
|
+ DEBUGPC("CHGWD10S ");
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_OOCC1,
|
|
+ PCF50606_OOCC1_WDTRST,
|
|
+ PCF50606_OOCC1_WDTRST);
|
|
+ }
|
|
+ if (pcfirq[1] & PCF50606_INT2_CHGWDEXP) {
|
|
+ /* Charger watchdog expires */
|
|
+ DEBUGPC("CHGWDEXP ");
|
|
+ /* FIXME: how to signal this to userspace */
|
|
+ }
|
|
+
|
|
+ if (pcfirq[2] & PCF50606_INT3_ADCRDY) {
|
|
+ /* ADC result ready */
|
|
+ DEBUGPC("ADCRDY ");
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50606_INT3_ACDINS) {
|
|
+ /* Accessory insertion detected */
|
|
+ DEBUGPC("ACDINS ");
|
|
+ if (pcf->pdata->cb)
|
|
+ pcf->pdata->cb(&pcf->client.dev,
|
|
+ PCF50606_FEAT_ACD, PMU_EVT_INSERT);
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50606_INT3_ACDREM) {
|
|
+ /* Accessory removal detected */
|
|
+ DEBUGPC("ACDREM ");
|
|
+ if (pcf->pdata->cb)
|
|
+ pcf->pdata->cb(&pcf->client.dev,
|
|
+ PCF50606_FEAT_ACD, PMU_EVT_REMOVE);
|
|
+ }
|
|
+ /* FIXME: TSCPRES */
|
|
+ if (pcfirq[2] & PCF50606_INT3_LOWBAT) {
|
|
+ /* 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, PCF50606_REG_OOCC1,
|
|
+ PCF50606_OOCC1_TOTRST,
|
|
+ PCF50606_OOCC1_TOTRST);
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50606_INT3_HIGHTMP) {
|
|
+ /* High temperature */
|
|
+ DEBUGPC("HIGHTMP ");
|
|
+ apm_queue_event(APM_CRITICAL_SUSPEND);
|
|
+ }
|
|
+
|
|
+ DEBUGPC("\n");
|
|
+
|
|
+ pcf->working = 0;
|
|
+ input_sync(pcf->input_dev);
|
|
+ put_device(&pcf->client.dev);
|
|
+ mutex_unlock(&pcf->working_lock);
|
|
+}
|
|
+
|
|
+static irqreturn_t pcf50606_irq(int irq, void *_pcf)
|
|
+{
|
|
+ struct pcf50606_data *pcf = _pcf;
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "entering(irq=%u, pcf=%p): scheduling work\n",
|
|
+ irq, _pcf);
|
|
+ get_device(&pcf->client.dev);
|
|
+ if (!schedule_work(&pcf->work) && !pcf->working)
|
|
+ dev_dbg(&pcf->client.dev, "work item may be lost\n");
|
|
+
|
|
+ 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 pcf50606_battvolt(struct pcf50606_data *pcf)
|
|
+{
|
|
+ u_int16_t adc;
|
|
+ adc = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_RES, NULL);
|
|
+
|
|
+ return adc_to_batt_millivolts(adc);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_battvolt);
|
|
+
|
|
+static ssize_t show_battvolt(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+
|
|
+ return sprintf(buf, "%u\n", pcf50606_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_dcd"))
|
|
+ reg_id = PCF50606_REGULATOR_DCD;
|
|
+ else if (!strcmp(name, "voltage_dcde"))
|
|
+ reg_id = PCF50606_REGULATOR_DCDE;
|
|
+ else if (!strcmp(name, "voltage_dcud"))
|
|
+ reg_id = PCF50606_REGULATOR_DCUD;
|
|
+ else if (!strcmp(name, "voltage_d1reg"))
|
|
+ reg_id = PCF50606_REGULATOR_D1REG;
|
|
+ else if (!strcmp(name, "voltage_d2reg"))
|
|
+ reg_id = PCF50606_REGULATOR_D2REG;
|
|
+ else if (!strcmp(name, "voltage_d3reg"))
|
|
+ reg_id = PCF50606_REGULATOR_D3REG;
|
|
+ else if (!strcmp(name, "voltage_lpreg"))
|
|
+ reg_id = PCF50606_REGULATOR_LPREG;
|
|
+ else if (!strcmp(name, "voltage_ioreg"))
|
|
+ reg_id = PCF50606_REGULATOR_IOREG;
|
|
+ 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 pcf50606_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 (pcf50606_onoff_get(pcf, reg_id) > 0)
|
|
+ return sprintf(buf, "%u\n", pcf50606_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 pcf50606_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;
|
|
+
|
|
+ dev_dbg(dev, "attempting to set %s(%d) to %lu mvolts\n",
|
|
+ attr->attr.name, reg_id, mvolts);
|
|
+
|
|
+ if (mvolts == 0) {
|
|
+ pcf50606_onoff_set(pcf, reg_id, 0);
|
|
+ } else {
|
|
+ if (pcf50606_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;
|
|
+ }
|
|
+ pcf50606_onoff_set(pcf, reg_id, 1);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(voltage_dcd, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_dcde, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_dcud, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_d1reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_d2reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_d3reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_lpreg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+static DEVICE_ATTR(voltage_ioreg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
|
|
+
|
|
+/***********************************************************************
|
|
+ * Charger Control
|
|
+ ***********************************************************************/
|
|
+
|
|
+/* Enable/disable fast charging (500mA in the GTA01) */
|
|
+void pcf50606_charge_fast(struct pcf50606_data *pcf, int on)
|
|
+{
|
|
+ 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);
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50606_charge_fast);
|
|
+
|
|
+static inline u_int16_t adc_to_rntc(struct pcf50606_data *pcf, u_int16_t adc)
|
|
+{
|
|
+ u_int32_t r_ntc = (adc * (u_int32_t)pcf->pdata->r_fix_batt) / (1023 - adc);
|
|
+
|
|
+ return r_ntc;
|
|
+}
|
|
+
|
|
+static inline int16_t rntc_to_temp(u_int16_t rntc)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ntc_table_10k_3370B); i++) {
|
|
+ if (rntc > ntc_table_10k_3370B[i])
|
|
+ return i - 10; /* First element is -10 */
|
|
+ }
|
|
+ return -99; /* Below our range */
|
|
+}
|
|
+
|
|
+static ssize_t show_battemp(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int16_t adc;
|
|
+
|
|
+ adc = adc_read(pcf, PCF50606_ADCMUX_BATTEMP, NULL);
|
|
+
|
|
+ return sprintf(buf, "%d\n", rntc_to_temp(adc_to_rntc(pcf, adc)));
|
|
+}
|
|
+static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL);
|
|
+
|
|
+static inline int16_t adc_to_chg_milliamps(struct pcf50606_data *pcf,
|
|
+ u_int16_t adc_adcin1,
|
|
+ u_int16_t adc_batvolt)
|
|
+{
|
|
+ int32_t res = (adc_adcin1 - adc_batvolt) * 2400;
|
|
+ return (res * 1000) / (pcf->pdata->r_sense_milli * 1024);
|
|
+}
|
|
+
|
|
+static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int16_t adc_batvolt, adc_adcin1;
|
|
+ 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, "%d\n", ma);
|
|
+}
|
|
+static DEVICE_ATTR(chgcur, S_IRUGO | S_IWUSR, show_chgcur, NULL);
|
|
+
|
|
+static const char *chgmode_names[] = {
|
|
+ [PCF50606_MBCC1_CHGMOD_QUAL] = "qualification",
|
|
+ [PCF50606_MBCC1_CHGMOD_PRE] = "pre",
|
|
+ [PCF50606_MBCC1_CHGMOD_TRICKLE] = "trickle",
|
|
+ [PCF50606_MBCC1_CHGMOD_FAST_CCCV] = "fast_cccv",
|
|
+ [PCF50606_MBCC1_CHGMOD_FAST_NOCC] = "fast_nocc",
|
|
+ [PCF50606_MBCC1_CHGMOD_FAST_NOCV] = "fast_nocv",
|
|
+ [PCF50606_MBCC1_CHGMOD_FAST_SW] = "fast_switch",
|
|
+ [PCF50606_MBCC1_CHGMOD_IDLE] = "idle",
|
|
+};
|
|
+
|
|
+static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
|
|
+ u_int8_t chgmod = (mbcc1 & PCF50606_MBCC1_CHGMOD_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 pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ 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);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(chgmode, S_IRUGO | S_IWUSR, show_chgmode, set_chgmode);
|
|
+
|
|
+static const char *chgstate_names[] = {
|
|
+ [PCF50606_F_CHG_FAST] = "fast_enabled",
|
|
+ [PCF50606_F_CHG_PRESENT] = "present",
|
|
+ [PCF50606_F_CHG_FOK] = "fast_ok",
|
|
+ [PCF50606_F_CHG_ERR] = "error",
|
|
+ [PCF50606_F_CHG_PROT] = "protection",
|
|
+ [PCF50606_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 pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ 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;
|
|
+}
|
|
+static DEVICE_ATTR(chgstate, S_IRUGO | S_IWUSR, show_chgstate, NULL);
|
|
+
|
|
+/***********************************************************************
|
|
+ * APM emulation
|
|
+ ***********************************************************************/
|
|
+
|
|
+static void pcf50606_get_power_status(struct apm_power_info *info)
|
|
+{
|
|
+ struct pcf50606_data *pcf = pcf50606_global;
|
|
+ u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
|
|
+ u_int8_t chgmod = mbcc1 & PCF50606_MBCC1_CHGMOD_MASK;
|
|
+ u_int16_t battvolt = pcf50606_battvolt(pcf);
|
|
+
|
|
+ if (reg_read(pcf, PCF50606_REG_OOCS) & PCF50606_OOCS_EXTON)
|
|
+ info->ac_line_status = APM_AC_ONLINE;
|
|
+ else
|
|
+ info->ac_line_status = APM_AC_OFFLINE;
|
|
+
|
|
+ switch (chgmod) {
|
|
+ case PCF50606_MBCC1_CHGMOD_QUAL:
|
|
+ case PCF50606_MBCC1_CHGMOD_PRE:
|
|
+ case PCF50606_MBCC1_CHGMOD_IDLE:
|
|
+ info->battery_life = battvolt_scale(battvolt);
|
|
+ break;
|
|
+ default:
|
|
+ info->battery_status = APM_BATTERY_STATUS_CHARGING;
|
|
+ info->battery_flag = APM_BATTERY_FLAG_CHARGING;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+ * RTC
|
|
+ ***********************************************************************/
|
|
+
|
|
+struct pcf50606_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 pcf50606_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 pcf50606_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 pcf50606_rtc_ioctl(struct device *dev, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ switch (cmd) {
|
|
+ case RTC_PIE_OFF:
|
|
+ /* disable periodic interrupt (hz tick) */
|
|
+ pcf->flags &= ~PCF50606_F_RTC_SECOND;
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_INT1M,
|
|
+ PCF50606_INT1_SECOND, PCF50606_INT1_SECOND);
|
|
+ return 0;
|
|
+ case RTC_PIE_ON:
|
|
+ /* ensable periodic interrupt (hz tick) */
|
|
+ pcf->flags |= PCF50606_F_RTC_SECOND;
|
|
+ reg_clear_bits(pcf, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENOIOCTLCMD;
|
|
+}
|
|
+
|
|
+static int pcf50606_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ struct pcf50606_time pcf_tm;
|
|
+
|
|
+ mutex_lock(&pcf->lock);
|
|
+ pcf_tm.sec = __reg_read(pcf, PCF50606_REG_RTCSC);
|
|
+ pcf_tm.min = __reg_read(pcf, PCF50606_REG_RTCMN);
|
|
+ pcf_tm.hour = __reg_read(pcf, PCF50606_REG_RTCHR);
|
|
+ pcf_tm.wkday = __reg_read(pcf, PCF50606_REG_RTCWD);
|
|
+ pcf_tm.day = __reg_read(pcf, PCF50606_REG_RTCDT);
|
|
+ pcf_tm.month = __reg_read(pcf, PCF50606_REG_RTCMT);
|
|
+ pcf_tm.year = __reg_read(pcf, PCF50606_REG_RTCYR);
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ dev_dbg(dev, "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);
|
|
+
|
|
+ dev_dbg(dev, "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 pcf50606_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ struct pcf50606_time pcf_tm;
|
|
+ u_int8_t int1m;
|
|
+
|
|
+ dev_dbg(dev, "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);
|
|
+ dev_dbg(dev, "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);
|
|
+
|
|
+ /* disable SECOND interrupt */
|
|
+ int1m = __reg_read(pcf, PCF50606_REG_INT1M);
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, int1m | PCF50606_INT1_SECOND);
|
|
+
|
|
+ __reg_write(pcf, PCF50606_REG_RTCSC, pcf_tm.sec);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCMN, pcf_tm.min);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCHR, pcf_tm.hour);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCWD, pcf_tm.wkday);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCDT, pcf_tm.day);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCMT, pcf_tm.month);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCYR, pcf_tm.year);
|
|
+
|
|
+ /* restore INT1M, potentially re-enable SECOND interrupt */
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, int1m);
|
|
+
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pcf50606_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ struct pcf50606_time pcf_tm;
|
|
+
|
|
+ mutex_lock(&pcf->lock);
|
|
+ alrm->enabled =
|
|
+ __reg_read(pcf, PCF50606_REG_INT1M) & PCF50606_INT1_ALARM ? 0 : 1;
|
|
+ pcf_tm.sec = __reg_read(pcf, PCF50606_REG_RTCSCA);
|
|
+ pcf_tm.min = __reg_read(pcf, PCF50606_REG_RTCMNA);
|
|
+ pcf_tm.hour = __reg_read(pcf, PCF50606_REG_RTCHRA);
|
|
+ pcf_tm.wkday = __reg_read(pcf, PCF50606_REG_RTCWDA);
|
|
+ pcf_tm.day = __reg_read(pcf, PCF50606_REG_RTCDTA);
|
|
+ pcf_tm.month = __reg_read(pcf, PCF50606_REG_RTCMTA);
|
|
+ pcf_tm.year = __reg_read(pcf, PCF50606_REG_RTCYRA);
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ pcf2rtc_time(&alrm->time, &pcf_tm);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pcf50606_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ struct pcf50606_time pcf_tm;
|
|
+ u_int8_t irqmask;
|
|
+
|
|
+ rtc2pcf_time(&pcf_tm, &alrm->time);
|
|
+
|
|
+ mutex_lock(&pcf->lock);
|
|
+
|
|
+ /* disable alarm interrupt */
|
|
+ irqmask = __reg_read(pcf, PCF50606_REG_INT1M);
|
|
+ irqmask |= PCF50606_INT1_ALARM;
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, irqmask);
|
|
+
|
|
+ __reg_write(pcf, PCF50606_REG_RTCSCA, pcf_tm.sec);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCMNA, pcf_tm.min);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCHRA, pcf_tm.hour);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCWDA, pcf_tm.wkday);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCDTA, pcf_tm.day);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCMTA, pcf_tm.month);
|
|
+ __reg_write(pcf, PCF50606_REG_RTCYRA, pcf_tm.year);
|
|
+
|
|
+ if (alrm->enabled) {
|
|
+ /* (re-)enaable alarm interrupt */
|
|
+ irqmask = __reg_read(pcf, PCF50606_REG_INT1M);
|
|
+ irqmask &= ~PCF50606_INT1_ALARM;
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, irqmask);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ /* FIXME */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct rtc_class_ops pcf50606_rtc_ops = {
|
|
+ .ioctl = pcf50606_rtc_ioctl,
|
|
+ .read_time = pcf50606_rtc_read_time,
|
|
+ .set_time = pcf50606_rtc_set_time,
|
|
+ .read_alarm = pcf50606_rtc_read_alarm,
|
|
+ .set_alarm = pcf50606_rtc_set_alarm,
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+ * Watchdog
|
|
+ ***********************************************************************/
|
|
+
|
|
+static void pcf50606_wdt_start(struct pcf50606_data *pcf)
|
|
+{
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_OOCC1, PCF50606_OOCC1_WDTRST,
|
|
+ PCF50606_OOCC1_WDTRST);
|
|
+}
|
|
+
|
|
+static void pcf50606_wdt_stop(struct pcf50606_data *pcf)
|
|
+{
|
|
+ reg_clear_bits(pcf, PCF50606_REG_OOCS, PCF50606_OOCS_WDTEXP);
|
|
+}
|
|
+
|
|
+static void pcf50606_wdt_keepalive(struct pcf50606_data *pcf)
|
|
+{
|
|
+ pcf50606_wdt_start(pcf);
|
|
+}
|
|
+
|
|
+static int pcf50606_wdt_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct pcf50606_data *pcf = pcf50606_global;
|
|
+
|
|
+ file->private_data = pcf;
|
|
+
|
|
+ /* start the timer */
|
|
+ pcf50606_wdt_start(pcf);
|
|
+
|
|
+ return nonseekable_open(inode, file);
|
|
+}
|
|
+
|
|
+static int pcf50606_wdt_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct pcf50606_data *pcf = file->private_data;
|
|
+
|
|
+ if (pcf->allow_close == CLOSE_STATE_ALLOW)
|
|
+ pcf50606_wdt_stop(pcf);
|
|
+ else {
|
|
+ printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n");
|
|
+ pcf50606_wdt_keepalive(pcf);
|
|
+ }
|
|
+
|
|
+ pcf->allow_close = CLOSE_STATE_NOT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t pcf50606_wdt_write(struct file *file, const char __user *data,
|
|
+ size_t len, loff_t *ppos)
|
|
+{
|
|
+ struct pcf50606_data *pcf = file->private_data;
|
|
+ if (len) {
|
|
+ size_t i;
|
|
+
|
|
+ for (i = 0; i != len; i++) {
|
|
+ char c;
|
|
+ if (get_user(c, data + i))
|
|
+ return -EFAULT;
|
|
+ if (c == 'V')
|
|
+ pcf->allow_close = CLOSE_STATE_ALLOW;
|
|
+ }
|
|
+ pcf50606_wdt_keepalive(pcf);
|
|
+ }
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static struct watchdog_info pcf50606_wdt_ident = {
|
|
+ .options = WDIOF_MAGICCLOSE,
|
|
+ .firmware_version = 0,
|
|
+ .identity = "PCF50606 Watchdog",
|
|
+};
|
|
+
|
|
+static int pcf50606_wdt_ioctl(struct inode *inode, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct pcf50606_data *pcf = file->private_data;
|
|
+ void __user *argp = (void __user *)arg;
|
|
+ int __user *p = argp;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case WDIOC_GETSUPPORT:
|
|
+ return copy_to_user(argp, &pcf50606_wdt_ident,
|
|
+ sizeof(pcf50606_wdt_ident)) ? -EFAULT : 0;
|
|
+ break;
|
|
+ case WDIOC_GETSTATUS:
|
|
+ case WDIOC_GETBOOTSTATUS:
|
|
+ return put_user(0, p);
|
|
+ case WDIOC_KEEPALIVE:
|
|
+ pcf50606_wdt_keepalive(pcf);
|
|
+ return 0;
|
|
+ case WDIOC_GETTIMEOUT:
|
|
+ return put_user(8, p);
|
|
+ default:
|
|
+ return -ENOIOCTLCMD;
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct file_operations pcf50606_wdt_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .llseek = no_llseek,
|
|
+ .write = &pcf50606_wdt_write,
|
|
+ .ioctl = &pcf50606_wdt_ioctl,
|
|
+ .open = &pcf50606_wdt_open,
|
|
+ .release = &pcf50606_wdt_release,
|
|
+};
|
|
+
|
|
+static struct miscdevice pcf50606_wdt_miscdev = {
|
|
+ .minor = WATCHDOG_MINOR,
|
|
+ .name = "watchdog",
|
|
+ .fops = &pcf50606_wdt_fops,
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+ * PWM
|
|
+ ***********************************************************************/
|
|
+
|
|
+static const char *pwm_dc_table[] = {
|
|
+ "0/16", "1/16", "2/16", "3/16",
|
|
+ "4/16", "5/16", "6/16", "7/16",
|
|
+ "8/16", "9/16", "10/16", "11/16",
|
|
+ "12/16", "13/16", "14/16", "15/16",
|
|
+};
|
|
+
|
|
+static ssize_t show_pwm_dc(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int8_t val;
|
|
+
|
|
+ val = reg_read(pcf, PCF50606_REG_PWMC1) >> PCF50606_PWMC1_DC_SHIFT;
|
|
+ val &= 0xf;
|
|
+
|
|
+ return sprintf(buf, "%s\n", pwm_dc_table[val]);
|
|
+}
|
|
+
|
|
+static ssize_t set_pwm_dc(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int8_t i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(pwm_dc_table); i++) {
|
|
+ if (!strncmp(buf, pwm_dc_table[i], strlen(pwm_dc_table[i]))) {
|
|
+ dev_dbg(dev, "setting pwm dc %s\n\r", pwm_dc_table[i]);
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_PWMC1, 0x1e,
|
|
+ (i << PCF50606_PWMC1_DC_SHIFT));
|
|
+ }
|
|
+ }
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(pwm_dc, S_IRUGO | S_IWUSR, show_pwm_dc, set_pwm_dc);
|
|
+
|
|
+static const char *pwm_clk_table[] = {
|
|
+ "512", "256", "128", "64",
|
|
+ "56300", "28100", "14100", "7000",
|
|
+};
|
|
+
|
|
+static ssize_t show_pwm_clk(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int8_t val;
|
|
+
|
|
+ val = reg_read(pcf, PCF50606_REG_PWMC1) >> PCF50606_PWMC1_CLK_SHIFT;
|
|
+ val &= 0x7;
|
|
+
|
|
+ return sprintf(buf, "%s\n", pwm_clk_table[val]);
|
|
+}
|
|
+
|
|
+static ssize_t set_pwm_clk(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+ u_int8_t i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(pwm_clk_table); i++) {
|
|
+ if (!strncmp(buf, pwm_clk_table[i], strlen(pwm_clk_table[i]))) {
|
|
+ dev_dbg(dev, "setting pwm clk %s\n\r",
|
|
+ pwm_clk_table[i]);
|
|
+ reg_set_bit_mask(pcf, PCF50606_REG_PWMC1, 0xe0,
|
|
+ (i << PCF50606_PWMC1_CLK_SHIFT));
|
|
+ }
|
|
+ }
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(pwm_clk, S_IRUGO | S_IWUSR, show_pwm_clk, set_pwm_clk);
|
|
+
|
|
+static int pcf50606bl_get_intensity(struct backlight_device *bd)
|
|
+{
|
|
+ struct pcf50606_data *pcf = bl_get_data(bd);
|
|
+ int intensity = reg_read(pcf, PCF50606_REG_PWMC1);
|
|
+ intensity = (intensity >> PCF50606_PWMC1_DC_SHIFT);
|
|
+
|
|
+ return intensity & 0xf;
|
|
+}
|
|
+
|
|
+static int pcf50606bl_set_intensity(struct backlight_device *bd)
|
|
+{
|
|
+ struct pcf50606_data *pcf = bl_get_data(bd);
|
|
+ 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, PCF50606_REG_PWMC1, 0x1e,
|
|
+ (intensity << PCF50606_PWMC1_DC_SHIFT));
|
|
+}
|
|
+
|
|
+static struct backlight_ops pcf50606bl_ops = {
|
|
+ .get_brightness = pcf50606bl_get_intensity,
|
|
+ .update_status = pcf50606bl_set_intensity,
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+ * Driver initialization
|
|
+ ***********************************************************************/
|
|
+
|
|
+#ifdef CONFIG_MACH_NEO1973_GTA01
|
|
+/* 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 = "neo1973-pm-gps",
|
|
+};
|
|
+
|
|
+static struct platform_device gta01_pm_bt_dev = {
|
|
+ .name = "neo1973-pm-bt",
|
|
+};
|
|
+#endif
|
|
+
|
|
+static struct attribute *pcf_sysfs_entries[16] = {
|
|
+ &dev_attr_voltage_dcd.attr,
|
|
+ &dev_attr_voltage_dcde.attr,
|
|
+ &dev_attr_voltage_dcud.attr,
|
|
+ &dev_attr_voltage_d1reg.attr,
|
|
+ &dev_attr_voltage_d2reg.attr,
|
|
+ &dev_attr_voltage_d3reg.attr,
|
|
+ &dev_attr_voltage_lpreg.attr,
|
|
+ &dev_attr_voltage_ioreg.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 pcf50606_data *pcf)
|
|
+{
|
|
+ int i = 0;
|
|
+ struct attribute **attr;
|
|
+
|
|
+ for (attr = pcf_sysfs_entries; *attr; attr++)
|
|
+ i++;
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_MBC) {
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_chgstate.attr;
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_chgmode.attr;
|
|
+ }
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_CHGCUR)
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_chgcur.attr;
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_BATVOLT)
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_battvolt.attr;
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_BATTEMP)
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_battemp.attr;
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_PWM) {
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_pwm_dc.attr;
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_pwm_clk.attr;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int pcf50606_detect(struct i2c_adapter *adapter, int address, int kind)
|
|
+{
|
|
+ struct i2c_client *new_client;
|
|
+ struct pcf50606_data *data;
|
|
+ int err = 0;
|
|
+ int irq;
|
|
+
|
|
+ if (!pcf50606_pdev) {
|
|
+ printk(KERN_ERR "pcf50606: driver needs a platform_device!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ irq = platform_get_irq(pcf50606_pdev, 0);
|
|
+ if (irq < 0) {
|
|
+ dev_err(&pcf50606_pdev->dev, "no irq in platform resources!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* At the moment, we only support one PCF50606 in a system */
|
|
+ if (pcf50606_global) {
|
|
+ dev_err(&pcf50606_pdev->dev,
|
|
+ "currently only one chip supported\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
+ if (!data)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mutex_init(&data->lock);
|
|
+ mutex_init(&data->working_lock);
|
|
+ INIT_WORK(&data->work, pcf50606_work);
|
|
+ data->irq = irq;
|
|
+ data->working = 0;
|
|
+ data->onkey_seconds = -1;
|
|
+ data->pdata = pcf50606_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 = &pcf50606_driver;
|
|
+ new_client->flags = 0;
|
|
+ strlcpy(new_client->name, "pcf50606", I2C_NAME_SIZE);
|
|
+
|
|
+ /* now we try to detect the chip */
|
|
+
|
|
+ /* register with i2c core */
|
|
+ err = i2c_attach_client(new_client);
|
|
+ if (err) {
|
|
+ dev_err(&new_client->dev,
|
|
+ "error during i2c_attach_client()\n");
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ 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' */
|
|
+
|
|
+ /* input device registration */
|
|
+ data->input_dev = input_allocate_device();
|
|
+ if (!data->input_dev)
|
|
+ goto exit_sysfs;
|
|
+
|
|
+ data->input_dev->name = "FIC Neo1973 PMU events";
|
|
+ data->input_dev->phys = "I2C";
|
|
+ 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);
|
|
+
|
|
+ err = input_register_device(data->input_dev);
|
|
+ if (err)
|
|
+ goto exit_sysfs;
|
|
+
|
|
+ /* register power off handler with core power management */
|
|
+ pm_power_off = &pcf50606_go_standby;
|
|
+
|
|
+ /* configure interrupt mask */
|
|
+ reg_write(data, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
|
|
+ reg_write(data, PCF50606_REG_INT2M, 0x00);
|
|
+ reg_write(data, PCF50606_REG_INT3M, PCF50606_INT3_TSCPRES);
|
|
+
|
|
+ err = request_irq(irq, pcf50606_irq, IRQF_TRIGGER_FALLING,
|
|
+ "pcf50606", data);
|
|
+ if (err < 0)
|
|
+ goto exit_input;
|
|
+
|
|
+ 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);
|
|
+
|
|
+ pcf50606_global = data;
|
|
+
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_RTC) {
|
|
+ data->rtc = rtc_device_register("pcf50606", &new_client->dev,
|
|
+ &pcf50606_rtc_ops, THIS_MODULE);
|
|
+ if (IS_ERR(data->rtc)) {
|
|
+ err = PTR_ERR(data->rtc);
|
|
+ goto exit_irq;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_WDT) {
|
|
+ err = misc_register(&pcf50606_wdt_miscdev);
|
|
+ if (err) {
|
|
+ dev_err(&new_client->dev, "cannot register miscdev on "
|
|
+ "minor=%d (%d)\n", WATCHDOG_MINOR, err);
|
|
+ goto exit_rtc;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_PWM) {
|
|
+ /* enable PWM controller */
|
|
+ reg_set_bit_mask(data, PCF50606_REG_PWMC1,
|
|
+ PCF50606_PWMC1_ACTSET,
|
|
+ PCF50606_PWMC1_ACTSET);
|
|
+ }
|
|
+
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_PWM_BL) {
|
|
+ data->backlight = backlight_device_register("pcf50606-bl",
|
|
+ &new_client->dev,
|
|
+ data,
|
|
+ &pcf50606bl_ops);
|
|
+ if (!data->backlight)
|
|
+ goto exit_misc;
|
|
+ data->backlight->props.max_brightness = 16;
|
|
+ data->backlight->props.power = FB_BLANK_UNBLANK;
|
|
+ data->backlight->props.brightness =
|
|
+ data->pdata->init_brightness;
|
|
+ backlight_update_status(data->backlight);
|
|
+ }
|
|
+
|
|
+ apm_get_power_status = pcf50606_get_power_status;
|
|
+
|
|
+#ifdef CONFIG_MACH_NEO1973_GTA01
|
|
+ if (machine_is_neo1973_gta01()) {
|
|
+ gta01_pm_gps_dev.dev.parent = &new_client->dev;
|
|
+ switch (system_rev) {
|
|
+ case GTA01Bv2_SYSTEM_REV:
|
|
+ case GTA01Bv3_SYSTEM_REV:
|
|
+ case GTA01Bv4_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);
|
|
+ /* a link for gllin compatibility */
|
|
+ err = sysfs_create_link(&platform_bus_type.devices.kobj,
|
|
+ >a01_pm_gps_dev.dev.kobj, "gta01-pm-gps.0");
|
|
+ if (err)
|
|
+ printk(KERN_ERR
|
|
+ "sysfs_create_link (gta01-pm-gps.0): %d\n", err);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_ACD)
|
|
+ reg_set_bit_mask(data, PCF50606_REG_ACDC1,
|
|
+ PCF50606_ACDC1_ACDAPE, PCF50606_ACDC1_ACDAPE);
|
|
+ else
|
|
+ reg_clear_bits(data, PCF50606_REG_ACDC1,
|
|
+ PCF50606_ACDC1_ACDAPE);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+exit_misc:
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_WDT)
|
|
+ misc_deregister(&pcf50606_wdt_miscdev);
|
|
+exit_rtc:
|
|
+ if (data->pdata->used_features & PCF50606_FEAT_RTC)
|
|
+ rtc_device_unregister(pcf50606_global->rtc);
|
|
+exit_irq:
|
|
+ free_irq(pcf50606_global->irq, pcf50606_global);
|
|
+ pcf50606_global = NULL;
|
|
+exit_input:
|
|
+ pm_power_off = NULL;
|
|
+ input_unregister_device(data->input_dev);
|
|
+exit_sysfs:
|
|
+ sysfs_remove_group(&new_client->dev.kobj, &pcf_attr_group);
|
|
+exit_detach:
|
|
+ i2c_detach_client(new_client);
|
|
+exit_free:
|
|
+ kfree(data);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int pcf50606_attach_adapter(struct i2c_adapter *adapter)
|
|
+{
|
|
+ return i2c_probe(adapter, &addr_data, &pcf50606_detect);
|
|
+}
|
|
+
|
|
+static int pcf50606_detach_client(struct i2c_client *client)
|
|
+{
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+
|
|
+ apm_get_power_status = NULL;
|
|
+ input_unregister_device(pcf->input_dev);
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_PWM_BL)
|
|
+ backlight_device_unregister(pcf->backlight);
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_FEAT_WDT)
|
|
+ misc_deregister(&pcf50606_wdt_miscdev);
|
|
+
|
|
+ if (pcf->pdata->used_features & PCF50606_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 (PCF50606_INT1_ALARM | \
|
|
+ PCF50606_INT1_ONKEYF | \
|
|
+ PCF50606_INT1_EXTONR)
|
|
+#define INT2M_RESUMERS (PCF50606_INT2_CHGWD10S | \
|
|
+ PCF50606_INT2_CHGPROT | \
|
|
+ PCF50606_INT2_CHGERR)
|
|
+#define INT3M_RESUMERS (PCF50606_INT3_LOWBAT | \
|
|
+ PCF50606_INT3_HIGHTMP | \
|
|
+ PCF50606_INT3_ACDINS)
|
|
+static int pcf50606_suspend(struct device *dev, pm_message_t state)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_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.dcdc1 = __reg_read(pcf, PCF50606_REG_DCDC1);
|
|
+ pcf->standby_regs.dcdc2 = __reg_read(pcf, PCF50606_REG_DCDC2);
|
|
+ pcf->standby_regs.dcdec1 = __reg_read(pcf, PCF50606_REG_DCDEC1);
|
|
+ pcf->standby_regs.dcudc1 = __reg_read(pcf, PCF50606_REG_DCUDC1);
|
|
+ pcf->standby_regs.ioregc = __reg_read(pcf, PCF50606_REG_IOREGC);
|
|
+ pcf->standby_regs.d1regc1 = __reg_read(pcf, PCF50606_REG_D1REGC1);
|
|
+ pcf->standby_regs.d2regc1 = __reg_read(pcf, PCF50606_REG_D2REGC1);
|
|
+ pcf->standby_regs.d3regc1 = __reg_read(pcf, PCF50606_REG_D3REGC1);
|
|
+ pcf->standby_regs.lpregc1 = __reg_read(pcf, PCF50606_REG_LPREGC1);
|
|
+ pcf->standby_regs.adcc1 = __reg_read(pcf, PCF50606_REG_ADCC1);
|
|
+ pcf->standby_regs.adcc2 = __reg_read(pcf, PCF50606_REG_ADCC2);
|
|
+ pcf->standby_regs.pwmc1 = __reg_read(pcf, PCF50606_REG_PWMC1);
|
|
+
|
|
+ /* switch off power supplies that are not needed during suspend */
|
|
+ for (i = 0; i < __NUM_PCF50606_REGULATORS; i++) {
|
|
+ if (!(pcf->pdata->rails[i].flags & PMU_VRAIL_F_SUSPEND_ON)) {
|
|
+ u_int8_t tmp;
|
|
+
|
|
+ /* IOREG powers the I@C interface so we cannot switch
|
|
+ * it off */
|
|
+ if (i == PCF50606_REGULATOR_IOREG)
|
|
+ continue;
|
|
+
|
|
+ dev_dbg(dev, "disabling pcf50606 regulator %u\n", i);
|
|
+ /* we cannot use pcf50606_onoff_set() because we're
|
|
+ * already under the mutex */
|
|
+ tmp = __reg_read(pcf, regulator_registers[i]);
|
|
+ tmp &= 0x1f;
|
|
+ __reg_write(pcf, regulator_registers[i], tmp);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ pcf->standby_regs.int1m = __reg_read(pcf, PCF50606_REG_INT1M);
|
|
+ pcf->standby_regs.int2m = __reg_read(pcf, PCF50606_REG_INT2M);
|
|
+ pcf->standby_regs.int3m = __reg_read(pcf, PCF50606_REG_INT3M);
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, ~INT1M_RESUMERS & 0xff);
|
|
+ __reg_write(pcf, PCF50606_REG_INT2M, ~INT2M_RESUMERS & 0xff);
|
|
+ __reg_write(pcf, PCF50606_REG_INT3M, ~INT3M_RESUMERS & 0xff);
|
|
+
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pcf50606_resume(struct device *dev)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pcf50606_data *pcf = i2c_get_clientdata(client);
|
|
+
|
|
+ mutex_lock(&pcf->lock);
|
|
+
|
|
+ /* Resume all saved registers that don't "survive" standby state */
|
|
+ __reg_write(pcf, PCF50606_REG_INT1M, pcf->standby_regs.int1m);
|
|
+ __reg_write(pcf, PCF50606_REG_INT2M, pcf->standby_regs.int2m);
|
|
+ __reg_write(pcf, PCF50606_REG_INT3M, pcf->standby_regs.int3m);
|
|
+
|
|
+ __reg_write(pcf, PCF50606_REG_DCDC1, pcf->standby_regs.dcdc1);
|
|
+ __reg_write(pcf, PCF50606_REG_DCDC2, pcf->standby_regs.dcdc2);
|
|
+ __reg_write(pcf, PCF50606_REG_DCDEC1, pcf->standby_regs.dcdec1);
|
|
+ __reg_write(pcf, PCF50606_REG_DCUDC1, pcf->standby_regs.dcudc1);
|
|
+ __reg_write(pcf, PCF50606_REG_IOREGC, pcf->standby_regs.ioregc);
|
|
+ __reg_write(pcf, PCF50606_REG_D1REGC1, pcf->standby_regs.d1regc1);
|
|
+ __reg_write(pcf, PCF50606_REG_D2REGC1, pcf->standby_regs.d2regc1);
|
|
+ __reg_write(pcf, PCF50606_REG_D3REGC1, pcf->standby_regs.d3regc1);
|
|
+ __reg_write(pcf, PCF50606_REG_LPREGC1, pcf->standby_regs.lpregc1);
|
|
+ __reg_write(pcf, PCF50606_REG_ADCC1, pcf->standby_regs.adcc1);
|
|
+ __reg_write(pcf, PCF50606_REG_ADCC2, pcf->standby_regs.adcc2);
|
|
+ __reg_write(pcf, PCF50606_REG_PWMC1, pcf->standby_regs.pwmc1);
|
|
+
|
|
+ mutex_unlock(&pcf->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+#define pcf50606_suspend NULL
|
|
+#define pcf50606_resume NULL
|
|
+#endif
|
|
+
|
|
+static struct i2c_driver pcf50606_driver = {
|
|
+ .driver = {
|
|
+ .name = "pcf50606",
|
|
+ .suspend = pcf50606_suspend,
|
|
+ .resume = pcf50606_resume,
|
|
+ },
|
|
+ .id = I2C_DRIVERID_PCF50606,
|
|
+ .attach_adapter = pcf50606_attach_adapter,
|
|
+ .detach_client = pcf50606_detach_client,
|
|
+};
|
|
+
|
|
+/* platform driver, since i2c devices don't have platform_data */
|
|
+static int __init pcf50606_plat_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct pcf50606_platform_data *pdata = pdev->dev.platform_data;
|
|
+
|
|
+ if (!pdata)
|
|
+ return -ENODEV;
|
|
+
|
|
+ pcf50606_pdev = pdev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pcf50606_plat_remove(struct platform_device *pdev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver pcf50606_plat_driver = {
|
|
+ .probe = pcf50606_plat_probe,
|
|
+ .remove = pcf50606_plat_remove,
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .name = "pcf50606",
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init pcf50606_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ rc = platform_driver_register(&pcf50606_plat_driver);
|
|
+ if (!rc)
|
|
+ rc = i2c_add_driver(&pcf50606_driver);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void pcf50606_exit(void)
|
|
+{
|
|
+ i2c_del_driver(&pcf50606_driver);
|
|
+ platform_driver_unregister(&pcf50606_plat_driver);
|
|
+}
|
|
+
|
|
+MODULE_DESCRIPTION("I2C chip driver for NXP PCF50606 power management unit");
|
|
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_init(pcf50606_init);
|
|
+module_exit(pcf50606_exit);
|
|
diff --git a/drivers/i2c/chips/pcf50606.h b/drivers/i2c/chips/pcf50606.h
|
|
new file mode 100644
|
|
index 0000000..15b350f
|
|
--- /dev/null
|
|
+++ b/drivers/i2c/chips/pcf50606.h
|
|
@@ -0,0 +1,302 @@
|
|
+#ifndef _PCF50606_H
|
|
+#define _PCF50606_H
|
|
+
|
|
+/* Philips PCF50606 Power Managemnt Unit (PMU) driver
|
|
+ * (C) 2006-2007 by OpenMoko, Inc.
|
|
+ * Author: Harald Welte <laforge@openmoko.org>
|
|
+ *
|
|
+ */
|
|
+
|
|
+enum pfc50606_regs {
|
|
+ PCF50606_REG_ID = 0x00,
|
|
+ PCF50606_REG_OOCS = 0x01,
|
|
+ PCF50606_REG_INT1 = 0x02, /* Interrupt Status */
|
|
+ PCF50606_REG_INT2 = 0x03, /* Interrupt Status */
|
|
+ PCF50606_REG_INT3 = 0x04, /* Interrupt Status */
|
|
+ PCF50606_REG_INT1M = 0x05, /* Interrupt Mask */
|
|
+ PCF50606_REG_INT2M = 0x06, /* Interrupt Mask */
|
|
+ PCF50606_REG_INT3M = 0x07, /* Interrupt Mask */
|
|
+ PCF50606_REG_OOCC1 = 0x08,
|
|
+ PCF50606_REG_OOCC2 = 0x09,
|
|
+ PCF50606_REG_RTCSC = 0x0a, /* Second */
|
|
+ PCF50606_REG_RTCMN = 0x0b, /* Minute */
|
|
+ PCF50606_REG_RTCHR = 0x0c, /* Hour */
|
|
+ PCF50606_REG_RTCWD = 0x0d, /* Weekday */
|
|
+ PCF50606_REG_RTCDT = 0x0e, /* Day */
|
|
+ PCF50606_REG_RTCMT = 0x0f, /* Month */
|
|
+ PCF50606_REG_RTCYR = 0x10, /* Year */
|
|
+ PCF50606_REG_RTCSCA = 0x11, /* Alarm Second */
|
|
+ PCF50606_REG_RTCMNA = 0x12, /* Alarm Minute */
|
|
+ PCF50606_REG_RTCHRA = 0x13, /* Alarm Hour */
|
|
+ PCF50606_REG_RTCWDA = 0x14, /* Alarm Weekday */
|
|
+ PCF50606_REG_RTCDTA = 0x15, /* Alarm Day */
|
|
+ PCF50606_REG_RTCMTA = 0x16, /* Alarm Month */
|
|
+ PCF50606_REG_RTCYRA = 0x17, /* Alarm Year */
|
|
+ PCF50606_REG_PSSC = 0x18, /* Power sequencing */
|
|
+ PCF50606_REG_PWROKM = 0x19, /* PWROK mask */
|
|
+ PCF50606_REG_PWROKS = 0x1a, /* PWROK status */
|
|
+ PCF50606_REG_DCDC1 = 0x1b,
|
|
+ PCF50606_REG_DCDC2 = 0x1c,
|
|
+ PCF50606_REG_DCDC3 = 0x1d,
|
|
+ PCF50606_REG_DCDC4 = 0x1e,
|
|
+ PCF50606_REG_DCDEC1 = 0x1f,
|
|
+ PCF50606_REG_DCDEC2 = 0x20,
|
|
+ PCF50606_REG_DCUDC1 = 0x21,
|
|
+ PCF50606_REG_DCUDC2 = 0x22,
|
|
+ PCF50606_REG_IOREGC = 0x23,
|
|
+ PCF50606_REG_D1REGC1 = 0x24,
|
|
+ PCF50606_REG_D2REGC1 = 0x25,
|
|
+ PCF50606_REG_D3REGC1 = 0x26,
|
|
+ PCF50606_REG_LPREGC1 = 0x27,
|
|
+ PCF50606_REG_LPREGC2 = 0x28,
|
|
+ PCF50606_REG_MBCC1 = 0x29,
|
|
+ PCF50606_REG_MBCC2 = 0x2a,
|
|
+ PCF50606_REG_MBCC3 = 0x2b,
|
|
+ PCF50606_REG_MBCS1 = 0x2c,
|
|
+ PCF50606_REG_BBCC = 0x2d,
|
|
+ PCF50606_REG_ADCC1 = 0x2e,
|
|
+ PCF50606_REG_ADCC2 = 0x2f,
|
|
+ PCF50606_REG_ADCS1 = 0x30,
|
|
+ PCF50606_REG_ADCS2 = 0x31,
|
|
+ PCF50606_REG_ADCS3 = 0x32,
|
|
+ PCF50606_REG_ACDC1 = 0x33,
|
|
+ PCF50606_REG_BVMC = 0x34,
|
|
+ PCF50606_REG_PWMC1 = 0x35,
|
|
+ PCF50606_REG_LEDC1 = 0x36,
|
|
+ PCF50606_REG_LEDC2 = 0x37,
|
|
+ PCF50606_REG_GPOC1 = 0x38,
|
|
+ PCF50606_REG_GPOC2 = 0x39,
|
|
+ PCF50606_REG_GPOC3 = 0x3a,
|
|
+ PCF50606_REG_GPOC4 = 0x3b,
|
|
+ PCF50606_REG_GPOC5 = 0x3c,
|
|
+ __NUM_PCF50606_REGS
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_oocs {
|
|
+ PFC50606_OOCS_ONKEY = 0x01,
|
|
+ PCF50606_OOCS_EXTON = 0x02,
|
|
+ PCF50606_OOCS_PWROKRST = 0x04,
|
|
+ PCF50606_OOCS_BATOK = 0x08,
|
|
+ PCF50606_OOCS_BACKOK = 0x10,
|
|
+ PCF50606_OOCS_CHGOK = 0x20,
|
|
+ PCF50606_OOCS_TEMPOK = 0x40,
|
|
+ PCF50606_OOCS_WDTEXP = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_oocc1 {
|
|
+ PCF50606_OOCC1_GOSTDBY = 0x01,
|
|
+ PCF50606_OOCC1_TOTRST = 0x02,
|
|
+ PCF50606_OOCC1_CLK32ON = 0x04,
|
|
+ PCF50606_OOCC1_WDTRST = 0x08,
|
|
+ PCF50606_OOCC1_RTCWAK = 0x10,
|
|
+ PCF50606_OOCC1_CHGWAK = 0x20,
|
|
+ PCF50606_OOCC1_EXTONWAK_HIGH = 0x40,
|
|
+ PCF50606_OOCC1_EXTONWAK_LOW = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_oocc2 {
|
|
+ PCF50606_OOCC2_ONKEYDB_NONE = 0x00,
|
|
+ PCF50606_OOCC2_ONKEYDB_14ms = 0x01,
|
|
+ PCF50606_OOCC2_ONKEYDB_62ms = 0x02,
|
|
+ PCF50606_OOCC2_ONKEYDB_500ms = 0x03,
|
|
+ PCF50606_OOCC2_EXTONDB_NONE = 0x00,
|
|
+ PCF50606_OOCC2_EXTONDB_14ms = 0x04,
|
|
+ PCF50606_OOCC2_EXTONDB_62ms = 0x08,
|
|
+ PCF50606_OOCC2_EXTONDB_500ms = 0x0c,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_int1 {
|
|
+ PCF50606_INT1_ONKEYR = 0x01, /* ONKEY rising edge */
|
|
+ PCF50606_INT1_ONKEYF = 0x02, /* ONKEY falling edge */
|
|
+ PCF50606_INT1_ONKEY1S = 0x04, /* OMKEY at least 1sec low */
|
|
+ PCF50606_INT1_EXTONR = 0x08, /* EXTON rising edge */
|
|
+ PCF50606_INT1_EXTONF = 0x10, /* EXTON falling edge */
|
|
+ PCF50606_INT1_SECOND = 0x40, /* RTC periodic second interrupt */
|
|
+ PCF50606_INT1_ALARM = 0x80, /* RTC alarm time is reached */
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_int2 {
|
|
+ PCF50606_INT2_CHGINS = 0x01, /* Charger inserted */
|
|
+ PCF50606_INT2_CHGRM = 0x02, /* Charger removed */
|
|
+ PCF50606_INT2_CHGFOK = 0x04, /* Fast charging OK */
|
|
+ PCF50606_INT2_CHGERR = 0x08, /* Error in charging mode */
|
|
+ PCF50606_INT2_CHGFRDY = 0x10, /* Fast charge completed */
|
|
+ PCF50606_INT2_CHGPROT = 0x20, /* Charging protection interrupt */
|
|
+ PCF50606_INT2_CHGWD10S = 0x40, /* Charger watchdig expires in 10s */
|
|
+ PCF50606_INT2_CHGWDEXP = 0x80, /* Charger watchdog expires */
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_int3 {
|
|
+ PCF50606_INT3_ADCRDY = 0x01, /* ADC conversion finished */
|
|
+ PCF50606_INT3_ACDINS = 0x02, /* Accessory inserted */
|
|
+ PCF50606_INT3_ACDREM = 0x04, /* Accessory removed */
|
|
+ PCF50606_INT3_TSCPRES = 0x08, /* Touch screen pressed */
|
|
+ PCF50606_INT3_LOWBAT = 0x40, /* Low battery voltage */
|
|
+ PCF50606_INT3_HIGHTMP = 0x80, /* High temperature */
|
|
+};
|
|
+
|
|
+/* used by PSSC, PWROKM, PWROKS, */
|
|
+enum pcf50606_regu {
|
|
+ PCF50606_REGU_DCD = 0x01, /* DCD in phase 2 */
|
|
+ PCF50606_REGU_DCDE = 0x02, /* DCDE in phase 2 */
|
|
+ PCF50606_REGU_DCUD = 0x04, /* DCDU in phase 2 */
|
|
+ PCF50606_REGU_IO = 0x08, /* IO in phase 2 */
|
|
+ PCF50606_REGU_D1 = 0x10, /* D1 in phase 2 */
|
|
+ PCF50606_REGU_D2 = 0x20, /* D2 in phase 2 */
|
|
+ PCF50606_REGU_D3 = 0x40, /* D3 in phase 2 */
|
|
+ PCF50606_REGU_LP = 0x80, /* LP in phase 2 */
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_dcdc4 {
|
|
+ PCF50606_DCDC4_MODE_AUTO = 0x00,
|
|
+ PCF50606_DCDC4_MODE_PWM = 0x01,
|
|
+ PCF50606_DCDC4_MODE_PCF = 0x02,
|
|
+ PCF50606_DCDC4_OFF_FLOAT = 0x00,
|
|
+ PCF50606_DCDC4_OFF_BYPASS = 0x04,
|
|
+ PCF50606_DCDC4_OFF_PULLDOWN = 0x08,
|
|
+ PCF50606_DCDC4_CURLIM_500mA = 0x00,
|
|
+ PCF50606_DCDC4_CURLIM_750mA = 0x10,
|
|
+ PCF50606_DCDC4_CURLIM_1000mA = 0x20,
|
|
+ PCF50606_DCDC4_CURLIM_1250mA = 0x30,
|
|
+ PCF50606_DCDC4_TOGGLE = 0x40,
|
|
+ PCF50606_DCDC4_REGSEL_DCDC2 = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_dcdec2 {
|
|
+ PCF50606_DCDEC2_MODE_AUTO = 0x00,
|
|
+ PCF50606_DCDEC2_MODE_PWM = 0x01,
|
|
+ PCF50606_DCDEC2_MODE_PCF = 0x02,
|
|
+ PCF50606_DCDEC2_OFF_FLOAT = 0x00,
|
|
+ PCF50606_DCDEC2_OFF_BYPASS = 0x04,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_dcudc2 {
|
|
+ PCF50606_DCUDC2_MODE_AUTO = 0x00,
|
|
+ PCF50606_DCUDC2_MODE_PWM = 0x01,
|
|
+ PCF50606_DCUDC2_MODE_PCF = 0x02,
|
|
+ PCF50606_DCUDC2_OFF_FLOAT = 0x00,
|
|
+ PCF50606_DCUDC2_OFF_BYPASS = 0x04,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_adcc1 {
|
|
+ PCF50606_ADCC1_TSCMODACT = 0x01,
|
|
+ PCF50606_ADCC1_TSCMODSTB = 0x02,
|
|
+ PCF50606_ADCC1_TRATSET = 0x04,
|
|
+ PCF50606_ADCC1_NTCSWAPE = 0x08,
|
|
+ PCF50606_ADCC1_NTCSWAOFF = 0x10,
|
|
+ PCF50606_ADCC1_EXTSYNCBREAK = 0x20,
|
|
+ /* reserved */
|
|
+ PCF50606_ADCC1_TSCINT = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_adcc2 {
|
|
+ PCF50606_ADCC2_ADCSTART = 0x01,
|
|
+ /* see enum pcf50606_adcc2_adcmux */
|
|
+ PCF50606_ADCC2_SYNC_NONE = 0x00,
|
|
+ PCF50606_ADCC2_SYNC_TXON = 0x20,
|
|
+ PCF50606_ADCC2_SYNC_PWREN1 = 0x40,
|
|
+ PCF50606_ADCC2_SYNC_PWREN2 = 0x60,
|
|
+ PCF50606_ADCC2_RES_10BIT = 0x00,
|
|
+ PCF50606_ADCC2_RES_8BIT = 0x80,
|
|
+};
|
|
+
|
|
+#define PCF50606_ADCC2_ADCMUX_MASK (0xf << 1)
|
|
+
|
|
+#define ADCMUX_SHIFT 1
|
|
+enum pcf50606_adcc2_adcmux {
|
|
+ PCF50606_ADCMUX_BATVOLT_RES = 0x0 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_BATVOLT_SUBTR = 0x1 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_ADCIN1_RES = 0x2 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_ADCIN1_SUBTR = 0x3 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_BATTEMP = 0x4 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_ADCIN2 = 0x5 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_ADCIN3 = 0x6 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_ADCIN3_RATIO = 0x7 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_XPOS = 0x8 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_YPOS = 0x9 << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_P1 = 0xa << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_P2 = 0xb << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_BATVOLT_ADCIN1 = 0xc << ADCMUX_SHIFT,
|
|
+ PCF50606_ADCMUX_XY_SEQUENCE = 0xe << ADCMUX_SHIFT,
|
|
+ PCF50606_P1_P2_RESISTANCE = 0xf << ADCMUX_SHIFT,
|
|
+};
|
|
+
|
|
+enum pcf50606_adcs2 {
|
|
+ PCF50606_ADCS2_ADCRDY = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_mbcc1 {
|
|
+ PCF50606_MBCC1_CHGAPE = 0x01,
|
|
+ PCF50606_MBCC1_AUTOFST = 0x02,
|
|
+#define PCF50606_MBCC1_CHGMOD_MASK 0x1c
|
|
+#define PCF50606_MBCC1_CHGMOD_SHIFT 2
|
|
+ PCF50606_MBCC1_CHGMOD_QUAL = 0x00,
|
|
+ PCF50606_MBCC1_CHGMOD_PRE = 0x04,
|
|
+ PCF50606_MBCC1_CHGMOD_TRICKLE = 0x08,
|
|
+ PCF50606_MBCC1_CHGMOD_FAST_CCCV = 0x0c,
|
|
+ PCF50606_MBCC1_CHGMOD_FAST_NOCC = 0x10,
|
|
+ PCF50606_MBCC1_CHGMOD_FAST_NOCV = 0x14,
|
|
+ PCF50606_MBCC1_CHGMOD_FAST_SW = 0x18,
|
|
+ PCF50606_MBCC1_CHGMOD_IDLE = 0x1c,
|
|
+ PCF50606_MBCC1_DETMOD_LOWCHG = 0x20,
|
|
+ PCF50606_MBCC1_DETMOD_WDRST = 0x40,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_acdc1 {
|
|
+ PCF50606_ACDC1_ACDDET = 0x01,
|
|
+ PCF50606_ACDC1_THRSHLD_1V0 = 0x00,
|
|
+ PCF50606_ACDC1_THRSHLD_1V2 = 0x02,
|
|
+ PCF50606_ACDC1_THRSHLD_1V4 = 0x04,
|
|
+ PCF50606_ACDC1_THRSHLD_1V6 = 0x06,
|
|
+ PCF50606_ACDC1_THRSHLD_1V8 = 0x08,
|
|
+ PCF50606_ACDC1_THRSHLD_2V0 = 0x0a,
|
|
+ PCF50606_ACDC1_THRSHLD_2V2 = 0x0c,
|
|
+ PCF50606_ACDC1_THRSHLD_2V4 = 0x0e,
|
|
+ PCF50606_ACDC1_DISDB = 0x10,
|
|
+ PCF50606_ACDC1_ACDAPE = 0x80,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_bvmc {
|
|
+ PCF50606_BVMC_LOWBAT = 0x01,
|
|
+ PCF50606_BVMC_THRSHLD_NULL = 0x00,
|
|
+ PCF50606_BVMC_THRSHLD_2V8 = 0x02,
|
|
+ PCF50606_BVMC_THRSHLD_2V9 = 0x04,
|
|
+ PCF50606_BVMC_THRSHLD_3V = 0x08,
|
|
+ PCF50606_BVMC_THRSHLD_3V1 = 0x08,
|
|
+ PCF50606_BVMC_THRSHLD_3V2 = 0x0a,
|
|
+ PCF50606_BVMC_THRSHLD_3V3 = 0x0c,
|
|
+ PCF50606_BVMC_THRSHLD_3V4 = 0x0e,
|
|
+ PCF50606_BVMC_DISDB = 0x10,
|
|
+};
|
|
+
|
|
+enum pcf50606_reg_pwmc1 {
|
|
+ PCF50606_PWMC1_ACTSET = 0x01,
|
|
+ PCF50606_PWMC1_PWMDC_0_16 = 0x00,
|
|
+ PCF50606_PWMC1_PWMDC_1_16 = 0x02,
|
|
+ PCF50606_PWMC1_PWMDC_2_16 = 0x04,
|
|
+ PCF50606_PWMC1_PWMDC_3_16 = 0x06,
|
|
+ PCF50606_PWMC1_PWMDC_4_16 = 0x08,
|
|
+ PCF50606_PWMC1_PWMDC_5_16 = 0x0a,
|
|
+ PCF50606_PWMC1_PWMDC_6_16 = 0x0c,
|
|
+ PCF50606_PWMC1_PWMDC_7_16 = 0x0e,
|
|
+ PCF50606_PWMC1_PWMDC_8_16 = 0x10,
|
|
+ PCF50606_PWMC1_PWMDC_9_16 = 0x12,
|
|
+ PCF50606_PWMC1_PWMDC_10_16 = 0x14,
|
|
+ PCF50606_PWMC1_PWMDC_11_16 = 0x16,
|
|
+ PCF50606_PWMC1_PWMDC_12_16 = 0x18,
|
|
+ PCF50606_PWMC1_PWMDC_13_16 = 0x1a,
|
|
+ PCF50606_PWMC1_PWMDC_14_16 = 0x1c,
|
|
+ PCF50606_PWMC1_PWMDC_15_16 = 0x1e,
|
|
+ PCF50606_PWMC1_PRESC_512Hz = 0x20,
|
|
+ PCF50606_PWMC1_PRESC_256Hz = 0x40,
|
|
+ PCF50606_PWMC1_PRESC_64Hz = 0x60,
|
|
+ PCF50606_PWMC1_PRESC_56kHz = 0x80,
|
|
+ PCF50606_PWMC1_PRESC_28kHz = 0xa0,
|
|
+ PCF50606_PWMC1_PRESC_14kHz = 0xc0,
|
|
+ PCF50606_PWMC1_PRESC_7kHz = 0xe0,
|
|
+};
|
|
+#define PCF50606_PWMC1_CLK_SHIFT 5
|
|
+#define PCF50606_PWMC1_DC_SHIFT 1
|
|
+
|
|
+#endif /* _PCF50606_H */
|
|
+
|
|
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
|
|
index 580acc9..2cda699 100644
|
|
--- a/include/linux/i2c-id.h
|
|
+++ b/include/linux/i2c-id.h
|
|
@@ -94,6 +94,7 @@
|
|
#define I2C_DRIVERID_I2CDEV 900
|
|
|
|
#define I2C_DRIVERID_OV7670 1048 /* Omnivision 7670 camera */
|
|
+#define I2C_DRIVERID_PCF50606 1049
|
|
|
|
/*
|
|
* ---- Adapter types ----------------------------------------------------
|
|
diff --git a/include/linux/pcf50606.h b/include/linux/pcf50606.h
|
|
new file mode 100644
|
|
index 0000000..bc98e47
|
|
--- /dev/null
|
|
+++ b/include/linux/pcf50606.h
|
|
@@ -0,0 +1,108 @@
|
|
+#ifndef _LINUX_PCF50606_H
|
|
+#define _LINUX_PCF50606_H
|
|
+
|
|
+/* public in-kernel pcf50606 api */
|
|
+enum pcf50606_regulator_id {
|
|
+ PCF50606_REGULATOR_DCD,
|
|
+ PCF50606_REGULATOR_DCDE,
|
|
+ PCF50606_REGULATOR_DCUD,
|
|
+ PCF50606_REGULATOR_D1REG,
|
|
+ PCF50606_REGULATOR_D2REG,
|
|
+ PCF50606_REGULATOR_D3REG,
|
|
+ PCF50606_REGULATOR_LPREG,
|
|
+ PCF50606_REGULATOR_IOREG,
|
|
+ __NUM_PCF50606_REGULATORS
|
|
+};
|
|
+
|
|
+struct pcf50606_data;
|
|
+
|
|
+/* This is an ugly construct on how to access the (currently single/global)
|
|
+ * pcf50606 handle from other code in the kernel. I didn't really come up with
|
|
+ * a more decent method of dynamically resolving this */
|
|
+extern struct pcf50606_data *pcf50606_global;
|
|
+
|
|
+extern void
|
|
+pcf50606_go_standby(void);
|
|
+
|
|
+extern void
|
|
+pcf50606_gpo0_set(struct pcf50606_data *pcf, int on);
|
|
+
|
|
+extern int
|
|
+pcf50606_gpo0_get(struct pcf50606_data *pcf);
|
|
+
|
|
+extern int
|
|
+pcf50606_voltage_set(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg,
|
|
+ unsigned int millivolts);
|
|
+extern unsigned int
|
|
+pcf50606_voltage_get(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg);
|
|
+extern int
|
|
+pcf50606_onoff_get(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg);
|
|
+
|
|
+extern int
|
|
+pcf50606_onoff_set(struct pcf50606_data *pcf,
|
|
+ enum pcf50606_regulator_id reg, int on);
|
|
+
|
|
+extern void
|
|
+pcf50606_charge_fast(struct pcf50606_data *pcf, int on);
|
|
+
|
|
+#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,
|
|
+ __NUM_PMU_EVTS
|
|
+};
|
|
+
|
|
+typedef int pmu_cb(struct device *dev, unsigned int feature,
|
|
+ enum pmu_event event);
|
|
+
|
|
+#define PCF50606_FEAT_EXTON 0x00000001 /* not yet supported */
|
|
+#define PCF50606_FEAT_MBC 0x00000002
|
|
+#define PCF50606_FEAT_BBC 0x00000004 /* not yet supported */
|
|
+#define PCF50606_FEAT_TSC 0x00000008 /* not yet supported */
|
|
+#define PCF50606_FEAT_WDT 0x00000010
|
|
+#define PCF50606_FEAT_ACD 0x00000020
|
|
+#define PCF50606_FEAT_RTC 0x00000040
|
|
+#define PCF50606_FEAT_PWM 0x00000080
|
|
+#define PCF50606_FEAT_CHGCUR 0x00000100
|
|
+#define PCF50606_FEAT_BATVOLT 0x00000200
|
|
+#define PCF50606_FEAT_BATTEMP 0x00000400
|
|
+#define PCF50606_FEAT_PWM_BL 0x00000800
|
|
+
|
|
+struct pcf50606_platform_data {
|
|
+ /* general */
|
|
+ unsigned int used_features;
|
|
+ unsigned int onkey_seconds_required;
|
|
+
|
|
+ /* voltage regulator related */
|
|
+ struct pmu_voltage_rail rails[__NUM_PCF50606_REGULATORS];
|
|
+ unsigned int used_regulators;
|
|
+
|
|
+ /* charger related */
|
|
+ unsigned int r_fix_batt;
|
|
+ unsigned int r_fix_batt_par;
|
|
+ unsigned int r_sense_milli;
|
|
+
|
|
+ /* backlight related */
|
|
+ unsigned int init_brightness;
|
|
+
|
|
+ struct {
|
|
+ u_int8_t mbcc3; /* charger voltage / current */
|
|
+ } charger;
|
|
+ pmu_cb *cb;
|
|
+};
|
|
+
|
|
+#endif
|
|
--
|
|
1.5.6.3
|
|
|