358 lines
9.0 KiB
C
358 lines
9.0 KiB
C
|
/* 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 <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");
|