aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYulay Rakhmangulov <yulayr@gmail.com>2010-07-21 01:18:40 -0700
committerKristoffer Ericson <kristoffer.ericson@gmail.com>2011-12-10 15:45:45 +0100
commit503ec726a60fa16a5c06a1c905c74b4cc3d58a4b (patch)
treeb4e217dc6f33bdb8a86f85219d2ffa05b1308be4
parent8de54aadfda4ef54dca996aca8ff2e153bbc81c3 (diff)
downloadlinux-hpc-503ec726a60fa16a5c06a1c905c74b4cc3d58a4b.tar.gz
ARM: NEC MobilePro900/c: battery and power manager platform driver.
ARM: NEC MobilePro900/c: battery and power manager platform driver. This driver depends on apm-emulation and mp900-pic drivers. Signed-off-by: Yulay Rakhmangulov <yulayr@gmail.com> Signed-off-by: Kristoffer Ericson <kristoffer.ericson@gmail.com>
-rw-r--r--drivers/power/Kconfig11
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/mp900_pm.c369
3 files changed, 381 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index faaa9b4d0d0711..87cb9c2b06852d 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -36,6 +36,17 @@ config MAX8925_POWER
Say Y here to enable support for the battery charger in the Maxim
MAX8925 PMIC.
+config MP900C_POWER
+ tristate "NEC MobilePro 900/c battery and power support"
+ depends on APM_POWER && MACH_MP900C
+ default y
+ help
+ Say Y here if you have a NEC MobilePro 900/c and want to
+ support the built-in battery and power button.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mp900_pm.
+
config WM831X_BACKUP
tristate "WM831X backup battery charger support"
depends on MFD_WM831X
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index a2ba7c85c97a55..8c7a2abd4cd3e8 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_PDA_POWER) += pda_power.o
obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
+obj-$(CONFIG_MP900C_POWER) += mp900_pm.o
obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
diff --git a/drivers/power/mp900_pm.c b/drivers/power/mp900_pm.c
new file mode 100644
index 00000000000000..c93067d0d545af
--- /dev/null
+++ b/drivers/power/mp900_pm.c
@@ -0,0 +1,369 @@
+/*
+ * Battery and Power Management driver for the NEC MobilePro900/c
+ *
+ * 2010(c) Yulay Rakhmangulov <yulayr@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/apm-emulation.h>
+#include <linux/suspend.h>
+#include <mach/pm.h>
+
+#include <mach/pic-pxa2xx.h>
+
+#define PFX "mp900-pm: "
+
+#define BATTERY_CHECK_INTERVAL (msecs_to_jiffies(1000)) /* 1 sec */
+
+#define MAIN_BATTERY_MASK 0x0e00
+#define BATTERY_V_MASK 0x0fff
+#define BATTERY_IS_CHARGING 0x8000
+
+/* TODO find good values (in millivolts) */
+#define MAIN_BATTERY_HIGH 800
+#define MAIN_BATTERY_LOW 720
+#define NO_MAIN_BATTERY 500
+
+#define BACKUP_BATTERY_HIGH 320
+#define BACKUP_BATTERY_LOW 200
+#define NO_BACKUP_BATTERY 10
+
+struct device *pm_dev;
+
+static int battery_voltage;
+static int backup_battery_voltage;
+static int backup_battery_status;
+static struct apm_power_info power_info;
+
+static int suspending;
+
+static void battery_check(struct work_struct *private_);
+static DECLARE_DELAYED_WORK(mp900_battery, battery_check);
+
+static inline void request_battery_state(void)
+{
+ unsigned char cmd[2] = {
+ PIC_MAIN_BAT_STATE_REQUEST,
+ PIC_BACKUP_BAT_STATE_REQUEST
+ };
+
+ send_to_pic(cmd, 2);
+}
+
+static void update_main_battery(int value)
+{
+ int status = APM_BATTERY_STATUS_CRITICAL;
+
+ /* TODO figure out how to get AC on/off status */
+//TODO if (value & BATTERY_IS_CHARGING)
+ power_info.ac_line_status = APM_AC_ONLINE;
+
+ battery_voltage = value & BATTERY_V_MASK;
+
+ if (battery_voltage > MAIN_BATTERY_HIGH)
+ status = APM_BATTERY_STATUS_HIGH;
+
+ else if (battery_voltage > MAIN_BATTERY_LOW)
+ status = APM_BATTERY_STATUS_LOW;
+
+ else if (battery_voltage < NO_MAIN_BATTERY)
+ status = APM_BATTERY_STATUS_NOT_PRESENT;
+
+ power_info.battery_status = status;
+ power_info.battery_flag = 1 << status;
+
+ dev_dbg(pm_dev, "Battery: voltage: %d, status: %d, percentage: %d\n",
+ battery_voltage, power_info.battery_status,
+ power_info.battery_life);
+
+ /* Suspend if battery level is critical */
+ if (power_info.ac_line_status != APM_AC_ONLINE
+ && power_info.battery_status == APM_BATTERY_STATUS_CRITICAL
+ && !suspending) {
+ suspending = 1;
+
+ dev_err(pm_dev, "Battery state is Critical -> Suspend\n");
+
+ apm_queue_event(APM_CRITICAL_SUSPEND);
+ }
+}
+
+static void update_backup_battery(int value)
+{
+ backup_battery_voltage = value;
+
+ if (value > BACKUP_BATTERY_HIGH)
+ backup_battery_status = APM_BATTERY_STATUS_HIGH;
+ else if (value > BACKUP_BATTERY_LOW)
+ backup_battery_status = APM_BATTERY_STATUS_LOW;
+ else if (value < NO_BACKUP_BATTERY)
+ backup_battery_status = APM_BATTERY_STATUS_NOT_PRESENT;
+ else
+ backup_battery_status = APM_BATTERY_STATUS_CRITICAL;
+}
+
+static void update_battery_state(const unsigned char *buf)
+{
+ int value = (buf[0] << 8) + buf[1];
+
+ if (value & MAIN_BATTERY_MASK)
+ update_main_battery(value);
+ else
+ update_backup_battery(value);
+}
+
+static void notify_lid_closed(void)
+{
+ /* TODO
+ * Suspend if user wants to. Should be controlled by sysfs parameter.
+ */
+ printk(KERN_INFO PFX "Received lid closed event\n");
+}
+
+static void suspend(void)
+{
+ printk(KERN_INFO PFX "Suspend requested...\n");
+ apm_queue_event(APM_SYS_SUSPEND);
+}
+
+static struct pic_powermanager_ops pic_powermanager_ops = {
+ .update_battery_state = update_battery_state,
+ .notify_lid_closed = notify_lid_closed,
+ .suspend = suspend,
+};
+
+static void schedule_battery_check(void)
+{
+ schedule_delayed_work(&mp900_battery, BATTERY_CHECK_INTERVAL);
+}
+
+static void battery_check(struct work_struct *private_)
+{
+ if (!suspending)
+ request_battery_state();
+
+ schedule_battery_check();
+}
+
+#ifdef CONFIG_PM
+static int mp900_pm_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ printk(KERN_INFO PFX "Suspend...\n");
+
+ suspending = 1;
+ flush_scheduled_work();
+ return 0;
+}
+
+static int mp900_pm_resume(struct platform_device *pdev)
+{
+ printk(KERN_INFO PFX "Resume...\n");
+
+ suspending = 0;
+ schedule_battery_check();
+
+ printk(KERN_INFO PFX "Resumed\n");
+ return 0;
+}
+#endif
+
+static ssize_t
+show_battery_percentage(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", power_info.battery_life);
+}
+
+static ssize_t
+show_battery_voltage(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", battery_voltage);
+}
+
+static ssize_t
+show_backup_battery_voltage(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", backup_battery_status);
+}
+
+static DEVICE_ATTR(battery_percentage, 0444, show_battery_percentage, NULL);
+static DEVICE_ATTR(battery_voltage, 0444, show_battery_voltage, NULL);
+static DEVICE_ATTR(backup_battery_voltage, 0444, show_backup_battery_voltage, NULL);
+
+/*
+ * apm callback implementation
+ * void (*apm_get_power_status)(struct apm_power_info *)
+ */
+static void mp900_apm_get_power_status(struct apm_power_info *info)
+{
+ memcpy(info, &power_info, sizeof(struct apm_power_info));
+}
+
+#ifdef CONFIG_PM_TEMPORARY_YR
+static void corgi_goto_sleep(unsigned long alarm_time, unsigned int alarm_enable, suspend_state_t state)
+{
+ dev_dbg(sharpsl_pm.dev, "Time is: %08x\n", RCNR);
+
+ dev_dbg(sharpsl_pm.dev, "Offline Charge Activate = %d\n", sharpsl_pm.flags & SHARPSL_DO_OFFLINE_CHRG);
+ /* not charging and AC-IN! */
+
+ if ((sharpsl_pm.flags & SHARPSL_DO_OFFLINE_CHRG) && (sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_ACIN))) {
+ dev_dbg(sharpsl_pm.dev, "Activating Offline Charger...\n");
+ sharpsl_pm.charge_mode = CHRG_OFF;
+ sharpsl_pm.flags &= ~SHARPSL_DO_OFFLINE_CHRG;
+ sharpsl_off_charge_battery();
+ }
+
+ sharpsl_pm.machinfo->presuspend();
+
+ PEDR = 0xffffffff; /* clear it */
+
+ sharpsl_pm.flags &= ~SHARPSL_ALARM_ACTIVE;
+ if ((sharpsl_pm.charge_mode == CHRG_ON) && ((alarm_enable && ((alarm_time - RCNR) > (SHARPSL_BATCHK_TIME_SUSPEND + 30))) || !alarm_enable)) {
+ RTSR &= RTSR_ALE;
+ RTAR = RCNR + SHARPSL_BATCHK_TIME_SUSPEND;
+ dev_dbg(sharpsl_pm.dev, "Charging alarm at: %08x\n", RTAR);
+ sharpsl_pm.flags |= SHARPSL_ALARM_ACTIVE;
+ } else if (alarm_enable) {
+ RTSR &= RTSR_ALE;
+ RTAR = alarm_time;
+ dev_dbg(sharpsl_pm.dev, "User alarm at: %08x\n", RTAR);
+ } else {
+ dev_dbg(sharpsl_pm.dev, "No alarms set.\n");
+ }
+
+ pxa_pm_enter(state);
+
+ sharpsl_pm.machinfo->postsuspend();
+
+ dev_dbg(sharpsl_pm.dev, "Corgi woken up from suspend: %08x\n", PEDR);
+}
+
+static int corgi_enter_suspend(unsigned long alarm_time, unsigned int alarm_enable, suspend_state_t state)
+{
+ if (!sharpsl_pm.machinfo->should_wakeup(!(sharpsl_pm.flags & SHARPSL_ALARM_ACTIVE) && alarm_enable)) {
+ if (!(sharpsl_pm.flags & SHARPSL_ALARM_ACTIVE)) {
+ dev_dbg(sharpsl_pm.dev, "No user triggered wakeup events and not charging. Strange. Suspend.\n");
+ corgi_goto_sleep(alarm_time, alarm_enable, state);
+ return 1;
+ }
+ if (sharpsl_off_charge_battery()) {
+ dev_dbg(sharpsl_pm.dev, "Charging. Suspend...\n");
+ corgi_goto_sleep(alarm_time, alarm_enable, state);
+ return 1;
+ }
+ dev_dbg(sharpsl_pm.dev, "User triggered wakeup in offline charger.\n");
+ }
+
+ if ((!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_LOCK)) ||
+ (!sharpsl_pm.machinfo->read_devdata(SHARPSL_STATUS_FATAL))) {
+ dev_err(sharpsl_pm.dev, "Fatal condition. Suspend.\n");
+ corgi_goto_sleep(alarm_time, alarm_enable, state);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int corgi_pxa_pm_enter(suspend_state_t state)
+{
+ unsigned long alarm_time = RTAR;
+ unsigned int alarm_status = ((RTSR & RTSR_ALE) != 0);
+
+ dev_dbg(sharpsl_pm.dev, "SharpSL suspending for first time.\n");
+
+ corgi_goto_sleep(alarm_time, alarm_status, state);
+
+ while (corgi_enter_suspend(alarm_time, alarm_status, state))
+ {}
+
+ if (sharpsl_pm.machinfo->earlyresume)
+ sharpsl_pm.machinfo->earlyresume();
+
+ dev_dbg(sharpsl_pm.dev, "SharpSL resuming...\n");
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static struct platform_suspend_ops mp900_suspend_ops = {
+ .prepare = pxa_pm_prepare,
+ .finish = pxa_pm_finish,
+ .enter = pxa_pm_enter,
+ .valid = suspend_valid_only_mem,
+};
+#endif
+
+static int __init mp900_pm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pm_dev = &pdev->dev;
+
+ ret = device_create_file(&pdev->dev, &dev_attr_battery_percentage);
+ ret |= device_create_file(&pdev->dev, &dev_attr_battery_voltage);
+ ret |= device_create_file(&pdev->dev, &dev_attr_backup_battery_voltage);
+ if (ret != 0)
+ dev_warn(&pdev->dev, "Failed to register attributes (%d)\n", ret);
+
+ pic_register_device(PIC_POWERMANAGER, (void*)&pic_powermanager_ops);
+
+ /* register callback to apm core */
+ apm_get_power_status = mp900_apm_get_power_status;
+
+#ifdef CONFIG_PM
+ suspend_set_ops(&mp900_suspend_ops);
+#endif
+ schedule_battery_check();
+
+ return 0;
+}
+
+static int __devexit mp900_pm_remove(struct platform_device *pdev)
+{
+ flush_scheduled_work();
+
+#ifdef CONFIG_PM
+ suspend_set_ops(NULL);
+#endif
+ apm_get_power_status = NULL;
+
+ pic_unregister_device(PIC_POWERMANAGER, (void *)&pic_powermanager_ops);
+
+ device_remove_file(&pdev->dev, &dev_attr_battery_percentage);
+ device_remove_file(&pdev->dev, &dev_attr_battery_voltage);
+ device_remove_file(&pdev->dev, &dev_attr_backup_battery_voltage);
+
+ return 0;
+}
+
+static struct platform_driver mp900_pm_driver = {
+ .probe = mp900_pm_probe,
+ .remove = mp900_pm_remove,
+#ifdef CONFIG_PM
+ .suspend = mp900_pm_suspend,
+ .resume = mp900_pm_resume,
+#endif
+ .driver = {
+ .name = "mp900-pm",
+ },
+};
+
+static int __devinit mp900_pm_init(void)
+{
+ return platform_driver_register(&mp900_pm_driver);
+}
+
+static void mp900_pm_exit(void)
+{
+ platform_driver_unregister(&mp900_pm_driver);
+}
+
+late_initcall(mp900_pm_init);
+module_exit(mp900_pm_exit);