/* Copyright (c) 2010, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include /* Config Regs and their bits*/ #define PM8058_CHG_TEST 0x75 #define IGNORE_LL 2 #define PM8058_CHG_TEST_2 0xEA #define PM8058_CHG_TEST_3 0xEB #define PM8058_OVP_TEST_REG 0xF6 #define FORCE_OVP_OFF 3 #define PM8058_CHG_CNTRL 0x1E #define CHG_TRICKLE_EN 7 #define CHG_USB_SUSPEND 6 #define CHG_IMON_CAL 5 #define CHG_IMON_GAIN 4 #define CHG_VBUS_FROM_BOOST_OVRD 2 #define CHG_CHARGE_DIS 1 #define CHG_VCP_EN 0 #define PM8058_CHG_CNTRL_2 0xD8 #define ATC_DIS 7 /* coincell backed */ #define CHARGE_AUTO_DIS 6 #define DUMB_CHG_OVRD 5 /* coincell backed */ #define ENUM_DONE 4 #define CHG_TEMP_MODE 3 #define CHG_BATT_TEMP_DIS 1 /* coincell backed */ #define CHG_FAILED_CLEAR 0 #define PM8058_CHG_VMAX_SEL 0x21 #define PM8058_CHG_VBAT_DET 0xD9 #define PM8058_CHG_IMAX 0x1F #define PM8058_CHG_TRICKLE 0xDB #define PM8058_CHG_ITERM 0xDC #define PM8058_CHG_TTRKL_MAX 0xE1 #define PM8058_CHG_TCHG_MAX 0xE4 #define PM8058_CHG_TEMP_THRESH 0xE2 #define PM8058_CHG_TEMP_REG 0xE3 #define PM8058_CHG_PULSE 0x22 /* IRQ STATUS and CLEAR */ #define PM8058_CHG_STATUS_CLEAR_IRQ_1 0x31 #define PM8058_CHG_STATUS_CLEAR_IRQ_3 0x33 #define PM8058_CHG_STATUS_CLEAR_IRQ_10 0xB3 #define PM8058_CHG_STATUS_CLEAR_IRQ_11 0xB4 /* IRQ MASKS */ #define PM8058_CHG_MASK_IRQ_1 0x38 #define PM8058_CHG_MASK_IRQ_3 0x3A #define PM8058_CHG_MASK_IRQ_10 0xBA #define PM8058_CHG_MASK_IRQ_11 0xBB /* IRQ Real time status regs */ #define PM8058_CHG_STATUS_RT_1 0x3F #define STATUS_RTCHGVAL 7 #define STATUS_RTCHGINVAL 6 #define STATUS_RTBATT_REPLACE 5 #define STATUS_RTVBATDET_LOW 4 #define STATUS_RTCHGILIM 3 #define STATUS_RTPCTDONE 1 #define STATUS_RTVCP 0 #define PM8058_CHG_STATUS_RT_3 0x41 #define PM8058_CHG_STATUS_RT_10 0xC1 #define PM8058_CHG_STATUS_RT_11 0xC2 /* VTRIM */ #define PM8058_CHG_VTRIM 0x1D #define PM8058_CHG_VBATDET_TRIM 0x1E #define PM8058_CHG_ITRIM 0x1F #define PM8058_CHG_TTRIM 0x20 #define AUTO_CHARGING_VMAXSEL 4200 #define AUTO_CHARGING_FAST_TIME_MAX_MINUTES 512 #define AUTO_CHARGING_TRICKLE_TIME_MINUTES 30 #define AUTO_CHARGING_VEOC_ITERM 100 #define AUTO_CHARGING_IEOC_ITERM 160 #define AUTO_CHARGING_VBATDET 4150 #define AUTO_CHARGING_VEOC_VBATDET 4100 #define AUTO_CHARGING_VEOC_TCHG 16 #define AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE 32 #define AUTO_CHARGING_VEOC_BEGIN_TIME_MS 5400000 #define AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS 60000 #define AUTO_CHARGING_RESUME_CHARGE_DETECTION_COUNTER 5 #define PM8058_CHG_I_STEP_MA 50 #define PM8058_CHG_I_MIN_MA 50 #define PM8058_CHG_T_TCHG_SHIFT 2 #define PM8058_CHG_I_TERM_STEP_MA 10 #define PM8058_CHG_V_STEP_MV 25 #define PM8058_CHG_V_MIN_MV 2400 /* * enum pmic_chg_interrupts: pmic interrupts * @CHGVAL_IRQ: charger V between 3.3 and 7.9 * @CHGINVAL_IRQ: charger V outside 3.3 and 7.9 * @VBATDET_LOW_IRQ: VBAT < VBATDET * @VCP_IRQ: VDD went below VBAT: BAT_FET is turned on * @CHGILIM_IRQ: mA consumed>IMAXSEL: chgloop draws less mA * @ATC_DONE_IRQ: Auto Trickle done * @ATCFAIL_IRQ: Auto Trickle fail * @AUTO_CHGDONE_IRQ: Auto chg done * @AUTO_CHGFAIL_IRQ: time exceeded w/o reaching term current * @CHGSTATE_IRQ: something happend causing a state change * @FASTCHG_IRQ: trkl charging completed: moving to fastchg * @CHG_END_IRQ: mA has dropped to termination current * @BATTTEMP_IRQ: batt temp is out of range * @CHGHOT_IRQ: the pass device is too hot * @CHGTLIMIT_IRQ: unused * @CHG_GONE_IRQ: charger was removed * @VCPMAJOR_IRQ: vcp major * @VBATDET_IRQ: VBAT >= VBATDET * @BATFET_IRQ: BATFET closed * @BATT_REPLACE_IRQ: * @BATTCONNECT_IRQ: */ enum pmic_chg_interrupts { CHGVAL_IRQ, CHGINVAL_IRQ, VBATDET_LOW_IRQ, VCP_IRQ, CHGILIM_IRQ, ATC_DONE_IRQ, ATCFAIL_IRQ, AUTO_CHGDONE_IRQ, AUTO_CHGFAIL_IRQ, CHGSTATE_IRQ, FASTCHG_IRQ, CHG_END_IRQ, BATTTEMP_IRQ, CHGHOT_IRQ, CHGTLIMIT_IRQ, CHG_GONE_IRQ, VCPMAJOR_IRQ, VBATDET_IRQ, BATFET_IRQ, BATT_REPLACE_IRQ, BATTCONNECT_IRQ, PMIC_CHG_MAX_INTS }; struct pm8058_charger { struct pmic_charger_pdata *pdata; struct pm8058_chip *pm_chip; struct device *dev; int pmic_chg_irq[PMIC_CHG_MAX_INTS]; DECLARE_BITMAP(enabled_irqs, PMIC_CHG_MAX_INTS); struct delayed_work check_vbat_low_work; struct delayed_work veoc_begin_work; int waiting_for_topoff; int waiting_for_veoc; int current_charger_current; struct msm_xo_voter *voter; struct dentry *dent; }; static struct pm8058_charger pm8058_chg; static int pm_chg_get_rt_status(int irq) { int count = 3; int ret; while ((ret = pm8058_irq_get_rt_status(pm8058_chg.pm_chip, irq)) == -EAGAIN && count--) { dev_info(pm8058_chg.dev, "%s trycount=%d\n", __func__, count); cpu_relax(); } if (ret == -EAGAIN) return 0; else return ret; } static int is_chg_plugged_in(void) { return pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]); } static irqreturn_t pm8058_chg_chgval_handler(int irq, void *dev_id) { u8 old, temp; int ret; if (!is_chg_plugged_in()) { /*this debounces it */ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, &old, 1); temp = old | BIT(FORCE_OVP_OFF); ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, &temp, 1); temp = 0xFC; ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); pr_debug("%s forced wrote 0xFC to test ret=%d\n", __func__, ret); /* 20 ms sleep is for the VCHG to discharge */ msleep(20); temp = 0xF0; ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, &old, 1); } return IRQ_HANDLED; } static void free_irqs(void) { int i; for (i = 0; i < PMIC_CHG_MAX_INTS; i++) if (pm8058_chg.pmic_chg_irq[i]) { free_irq(pm8058_chg.pmic_chg_irq[i], NULL); pm8058_chg.pmic_chg_irq[i] = 0; } } static int __devinit request_irqs(struct platform_device *pdev) { struct resource *res; int ret; ret = 0; bitmap_fill(pm8058_chg.enabled_irqs, PMIC_CHG_MAX_INTS); res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGVAL"); if (res == NULL) { dev_err(pm8058_chg.dev, "%s:couldnt find resource CHGVAL\n", __func__); goto err_out; } else { ret = request_any_context_irq(res->start, pm8058_chg_chgval_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, res->name, NULL); if (ret < 0) { dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", __func__, res->start, ret); goto err_out; } else { pm8058_chg.pmic_chg_irq[CHGVAL_IRQ] = res->start; } } return 0; err_out: free_irqs(); return -EINVAL; } static int pm8058_usb_voltage_lower_limit(void) { u8 temp, old; int ret = 0; temp = 0x10; ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); ret |= pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST, &old, 1); old = old & ~BIT(IGNORE_LL); temp = 0x90 | (0xF & old); pr_debug("%s writing 0x%x to test\n", __func__, temp); ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); return ret; } static int __devinit pm8058_charger_probe(struct platform_device *pdev) { struct pm8058_chip *pm_chip; pm_chip = dev_get_drvdata(pdev->dev.parent); if (pm_chip == NULL) { pr_err("%s:no parent data passed in.\n", __func__); return -EFAULT; } pm8058_chg.pm_chip = pm_chip; pm8058_chg.pdata = pdev->dev.platform_data; pm8058_chg.dev = &pdev->dev; if (request_irqs(pdev)) { pr_err("%s: couldnt register interrupts\n", __func__); return -EINVAL; } if (pm8058_usb_voltage_lower_limit()) { pr_err("%s: couldnt write to IGNORE_LL\n", __func__); return -EINVAL; } return 0; } static int __devexit pm8058_charger_remove(struct platform_device *pdev) { free_irqs(); return 0; } static struct platform_driver pm8058_charger_driver = { .probe = pm8058_charger_probe, .remove = __devexit_p(pm8058_charger_remove), .driver = { .name = "pm-usb-fix", .owner = THIS_MODULE, }, }; static int __init pm8058_charger_init(void) { return platform_driver_register(&pm8058_charger_driver); } static void __exit pm8058_charger_exit(void) { platform_driver_unregister(&pm8058_charger_driver); } late_initcall(pm8058_charger_init); module_exit(pm8058_charger_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("PMIC8058 BATTERY driver"); MODULE_VERSION("1.0"); MODULE_ALIAS("platform:pm8058_charger");