| /***************************************************************************** |
| * Copyright 2011 Broadcom Corporation. All rights reserved. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2, available at |
| * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a |
| * license other than the GPL, without Broadcom's express prior written |
| * consent. |
| *****************************************************************************/ |
| |
| /* |
| * A driver to monitor the MAX17040 or ADC121C021, the AC power and charge state |
| * of the battery. |
| * |
| * Driver works in conjunction with frameworks defined in linux/power_supply.h |
| * and linux/pm.h. |
| * |
| * 2 power_supply devices - power and battery are registered/unregistered with |
| * power supply(PSY) framework and report properties to PSY. The list of |
| * properties supported by this driver is defined in battery_data_props and |
| * power_data_props arrays. |
| * |
| * Another PSY - USB is added to comply with Android's expectations. For now |
| * this is a mock PSY, that provides one propery - "online" that is always set |
| * to false. In the future boards (when the USB supply/charger is available) |
| * this will be changed. |
| * |
| * Driver works in cooperation with battery monitor that registers with the |
| * driver via exported register_battery_monitor function. Driver relies on |
| * battery monitor for methods to determine volatage and charge and for supply |
| * of some essential data such as GPIO values. Only one battery monitor may be |
| * registered at a time. Battery driver will be nonfunctional if there is no |
| * battery monitor registered. |
| * |
| * Platform information is passed into the driver via the platform_data pointer. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/power_supply.h> |
| #include <linux/pm.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/gpio.h> |
| #include <linux/wakelock.h> |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| #include <linux/i2c.h> |
| |
| #include <linux/broadcom/cmp_battery_multi.h> |
| |
| EXPORT_SYMBOL(register_battery_monitor); |
| |
| /* ---- Public Functions ------------------------------------------------- */ |
| /* after power_supply devices are registered, these functions are used to get |
| * power and battery properies */ |
| static int battery_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val); |
| static int power_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val); |
| |
| static int usbpower_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val); |
| |
| static void battery_external_power_changed(struct power_supply *psy); |
| |
| /* ---- Private Constants and Types -------------------------------------- */ |
| |
| /* Main structure used in this driver. */ |
| struct multi_data |
| { |
| /* Battery PSY */ |
| struct power_supply battery_psy; |
| |
| /* AC power PSY */ |
| struct power_supply ac_power_psy; |
| |
| /* task to handle AC power plug / remove. */ |
| struct work_struct t_ac_power_work; |
| |
| /* task to periodically send battery uEvent */ |
| struct delayed_work t_battery_dwork; |
| |
| /* battery monitor instance and data that is registered with the driver */ |
| struct battery_monitor *pt_battery_monitor; |
| void *pt_battery_monitor_data; |
| |
| /* wake lock. When taken keeps device awake */ |
| struct wake_lock wakelock; |
| |
| /* Reference to platform_data passed in by probe. */ |
| struct cbm_platform_data *pt_platform_data; |
| }; |
| |
| #define DEBOUNCE_TIME_USECS 128000 |
| #define MULTI_WAIT_PERIOD (30*HZ) |
| #define MULTI_NO_WAIT 0 |
| |
| #define INT_SETUP_SLEEP_MSECS 50 |
| |
| /* ---- Private Variables ------------------------------------------------ */ |
| |
| /* Debug macro */ |
| #define PRINT_DEBUG(format, args...) \ |
| do { if (mod_debug) printk(KERN_WARNING "[cmp_battery_multi]: " format, ## args); } while (0) |
| |
| /* define mod_debug module parameter to enable/disable debug messages */ |
| static int mod_debug = 0x0; |
| module_param(mod_debug, int, 0644); |
| |
| static const __devinitconst char gBanner[] = |
| KERN_INFO "Multi Battery Driver: 1.02\n"; |
| |
| /* workqueue and work task for debouncing power input signal */ |
| static struct workqueue_struct *isr_wq; |
| |
| static const char *wake_lock_name = "multi_battery_driver"; |
| |
| static char *power_supplied_to[] = |
| { |
| "battery", |
| }; |
| |
| /* array of properties supported by battery PSY */ |
| static enum power_supply_property battery_data_props[] = |
| { |
| POWER_SUPPLY_PROP_STATUS, /* charging, discharging ... */ |
| POWER_SUPPLY_PROP_HEALTH, /* good, overheat, dead ... */ |
| POWER_SUPPLY_PROP_PRESENT, /* */ |
| POWER_SUPPLY_PROP_TECHNOLOGY, /* NiMH|LION|... */ |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, /* battery_max */ |
| POWER_SUPPLY_PROP_VOLTAGE_MIN, /* battery_min */ |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, /* */ |
| POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, /* */ |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, /* battery voltage(mV) */ |
| POWER_SUPPLY_PROP_CAPACITY, /* Capacity in percent */ |
| POWER_SUPPLY_PROP_TEMP, /* Degrees celsius */ |
| }; |
| |
| /* array of properties supported by AC power PSY */ |
| static enum power_supply_property power_data_props[] = { |
| POWER_SUPPLY_PROP_ONLINE, /* AC plugged in or not */ |
| }; |
| |
| /* array of properties supported by USB power PSY */ |
| static enum power_supply_property usbpower_data_props[] = { |
| POWER_SUPPLY_PROP_ONLINE, /* USB power plugged in or not */ |
| }; |
| |
| /* initialize PSY in multi_data struct */ |
| static struct multi_data g_multi_data = |
| { |
| .battery_psy = |
| { |
| .name = "battery", |
| .type = POWER_SUPPLY_TYPE_BATTERY, |
| .properties = battery_data_props, |
| .num_properties = ARRAY_SIZE(battery_data_props), |
| .get_property = battery_get_property, |
| .external_power_changed = battery_external_power_changed, |
| .use_for_apm = 1, |
| }, |
| |
| .ac_power_psy = |
| { |
| .name = "power", |
| .type = POWER_SUPPLY_TYPE_MAINS, |
| .supplied_to = power_supplied_to, |
| .num_supplicants = ARRAY_SIZE(power_supplied_to), |
| .properties = power_data_props, |
| .num_properties = ARRAY_SIZE(power_data_props), |
| .get_property = power_get_property, |
| }, |
| }; |
| |
| /* initialize usb power PSY */ |
| static struct power_supply usb_power_psy = |
| { |
| .name = "usbpower", |
| .type = POWER_SUPPLY_TYPE_USB, |
| .supplied_to = power_supplied_to, |
| .num_supplicants = ARRAY_SIZE(power_supplied_to), |
| .properties = usbpower_data_props, |
| .num_properties = ARRAY_SIZE(power_data_props), |
| .get_property = usbpower_get_property, |
| }; |
| |
| static int g_gpio_power_control = -1; |
| |
| /* flag inidicating if the driver is ready for battery monitor drivers |
| to register */ |
| static bool g_is_driver_ok = false; |
| |
| /* ---- Private Function Prototypes -------------------------------------- */ |
| static void cmp_battery_multi_power_off(void); |
| static bool is_ac_connected (void); |
| static void multi_battery_work (struct work_struct *p_work); |
| static int setup_gpios (struct battery_monitor *pt_battery_monitor); |
| static void close_gpios (struct battery_monitor *pt_battery_monitor); |
| static void ac_power_isr_handler_work (struct work_struct *p_work); |
| static int get_voltage(void); |
| static int get_charge(void); |
| |
| /* ---- Public Functions ------------------------------------------------- */ |
| |
| /* this is exported function that is called by battery monitor to register with |
| the battery driver */ |
| int register_battery_monitor(struct battery_monitor *p_monitor, void *p_data) |
| { |
| int rc; |
| |
| if (g_is_driver_ok == false) |
| { |
| printk(KERN_ERR "%s() battery monitor %s cannot register. " |
| "Battery driver is not ready.\n", __FUNCTION__, p_monitor->name); |
| return -ENODATA; |
| } |
| |
| /* Check the fundamental assumption that the AC power GPIO must be supplied |
| by the battery monitor. Note that after this check we do not need to |
| check AC power gpio presence again */ |
| if (p_monitor->gpio_ac_power < 0) |
| { |
| printk(KERN_ERR "%s() battery monitor %s did not supply " |
| "valid AC power GPIO. The value is %d\n", |
| __FUNCTION__, p_monitor->name, p_monitor->gpio_ac_power); |
| return -ENODATA; |
| } |
| |
| printk(KERN_INFO "%s() battery monitor %s successfully registered " |
| "with multi battery driver\n", __FUNCTION__, p_monitor->name); |
| |
| /* store monitor and monitor data in multi struct */ |
| g_multi_data.pt_battery_monitor = p_monitor; |
| g_multi_data.pt_battery_monitor_data = p_data; |
| |
| /* initialize ISR work */ |
| INIT_WORK(&g_multi_data.t_ac_power_work, ac_power_isr_handler_work); |
| |
| /* setup GPIOs. Note that setup_gpios also sets up AC power interrupt so this |
| should be done after t_ac_power_work is initialized */ |
| if ((rc = setup_gpios(p_monitor)) != 0) |
| { |
| printk(KERN_ERR "%s() setup_gpios() returned %d\n", __FUNCTION__, rc); |
| return rc; |
| } |
| |
| /* take wakelock if the AC power is connected */ |
| if (is_ac_connected()) |
| { |
| wake_lock(&g_multi_data.wakelock); |
| printk(KERN_INFO "%s() taking wake lock\n", __FUNCTION__); |
| } |
| |
| /* schedule delayed work for battery PSY */ |
| schedule_delayed_work(&g_multi_data.t_battery_dwork, MULTI_NO_WAIT); |
| return 0; |
| } |
| |
| /* ---- Private Functions ------------------------------------------------ */ |
| |
| static bool is_ac_connected(void) |
| { |
| /* Note that this function may be called before battery monitor is |
| registered */ |
| if (g_multi_data.pt_battery_monitor == NULL) |
| return false; |
| |
| int ac_gpio_level = |
| gpio_get_value(g_multi_data.pt_battery_monitor->gpio_ac_power); |
| |
| /* AC power GPIO level differs between boards. */ |
| |
| /* if GPIO high indicates AC power is connected. */ |
| if (g_multi_data.pt_battery_monitor->ac_power_on_level > 0) |
| return (ac_gpio_level > 0) ? true : false; |
| else |
| /* GPIO low indicates AC power is connected. */ |
| return (ac_gpio_level > 0) ? false : true; |
| } |
| |
| /* get battery charging status */ |
| /* Note that the P11 tablet uses MAX 17040 monitor that does not have charge |
| indicator pin */ |
| static int get_battery_status(void) |
| { |
| /* Note that this function may be called before battery monitor is |
| registered */ |
| if (g_multi_data.pt_battery_monitor == NULL) |
| return POWER_SUPPLY_STATUS_UNKNOWN; |
| |
| /* if AC power is disconnected */ |
| if (!is_ac_connected()) |
| return POWER_SUPPLY_STATUS_DISCHARGING; |
| |
| /* else if there is charging pin that does not indicate charging */ |
| /* note that we are assuming that charging GPIO set to high indicates full |
| charge (no charging) */ |
| else if (g_multi_data.pt_battery_monitor->gpio_charger != -1 && |
| gpio_get_value(g_multi_data.pt_battery_monitor->gpio_charger)) |
| return POWER_SUPPLY_STATUS_NOT_CHARGING; |
| else |
| return POWER_SUPPLY_STATUS_CHARGING; |
| } |
| |
| static int get_voltage(void) |
| { |
| int voltage; |
| struct battery_monitor *pt_battery_monitor = g_multi_data.pt_battery_monitor; |
| |
| /* if monitor provides function to access voltage */ |
| if (pt_battery_monitor != NULL && pt_battery_monitor->get_voltage_fn != NULL) |
| { |
| /* get value from the monitor */ |
| voltage = pt_battery_monitor->get_voltage_fn( |
| g_multi_data.pt_battery_monitor_data)*1000; |
| |
| /* do not want to return 0, may cause tablet to restart */ |
| if (voltage == 0) |
| { |
| voltage = g_multi_data.pt_platform_data->battery_min_voltage*1000; |
| } |
| |
| PRINT_DEBUG("%s() battery_voltage: %d\n", __FUNCTION__, voltage); |
| } |
| else |
| { |
| PRINT_DEBUG("%s() battery monitor get_battery_voltage_fn() " |
| "not configured\n", __FUNCTION__); |
| voltage = g_multi_data.pt_platform_data->battery_max_voltage*1000; |
| } |
| return voltage; |
| } |
| |
| static int get_charge() |
| { |
| int charge_percent; |
| struct battery_monitor *pt_battery_monitor = g_multi_data.pt_battery_monitor; |
| |
| /* if monitor provides function to access charge */ |
| if (pt_battery_monitor != NULL && pt_battery_monitor->get_charge_fn != NULL) |
| { |
| charge_percent = |
| pt_battery_monitor->get_charge_fn(g_multi_data.pt_battery_monitor_data); |
| PRINT_DEBUG("%s() battery_charge: %d\n", __FUNCTION__, charge_percent); |
| } |
| else |
| { |
| PRINT_DEBUG("%s() battery monitor get_charge_fn() not configured\n", |
| __FUNCTION__); |
| |
| /* calculate charge */ |
| charge_percent = |
| (get_voltage() - g_multi_data.pt_platform_data->battery_min_voltage)*100/ |
| (g_multi_data.pt_platform_data->battery_max_voltage - |
| g_multi_data.pt_platform_data->battery_min_voltage); |
| } |
| |
| /* |
| * It is important that the charge level of the battery returned by |
| * POWER_SUPPLY_PROP_CAPACITY never be accidentally set to zero. This might |
| * cause a reboot by the parent OS. It is also bad if the value is greater |
| * than 100. |
| */ |
| if (charge_percent <= 0 || charge_percent > 100) |
| { |
| /* Need to keep this value in range. */ |
| charge_percent = 100; |
| PRINT_DEBUG("%s() charge out of range %d, setting to 100\n", |
| __FUNCTION__, charge_percent); |
| } |
| return charge_percent; |
| } |
| |
| static int battery_get_property(struct power_supply *pt_power_supply, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| int ret = 0; |
| PRINT_DEBUG("%s() requested property: %d\n", __FUNCTION__, psp); |
| |
| switch (psp) |
| { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = get_battery_status(); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| /* assume that the battery is always present */ |
| /* this assumption is valid for tablet */ |
| val->intval = 1; |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = g_multi_data.pt_platform_data->battery_technology; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| val->intval = g_multi_data.pt_platform_data->battery_max_voltage*1000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
| val->intval = g_multi_data.pt_platform_data->battery_min_voltage*1000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| val->intval = g_multi_data.pt_platform_data->battery_max_voltage*1000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| val->intval = g_multi_data.pt_platform_data->battery_min_voltage*1000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = get_voltage(); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = get_charge(); |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| /* current battery monitors do not supply this data */ |
| val->intval = 0; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| PRINT_DEBUG("%s() property: %d value: %d\n", __FUNCTION__, psp, val->intval); |
| return ret; |
| } |
| |
| static int power_get_property(struct power_supply *pt_power_supply, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| switch (psp) |
| { |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = is_ac_connected(); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| PRINT_DEBUG("%s() property: %d value: %d\n", |
| __FUNCTION__, psp, val->intval); |
| return 0; |
| } |
| |
| static int usbpower_get_property(struct power_supply *pt_power_supply, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| switch (psp) |
| { |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = 0; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| PRINT_DEBUG("%s() property: %d value: %d\n", |
| __FUNCTION__, psp, val->intval); |
| return 0; |
| } |
| |
| /* Called when the device is plugged into or removed from an AC supply. */ |
| static irqreturn_t ac_power_gpio_isr(int irq, void *data) |
| { |
| struct multi_data *pt_multi_data = (struct multi_data *)data; |
| |
| if (pt_multi_data == NULL) |
| { |
| printk(KERN_ERR "%s() pt_multi_data == NULL!\n", __FUNCTION__); |
| return IRQ_HANDLED; |
| } |
| |
| queue_work(isr_wq, &pt_multi_data->t_ac_power_work); |
| return IRQ_HANDLED; |
| } |
| |
| /* battery PSY delayed work. */ |
| static void multi_battery_work(struct work_struct *p_work) |
| { |
| /* send uEvent indicating that battery PSY has been changed. Note that we |
| are not attempting (at the driver level) to check if any of the battery |
| properties changed, leaving it to the uEvent handler to do all the analysis */ |
| power_supply_changed(&g_multi_data.battery_psy); |
| |
| /* reschedule delayed work to keep periodic uEvent sending */ |
| schedule_delayed_work(&g_multi_data.t_battery_dwork, MULTI_WAIT_PERIOD); |
| } |
| |
| /* AC power change ISR work */ |
| static void ac_power_isr_handler_work(struct work_struct *p_work) |
| { |
| /* if AC power is connected */ |
| if (is_ac_connected()) |
| { |
| wake_lock(&g_multi_data.wakelock); |
| PRINT_DEBUG("%s() taking wake lock\n", __FUNCTION__); |
| } |
| else |
| { |
| wake_unlock(&g_multi_data.wakelock); |
| PRINT_DEBUG("%s() releasing wake lock\n", __FUNCTION__); |
| } |
| |
| /* send uEvent indicating that power PSY has been changed */ |
| power_supply_changed(&g_multi_data.ac_power_psy); |
| } |
| |
| static void battery_external_power_changed(struct power_supply *t_power_supply) |
| { |
| PRINT_DEBUG("%s() called\n", __FUNCTION__); |
| } |
| |
| static void cmp_battery_multi_power_off(void) |
| { |
| printk(KERN_INFO "%s() power off called\n", __FUNCTION__); |
| gpio_set_value(g_gpio_power_control, 0); |
| } |
| |
| static int setup_gpios(struct battery_monitor *pt_battery_monitor) |
| { |
| int rc; |
| |
| printk(KERN_INFO "%s() power on/off gpio: %d ac power gpio: %d " |
| "charger gpio: %d\n", |
| __FUNCTION__, |
| g_gpio_power_control, |
| pt_battery_monitor->gpio_ac_power, |
| pt_battery_monitor->gpio_charger); |
| |
| if (g_gpio_power_control >= 0) |
| { |
| rc = gpio_request_one(g_gpio_power_control, |
| GPIOF_OUT_INIT_HIGH, "switch off"); |
| if (rc) |
| { |
| close_gpios(pt_battery_monitor); |
| return rc; |
| } |
| } |
| |
| if (pt_battery_monitor->gpio_charger >= 0) |
| { |
| rc = gpio_request_one(pt_battery_monitor->gpio_charger, |
| GPIOF_IN, "charger"); |
| if (rc) |
| { |
| close_gpios(pt_battery_monitor); |
| return rc; |
| } |
| } |
| |
| /* handle AC power gpio. We are assuming that its presence was already |
| checked */ |
| rc = gpio_request_one(pt_battery_monitor->gpio_ac_power, |
| GPIOF_IN, "ac power"); |
| if (rc) |
| { |
| close_gpios(pt_battery_monitor); |
| return rc; |
| } |
| |
| /* |
| * power on/off gpio signal bounces up and down before settling in either |
| * case, so we need to debounce it. |
| */ |
| rc = gpio_set_debounce(pt_battery_monitor->gpio_ac_power, |
| DEBOUNCE_TIME_USECS); |
| if (rc < 0) { |
| pr_err("%s: gpio_ac_power set debounce failed. gpio: %d, return: %d\n", |
| __FUNCTION__, pt_battery_monitor->gpio_ac_power, rc); |
| close_gpios(pt_battery_monitor); |
| return rc; |
| } |
| |
| /* |
| * Setup the interrupt to detect if external power is plugged in or not. |
| * IRQF_TRIGGER_FALLING - external power is connected |
| * IRQF_TRIGGER_RISING - external power is disconnected |
| */ |
| rc = request_irq(gpio_to_irq(pt_battery_monitor->gpio_ac_power), |
| ac_power_gpio_isr, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "ac detect", &g_multi_data); |
| if (rc) |
| { |
| printk(KERN_ERR "%s(): request_irq(gpio_to_irq(%d)) returned %d\n", |
| __FUNCTION__, pt_battery_monitor->gpio_ac_power, rc); |
| close_gpios(pt_battery_monitor); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void close_gpios(struct battery_monitor *pt_battery_monitor) |
| { |
| if (pt_battery_monitor->gpio_ac_power >= 0) { |
| free_irq(gpio_to_irq(pt_battery_monitor->gpio_ac_power), &g_multi_data); |
| gpio_free(pt_battery_monitor->gpio_ac_power); |
| } |
| |
| if (g_gpio_power_control >= 0) |
| gpio_free(g_gpio_power_control); |
| |
| if (pt_battery_monitor->gpio_charger >= 0) |
| gpio_free(pt_battery_monitor->gpio_charger); |
| } |
| |
| #ifdef CONFIG_PM |
| static int battery_suspend(struct platform_device *p_dev, pm_message_t state) |
| { |
| /* flush pending ISR work */ |
| flush_scheduled_work(); |
| flush_workqueue(isr_wq); |
| |
| /* flush delayed work. */ |
| cancel_delayed_work_sync(&g_multi_data.t_battery_dwork); |
| return 0; |
| } |
| |
| static int battery_resume(struct platform_device *p_dev) |
| { |
| /* take wakelock if AC is connected. This is needed as we do not know what |
| was happening when we were suspended */ |
| if (is_ac_connected()) |
| { |
| wake_lock(&g_multi_data.wakelock); |
| printk(KERN_INFO "%s() taking wake lock\n", __FUNCTION__); |
| } |
| |
| /* How to do in a SMP centric safe manner? */ |
| schedule_delayed_work(&g_multi_data.t_battery_dwork, MULTI_WAIT_PERIOD); |
| return 0; |
| } |
| #endif |
| |
| static int __devinit battery_probe(struct platform_device *p_dev) |
| { |
| int ret; |
| /* CBM (C)mp (B)attery (M)ulti. */ |
| struct cbm_platform_data *pt_cbm_platform_data; |
| |
| printk(gBanner); |
| |
| if (p_dev->dev.platform_data == NULL) |
| { /* Need this information. */ |
| printk(KERN_ERR "%s() error p_dev->dev.platform_data == NULL\n", |
| __FUNCTION__); |
| return -ENODATA; |
| } |
| |
| pt_cbm_platform_data = (struct cbm_platform_data *)p_dev->dev.platform_data; |
| |
| printk(KERN_INFO "%s() battery max voltage: %d battery min voltage: %d\n", |
| __FUNCTION__, |
| pt_cbm_platform_data->battery_max_voltage, |
| pt_cbm_platform_data->battery_min_voltage); |
| |
| /* Needed to turn off the device. */ |
| g_gpio_power_control = pt_cbm_platform_data->gpio_power_control; |
| |
| /* store platform data */ |
| g_multi_data.pt_platform_data = pt_cbm_platform_data; |
| |
| /* Tell the system how to turn off the power. */ |
| /* pm_power_off is a global function pointer. It is declared as external in |
| linux/pm.h. Somehow this is called when Tablet is powered off */ |
| pm_power_off = cmp_battery_multi_power_off; |
| |
| isr_wq = create_workqueue("multi_isr_wq"); |
| if (isr_wq == NULL) |
| { |
| printk(KERN_ERR "%s() Can not create ISR workqueue\n", __FUNCTION__); |
| ret = -1; |
| goto err_isr_wq; |
| } |
| |
| /* Use a wake lock to keep the tablet awake. */ |
| wake_lock_init(&g_multi_data.wakelock, WAKE_LOCK_SUSPEND, wake_lock_name); |
| |
| /* Prepare to start the delayed work. */ |
| INIT_DELAYED_WORK_DEFERRABLE(&g_multi_data.t_battery_dwork, |
| multi_battery_work); |
| |
| /* register battery PSY with power supply framework */ |
| ret = power_supply_register(&p_dev->dev, &g_multi_data.battery_psy); |
| if (ret) |
| goto err_psy_reg_battery; |
| |
| /* register AC power PSY with power supply framework */ |
| ret = power_supply_register(&p_dev->dev, &g_multi_data.ac_power_psy); |
| if (ret) |
| goto err_psy_reg_power; |
| |
| /* register USB power PSY with power supply framework */ |
| ret = power_supply_register(&p_dev->dev, &usb_power_psy); |
| if (ret) |
| goto err_psy_reg_usbpower; |
| |
| /* return success */ |
| g_is_driver_ok = true; |
| return 0; |
| |
| err_psy_reg_usbpower: |
| power_supply_unregister(&g_multi_data.ac_power_psy); |
| |
| err_psy_reg_power: |
| power_supply_unregister(&g_multi_data.battery_psy); |
| |
| err_psy_reg_battery: |
| wake_lock_destroy(&g_multi_data.wakelock); |
| |
| /* see comment in battery_remove */ |
| flush_scheduled_work(); |
| flush_workqueue(isr_wq); |
| destroy_workqueue(isr_wq); |
| err_isr_wq: |
| pm_power_off = NULL; |
| return ret; |
| } |
| |
| static int __devexit battery_remove(struct platform_device *p_dev) |
| { |
| /* unregister power supplies from PSY framework */ |
| power_supply_unregister(&g_multi_data.battery_psy); |
| power_supply_unregister(&g_multi_data.ac_power_psy); |
| power_supply_unregister(&usb_power_psy); |
| |
| /* |
| * now flush all pending work. |
| * we won't get any more schedules, since all |
| * sources (isr and external_power_changed) |
| * are unregistered now. |
| */ |
| flush_scheduled_work(); |
| flush_workqueue(isr_wq); |
| destroy_workqueue(isr_wq); |
| |
| /* flush the delayed work. */ |
| cancel_delayed_work_sync(&g_multi_data.t_battery_dwork); |
| |
| pm_power_off = NULL; |
| g_is_driver_ok = false; |
| |
| if (g_multi_data.pt_battery_monitor != NULL) |
| close_gpios(g_multi_data.pt_battery_monitor); |
| |
| wake_lock_destroy(&g_multi_data.wakelock); |
| return 0; |
| } |
| |
| static struct platform_driver battery_driver = |
| { |
| .driver.name = HW_CMP_MULTI_DRIVER_NAME, |
| .driver.owner = THIS_MODULE, |
| .probe = battery_probe, |
| .remove = __devexit_p(battery_remove), |
| #ifdef CONFIG_PM |
| .suspend = battery_suspend, |
| .resume = battery_resume, |
| #endif |
| }; |
| |
| static int __init battery_init(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&battery_driver); |
| if (ret) |
| { |
| printk(KERN_ERR "%s(): platform_driver_register failed %d\n", |
| __FUNCTION__, ret); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static void __exit battery_exit(void) |
| { |
| platform_driver_unregister(&battery_driver); |
| } |
| |
| module_init(battery_init); |
| module_exit(battery_exit); |
| |
| MODULE_AUTHOR("Broadcom"); |
| MODULE_DESCRIPTION("Broadcom CMP Battery Multi Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION("1.02"); |