android_kernel_google_msm/drivers/power/pm8058_usb_fix.c
Abhijeet Dharmapurikar 842ab8e171 power: Add pm8058_usb_fix driver
Implement workaround for stuck VCHG.

Some boards do not use the charging feature of the pmic8058 chip. They
however need the software workaround for an issue where the VCHG remains
high even when the USB cable it removed.

Change-Id: Ic92ac0cd913ce88b86706ea013d17789f1acf791
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
2013-02-25 11:32:26 -08:00

357 lines
9 KiB
C

/* Copyright (c) 2010, Code Aurora Forum. 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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/mfd/pmic8058.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/workqueue.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <mach/msm_xo.h>
#include <mach/msm_hsusb.h>
/* 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");