| /* | |
| * intel_mid_battery.c - Intel MID 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 <[email protected]> | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/init.h> | |
| #include <linux/err.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/workqueue.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/param.h> | |
| #include <linux/device.h> | |
| #include <linux/spi/spi.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/power_supply.h> | |
| #include <asm/intel_scu_ipc.h> | |
| #define DRIVER_NAME "pmic_battery" | |
| /********************************************************************* | |
| * Generic defines | |
| *********************************************************************/ | |
| static int debug; | |
| module_param(debug, int, 0444); | |
| MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages."); | |
| #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 0x86 | |
| #define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31) | |
| #define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF | |
| /* pmic ipc related */ | |
| #define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4 | |
| #define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6 | |
| /* 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 properties | |
| *********************************************************************/ | |
| /* | |
| * pmic battery info | |
| */ | |
| struct pmic_power_module_info { | |
| bool is_dev_info_updated; | |
| struct device *dev; | |
| /* 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, | |
| }; | |
| /* | |
| * Glue functions for talking to the IPC | |
| */ | |
| struct battery_property { | |
| u32 capacity; /* Charger capacity */ | |
| u8 crnt; /* Quick charge current value*/ | |
| u8 volt; /* Fine adjustment of constant charge voltage */ | |
| u8 prot; /* CHRGPROT register value */ | |
| u8 prot2; /* CHRGPROT1 register value */ | |
| u8 timer; /* Charging timer */ | |
| }; | |
| #define IPCMSG_BATTERY 0xEF | |
| /* Battery coulomb counter accumulator commands */ | |
| #define IPC_CMD_CC_WR 0 /* Update coulomb counter value */ | |
| #define IPC_CMD_CC_RD 1 /* Read coulomb counter value */ | |
| #define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */ | |
| /** | |
| * pmic_scu_ipc_battery_cc_read - read battery cc | |
| * @value: battery coulomb counter read | |
| * | |
| * Reads the battery couloumb counter value, returns 0 on success, or | |
| * an error code | |
| * | |
| * This function may sleep. Locking for SCU accesses is handled for | |
| * the caller. | |
| */ | |
| static int pmic_scu_ipc_battery_cc_read(u32 *value) | |
| { | |
| return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD, | |
| NULL, 0, value, 1); | |
| } | |
| /** | |
| * pmic_scu_ipc_battery_property_get - fetch properties | |
| * @prop: battery properties | |
| * | |
| * Retrieve the battery properties from the power management | |
| * | |
| * This function may sleep. Locking for SCU accesses is handled for | |
| * the caller. | |
| */ | |
| static int pmic_scu_ipc_battery_property_get(struct battery_property *prop) | |
| { | |
| u32 data[3]; | |
| u8 *p = (u8 *)&data[1]; | |
| int err = intel_scu_ipc_command(IPCMSG_BATTERY, | |
| IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3); | |
| prop->capacity = data[0]; | |
| prop->crnt = *p++; | |
| prop->volt = *p++; | |
| prop->prot = *p++; | |
| prop->prot2 = *p++; | |
| prop->timer = *p++; | |
| return err; | |
| } | |
| /** | |
| * pmic_scu_ipc_set_charger - set charger | |
| * @charger: charger to select | |
| * | |
| * Switch the charging mode for the SCU | |
| */ | |
| static int pmic_scu_ipc_set_charger(int charger) | |
| { | |
| return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger); | |
| } | |
| /** | |
| * 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) | |
| { | |
| printk(KERN_WARNING "pmic-battery: "); | |
| switch (event) { | |
| case BATT_EVENT_BATOVP_EXCPT: | |
| printk(KERN_CONT "battery overvoltage condition\n"); | |
| break; | |
| case BATT_EVENT_USBOVP_EXCPT: | |
| printk(KERN_CONT "usb charger overvoltage condition\n"); | |
| break; | |
| case BATT_EVENT_TEMP_EXCPT: | |
| printk(KERN_CONT "high battery temperature condition\n"); | |
| break; | |
| case BATT_EVENT_DCLMT_EXCPT: | |
| printk(KERN_CONT "over battery charge current condition\n"); | |
| break; | |
| default: | |
| printk(KERN_CONT "charger/battery exception %d\n", event); | |
| 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; | |
| unsigned int chrg_val; | |
| u32 ccval; | |
| u8 r8; | |
| struct battery_property batt_prop; | |
| 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 */ | |
| if (pmic_scu_ipc_battery_cc_read(&ccval)) { | |
| dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", | |
| __func__); | |
| return; | |
| } | |
| if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { | |
| dev_warn(pbi->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 (r8 & 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 (r8 & 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 (r8 & 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; | |
| if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) { | |
| /* PMIC will change charging current automatically */ | |
| pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT); | |
| } | |
| } | |
| } | |
| /* set usb_is_present */ | |
| if (r8 & 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 (r8 & 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 & PMIC_BATT_ADC_ACCCHRGVAL_MASK; | |
| /* set batt_prev_charge_full to battery capacity the first time */ | |
| if (!pbi->is_dev_info_updated) { | |
| if (pmic_scu_ipc_battery_property_get(&batt_prop)) { | |
| dev_warn(pbi->dev, "%s(): ipc config cmd failed\n", | |
| __func__); | |
| return; | |
| } | |
| pbi->batt_prev_charge_full = batt_prop.capacity; | |
| } | |
| /* set batt_status */ | |
| if (batt_present && !batt_exception) { | |
| if (r8 & PMIC_BATT_CHR_SCOMP_MASK) { | |
| pbi->batt_status = POWER_SUPPLY_STATUS_FULL; | |
| pbi->batt_prev_charge_full = chrg_val; | |
| } else if (ccval & 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 = power_supply_get_drvdata(psy); | |
| /* 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; | |
| } | |
| static inline unsigned long mAStouAh(unsigned long v) | |
| { | |
| /* seconds to hours, mA to µA */ | |
| return (v * 1000) / 3600; | |
| } | |
| /** | |
| * 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 = power_supply_get_drvdata(psy); | |
| /* 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 = mAStouAh(pbi->batt_charge_now); | |
| break; | |
| case POWER_SUPPLY_PROP_CHARGE_FULL: | |
| val->intval = mAStouAh(pbi->batt_prev_charge_full); | |
| 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 = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID); | |
| break; | |
| case BATT_USBOTG_TRICKLE_CHARGE: | |
| retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID); | |
| break; | |
| default: | |
| dev_warn(pbi->dev, "%s(): out of range usb charger " | |
| "charge detected\n", __func__); | |
| return -EINVAL; | |
| } | |
| if (retval) { | |
| dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", | |
| __func__); | |
| return retval; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * 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 = 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 pmic_power_module_info *pbi = container_of(work, | |
| struct pmic_power_module_info, handler); | |
| enum batt_charge_type chrg; | |
| u8 r8; | |
| if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) { | |
| dev_warn(pbi->dev, "%s(): ipc pmic read failed\n", | |
| __func__); | |
| return; | |
| } | |
| /* find the cause of the interrupt */ | |
| if (r8 & 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 (r8 & 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 (r8 & PMIC_BATT_CHR_SCOMP_MASK) { | |
| u32 ccval; | |
| pbi->batt_status = POWER_SUPPLY_STATUS_FULL; | |
| if (pmic_scu_ipc_battery_cc_read(&ccval)) { | |
| dev_warn(pbi->dev, "%s(): ipc config cmd " | |
| "failed\n", __func__); | |
| return; | |
| } | |
| pbi->batt_prev_charge_full = ccval & | |
| PMIC_BATT_ADC_ACCCHRGVAL_MASK; | |
| return; | |
| } | |
| if (r8 & 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 */ | |
| #if 0 | |
| /* check usb otg power capability and set charger accordingly */ | |
| retval = langwell_udc_maxpower(&power); | |
| if (retval) { | |
| dev_warn(pbi->dev, | |
| "%s(): usb otg power query failed with error code %d\n", | |
| __func__, retval); | |
| return; | |
| } | |
| if (power >= 500) | |
| chrg = BATT_USBOTG_500MA_CHARGE; | |
| else | |
| #endif | |
| chrg = BATT_USBOTG_TRICKLE_CHARGE; | |
| /* enable battery charging */ | |
| if (pmic_battery_set_charger(pbi, chrg)) { | |
| dev_warn(pbi->dev, | |
| "%s(): failed to set up battery charging\n", __func__); | |
| return; | |
| } | |
| dev_dbg(pbi->dev, | |
| "pmic-battery: %s() - setting up battery charger successful\n", | |
| __func__); | |
| } | |
| /* | |
| * Description of power supplies | |
| */ | |
| static const struct power_supply_desc pmic_usb_desc = { | |
| .name = "pmic-usb", | |
| .type = POWER_SUPPLY_TYPE_USB, | |
| .properties = pmic_usb_props, | |
| .num_properties = ARRAY_SIZE(pmic_usb_props), | |
| .get_property = pmic_usb_get_property, | |
| }; | |
| static const struct power_supply_desc pmic_batt_desc = { | |
| .name = "pmic-batt", | |
| .type = POWER_SUPPLY_TYPE_BATTERY, | |
| .properties = pmic_battery_props, | |
| .num_properties = ARRAY_SIZE(pmic_battery_props), | |
| .get_property = pmic_battery_get_property, | |
| }; | |
| /** | |
| * pmic_battery_probe - pmic battery initialize | |
| * @irq: pmic battery device irq | |
| * @dev: pmic battery device structure | |
| * Context: can sleep | |
| * | |
| * PMIC battery initializes its internal data structue and other | |
| * infrastructure components for it to work as expected. | |
| */ | |
| static int probe(int irq, struct device *dev) | |
| { | |
| int retval = 0; | |
| struct pmic_power_module_info *pbi; | |
| struct power_supply_config psy_cfg = {}; | |
| dev_dbg(dev, "pmic-battery: found pmic battery device\n"); | |
| pbi = kzalloc(sizeof(*pbi), GFP_KERNEL); | |
| if (!pbi) { | |
| dev_err(dev, "%s(): memory allocation failed\n", | |
| __func__); | |
| return -ENOMEM; | |
| } | |
| pbi->dev = dev; | |
| pbi->irq = irq; | |
| dev_set_drvdata(dev, pbi); | |
| psy_cfg.drv_data = pbi; | |
| /* initialize all required framework before enabling interrupts */ | |
| INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt); | |
| INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor); | |
| pbi->monitor_wqueue = | |
| create_singlethread_workqueue(dev_name(dev)); | |
| if (!pbi->monitor_wqueue) { | |
| dev_err(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(dev, "%s(): cannot get IRQ\n", __func__); | |
| goto requestirq_failed; | |
| } | |
| /* register pmic-batt with power supply subsystem */ | |
| pbi->batt = power_supply_register(dev, &pmic_usb_desc, &psy_cfg); | |
| if (IS_ERR(pbi->batt)) { | |
| dev_err(dev, | |
| "%s(): failed to register pmic battery device with power supply subsystem\n", | |
| __func__); | |
| retval = PTR_ERR(pbi->batt); | |
| goto power_reg_failed; | |
| } | |
| dev_dbg(dev, "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 = power_supply_register(dev, &pmic_batt_desc, &psy_cfg); | |
| if (IS_ERR(pbi->usb)) { | |
| dev_err(dev, | |
| "%s(): failed to register pmic usb device with power supply subsystem\n", | |
| __func__); | |
| retval = PTR_ERR(pbi->usb); | |
| goto power_reg_failed_1; | |
| } | |
| if (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_delayed_work_sync(&pbi->monitor_battery); | |
| requestirq_failed: | |
| destroy_workqueue(pbi->monitor_wqueue); | |
| wqueue_failed: | |
| kfree(pbi); | |
| return retval; | |
| } | |
| static int platform_pmic_battery_probe(struct platform_device *pdev) | |
| { | |
| return probe(pdev->id, &pdev->dev); | |
| } | |
| /** | |
| * pmic_battery_remove - pmic battery finalize | |
| * @dev: pmic battery 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 platform_pmic_battery_remove(struct platform_device *pdev) | |
| { | |
| struct pmic_power_module_info *pbi = platform_get_drvdata(pdev); | |
| free_irq(pbi->irq, pbi); | |
| cancel_delayed_work_sync(&pbi->monitor_battery); | |
| destroy_workqueue(pbi->monitor_wqueue); | |
| power_supply_unregister(pbi->usb); | |
| power_supply_unregister(pbi->batt); | |
| cancel_work_sync(&pbi->handler); | |
| kfree(pbi); | |
| return 0; | |
| } | |
| static struct platform_driver platform_pmic_battery_driver = { | |
| .driver = { | |
| .name = DRIVER_NAME, | |
| }, | |
| .probe = platform_pmic_battery_probe, | |
| .remove = platform_pmic_battery_remove, | |
| }; | |
| module_platform_driver(platform_pmic_battery_driver); | |
| MODULE_AUTHOR("Nithish Mahalingam <[email protected]>"); | |
| MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); | |
| MODULE_LICENSE("GPL"); |