mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-09-12 09:33:20 +03:00
(stable-branch of Openmoko) git-svn-id: svn://svn.openwrt.org/openwrt/trunk@13613 3c298f89-4303-0410-b956-a3cf2f4a3e73
2563 lines
75 KiB
Diff
2563 lines
75 KiB
Diff
From d485cabe5b95e0b277459ca1c605a0751f5e1dea Mon Sep 17 00:00:00 2001
|
|
From: mokopatches <mokopatches@openmoko.org>
|
|
Date: Fri, 4 Apr 2008 11:34:36 +0100
|
|
Subject: [PATCH] pcf50633.patch
|
|
|
|
---
|
|
drivers/i2c/chips/Kconfig | 9 +
|
|
drivers/i2c/chips/Makefile | 1 +
|
|
drivers/i2c/chips/pcf50633.c | 1964 ++++++++++++++++++++++++++++++++++++++++++
|
|
drivers/i2c/chips/pcf50633.h | 402 +++++++++
|
|
include/linux/i2c-id.h | 1 +
|
|
include/linux/pcf50633.h | 114 +++
|
|
6 files changed, 2491 insertions(+), 0 deletions(-)
|
|
create mode 100644 drivers/i2c/chips/pcf50633.c
|
|
create mode 100644 drivers/i2c/chips/pcf50633.h
|
|
create mode 100644 include/linux/pcf50633.h
|
|
|
|
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
|
|
index bf0da3a..1d6d36f 100644
|
|
--- a/drivers/i2c/chips/Kconfig
|
|
+++ b/drivers/i2c/chips/Kconfig
|
|
@@ -61,6 +61,15 @@ config SENSORS_PCF50606
|
|
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"
|
|
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
|
|
index a4772c7..4b6ba04 100644
|
|
--- a/drivers/i2c/chips/Makefile
|
|
+++ b/drivers/i2c/chips/Makefile
|
|
@@ -10,6 +10,7 @@ obj-$(CONFIG_SENSORS_MAX6875) += max6875.o
|
|
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
|
|
diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
|
|
new file mode 100644
|
|
index 0000000..5488084
|
|
--- /dev/null
|
|
+++ b/drivers/i2c/chips/pcf50633.c
|
|
@@ -0,0 +1,1964 @@
|
|
+/* Philips PCF50633 Power Management Unit (PMU) driver
|
|
+ *
|
|
+ * (C) 2006-2007 by OpenMoko, Inc.
|
|
+ * Author: Harald Welte <laforge@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
|
|
+ * - adc driver (hw_sensors like)
|
|
+ * - 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/delay.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/pcf50633.h>
|
|
+#include <linux/apm-emulation.h>
|
|
+
|
|
+#include <asm/mach-types.h>
|
|
+#include <asm/arch/gta02.h>
|
|
+
|
|
+#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_FIDX_CHG_ENABLED 0 /* Charger enabled */
|
|
+#define PCF50633_FIDX_CHG_PRESENT 1 /* Charger present */
|
|
+#define PCF50633_FIDX_CHG_ERR 3 /* Charger Error */
|
|
+#define PCF50633_FIDX_CHG_PROT 4 /* Charger Protection */
|
|
+#define PCF50633_FIDX_CHG_READY 5 /* Charging completed */
|
|
+#define PCF50633_FIDX_PWR_PRESSED 8
|
|
+#define PCF50633_FIDX_RTC_SECOND 9
|
|
+#define PCF50633_FIDX_USB_PRESENT 10
|
|
+
|
|
+#define PCF50633_F_CHG_ENABLED (1 << PCF50633_FIDX_CHG_ENABLED)
|
|
+#define PCF50633_F_CHG_PRESENT (1 << PCF50633_FIDX_CHG_PRESENT)
|
|
+#define PCF50633_F_CHG_ERR (1 << PCF50633_FIDX_CHG_ERR)
|
|
+#define PCF50633_F_CHG_PROT (1 << PCF50633_FIDX_CHG_PROT)
|
|
+#define PCF50633_F_CHG_READY (1 << PCF50633_FIDX_CHG_READY)
|
|
+
|
|
+#define PCF50633_F_CHG_MASK 0x000000fc
|
|
+
|
|
+#define PCF50633_F_PWR_PRESSED (1 << PCF50633_FIDX_PWR_PRESSED)
|
|
+#define PCF50633_F_RTC_SECOND (1 << PCF50633_FIDX_RTC_SECOND)
|
|
+#define PCF50633_F_USB_PRESENT (1 << PCF50633_FIDX_USB_PRESENT)
|
|
+
|
|
+enum close_state {
|
|
+ CLOSE_STATE_NOT,
|
|
+ CLOSE_STATE_ALLOW = 0x2342,
|
|
+};
|
|
+
|
|
+enum charger_type {
|
|
+ CHARGER_TYPE_NONE = 0,
|
|
+ CHARGER_TYPE_HOSTUSB,
|
|
+ CHARGER_TYPE_1A
|
|
+};
|
|
+
|
|
+#define ADC_NOM_CHG_DETECT_1A 6
|
|
+#define ADC_NOM_CHG_DETECT_NONE 43
|
|
+
|
|
+#define MAX_ADC_FIFO_DEPTH 8
|
|
+
|
|
+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 mutex working_lock;
|
|
+ struct work_struct work;
|
|
+ struct rtc_device *rtc;
|
|
+ struct input_dev *input_dev;
|
|
+ int allow_close;
|
|
+ int onkey_seconds;
|
|
+ int irq;
|
|
+
|
|
+ int coldplug_done; /* cleared by probe, set by first work service */
|
|
+ int flag_bat_voltage_read; /* ipc to /sys batt voltage read func */
|
|
+
|
|
+ int charger_adc_result_raw;
|
|
+ enum charger_type charger_type;
|
|
+
|
|
+ /* we have a FIFO of ADC measurement requests that are used only by
|
|
+ * the workqueue service code after the ADC completion interrupt
|
|
+ */
|
|
+ int adc_queue_mux[MAX_ADC_FIFO_DEPTH]; /* which ADC input to use */
|
|
+ int adc_queue_avg[MAX_ADC_FIFO_DEPTH]; /* amount of averaging */
|
|
+ int adc_queue_head; /* head owned by foreground code */
|
|
+ int adc_queue_tail; /* tail owned by service code */
|
|
+
|
|
+#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_GPL(pcf50633_global);
|
|
+
|
|
+static struct platform_device *pcf50633_pdev;
|
|
+
|
|
+/***********************************************************************
|
|
+ * Low-Level routines
|
|
+ ***********************************************************************/
|
|
+
|
|
+static 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 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;
|
|
+}
|
|
+
|
|
+/* asynchronously setup reading one ADC channel */
|
|
+static void async_adc_read_setup(struct pcf50633_data *pcf,
|
|
+ int channel, int avg)
|
|
+{
|
|
+ channel &= PCF50633_ADCC1_ADCMUX_MASK;
|
|
+
|
|
+ /* kill ratiometric, but enable ACCSW biasing */
|
|
+ __reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
|
|
+ __reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
|
|
+
|
|
+ /* start ADC conversion of selected channel */
|
|
+ __reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
|
|
+ PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
|
|
+
|
|
+}
|
|
+
|
|
+static u_int16_t async_adc_complete(struct pcf50633_data *pcf)
|
|
+{
|
|
+ u_int16_t ret = (__reg_read(pcf, PCF50633_REG_ADCS1) << 2) |
|
|
+ (__reg_read(pcf, PCF50633_REG_ADCS3) &
|
|
+ PCF50633_ADCS3_ADCDAT1L_MASK);
|
|
+
|
|
+ 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_GPL(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_GPL(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_GPL(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_GPL(pcf50633_voltage_get);
|
|
+
|
|
+/* go into 'STANDBY' mode, i.e. power off the main CPU and peripherals */
|
|
+void pcf50633_go_standby(void)
|
|
+{
|
|
+ reg_set_bit_mask(pcf50633_global, PCF50633_REG_OOCSHDWN,
|
|
+ PCF50633_OOCSHDWN_GOSTDBY, PCF50633_OOCSHDWN_GOSTDBY);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50633_go_standby);
|
|
+
|
|
+void pcf50633_gpio_set(struct pcf50633_data *pcf, enum pcf50633_gpio gpio,
|
|
+ int on)
|
|
+{
|
|
+ u_int8_t reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
|
+
|
|
+ if (on)
|
|
+ reg_set_bit_mask(pcf, reg, 0x0f, 0x07);
|
|
+ else
|
|
+ reg_set_bit_mask(pcf, reg, 0x0f, 0x00);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50633_gpio_set);
|
|
+
|
|
+int pcf50633_gpio_get(struct pcf50633_data *pcf, enum pcf50633_gpio gpio)
|
|
+{
|
|
+ u_int8_t reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
|
+ u_int8_t val = reg_read(pcf, reg) & 0x0f;
|
|
+
|
|
+ if (val == PCF50633_GPOCFG_GPOSEL_1 ||
|
|
+ val == (PCF50633_GPOCFG_GPOSEL_0|PCF50633_GPOCFG_GPOSEL_INVERSE))
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
|
|
+
|
|
+static int interpret_charger_type_from_adc(struct pcf50633_data *pcf,
|
|
+ int sample)
|
|
+{
|
|
+ /* 1A capable charger? */
|
|
+
|
|
+ if (sample < ((ADC_NOM_CHG_DETECT_NONE + ADC_NOM_CHG_DETECT_1A) / 2))
|
|
+ return CHARGER_TYPE_1A;
|
|
+
|
|
+ /* well then, nothing in the USB hole, or USB host / unk adapter */
|
|
+
|
|
+ if (pcf->flags & PCF50633_F_USB_PRESENT) /* ooh power is in there */
|
|
+ return CHARGER_TYPE_HOSTUSB; /* HOSTUSB is the catchall */
|
|
+
|
|
+ return CHARGER_TYPE_NONE; /* no really -- nothing in there */
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+static void configure_pmu_for_charger(struct pcf50633_data *pcf,
|
|
+ enum charger_type type)
|
|
+{
|
|
+ switch (type) {
|
|
+ case CHARGER_TYPE_NONE:
|
|
+ __reg_write(pcf, PCF50633_REG_MBCC7,
|
|
+ PCF50633_MBCC7_USB_SUSPEND);
|
|
+ break;
|
|
+ /*
|
|
+ * the PCF50633 has a feature that it will supply only excess current
|
|
+ * from the charger that is not used to power the device. So this
|
|
+ * 500mA setting is "up to 500mA" according to that.
|
|
+ */
|
|
+ case CHARGER_TYPE_HOSTUSB:
|
|
+ __reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_500mA);
|
|
+ break;
|
|
+ case CHARGER_TYPE_1A:
|
|
+ __reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_1000mA);
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void trigger_next_adc_job_if_any(struct pcf50633_data *pcf)
|
|
+{
|
|
+ if (pcf->adc_queue_head == pcf->adc_queue_tail)
|
|
+ return;
|
|
+ async_adc_read_setup(pcf,
|
|
+ pcf->adc_queue_mux[pcf->adc_queue_tail],
|
|
+ pcf->adc_queue_avg[pcf->adc_queue_tail]);
|
|
+}
|
|
+
|
|
+static void add_request_to_adc_queue(struct pcf50633_data *pcf,
|
|
+ int mux, int avg)
|
|
+{
|
|
+ int old_head = pcf->adc_queue_head;
|
|
+ pcf->adc_queue_mux[pcf->adc_queue_head] = mux;
|
|
+ pcf->adc_queue_avg[pcf->adc_queue_head] = avg;
|
|
+
|
|
+ pcf->adc_queue_head = (pcf->adc_queue_head + 1) &
|
|
+ (MAX_ADC_FIFO_DEPTH - 1);
|
|
+
|
|
+ /* it was idle before we just added this? we need to kick it then */
|
|
+ if (old_head == pcf->adc_queue_tail)
|
|
+ trigger_next_adc_job_if_any(pcf);
|
|
+}
|
|
+
|
|
+static void pcf50633_work(struct work_struct *work)
|
|
+{
|
|
+ struct pcf50633_data *pcf =
|
|
+ container_of(work, struct pcf50633_data, work);
|
|
+ u_int8_t pcfirq[5];
|
|
+ int ret;
|
|
+ int tail;
|
|
+
|
|
+ mutex_lock(&pcf->working_lock);
|
|
+ pcf->working = 1;
|
|
+ /*
|
|
+ * datasheet says we have to read the five IRQ
|
|
+ * status regs in one transaction
|
|
+ */
|
|
+ ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50633_REG_INT1, 5,
|
|
+ pcfirq);
|
|
+ if (ret != 5)
|
|
+ DEBUGP("Oh crap PMU IRQ register read failed %d\n", ret);
|
|
+
|
|
+ if (!pcf->coldplug_done) {
|
|
+ DEBUGP("PMU Coldplug init\n");
|
|
+
|
|
+ /* we used SECOND to kick ourselves started -- turn it off */
|
|
+ pcfirq[0] &= ~PCF50633_INT1_SECOND;
|
|
+ reg_set_bit_mask(pcf, PCF50633_REG_INT1M,
|
|
+ PCF50633_INT1_SECOND,
|
|
+ PCF50633_INT1_SECOND);
|
|
+
|
|
+ /* coldplug the USB if present */
|
|
+ if ((__reg_read(pcf, PCF50633_REG_MBCS1) &
|
|
+ (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) ==
|
|
+ (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) {
|
|
+ DEBUGPC("COLD USBINS\n");
|
|
+ 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);
|
|
+ }
|
|
+
|
|
+ /* figure out our initial charging stance */
|
|
+ add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
|
|
+ PCF50633_ADCC1_AVERAGE_16);
|
|
+
|
|
+ pcf->coldplug_done = 1;
|
|
+ }
|
|
+
|
|
+ DEBUGP("INT1=0x%02x INT2=0x%02x INT3=0x%02x INT4=0x%02x INT5=0x%02x\n",
|
|
+ pcfirq[0], pcfirq[1], pcfirq[2], pcfirq[3], pcfirq[4]);
|
|
+
|
|
+ if (pcfirq[0] & 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 (pcfirq[0] & 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 (pcfirq[0] & 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);
|
|
+ /* completion irq will figure out our charging stance */
|
|
+ add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
|
|
+ PCF50633_ADCC1_AVERAGE_16);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50633_INT1_USBREM) {
|
|
+ DEBUGPC("USBREM ");
|
|
+ /* only deal if we had understood it was in */
|
|
+ if (pcf->flags & PCF50633_F_USB_PRESENT) {
|
|
+ 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);
|
|
+ /* completion irq will figure out our charging stance */
|
|
+ add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
|
|
+ PCF50633_ADCC1_AVERAGE_16);
|
|
+ }
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50633_INT1_ALARM) {
|
|
+ DEBUGPC("ALARM ");
|
|
+ if (pcf->pdata->used_features & PCF50633_FEAT_RTC)
|
|
+ rtc_update_irq(pcf->rtc, 1, RTC_AF | RTC_IRQF);
|
|
+ }
|
|
+ if (pcfirq[0] & PCF50633_INT1_SECOND) {
|
|
+ DEBUGPC("SECOND ");
|
|
+ if (pcf->flags & PCF50633_F_RTC_SECOND)
|
|
+ rtc_update_irq(pcf->rtc, 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_sig_init) {
|
|
+ /* Ask init to do 'ctrlaltdel' */
|
|
+ DEBUGPC("SIGINT(init) ");
|
|
+ kill_proc(1, SIGINT, 1);
|
|
+ /* FIXME: what if userspace doesn't shut down? */
|
|
+ }
|
|
+ if (pcf->onkey_seconds >=
|
|
+ pcf->pdata->onkey_seconds_shutdown) {
|
|
+ DEBUGPC("Power Off ");
|
|
+ pcf50633_go_standby();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (pcfirq[1] & 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 (pcfirq[1] & 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 (pcfirq[2] & PCF50633_INT3_BATFULL) {
|
|
+ DEBUGPC("BATFULL ");
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_CHGHALT) {
|
|
+ DEBUGPC("CHGHALT ");
|
|
+ /*
|
|
+ * this is really "battery not pulling current" -- it can
|
|
+ * appear with no battery attached
|
|
+ */
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_THLIMON) {
|
|
+ DEBUGPC("THLIMON ");
|
|
+ pcf->flags |= PCF50633_F_CHG_PROT;
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_THLIMOFF) {
|
|
+ DEBUGPC("THLIMOFF ");
|
|
+ pcf->flags &= ~PCF50633_F_CHG_PROT;
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_USBLIMON) {
|
|
+ DEBUGPC("USBLIMON ");
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_USBLIMOFF) {
|
|
+ DEBUGPC("USBLIMOFF ");
|
|
+ /* FIXME: signal this to userspace */
|
|
+ }
|
|
+ if (pcfirq[2] & PCF50633_INT3_ADCRDY) {
|
|
+ /* ADC result ready */
|
|
+ DEBUGPC("ADCRDY ");
|
|
+ tail = pcf->adc_queue_tail;
|
|
+ pcf->adc_queue_tail = (pcf->adc_queue_tail + 1) &
|
|
+ (MAX_ADC_FIFO_DEPTH - 1);
|
|
+
|
|
+ switch (pcf->adc_queue_mux[tail]) {
|
|
+ case PCF50633_ADCC1_MUX_BATSNS_RES: /* battery voltage */
|
|
+ pcf->flag_bat_voltage_read =
|
|
+ async_adc_complete(pcf);
|
|
+ break;
|
|
+ case PCF50633_ADCC1_MUX_ADCIN1: /* charger type */
|
|
+ pcf->charger_adc_result_raw = async_adc_complete(pcf);
|
|
+ pcf->charger_type = interpret_charger_type_from_adc(
|
|
+ pcf, pcf->charger_adc_result_raw);
|
|
+ configure_pmu_for_charger(pcf, pcf->charger_type);
|
|
+ break;
|
|
+ default:
|
|
+ async_adc_complete(pcf);
|
|
+ break;
|
|
+ }
|
|
+ trigger_next_adc_job_if_any(pcf);
|
|
+ }
|
|
+ if (pcfirq[2] & 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 (pcfirq[3] & (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 (pcfirq[3] & PCF50633_INT4_HIGHTMP) {
|
|
+ /* High temperature */
|
|
+ DEBUGPC("HIGHTMP ");
|
|
+ apm_queue_event(APM_CRITICAL_SUSPEND);
|
|
+ }
|
|
+ if (pcfirq[3] & PCF50633_INT4_AUTOPWRFAIL) {
|
|
+ DEBUGPC("PCF50633_INT4_AUTOPWRFAIL ");
|
|
+ /* FIXME: deal with this */
|
|
+ }
|
|
+ if (pcfirq[3] & PCF50633_INT4_DWN1PWRFAIL) {
|
|
+ DEBUGPC("PCF50633_INT4_DWN1PWRFAIL ");
|
|
+ /* FIXME: deal with this */
|
|
+ }
|
|
+ if (pcfirq[3] & PCF50633_INT4_DWN2PWRFAIL) {
|
|
+ DEBUGPC("PCF50633_INT4_DWN2PWRFAIL ");
|
|
+ /* FIXME: deal with this */
|
|
+ }
|
|
+ if (pcfirq[3] & PCF50633_INT4_LEDPWRFAIL) {
|
|
+ DEBUGPC("PCF50633_INT4_LEDPWRFAIL ");
|
|
+ /* FIXME: deal with this */
|
|
+ }
|
|
+ if (pcfirq[3] & PCF50633_INT4_LEDOVP) {
|
|
+ DEBUGPC("PCF50633_INT4_LEDOVP ");
|
|
+ /* FIXME: deal with this */
|
|
+ }
|
|
+
|
|
+ DEBUGPC("\n");
|
|
+
|
|
+ pcf->working = 0;
|
|
+ input_sync(pcf->input_dev);
|
|
+ put_device(&pcf->client.dev);
|
|
+ mutex_unlock(&pcf->working_lock);
|
|
+}
|
|
+
|
|
+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);
|
|
+
|
|
+ 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 pcf50633_battvolt(struct pcf50633_data *pcf)
|
|
+{
|
|
+ int count = 10;
|
|
+
|
|
+ pcf->flag_bat_voltage_read = -1;
|
|
+ add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_BATSNS_RES,
|
|
+ PCF50633_ADCC1_AVERAGE_16);
|
|
+
|
|
+ while ((count--) && (pcf->flag_bat_voltage_read < 0))
|
|
+ msleep(1);
|
|
+
|
|
+ if (count < 0) { /* timeout somehow */
|
|
+ DEBUGPC("pcf50633_battvolt timeout :-(\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return adc_to_batt_millivolts(pcf->flag_bat_voltage_read);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(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
|
|
+ ***********************************************************************/
|
|
+
|
|
+/* Set maximum USB current limit */
|
|
+void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma)
|
|
+{
|
|
+ u_int8_t bits;
|
|
+
|
|
+ dev_dbg(&pcf->client.dev, "setting usb current limit to %d ma", ma);
|
|
+
|
|
+ if (ma >= 1000)
|
|
+ bits = PCF50633_MBCC7_USB_1000mA;
|
|
+ else if (ma >= 500)
|
|
+ bits = PCF50633_MBCC7_USB_500mA;
|
|
+ else if (ma >= 100)
|
|
+ bits = PCF50633_MBCC7_USB_100mA;
|
|
+ else
|
|
+ bits = PCF50633_MBCC7_USB_SUSPEND;
|
|
+
|
|
+ reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, PCF56033_MBCC7_USB_MASK,
|
|
+ bits);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50633_usb_curlim_set);
|
|
+
|
|
+static ssize_t show_usblim(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 usblim = reg_read(pcf, PCF50633_REG_MBCC7) &
|
|
+ PCF56033_MBCC7_USB_MASK;
|
|
+ unsigned int ma;
|
|
+
|
|
+ if (usblim == PCF50633_MBCC7_USB_1000mA)
|
|
+ ma = 1000;
|
|
+ else if (usblim == PCF50633_MBCC7_USB_500mA)
|
|
+ ma = 500;
|
|
+ else if (usblim == PCF50633_MBCC7_USB_100mA)
|
|
+ ma = 100;
|
|
+ else
|
|
+ ma = 0;
|
|
+
|
|
+ return sprintf(buf, "%u\n", ma);
|
|
+}
|
|
+static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, NULL);
|
|
+
|
|
+/* Enable/disable charging */
|
|
+void pcf50633_charge_enable(struct pcf50633_data *pcf, int on)
|
|
+{
|
|
+ u_int8_t bits;
|
|
+
|
|
+ if (!(pcf->pdata->used_features & PCF50633_FEAT_MBC))
|
|
+ return;
|
|
+
|
|
+ if (on) {
|
|
+ pcf->flags |= PCF50633_F_CHG_ENABLED;
|
|
+ bits = PCF50633_MBCC1_CHGENA;
|
|
+ } else {
|
|
+ pcf->flags &= ~PCF50633_F_CHG_ENABLED;
|
|
+ bits = 0;
|
|
+ }
|
|
+ reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, PCF50633_MBCC1_CHGENA,
|
|
+ bits);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pcf50633_charge_enable);
|
|
+
|
|
+#if 0
|
|
+#define ONE 1000000
|
|
+static 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;
|
|
+}
|
|
+#endif
|
|
+static ssize_t show_battemp(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ return sprintf(buf, "\n");
|
|
+}
|
|
+static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL);
|
|
+#if 0
|
|
+static 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);
|
|
+}
|
|
+#endif
|
|
+static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ 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);
|
|
+
|
|
+ /* As opposed to the PCF50606, we can only enable or disable
|
|
+ * charging and not directly jump into a certain mode! */
|
|
+
|
|
+ if (!strcmp(buf, "0\n"))
|
|
+ pcf50633_charge_enable(pcf, 0);
|
|
+ else
|
|
+ pcf50633_charge_enable(pcf, 1);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(chgmode, S_IRUGO | S_IWUSR, show_chgmode, set_chgmode);
|
|
+
|
|
+static const char *chgstate_names[] = {
|
|
+ [PCF50633_FIDX_CHG_ENABLED] = "enabled",
|
|
+ [PCF50633_FIDX_CHG_PRESENT] = "charger_present",
|
|
+ [PCF50633_FIDX_USB_PRESENT] = "usb_present",
|
|
+ [PCF50633_FIDX_CHG_ERR] = "error",
|
|
+ [PCF50633_FIDX_CHG_PROT] = "protection",
|
|
+ [PCF50633_FIDX_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);
|
|
+
|
|
+ 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
|
|
+ ***********************************************************************/
|
|
+
|
|
+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 = pcf50633_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_PLAY:
|
|
+ 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);
|
|
+ alrm->enabled =
|
|
+ __reg_read(pcf, PCF50633_REG_INT1M) & PCF50633_INT1_ALARM ? 0 : 1;
|
|
+ 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 = bl_get_data(bd);
|
|
+ int intensity = reg_read(pcf, PCF50633_REG_LEDOUT);
|
|
+
|
|
+ return intensity & 0x3f;
|
|
+}
|
|
+
|
|
+static int pcf50633bl_set_intensity(struct backlight_device *bd)
|
|
+{
|
|
+ struct pcf50633_data *pcf = bl_get_data(bd);
|
|
+ int intensity = bd->props.brightness;
|
|
+ int old_intensity = reg_read(pcf, PCF50633_REG_LEDOUT);
|
|
+ u_int8_t ledena;
|
|
+ int ret;
|
|
+
|
|
+ if (bd->props.power != FB_BLANK_UNBLANK)
|
|
+ intensity = 0;
|
|
+ if (bd->props.fb_blank != FB_BLANK_UNBLANK)
|
|
+ intensity = 0;
|
|
+
|
|
+ /* The PCF50633 seems to have some kind of oddity (bug?) when
|
|
+ * the intensity was 0, you need to completely switch it off
|
|
+ * and re-enable it, before it produces any output voltage again */
|
|
+
|
|
+ if (intensity != 0 && old_intensity == 0) {
|
|
+ ledena = reg_read(pcf, PCF50633_REG_LEDENA);
|
|
+ reg_write(pcf, PCF50633_REG_LEDENA, 0x00);
|
|
+ }
|
|
+
|
|
+ ret = reg_set_bit_mask(pcf, PCF50633_REG_LEDOUT, 0x3f,
|
|
+ intensity);
|
|
+
|
|
+ if (intensity != 0 && old_intensity == 0)
|
|
+ reg_write(pcf, PCF50633_REG_LEDENA, ledena);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct backlight_ops pcf50633bl_ops = {
|
|
+ .get_brightness = pcf50633bl_get_intensity,
|
|
+ .update_status = pcf50633bl_set_intensity,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Charger type
|
|
+ */
|
|
+
|
|
+static ssize_t show_charger_type(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);
|
|
+ static const char *names_charger_type[] = {
|
|
+ [CHARGER_TYPE_NONE] = "none",
|
|
+ [CHARGER_TYPE_HOSTUSB] = "host/500mA usb",
|
|
+ [CHARGER_TYPE_1A] = "charger 1A",
|
|
+ };
|
|
+ static const char *names_charger_modes[] = {
|
|
+ [PCF50633_MBCC7_USB_1000mA] = "1A",
|
|
+ [PCF50633_MBCC7_USB_500mA] = "500mA",
|
|
+ [PCF50633_MBCC7_USB_100mA] = "100mA",
|
|
+ [PCF50633_MBCC7_USB_SUSPEND] = "suspend",
|
|
+ };
|
|
+ int mode = reg_read(pcf, PCF50633_REG_MBCC7) & PCF56033_MBCC7_USB_MASK;
|
|
+
|
|
+ return sprintf(buf, "%s mode %s\n",
|
|
+ names_charger_type[pcf->charger_type],
|
|
+ names_charger_modes[mode]);
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(charger_type, 0444, show_charger_type, NULL);
|
|
+
|
|
+/*
|
|
+ * Charger adc
|
|
+ */
|
|
+
|
|
+static ssize_t show_charger_adc(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, "%d\n", pcf->charger_adc_result_raw);
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(charger_adc, 0444, show_charger_adc, NULL);
|
|
+
|
|
+/*
|
|
+ * Dump regs
|
|
+ */
|
|
+
|
|
+static ssize_t show_dump_regs(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);
|
|
+ u8 dump[16];
|
|
+ int n, n1, idx = 0;
|
|
+ char *buf1 = buf;
|
|
+ static u8 address_no_read[] = { /* must be ascending */
|
|
+ PCF50633_REG_INT1,
|
|
+ PCF50633_REG_INT2,
|
|
+ PCF50633_REG_INT3,
|
|
+ PCF50633_REG_INT4,
|
|
+ PCF50633_REG_INT5,
|
|
+ 0 /* terminator */
|
|
+ };
|
|
+
|
|
+ for (n = 0; n < 256; n += sizeof(dump)) {
|
|
+
|
|
+ for (n1 = 0; n1 < sizeof(dump); n1++)
|
|
+ if (n == address_no_read[idx]) {
|
|
+ idx++;
|
|
+ dump[n1] = 0x00;
|
|
+ } else
|
|
+ dump[n1] = reg_read(pcf, n + n1);
|
|
+
|
|
+ hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0);
|
|
+ buf1 += strlen(buf1);
|
|
+ *buf1++ = '\n';
|
|
+ *buf1 = '\0';
|
|
+ }
|
|
+
|
|
+ return buf1 - buf;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+ * 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 = "neo1973-pm-gps",
|
|
+};
|
|
+
|
|
+static struct platform_device gta01_pm_bt_dev = {
|
|
+ .name = "neo1973-pm-bt",
|
|
+};
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * CARE! This table is modified at runtime!
|
|
+ */
|
|
+static struct attribute *pcf_sysfs_entries[] = {
|
|
+ &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,
|
|
+ &dev_attr_charger_type.attr,
|
|
+ &dev_attr_charger_adc.attr,
|
|
+ &dev_attr_dump_regs.attr,
|
|
+ NULL, /* going to add things at this point! */
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ 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;
|
|
+ pcf_sysfs_entries[i++] = &dev_attr_usb_curlim.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);
|
|
+ mutex_init(&data->working_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;
|
|
+
|
|
+ data->input_dev = input_allocate_device();
|
|
+ if (!data->input_dev)
|
|
+ goto exit_sysfs;
|
|
+
|
|
+ data->input_dev->name = "GTA02 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);
|
|
+
|
|
+ err = input_register_device(data->input_dev);
|
|
+ if (err)
|
|
+ goto exit_sysfs;
|
|
+
|
|
+ /* configure interrupt mask */
|
|
+ reg_write(data, PCF50633_REG_INT1M, 0x00); /* we want SECOND to kick */
|
|
+ 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, IRQF_TRIGGER_FALLING,
|
|
+ "pcf50633", 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);
|
|
+
|
|
+ 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 = 0x3f;
|
|
+ data->backlight->props.power = FB_BLANK_UNBLANK;
|
|
+ data->backlight->props.fb_blank = FB_BLANK_UNBLANK;
|
|
+ data->backlight->props.brightness =
|
|
+ data->backlight->props.max_brightness;
|
|
+ backlight_update_status(data->backlight);
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ gta01_pm_bt_dev.dev.parent = &new_client->dev;
|
|
+ platform_device_register(>a01_pm_bt_dev);
|
|
+ platform_device_register(>a01_pm_gps_dev);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+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_input:
|
|
+ input_unregister_device(data->input_dev);
|
|
+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;
|
|
+
|
|
+ free_irq(pcf->irq, pcf);
|
|
+
|
|
+ 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);
|
|
+
|
|
+#ifdef CONFIG_MACH_NEO1973_GTA02
|
|
+ if (machine_is_neo1973_gta02()) {
|
|
+ platform_device_unregister(>a01_pm_bt_dev);
|
|
+ platform_device_unregister(>a01_pm_gps_dev);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ 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);
|
|
+
|
|
+ pcf50633_irq(pcf->irq, pcf);
|
|
+
|
|
+ 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_DESCRIPTION("I2C chip driver for NXP PCF50633 power management unit");
|
|
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_init(pcf50633_init);
|
|
+module_exit(pcf50633_exit);
|
|
diff --git a/drivers/i2c/chips/pcf50633.h b/drivers/i2c/chips/pcf50633.h
|
|
new file mode 100644
|
|
index 0000000..9aad206
|
|
--- /dev/null
|
|
+++ b/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 <laforge@openmoko.org>
|
|
+ *
|
|
+ */
|
|
+
|
|
+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 */
|
|
+
|
|
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
|
|
index a089554..ee60156 100644
|
|
--- a/include/linux/i2c-id.h
|
|
+++ b/include/linux/i2c-id.h
|
|
@@ -169,6 +169,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 ----------------------------------------------------
|
|
diff --git a/include/linux/pcf50633.h b/include/linux/pcf50633.h
|
|
new file mode 100644
|
|
index 0000000..5f32004
|
|
--- /dev/null
|
|
+++ b/include/linux/pcf50633.h
|
|
@@ -0,0 +1,114 @@
|
|
+#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);
|
|
+
|
|
+enum pcf50633_gpio {
|
|
+ PCF50633_GPIO1 = 1,
|
|
+ PCF50633_GPIO2 = 2,
|
|
+ PCF50633_GPIO3 = 3,
|
|
+ PCF50633_GPO = 4,
|
|
+};
|
|
+
|
|
+extern void
|
|
+pcf50633_gpio_set(struct pcf50633_data *pcf, enum pcf50633_gpio gpio, int on);
|
|
+
|
|
+extern int
|
|
+pcf50633_gpio_get(struct pcf50633_data *pcf, enum pcf50633_gpio gpio);
|
|
+
|
|
+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_usb_curlim_set(struct pcf50633_data *pcf, int ma);
|
|
+
|
|
+extern void
|
|
+pcf50633_charge_enable(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_sig_init;
|
|
+ unsigned int onkey_seconds_shutdown;
|
|
+
|
|
+ /* 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 */
|
|
--
|
|
1.5.6.5
|
|
|