/* arch/arm/mach-msm/htc_battery.c * * Copyright (C) 2008 HTC Corporation. * Copyright (C) 2008 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include static struct wake_lock vbus_wake_lock; #define TRACE_BATT 0 #if TRACE_BATT #define BATT(x...) printk(KERN_INFO "[BATT] " x) #else #define BATT(x...) do {} while (0) #endif /* rpc related */ #define APP_BATT_PDEV_NAME "rs30100001" #define APP_BATT_PROG 0x30100001 #define APP_BATT_VER 0 #define HTC_PROCEDURE_BATTERY_NULL 0 #define HTC_PROCEDURE_GET_BATT_LEVEL 1 #define HTC_PROCEDURE_GET_BATT_INFO 2 #define HTC_PROCEDURE_GET_CABLE_STATUS 3 #define HTC_PROCEDURE_SET_BATT_DELTA 4 /* module debugger */ #define HTC_BATTERY_DEBUG 1 #define BATTERY_PREVENTION 1 /* Enable this will shut down if no battery */ #define ENABLE_BATTERY_DETECTION 0 #define GPIO_BATTERY_DETECTION 21 #define GPIO_BATTERY_CHARGER_EN 128 /* Charge current selection */ #define GPIO_BATTERY_CHARGER_CURRENT 129 typedef enum { DISABLE = 0, ENABLE_SLOW_CHG, ENABLE_FAST_CHG } batt_ctl_t; /* This order is the same as htc_power_supplies[] * And it's also the same as htc_cable_status_update() */ typedef enum { CHARGER_BATTERY = 0, CHARGER_USB, CHARGER_AC } charger_type_t; struct battery_info_reply { u32 batt_id; /* Battery ID from ADC */ u32 batt_vol; /* Battery voltage from ADC */ u32 batt_temp; /* Battery Temperature (C) from formula and ADC */ u32 batt_current; /* Battery current from ADC */ u32 level; /* formula */ u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ u32 charging_enabled; /* 0: Disable, 1: Enable */ u32 full_bat; /* Full capacity of battery (mAh) */ }; struct htc_battery_info { int present; unsigned long update_time; /* lock to protect the battery info */ struct mutex lock; /* lock held while calling the arm9 to query the battery info */ struct mutex rpc_lock; struct battery_info_reply rep; }; static struct msm_rpc_endpoint *endpoint; static struct htc_battery_info htc_batt_info; static unsigned int cache_time = 1000; static int htc_battery_initial = 0; static enum power_supply_property htc_battery_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_CAPACITY, }; static enum power_supply_property htc_power_properties[] = { POWER_SUPPLY_PROP_ONLINE, }; static char *supply_list[] = { "battery", }; /* HTC dedicated attributes */ static ssize_t htc_battery_show_property(struct device *dev, struct device_attribute *attr, char *buf); static int htc_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val); static int htc_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val); static struct power_supply htc_power_supplies[] = { { .name = "battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = htc_battery_properties, .num_properties = ARRAY_SIZE(htc_battery_properties), .get_property = htc_battery_get_property, }, { .name = "usb", .type = POWER_SUPPLY_TYPE_USB, .supplied_to = supply_list, .num_supplicants = ARRAY_SIZE(supply_list), .properties = htc_power_properties, .num_properties = ARRAY_SIZE(htc_power_properties), .get_property = htc_power_get_property, }, { .name = "ac", .type = POWER_SUPPLY_TYPE_MAINS, .supplied_to = supply_list, .num_supplicants = ARRAY_SIZE(supply_list), .properties = htc_power_properties, .num_properties = ARRAY_SIZE(htc_power_properties), .get_property = htc_power_get_property, }, }; /* -------------------------------------------------------------------------- */ #if defined(CONFIG_DEBUG_FS) int htc_battery_set_charging(batt_ctl_t ctl); static int batt_debug_set(void *data, u64 val) { return htc_battery_set_charging((batt_ctl_t) val); } static int batt_debug_get(void *data, u64 *val) { return -ENOSYS; } DEFINE_SIMPLE_ATTRIBUTE(batt_debug_fops, batt_debug_get, batt_debug_set, "%llu\n"); static int __init batt_debug_init(void) { struct dentry *dent; dent = debugfs_create_dir("htc_battery", 0); if (IS_ERR(dent)) return PTR_ERR(dent); debugfs_create_file("charger_state", 0644, dent, NULL, &batt_debug_fops); return 0; } device_initcall(batt_debug_init); #endif static int init_batt_gpio(void) { if (gpio_request(GPIO_BATTERY_DETECTION, "batt_detect") < 0) goto gpio_failed; if (gpio_request(GPIO_BATTERY_CHARGER_EN, "charger_en") < 0) goto gpio_failed; if (gpio_request(GPIO_BATTERY_CHARGER_CURRENT, "charge_current") < 0) goto gpio_failed; return 0; gpio_failed: return -EINVAL; } /* * battery_charging_ctrl - battery charing control. * @ctl: battery control command * */ static int battery_charging_ctrl(batt_ctl_t ctl) { int result = 0; switch (ctl) { case DISABLE: BATT("charger OFF\n"); /* 0 for enable; 1 disable */ result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 1); break; case ENABLE_SLOW_CHG: BATT("charger ON (SLOW)\n"); result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 0); result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); break; case ENABLE_FAST_CHG: BATT("charger ON (FAST)\n"); result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 1); result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); break; default: printk(KERN_ERR "Not supported battery ctr called.!\n"); result = -EINVAL; break; } return result; } int htc_battery_set_charging(batt_ctl_t ctl) { int rc; if ((rc = battery_charging_ctrl(ctl)) < 0) goto result; if (!htc_battery_initial) { htc_batt_info.rep.charging_enabled = ctl & 0x3; } else { mutex_lock(&htc_batt_info.lock); htc_batt_info.rep.charging_enabled = ctl & 0x3; mutex_unlock(&htc_batt_info.lock); } result: return rc; } int htc_battery_status_update(u32 curr_level) { int notify; if (!htc_battery_initial) return 0; mutex_lock(&htc_batt_info.lock); notify = (htc_batt_info.rep.level != curr_level); htc_batt_info.rep.level = curr_level; mutex_unlock(&htc_batt_info.lock); if (notify) power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); return 0; } int htc_cable_status_update(int status) { int rc = 0; unsigned source; if (!htc_battery_initial) return 0; mutex_lock(&htc_batt_info.lock); switch(status) { case CHARGER_BATTERY: BATT("cable NOT PRESENT\n"); htc_batt_info.rep.charging_source = CHARGER_BATTERY; break; case CHARGER_USB: BATT("cable USB\n"); htc_batt_info.rep.charging_source = CHARGER_USB; break; case CHARGER_AC: BATT("cable AC\n"); htc_batt_info.rep.charging_source = CHARGER_AC; break; default: printk(KERN_ERR "%s: Not supported cable status received!\n", __FUNCTION__); rc = -EINVAL; } source = htc_batt_info.rep.charging_source; mutex_unlock(&htc_batt_info.lock); msm_hsusb_set_vbus_state(source == CHARGER_USB); if (source == CHARGER_USB) { wake_lock(&vbus_wake_lock); } else { /* give userspace some time to see the uevent and update * LED state or whatnot... */ wake_lock_timeout(&vbus_wake_lock, HZ / 2); } /* if the power source changes, all power supplies may change state */ power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); power_supply_changed(&htc_power_supplies[CHARGER_USB]); power_supply_changed(&htc_power_supplies[CHARGER_AC]); return rc; } static int htc_get_batt_info(struct battery_info_reply *buffer) { struct rpc_request_hdr req; struct htc_get_batt_info_rep { struct rpc_reply_hdr hdr; struct battery_info_reply info; } rep; int rc; if (buffer == NULL) return -EINVAL; rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_BATT_INFO, &req, sizeof(req), &rep, sizeof(rep), 5 * HZ); if ( rc < 0 ) return rc; mutex_lock(&htc_batt_info.lock); buffer->batt_id = be32_to_cpu(rep.info.batt_id); buffer->batt_vol = be32_to_cpu(rep.info.batt_vol); buffer->batt_temp = be32_to_cpu(rep.info.batt_temp); buffer->batt_current = be32_to_cpu(rep.info.batt_current); buffer->level = be32_to_cpu(rep.info.level); buffer->charging_source = be32_to_cpu(rep.info.charging_source); buffer->charging_enabled = be32_to_cpu(rep.info.charging_enabled); buffer->full_bat = be32_to_cpu(rep.info.full_bat); mutex_unlock(&htc_batt_info.lock); return 0; } #if 0 static int htc_get_cable_status(void) { struct rpc_request_hdr req; struct htc_get_cable_status_rep { struct rpc_reply_hdr hdr; int status; } rep; int rc; rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_CABLE_STATUS, &req, sizeof(req), &rep, sizeof(rep), 5 * HZ); if (rc < 0) return rc; return be32_to_cpu(rep.status); } #endif /* -------------------------------------------------------------------------- */ static int htc_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { charger_type_t charger; mutex_lock(&htc_batt_info.lock); charger = htc_batt_info.rep.charging_source; mutex_unlock(&htc_batt_info.lock); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: if (psy->type == POWER_SUPPLY_TYPE_MAINS) val->intval = (charger == CHARGER_AC ? 1 : 0); else if (psy->type == POWER_SUPPLY_TYPE_USB) val->intval = (charger == CHARGER_USB ? 1 : 0); else val->intval = 0; break; default: return -EINVAL; } return 0; } static int htc_battery_get_charging_status(void) { u32 level; charger_type_t charger; int ret; mutex_lock(&htc_batt_info.lock); charger = htc_batt_info.rep.charging_source; switch (charger) { case CHARGER_BATTERY: ret = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case CHARGER_USB: case CHARGER_AC: level = htc_batt_info.rep.level; if (level == 100) ret = POWER_SUPPLY_STATUS_FULL; else ret = POWER_SUPPLY_STATUS_CHARGING; break; default: ret = POWER_SUPPLY_STATUS_UNKNOWN; } mutex_unlock(&htc_batt_info.lock); return ret; } static int htc_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = htc_battery_get_charging_status(); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = POWER_SUPPLY_HEALTH_GOOD; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = htc_batt_info.present; break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CAPACITY: mutex_lock(&htc_batt_info.lock); val->intval = htc_batt_info.rep.level; mutex_unlock(&htc_batt_info.lock); break; default: return -EINVAL; } return 0; } #define HTC_BATTERY_ATTR(_name) \ { \ .attr = { .name = #_name, .mode = S_IRUGO, .owner = THIS_MODULE }, \ .show = htc_battery_show_property, \ .store = NULL, \ } static struct device_attribute htc_battery_attrs[] = { HTC_BATTERY_ATTR(batt_id), HTC_BATTERY_ATTR(batt_vol), HTC_BATTERY_ATTR(batt_temp), HTC_BATTERY_ATTR(batt_current), HTC_BATTERY_ATTR(charging_source), HTC_BATTERY_ATTR(charging_enabled), HTC_BATTERY_ATTR(full_bat), }; enum { BATT_ID = 0, BATT_VOL, BATT_TEMP, BATT_CURRENT, CHARGING_SOURCE, CHARGING_ENABLED, FULL_BAT, }; static int htc_rpc_set_delta(unsigned delta) { struct set_batt_delta_req { struct rpc_request_hdr hdr; uint32_t data; } req; req.data = cpu_to_be32(delta); return msm_rpc_call(endpoint, HTC_PROCEDURE_SET_BATT_DELTA, &req, sizeof(req), 5 * HZ); } static ssize_t htc_battery_set_delta(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int rc; unsigned long delta = 0; delta = simple_strtoul(buf, NULL, 10); if (delta > 100) return -EINVAL; mutex_lock(&htc_batt_info.rpc_lock); rc = htc_rpc_set_delta(delta); mutex_unlock(&htc_batt_info.rpc_lock); if (rc < 0) return rc; return count; } static struct device_attribute htc_set_delta_attrs[] = { __ATTR(delta, S_IWUSR | S_IWGRP, NULL, htc_battery_set_delta), }; static int htc_battery_create_attrs(struct device * dev) { int i, j, rc; for (i = 0; i < ARRAY_SIZE(htc_battery_attrs); i++) { rc = device_create_file(dev, &htc_battery_attrs[i]); if (rc) goto htc_attrs_failed; } for (j = 0; j < ARRAY_SIZE(htc_set_delta_attrs); j++) { rc = device_create_file(dev, &htc_set_delta_attrs[j]); if (rc) goto htc_delta_attrs_failed; } goto succeed; htc_attrs_failed: while (i--) device_remove_file(dev, &htc_battery_attrs[i]); htc_delta_attrs_failed: while (j--) device_remove_file(dev, &htc_set_delta_attrs[i]); succeed: return rc; } static ssize_t htc_battery_show_property(struct device *dev, struct device_attribute *attr, char *buf) { int i = 0; const ptrdiff_t off = attr - htc_battery_attrs; /* rpc lock is used to prevent two threads from calling * into the get info rpc at the same time */ mutex_lock(&htc_batt_info.rpc_lock); /* check cache time to decide if we need to update */ if (htc_batt_info.update_time && time_before(jiffies, htc_batt_info.update_time + msecs_to_jiffies(cache_time))) goto dont_need_update; if (htc_get_batt_info(&htc_batt_info.rep) < 0) printk(KERN_ERR "%s: rpc failed!!!\n", __FUNCTION__); else htc_batt_info.update_time = jiffies; dont_need_update: mutex_unlock(&htc_batt_info.rpc_lock); mutex_lock(&htc_batt_info.lock); switch (off) { case BATT_ID: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.batt_id); break; case BATT_VOL: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.batt_vol); break; case BATT_TEMP: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.batt_temp); break; case BATT_CURRENT: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.batt_current); break; case CHARGING_SOURCE: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.charging_source); break; case CHARGING_ENABLED: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.charging_enabled); break; case FULL_BAT: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", htc_batt_info.rep.full_bat); break; default: i = -EINVAL; } mutex_unlock(&htc_batt_info.lock); return i; } static int htc_battery_probe(struct platform_device *pdev) { int i, rc; if (pdev->id != (APP_BATT_VER & RPC_VERSION_MAJOR_MASK)) return -EINVAL; /* init battery gpio */ if ((rc = init_batt_gpio()) < 0) { printk(KERN_ERR "%s: init battery gpio failed!\n", __FUNCTION__); return rc; } /* init structure data member */ htc_batt_info.update_time = jiffies; htc_batt_info.present = gpio_get_value(GPIO_BATTERY_DETECTION); /* init rpc */ endpoint = msm_rpc_connect(APP_BATT_PROG, APP_BATT_VER, 0); if (IS_ERR(endpoint)) { printk(KERN_ERR "%s: init rpc failed! rc = %ld\n", __FUNCTION__, PTR_ERR(endpoint)); return rc; } /* init power supplier framework */ for (i = 0; i < ARRAY_SIZE(htc_power_supplies); i++) { rc = power_supply_register(&pdev->dev, &htc_power_supplies[i]); if (rc) printk(KERN_ERR "Failed to register power supply (%d)\n", rc); } /* create htc detail attributes */ htc_battery_create_attrs(htc_power_supplies[CHARGER_BATTERY].dev); /* After battery driver gets initialized, send rpc request to inquiry * the battery status in case of we lost some info */ htc_battery_initial = 1; mutex_lock(&htc_batt_info.rpc_lock); if (htc_get_batt_info(&htc_batt_info.rep) < 0) printk(KERN_ERR "%s: get info failed\n", __FUNCTION__); htc_cable_status_update(htc_batt_info.rep.charging_source); battery_charging_ctrl(htc_batt_info.rep.charging_enabled ? ENABLE_SLOW_CHG : DISABLE); if (htc_rpc_set_delta(1) < 0) printk(KERN_ERR "%s: set delta failed\n", __FUNCTION__); htc_batt_info.update_time = jiffies; mutex_unlock(&htc_batt_info.rpc_lock); if (htc_batt_info.rep.charging_enabled == 0) battery_charging_ctrl(DISABLE); return 0; } static struct platform_driver htc_battery_driver = { .probe = htc_battery_probe, .driver = { .name = APP_BATT_PDEV_NAME, .owner = THIS_MODULE, }, }; /* batt_mtoa server definitions */ #define BATT_MTOA_PROG 0x30100000 #define BATT_MTOA_VERS 0 #define RPC_BATT_MTOA_NULL 0 #define RPC_BATT_MTOA_SET_CHARGING_PROC 1 #define RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC 2 #define RPC_BATT_MTOA_LEVEL_UPDATE_PROC 3 struct rpc_batt_mtoa_set_charging_args { int enable; }; struct rpc_batt_mtoa_cable_status_update_args { int status; }; struct rpc_dem_battery_update_args { uint32_t level; }; static int handle_battery_call(struct msm_rpc_server *server, struct rpc_request_hdr *req, unsigned len) { switch (req->procedure) { case RPC_BATT_MTOA_NULL: return 0; case RPC_BATT_MTOA_SET_CHARGING_PROC: { struct rpc_batt_mtoa_set_charging_args *args; args = (struct rpc_batt_mtoa_set_charging_args *)(req + 1); args->enable = be32_to_cpu(args->enable); BATT("set_charging: enable=%d\n",args->enable); htc_battery_set_charging(args->enable); return 0; } case RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC: { struct rpc_batt_mtoa_cable_status_update_args *args; args = (struct rpc_batt_mtoa_cable_status_update_args *)(req + 1); args->status = be32_to_cpu(args->status); BATT("cable_status_update: status=%d\n",args->status); htc_cable_status_update(args->status); return 0; } case RPC_BATT_MTOA_LEVEL_UPDATE_PROC: { struct rpc_dem_battery_update_args *args; args = (struct rpc_dem_battery_update_args *)(req + 1); args->level = be32_to_cpu(args->level); BATT("dem_battery_update: level=%d\n",args->level); htc_battery_status_update(args->level); return 0; } default: printk(KERN_ERR "%s: program 0x%08x:%d: unknown procedure %d\n", __FUNCTION__, req->prog, req->vers, req->procedure); return -ENODEV; } } static struct msm_rpc_server battery_server = { .prog = BATT_MTOA_PROG, .vers = BATT_MTOA_VERS, .rpc_call = handle_battery_call, }; static int __init htc_battery_init(void) { wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present"); mutex_init(&htc_batt_info.lock); mutex_init(&htc_batt_info.rpc_lock); msm_rpc_create_server(&battery_server); platform_driver_register(&htc_battery_driver); return 0; } module_init(htc_battery_init); MODULE_DESCRIPTION("HTC Battery Driver"); MODULE_LICENSE("GPL");