diff options
author | Yulay Rakhmangulov <yulayr@gmail.com> | 2010-07-21 01:18:40 -0700 |
---|---|---|
committer | Kristoffer Ericson <kristoffer.ericson@gmail.com> | 2011-12-10 15:45:45 +0100 |
commit | 503ec726a60fa16a5c06a1c905c74b4cc3d58a4b (patch) | |
tree | b4e217dc6f33bdb8a86f85219d2ffa05b1308be4 | |
parent | 8de54aadfda4ef54dca996aca8ff2e153bbc81c3 (diff) | |
download | linux-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/Kconfig | 11 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/mp900_pm.c | 369 |
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); |