| /* |
| * 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 <nithish.mahalingam@intel.com> |
| */ |
| |
| #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 <nithish.mahalingam@intel.com>"); |
| MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); |
| MODULE_LICENSE("GPL"); |