From 10551a86fa76709587b48a644caeb78dd07690be Mon Sep 17 00:00:00 2001 From: Nithish Mahalingam Date: Tue, 29 Dec 2009 22:42:48 +0530 Subject: [PATCH 068/104] Adding Intel Moorestown PMIC Battery Driver PMIC Battery driver provides battery charging and battery gauge functionality on Intel Moorestown platform. Signed-off-by: Nithish Mahalingam --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/pmic_battery.c | 799 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 807 insertions(+), 0 deletions(-) create mode 100644 drivers/power/pmic_battery.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index d4b3d67..6936bc8 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -124,4 +124,11 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. +config BATTERY_MRSTPMIC + tristate "PMIC battery driver for Intel Moorestown platform" + depends on SPI_MRST && LNW_IPC && USB_GADGET_LANGWELL + help + Say Y here to enable battery driver on Intel Moorestown + platform. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 573597c..97af4b4 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_BATTERY_MRSTPMIC) += pmic_battery.o diff --git a/drivers/power/pmic_battery.c b/drivers/power/pmic_battery.c new file mode 100644 index 0000000..6e3c46a --- /dev/null +++ b/drivers/power/pmic_battery.c @@ -0,0 +1,799 @@ +/* + * pmic_battery.c - Intel Moorestown PMIC Battery Driver + * + * Copyright (C) 2009 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Author: Nithish Mahalingam + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +MODULE_AUTHOR("Nithish Mahalingam "); +MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); +MODULE_LICENSE("GPL"); + +#define DRIVER_NAME "pmic_battery" + +/********************************************************************* + * Generic defines + *********************************************************************/ + +static int pmicbatteryDebug; +module_param(pmicbatteryDebug, int, 0444); +MODULE_PARM_DESC(pmicbatteryDebug, + "Flag to enable PMIC Battery debug messages."); + +#define PMIC_BATT_DEBUG (pmicbatteryDebug) + +#define PMIC_BATT_DRV_INFO_UPDATED 1 +#define PMIC_BATT_PRESENT 1 +#define PMIC_BATT_NOT_PRESENT 0 +#define PMIC_USB_PRESENT PMIC_BATT_PRESENT +#define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT + +/* pmic battery register related */ +#define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2 +#define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1) +#define PMIC_BATT_CHR_STEMP_MASK (1 << 2) +#define PMIC_BATT_CHR_SCOMP_MASK (1 << 3) +#define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4) +#define PMIC_BATT_CHR_SBATDET_MASK (1 << 5) +#define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6) +#define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7) +#define PMIC_BATT_CHR_EXCPT_MASK 0xC6 +#define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31) +#define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF + +/* pmic ipc related */ +#define PMIC_BATT_CHR_IPC_CMDID 0xEF +#define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4 +#define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6 + +/* internal return values */ +#define BATTSUCCESS 0 +#define EBATTFAIL 1 +#define EBATTERR 2 + +/* types of battery charging */ +enum batt_charge_type { + BATT_USBOTG_500MA_CHARGE, + BATT_USBOTG_TRICKLE_CHARGE, +}; + +/* valid battery events */ +enum batt_event { + BATT_EVENT_BATOVP_EXCPT, + BATT_EVENT_USBOVP_EXCPT, + BATT_EVENT_TEMP_EXCPT, + BATT_EVENT_DCLMT_EXCPT, + BATT_EVENT_EXCPT +}; + +/* battery cca value */ +struct batt_cca_data { + signed int cca_val; +}; + +/* battery property structure */ +struct batt_prop_data { + unsigned int batt_capacity; + char batt_chrg_crnt; + char batt_chrg_volt; + char batt_chrg_prot; + char batt_chrg_prot2; + char batt_chrg_timer; +} __attribute__((packed)); + + +/********************************************************************* + * Battery properties + *********************************************************************/ + +/* + * pmic battery info + */ +struct pmic_power_module_info { + bool is_dev_info_updated; + struct spi_device *spi; + /* pmic battery data */ + unsigned long update_time; /* jiffies when data read */ + unsigned int usb_is_present; + unsigned int batt_is_present; + unsigned int batt_health; + unsigned int usb_health; + unsigned int batt_status; + unsigned int batt_charge_now; /* in mAS */ + unsigned int batt_prev_charge_full; /* in mAS */ + unsigned int batt_charge_rate; /* in units per second */ + + struct power_supply usb; + struct power_supply batt; + int irq; /* GPE_ID or IRQ# */ + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_battery; + struct work_struct handler; +}; + +static unsigned int delay_time = 2000; /* in ms */ + +/* + * pmic ac properties + */ +static enum power_supply_property pmic_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, +}; + +/* + * pmic battery properties + */ +static enum power_supply_property pmic_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_AVG, +}; + + +/** + * pmic_battery_log_event - log battery events + * @event: battery event to be logged + * Context: can sleep + * + * There are multiple battery events which may be of interest to users; + * this battery function logs the different battery events onto the + * kernel log messages. + */ +static void pmic_battery_log_event(enum batt_event event) +{ + switch (event) { + case BATT_EVENT_BATOVP_EXCPT: + printk(KERN_WARNING "pmic-battery: battery overvoltage " + "condition detected\n"); + break; + case BATT_EVENT_USBOVP_EXCPT: + printk(KERN_WARNING "pmic-battery: usb charger overvoltage " + "condition detected\n"); + break; + case BATT_EVENT_TEMP_EXCPT: + printk(KERN_WARNING "pmic-battery: high battery temperature " + "condition detected\n"); + break; + case BATT_EVENT_DCLMT_EXCPT: + printk(KERN_WARNING "pmic-battery: over battery charge " + " current condition detected\n"); + break; + default: + printk(KERN_WARNING "pmic-battery: charger/battery " + " exception detected\n"); + break; + } +} + +/** + * pmic_battery_read_status - read battery status information + * @pbi: device info structure to update the read information + * Context: can sleep + * + * PMIC power source information need to be updated based on the data read + * from the PMIC battery registers. + * + */ +static void pmic_battery_read_status(struct pmic_power_module_info *pbi) +{ + unsigned int update_time_intrvl = 0; + unsigned int chrg_val = 0; + struct ipc_pmic_reg_data pmic_batt_reg = {0}; + struct ipc_cmd_type pmic_batt_cmd = {0}; + struct batt_cca_data ccval = {0}; + struct batt_prop_data batt_prop = {0}; + int batt_present = 0; + int usb_present = 0; + int batt_exception = 0; + + /* make sure the last batt_status read happened delay_time before */ + if (pbi->update_time && time_before(jiffies, pbi->update_time + + msecs_to_jiffies(delay_time))) + return; + + update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time); + pbi->update_time = jiffies; + + /* read coulomb counter registers and schrgint register */ + + pmic_batt_cmd.ioc = TRUE; + pmic_batt_cmd.cmd = IPC_BATT_CCA_READ; + if (ipc_config_cmd(pmic_batt_cmd, sizeof(struct batt_cca_data), + &ccval)) { + dev_warn(&pbi->spi->dev, "%s(): ipc config cmd failed\n", + __func__); + return; + } + + pmic_batt_reg.ioc = TRUE; + pmic_batt_reg.pmic_reg_data[0].register_address = + PMIC_BATT_CHR_SCHRGINT_ADDR; + pmic_batt_reg.num_entries = 1; + + if (ipc_pmic_register_read(&pmic_batt_reg)) { + dev_warn(&pbi->spi->dev, "%s(): ipc pmic read failed\n", + __func__); + return; + } + + /* + * set pmic_power_module_info members based on pmic register values + * read. + */ + + /* set batt_is_present */ + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SBATDET_MASK) { + pbi->batt_is_present = PMIC_BATT_PRESENT; + batt_present = 1; + } else { + pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + + /* set batt_health */ + if (batt_present) { + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SBATOVP_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT); + batt_exception = 1; + } else if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SDCLMT_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT); + batt_exception = 1; + } else if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_STEMP_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT); + batt_exception = 1; + } else { + pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; + } + } + + /* set usb_is_present */ + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SUSBDET_MASK) { + pbi->usb_is_present = PMIC_USB_PRESENT; + usb_present = 1; + } else { + pbi->usb_is_present = PMIC_USB_NOT_PRESENT; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (usb_present) { + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SUSBOVP_MASK) { + pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT); + } else { + pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; + } + } + + chrg_val = ccval.cca_val & PMIC_BATT_ADC_ACCCHRGVAL_MASK; + + /* set batt_prev_charge_full to battery capacity the first time */ + if (!pbi->is_dev_info_updated) { + pmic_batt_cmd.ioc = TRUE; + pmic_batt_cmd.cmd = IPC_BATT_GET_PROP; + if (ipc_config_cmd(pmic_batt_cmd, + sizeof(struct batt_prop_data), &batt_prop)) { + dev_warn(&pbi->spi->dev, "%s(): ipc config cmd " + "failed\n", __func__); + return; + } + pbi->batt_prev_charge_full = batt_prop.batt_capacity; + } + + /* set batt_status */ + if ((batt_present) && (!batt_exception)) { + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_SCOMP_MASK) { + pbi->batt_status = POWER_SUPPLY_STATUS_FULL; + pbi->batt_prev_charge_full = chrg_val; + } else if (ccval.cca_val & PMIC_BATT_ADC_ACCCHRG_MASK) { + pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING; + } + } + + /* set batt_charge_rate */ + if ((pbi->is_dev_info_updated) && (batt_present) && (!batt_exception)) { + if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (pbi->batt_charge_now - chrg_val) { + pbi->batt_charge_rate = ((pbi->batt_charge_now - + chrg_val) * 1000 * 60) / + update_time_intrvl; + } + } else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) { + if (chrg_val - pbi->batt_charge_now) { + pbi->batt_charge_rate = ((chrg_val - + pbi->batt_charge_now) * 1000 * 60) / + update_time_intrvl; + } + } else + pbi->batt_charge_rate = 0; + } else { + pbi->batt_charge_rate = -1; + } + + /* batt_charge_now */ + if ((batt_present) && (!batt_exception)) + pbi->batt_charge_now = chrg_val; + else + pbi->batt_charge_now = -1; + + pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED; +} + +/** + * pmic_usb_get_property - usb power source get property + * @psy: usb power supply context + * @psp: usb power source property + * @val: usb power source property value + * Context: can sleep + * + * PMIC usb power source property needs to be provided to power_supply + * subsytem for it to provide the information to users. + */ +static int pmic_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmic_power_module_info *pbi = container_of(psy, + struct pmic_power_module_info, usb); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pbi->usb_is_present; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = pbi->usb_health; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * pmic_battery_get_property - battery power source get property + * @psy: battery power supply context + * @psp: battery power source property + * @val: battery power source property value + * Context: can sleep + * + * PMIC battery power source property needs to be provided to power_supply + * subsytem for it to provide the information to users. + */ +static int pmic_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pmic_power_module_info *pbi = container_of(psy, + struct pmic_power_module_info, batt); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = pbi->batt_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = pbi->batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = pbi->batt_is_present; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = pbi->batt_charge_now; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = pbi->batt_prev_charge_full; + break; + case POWER_SUPPLY_PROP_CHARGE_AVG: + val->intval = pbi->batt_charge_rate; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * pmic_battery_monitor - monitor battery status + * @work: work structure + * Context: can sleep + * + * PMIC battery status needs to be monitored for any change + * and information needs to be frequently updated. + */ +static void pmic_battery_monitor(struct work_struct *work) +{ + struct pmic_power_module_info *pbi = container_of(work, + struct pmic_power_module_info, monitor_battery.work); + + /* update pmic_power_module_info members */ + pmic_battery_read_status(pbi); + queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10); +} + +/** + * pmic_battery_set_charger - set battery charger + * @pbi: device info structure + * @chrg: charge mode to set battery charger in + * Context: can sleep + * + * PMIC battery charger needs to be enabled based on the usb charge + * capabilities connected to the platform. + */ +static int pmic_battery_set_charger(struct pmic_power_module_info *pbi, + enum batt_charge_type chrg) +{ + int retval; + + /* set usblmt bits and chrgcntl register bits appropriately */ + switch (chrg) { + case BATT_USBOTG_500MA_CHARGE: + retval = lnw_ipc_single_cmd(PMIC_BATT_CHR_IPC_CMDID, + PMIC_BATT_CHR_IPC_FCHRG_SUBID, 0, 0); + break; + case BATT_USBOTG_TRICKLE_CHARGE: + retval = lnw_ipc_single_cmd(PMIC_BATT_CHR_IPC_CMDID, + PMIC_BATT_CHR_IPC_TCHRG_SUBID, 0, 0); + break; + default: + dev_warn(&pbi->spi->dev, "%s(): out of range usb charger " + "charge detected\n", __func__); + return -EBATTFAIL; + } + + if (retval) { + dev_warn(&pbi->spi->dev, "%s(): ipc pmic read failed\n", + __func__); + return -EBATTFAIL; + } + + return BATTSUCCESS; +} + +/** + * pmic_battery_interrupt_handler - pmic battery interrupt handler + * Context: interrupt context + * + * PMIC battery interrupt handler which will be called with either + * battery full condition occurs or usb otg & battery connect + * condition occurs. + */ +static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev) +{ + struct pmic_power_module_info *pbi = + (struct pmic_power_module_info *)dev; + + schedule_work(&pbi->handler); + + return IRQ_HANDLED; +} + +/** + * pmic_battery_handle_intrpt - pmic battery service interrupt + * @work: work structure + * Context: can sleep + * + * PMIC battery needs to either update the battery status as full + * if it detects battery full condition caused the interrupt or needs + * to enable battery charger if it detects usb and battery detect + * caused the source of interrupt. + */ +static void pmic_battery_handle_intrpt(struct work_struct *work) +{ + struct ipc_pmic_reg_data pmic_batt_reg = {0}; + struct pmic_power_module_info *pbi = container_of(work, + struct pmic_power_module_info, handler); + int power = 0; + enum batt_charge_type chrg; + int retval = 0; + + /* check if pmic_power_module_info is initialized */ + if (!pbi) + return; + + /* read schrgint register to interpret cause of interrupt */ + pmic_batt_reg.ioc = TRUE; + pmic_batt_reg.pmic_reg_data[0].register_address = + PMIC_BATT_CHR_SCHRGINT_ADDR; + pmic_batt_reg.num_entries = 1; + + if (ipc_pmic_register_read(&pmic_batt_reg)) { + dev_warn(&pbi->spi->dev, "%s(): ipc pmic read failed\n", + __func__); + return; + } + + /* find the cause of the interrupt */ + + if (pmic_batt_reg.pmic_reg_data[0].value & PMIC_BATT_CHR_SBATDET_MASK) { + pbi->batt_is_present = PMIC_BATT_PRESENT; + } else { + pbi->batt_is_present = PMIC_BATT_NOT_PRESENT; + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; + return; + } + + if (pmic_batt_reg.pmic_reg_data[0].value & + PMIC_BATT_CHR_EXCPT_MASK) { + pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + pmic_battery_log_event(BATT_EVENT_EXCPT); + return; + } else { + pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD; + pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD; + } + + if (pmic_batt_reg.pmic_reg_data[0].value & PMIC_BATT_CHR_SCOMP_MASK) { + struct ipc_cmd_type pmic_batt_cmd = {0}; + struct batt_cca_data ccval = {0}; + + pbi->batt_status = POWER_SUPPLY_STATUS_FULL; + pmic_batt_cmd.ioc = TRUE; + pmic_batt_cmd.cmd = IPC_BATT_CCA_READ; + if (ipc_config_cmd(pmic_batt_cmd, + sizeof(struct batt_cca_data), &ccval)) { + dev_warn(&pbi->spi->dev, "%s(): ipc config cmd " + "failed\n", __func__); + return; + } + pbi->batt_prev_charge_full = ccval.cca_val & + PMIC_BATT_ADC_ACCCHRGVAL_MASK; + return; + } + + if (pmic_batt_reg.pmic_reg_data[0].value & PMIC_BATT_CHR_SUSBDET_MASK) { + pbi->usb_is_present = PMIC_USB_PRESENT; + } else { + pbi->usb_is_present = PMIC_USB_NOT_PRESENT; + pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + return; + } + + /* setup battery charging */ + + /* check usb otg power capability and set charger accordingly */ + retval = langwell_udc_maxpower(&power); + if (retval) { + dev_warn(&pbi->spi->dev, "%s(): usb otg power query failed " + "with error code %d\n", __func__, retval); + return; + } + + if (power >= 500) + chrg = BATT_USBOTG_500MA_CHARGE; + else + chrg = BATT_USBOTG_TRICKLE_CHARGE; + + /* enable battery charging */ + if (pmic_battery_set_charger(pbi, chrg)) { + dev_warn(&pbi->spi->dev, "%s(): failed to setup battery " + "charging\n", __func__); + return; + } + + if (PMIC_BATT_DEBUG) + printk(KERN_INFO "pmic-battery: %s() - setting up battery " + "charger successful\n", __func__); +} + +/** + * pmic_battery_probe - pmic battery initialize + * @spi: pmic battery spi structure + * Context: can sleep + * + * PMIC battery initializes its internal data structue and other + * infrastructure components for it to work as expected. + */ +static int pmic_battery_probe(struct spi_device *spi) +{ + int retval = 0; + struct pmic_power_module_info *pbi = 0; + + if (PMIC_BATT_DEBUG) + printk(KERN_INFO "pmic-battery: %s() - found pmic battery " + "device\n", __func__); + + pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); + if (!pbi) { + dev_err(&spi->dev, "%s(): memory allocation failed\n", + __func__); + return -ENOMEM; + } + + pbi->spi = spi; + pbi->irq = spi->irq; + dev_set_drvdata(&spi->dev, pbi); + + /* initialize all required framework before enabling interrupts */ + INIT_WORK(&pbi->handler, (void *)pmic_battery_handle_intrpt); + INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor); + pbi->monitor_wqueue = + create_singlethread_workqueue(dev_name(&spi->dev)); + if (!pbi->monitor_wqueue) { + dev_err(&spi->dev, "%s(): wqueue init failed\n", __func__); + retval = -ESRCH; + goto wqueue_failed; + } + + /* register interrupt */ + retval = request_irq(pbi->irq, pmic_battery_interrupt_handler, + 0, DRIVER_NAME, pbi); + if (retval) { + dev_err(&spi->dev, "%s(): cannot get IRQ\n", __func__); + goto requestirq_failed; + } + + /* register pmic-batt with power supply subsystem */ + pbi->batt.name = "pmic-batt"; + pbi->batt.type = POWER_SUPPLY_TYPE_BATTERY; + pbi->batt.properties = pmic_battery_props; + pbi->batt.num_properties = ARRAY_SIZE(pmic_battery_props); + pbi->batt.get_property = pmic_battery_get_property; + retval = power_supply_register(&spi->dev, &pbi->batt); + if (retval) { + dev_err(&spi->dev, "%s(): failed to register pmic battery " + "device with power supply subsystem\n", + __func__); + goto power_reg_failed; + } + + if (PMIC_BATT_DEBUG) + printk(KERN_INFO "pmic-battery: %s() - pmic battery device " + "registration with power supply subsystem " + "successful\n", __func__); + + queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1); + + /* register pmic-usb with power supply subsystem */ + pbi->usb.name = "pmic-usb"; + pbi->usb.type = POWER_SUPPLY_TYPE_USB; + pbi->usb.properties = pmic_usb_props; + pbi->usb.num_properties = ARRAY_SIZE(pmic_usb_props); + pbi->usb.get_property = pmic_usb_get_property; + retval = power_supply_register(&spi->dev, &pbi->usb); + if (retval) { + dev_err(&spi->dev, "%s(): failed to register pmic usb " + "device with power supply subsystem\n", + __func__); + goto power_reg_failed_1; + } + + if (PMIC_BATT_DEBUG) + printk(KERN_INFO "pmic-battery: %s() - pmic usb device " + "registration with power supply subsystem successful\n", + __func__); + + return retval; + +power_reg_failed_1: + power_supply_unregister(&pbi->batt); +power_reg_failed: + cancel_rearming_delayed_workqueue(pbi->monitor_wqueue, + &pbi->monitor_battery); +requestirq_failed: + destroy_workqueue(pbi->monitor_wqueue); +wqueue_failed: + kfree(pbi); + + return retval; +} + +/** + * pmic_battery_remove - pmic battery finalize + * @spi: pmic battery spi device structure + * Context: can sleep + * + * PMIC battery finalizes its internal data structue and other + * infrastructure components that it initialized in + * pmic_battery_probe. + */ +static int pmic_battery_remove(struct spi_device *spi) +{ + struct pmic_power_module_info *pbi = dev_get_drvdata(&spi->dev); + + if (pbi) { + free_irq(pbi->irq, pbi); + + cancel_rearming_delayed_workqueue(pbi->monitor_wqueue, + &pbi->monitor_battery); + destroy_workqueue(pbi->monitor_wqueue); + + power_supply_unregister(&pbi->usb); + power_supply_unregister(&pbi->batt); + + flush_scheduled_work(); + + kfree(pbi); + } + + return 0; +} + + +/********************************************************************* + * Driver initialisation and finalization + *********************************************************************/ + +static struct spi_driver pmic_battery_driver = { + .driver = { + .name = DRIVER_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = pmic_battery_probe, + .remove = __devexit_p(pmic_battery_remove), +}; + + +static int __init pmic_battery_module_init(void) +{ + return spi_register_driver(&pmic_battery_driver); +} + +static void __exit pmic_battery_module_exit(void) +{ + spi_unregister_driver(&pmic_battery_driver); +} + +module_init(pmic_battery_module_init); +module_exit(pmic_battery_module_exit); -- 1.6.2.5