/* * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /* * Includes */ #include #include #include #include #include #include #include #define BIT_CHG_VOL_LSH 0 #define BIT_CHG_VOL_WID 3 #define BIT_CHG_CURR_LSH 3 #define BIT_CHG_CURR_WID 4 #define BIT_CHG_PLIM_LSH 15 #define BIT_CHG_PLIM_WID 2 #define BIT_CHG_DETS_LSH 6 #define BIT_CHG_DETS_WID 1 #define BIT_CHG_CURRS_LSH 11 #define BIT_CHG_CURRS_WID 1 #define TRICKLE_CHG_EN_LSH 7 #define LOW_POWER_BOOT_ACK_LSH 8 #define BAT_TH_CHECK_DIS_LSH 9 #define BATTFET_CTL_EN_LSH 10 #define BATTFET_CTL_LSH 11 #define REV_MOD_EN_LSH 13 #define PLIM_DIS_LSH 17 #define CHG_LED_EN_LSH 18 #define RESTART_CHG_STAT_LSH 20 #define AUTO_CHG_DIS_LSH 21 #define CYCLING_DIS_LSH 22 #define VI_PROGRAM_EN_LSH 23 #define TRICKLE_CHG_EN_WID 1 #define LOW_POWER_BOOT_ACK_WID 1 #define BAT_TH_CHECK_DIS_WID 1 #define BATTFET_CTL_EN_WID 1 #define BATTFET_CTL_WID 1 #define REV_MOD_EN_WID 1 #define PLIM_DIS_WID 1 #define CHG_LED_EN_WID 1 #define RESTART_CHG_STAT_WID 1 #define AUTO_CHG_DIS_WID 1 #define CYCLING_DIS_WID 1 #define VI_PROGRAM_EN_WID 1 #define ACC_STARTCC_LSH 0 #define ACC_STARTCC_WID 1 #define ACC_RSTCC_LSH 1 #define ACC_RSTCC_WID 1 #define ACC_CCFAULT_LSH 7 #define ACC_CCFAULT_WID 7 #define ACC_CCOUT_LSH 8 #define ACC_CCOUT_WID 16 #define ACC1_ONEC_LSH 0 #define ACC1_ONEC_WID 15 #define ACC_CALIBRATION 0x17 #define ACC_START_COUNTER 0x07 #define ACC_STOP_COUNTER 0x2 #define ACC_CONTROL_BIT_MASK 0x1f #define ACC_ONEC_VALUE 2621 #define ACC_COULOMB_PER_LSB 1 #define ACC_CALIBRATION_DURATION_MSECS 20 #define BAT_VOLTAGE_UNIT_UV 4692 #define BAT_CURRENT_UNIT_UA 5870 #define CHG_VOLTAGE_UINT_UV 23474 #define CHG_MIN_CURRENT_UA 3500 #define COULOMB_TO_UAH(c) (10000 * c / 36) enum chg_setting { TRICKLE_CHG_EN, LOW_POWER_BOOT_ACK, BAT_TH_CHECK_DIS, BATTFET_CTL_EN, BATTFET_CTL, REV_MOD_EN, PLIM_DIS, CHG_LED_EN, RESTART_CHG_STAT, AUTO_CHG_DIS, CYCLING_DIS, VI_PROGRAM_EN }; static unsigned int max_voltage_design = 3800000; module_param(max_voltage_design, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(max_voltage_design, "Maximum battery voltage by design."); static unsigned int min_voltage_design = 3300000; module_param(min_voltage_design, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(min_voltage_design, "Minimum battery voltage by design."); static unsigned int main_charger_current = 0x8; /* 720 mA */ module_param(main_charger_current, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(main_charger_current, "Main charge path regulator current limit."); static int pmic_set_chg_current(unsigned short curr) { unsigned int mask; unsigned int value; value = BITFVAL(BIT_CHG_CURR, curr); mask = BITFMASK(BIT_CHG_CURR); CHECK_ERROR(pmic_write_reg(REG_CHARGE, value, mask)); return 0; } static int pmic_set_chg_misc(enum chg_setting type, unsigned short flag) { unsigned int reg_value = 0; unsigned int mask = 0; switch (type) { case TRICKLE_CHG_EN: reg_value = BITFVAL(TRICKLE_CHG_EN, flag); mask = BITFMASK(TRICKLE_CHG_EN); break; case LOW_POWER_BOOT_ACK: reg_value = BITFVAL(LOW_POWER_BOOT_ACK, flag); mask = BITFMASK(LOW_POWER_BOOT_ACK); break; case BAT_TH_CHECK_DIS: reg_value = BITFVAL(BAT_TH_CHECK_DIS, flag); mask = BITFMASK(BAT_TH_CHECK_DIS); break; case BATTFET_CTL_EN: reg_value = BITFVAL(BATTFET_CTL_EN, flag); mask = BITFMASK(BATTFET_CTL_EN); break; case BATTFET_CTL: reg_value = BITFVAL(BATTFET_CTL, flag); mask = BITFMASK(BATTFET_CTL); break; case REV_MOD_EN: reg_value = BITFVAL(REV_MOD_EN, flag); mask = BITFMASK(REV_MOD_EN); break; case PLIM_DIS: reg_value = BITFVAL(PLIM_DIS, flag); mask = BITFMASK(PLIM_DIS); break; case CHG_LED_EN: reg_value = BITFVAL(CHG_LED_EN, flag); mask = BITFMASK(CHG_LED_EN); break; case RESTART_CHG_STAT: reg_value = BITFVAL(RESTART_CHG_STAT, flag); mask = BITFMASK(RESTART_CHG_STAT); break; case AUTO_CHG_DIS: reg_value = BITFVAL(AUTO_CHG_DIS, flag); mask = BITFMASK(AUTO_CHG_DIS); break; case CYCLING_DIS: reg_value = BITFVAL(CYCLING_DIS, flag); mask = BITFMASK(CYCLING_DIS); break; case VI_PROGRAM_EN: reg_value = BITFVAL(VI_PROGRAM_EN, flag); mask = BITFMASK(VI_PROGRAM_EN); break; default: return PMIC_PARAMETER_ERROR; } CHECK_ERROR(pmic_write_reg(REG_CHARGE, reg_value, mask)); return 0; } static void pmic_stop_charging(void) { pmic_set_chg_misc(AUTO_CHG_DIS, 0); pmic_set_chg_current(0); } static int pmic_restart_charging(void) { pmic_set_chg_misc(BAT_TH_CHECK_DIS, 1); pmic_set_chg_misc(AUTO_CHG_DIS, 0); pmic_set_chg_misc(VI_PROGRAM_EN, 1); pmic_set_chg_current(main_charger_current); pmic_set_chg_misc(RESTART_CHG_STAT, 1); return 0; } static int pmic_get_batt_voltage(unsigned short *voltage) { t_channel channel; unsigned short result[8]; pmic_stop_charging(); channel = BATTERY_VOLTAGE; CHECK_ERROR(pmic_adc_convert(channel, result)); *voltage = result[0]; pmic_restart_charging(); return 0; } static int pmic_get_batt_current(unsigned short *curr) { t_channel channel; unsigned short result[8]; pmic_stop_charging(); channel = BATTERY_CURRENT; CHECK_ERROR(pmic_adc_convert(channel, result)); *curr = result[0]; pmic_restart_charging(); return 0; } static int coulomb_counter_calibration; static unsigned int coulomb_counter_start_time_msecs; static int pmic_start_coulomb_counter(void) { /* set scaler */ CHECK_ERROR(pmic_write_reg(REG_ACC1, ACC_COULOMB_PER_LSB * ACC_ONEC_VALUE, BITFMASK(ACC1_ONEC))); CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_START_COUNTER, ACC_CONTROL_BIT_MASK)); coulomb_counter_start_time_msecs = jiffies_to_msecs(jiffies); pr_debug("coulomb counter start time %u\n", coulomb_counter_start_time_msecs); return 0; } static int pmic_stop_coulomb_counter(void) { CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_STOP_COUNTER, ACC_CONTROL_BIT_MASK)); return 0; } static int pmic_calibrate_coulomb_counter(void) { int ret; unsigned int value; /* set scaler */ CHECK_ERROR(pmic_write_reg(REG_ACC1, 0x1, BITFMASK(ACC1_ONEC))); CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_CALIBRATION, ACC_CONTROL_BIT_MASK)); msleep(ACC_CALIBRATION_DURATION_MSECS); ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); if (ret != 0) return -1; value = BITFEXT(value, ACC_CCOUT); pr_debug("calibrate value = %x\n", value); coulomb_counter_calibration = (int)((s16)((u16) value)); pr_debug("coulomb_counter_calibration = %d\n", coulomb_counter_calibration); return 0; } static int pmic_get_charger_coulomb(int *coulomb) { int ret; unsigned int value; int calibration; unsigned int time_diff_msec; ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); if (ret != 0) return -1; value = BITFEXT(value, ACC_CCOUT); pr_debug("counter value = %x\n", value); *coulomb = ((s16)((u16)value)) * ACC_COULOMB_PER_LSB; if (abs(*coulomb) >= ACC_COULOMB_PER_LSB) { /* calibrate */ time_diff_msec = jiffies_to_msecs(jiffies); time_diff_msec = (time_diff_msec > coulomb_counter_start_time_msecs) ? (time_diff_msec - coulomb_counter_start_time_msecs) : (0xffffffff - coulomb_counter_start_time_msecs + time_diff_msec); calibration = coulomb_counter_calibration * (int)time_diff_msec / (ACC_ONEC_VALUE * ACC_CALIBRATION_DURATION_MSECS); *coulomb -= calibration; } return 0; } struct mc13892_dev_info { struct device *dev; unsigned short voltage_raw; int voltage_uV; unsigned short current_raw; int current_uA; int battery_status; int full_counter; int charger_online; int charger_voltage_uV; int accum_current_uAh; struct power_supply bat; struct power_supply charger; struct workqueue_struct *monitor_wqueue; struct delayed_work monitor_work; }; #define mc13892_SENSER 25 #define to_mc13892_dev_info(x) container_of((x), struct mc13892_dev_info, \ bat); static enum power_supply_property mc13892_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_STATUS, }; static enum power_supply_property mc13892_charger_props[] = { POWER_SUPPLY_PROP_ONLINE, }; static int mc13892_charger_update_status(struct mc13892_dev_info *di) { int ret; unsigned int value; int online; ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS)); if (ret == 0) { online = BITFEXT(value, BIT_CHG_DETS); if (online != di->charger_online) { di->charger_online = online; dev_info(di->charger.dev, "charger status: %s\n", online ? "online" : "offline"); power_supply_changed(&di->charger); cancel_delayed_work(&di->monitor_work); queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 10); if (online) { pmic_start_coulomb_counter(); pmic_restart_charging(); } else pmic_stop_coulomb_counter(); } } return ret; } static int mc13892_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct mc13892_dev_info *di = container_of((psy), struct mc13892_dev_info, charger); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = di->charger_online; return 0; default: break; } return -EINVAL; } static int mc13892_battery_read_status(struct mc13892_dev_info *di) { int retval; int coulomb; retval = pmic_get_batt_voltage(&(di->voltage_raw)); if (retval == 0) di->voltage_uV = di->voltage_raw * BAT_VOLTAGE_UNIT_UV; retval = pmic_get_batt_current(&(di->current_raw)); if (retval == 0) { if (di->current_raw & 0x200) di->current_uA = (0x1FF - (di->current_raw & 0x1FF)) * BAT_CURRENT_UNIT_UA * (-1); else di->current_uA = (di->current_raw & 0x1FF) * BAT_CURRENT_UNIT_UA; } retval = pmic_get_charger_coulomb(&coulomb); if (retval == 0) di->accum_current_uAh = COULOMB_TO_UAH(coulomb); return retval; } static void mc13892_battery_update_status(struct mc13892_dev_info *di) { unsigned int value; int retval; int old_battery_status = di->battery_status; if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) di->full_counter = 0; if (di->charger_online) { retval = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_CURRS)); if (retval == 0) { value = BITFEXT(value, BIT_CHG_CURRS); if (value) di->battery_status = POWER_SUPPLY_STATUS_CHARGING; else di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; } if (di->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) di->full_counter++; else di->full_counter = 0; } else { di->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; di->full_counter = 0; } dev_dbg(di->bat.dev, "bat status: %d\n", di->battery_status); if (di->battery_status != old_battery_status) power_supply_changed(&di->bat); } static void mc13892_battery_work(struct work_struct *work) { struct mc13892_dev_info *di = container_of(work, struct mc13892_dev_info, monitor_work.work); const int interval = HZ * 60; dev_dbg(di->dev, "%s\n", __func__); mc13892_battery_update_status(di); queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); } static void charger_online_event_callback(void *para) { struct mc13892_dev_info *di = (struct mc13892_dev_info *) para; pr_info("\n\n DETECTED charger plug/unplug event\n"); mc13892_charger_update_status(di); } static int mc13892_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct mc13892_dev_info *di = to_mc13892_dev_info(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) { mc13892_charger_update_status(di); mc13892_battery_update_status(di); } val->intval = di->battery_status; return 0; default: break; } mc13892_battery_read_status(di); switch (psp) { case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = di->voltage_uV; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = di->current_uA; break; case POWER_SUPPLY_PROP_CHARGE_NOW: val->intval = di->accum_current_uAh; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = max_voltage_design; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = min_voltage_design; break; default: return -EINVAL; } return 0; } static int pmic_battery_remove(struct platform_device *pdev) { pmic_event_callback_t bat_event_callback; struct mc13892_dev_info *di = platform_get_drvdata(pdev); bat_event_callback.func = charger_online_event_callback; bat_event_callback.param = (void *) di; pmic_event_unsubscribe(EVENT_CHGDETI, bat_event_callback); cancel_rearming_delayed_workqueue(di->monitor_wqueue, &di->monitor_work); destroy_workqueue(di->monitor_wqueue); power_supply_unregister(&di->bat); power_supply_unregister(&di->charger); kfree(di); return 0; } static int pmic_battery_probe(struct platform_device *pdev) { int retval = 0; struct mc13892_dev_info *di; pmic_event_callback_t bat_event_callback; pmic_version_t pmic_version; /* Only apply battery driver for MC13892 V2.0 due to ENGR108085 */ pmic_version = pmic_get_version(); if (pmic_version.revision < 20) { pr_debug("Battery driver is only applied for MC13892 V2.0\n"); return -1; } if (machine_is_mx51_babbage() || machine_is_mx50_arm2()) { pr_debug("mc13892 charger is not used for this platform\n"); return -1; } di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) { retval = -ENOMEM; goto di_alloc_failed; } platform_set_drvdata(pdev, di); di->dev = &pdev->dev; di->bat.name = "mc13892_bat"; di->bat.type = POWER_SUPPLY_TYPE_BATTERY; di->bat.properties = mc13892_battery_props; di->bat.num_properties = ARRAY_SIZE(mc13892_battery_props); di->bat.get_property = mc13892_battery_get_property; di->bat.use_for_apm = 1; di->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; retval = power_supply_register(&pdev->dev, &di->bat); if (retval) { dev_err(di->dev, "failed to register battery\n"); goto batt_failed; } di->charger.name = "mc13892_charger"; di->charger.type = POWER_SUPPLY_TYPE_MAINS; di->charger.properties = mc13892_charger_props; di->charger.num_properties = ARRAY_SIZE(mc13892_charger_props); di->charger.get_property = mc13892_charger_get_property; retval = power_supply_register(&pdev->dev, &di->charger); if (retval) { dev_err(di->dev, "failed to register charger\n"); goto charger_failed; } INIT_DELAYED_WORK(&di->monitor_work, mc13892_battery_work); di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!di->monitor_wqueue) { retval = -ESRCH; goto workqueue_failed; } queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 10); bat_event_callback.func = charger_online_event_callback; bat_event_callback.param = (void *) di; pmic_event_subscribe(EVENT_CHGDETI, bat_event_callback); pmic_stop_coulomb_counter(); pmic_calibrate_coulomb_counter(); goto success; workqueue_failed: power_supply_unregister(&di->charger); charger_failed: power_supply_unregister(&di->bat); batt_failed: kfree(di); di_alloc_failed: success: dev_dbg(di->dev, "%s battery probed!\n", __func__); return retval; return 0; } static struct platform_driver pmic_battery_driver_ldm = { .driver = { .name = "pmic_battery", .bus = &platform_bus_type, }, .probe = pmic_battery_probe, .remove = pmic_battery_remove, }; static int __init pmic_battery_init(void) { pr_debug("PMIC Battery driver loading...\n"); return platform_driver_register(&pmic_battery_driver_ldm); } static void __exit pmic_battery_exit(void) { platform_driver_unregister(&pmic_battery_driver_ldm); pr_debug("PMIC Battery driver successfully unloaded\n"); } module_init(pmic_battery_init); module_exit(pmic_battery_exit); MODULE_DESCRIPTION("pmic_battery driver"); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_LICENSE("GPL");