blob: 0c41f16063100b9a32c215953fa2d34bc9795d03 [file] [log] [blame]
/*
* Copyright (C) 2014 Marvell International Ltd.
* Fenghang Yin <yinfh@marvell.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <power/pmic.h>
#include <power/88pm830.h>
#include <i2c.h>
#include <errno.h>
#include <power/power_chrg.h>
#include <power/battery.h>
#define BAT_PRIO_EN_THRESH (3450000)
static struct pmic *g_pmic;
static int pm830_charger_type(struct pmic *p)
{
u32 val;
int charger;
/* if probe failed, return cable none */
if (pmic_probe(p))
return NULL_CHARGER;
/*
* just check the charger present status and if it's present, it will be
* recognized as USB charger
*/
pmic_reg_read(p, PM830_REG_STATUS, &val);
if (val & PM830_CHG_PRESENT)
charger = get_usb_charger_type();
else
charger = NULL_CHARGER;
return charger;
}
static int pm830_charger_enable(struct pmic *p, int enable)
{
u32 data;
if (pmic_probe(p))
return -1;
/* start charging, but disable APC */
pmic_reg_read(p, PM830_REG_CHG_CTRL1, &data);
if (enable)
data |= 0x1;
else
data &= ~0x1;
pmic_reg_write(p, PM830_REG_CHG_CTRL1, data);
pmic_reg_read(p, PM830_REG_CHG_CTRL1, &data);
debug("88pm830 charger is %s\n",
((data & 0x1) ? "enabled" : "disable"));
return 0;
}
static inline int get_fastchg_cur(int fastchg_cur)
{
static int ret;
ret = (fastchg_cur - 500) / 100;
return (ret < 0) ? 0 : ret;
}
static inline int get_limit_cur(int current)
{
unsigned int ret = 0;
if (current > 2400)
current = 2400;
else if (current < 0)
current = 0;
if (current > 1600) {
current *= 2;
current /= 3;
ret = 1 << 7;
}
ret |= ((current / 90) - 1);
printf("%s: current limitation = 0x%x\n", __func__, ret);
return ret;
}
/*
* if it's dead battery case, do pre-charge for 2 seconds, and then stop charging
* else if +/- pin real short, stop charging and return error
* else it's good battery case, just return
*/
static int pm830_handle_dead_battery(struct pmic *p)
{
u32 val, vbat;
/* handle dead battery case */
pmic_reg_read(p, PM830_REG_CHG_FLAG, &val);
if (val & PM830_BAT_SHRT) {
/*
* battery short detect, we need to pre-charge for about 2 seconds firstly, if
* battery voltage raise up to 2.2V, it should be dead battery , otherwise the
* battery is real shorted.
*/
printf("battery short or not?\n");
printf("disable battery short detect and try to pre-charge\n");
pmic_reg_read(p, PM830_REG_CHG_CTRL2, &val);
val &= ~PM830_BAT_SHRT_EN;
pmic_reg_write(p, PM830_REG_CHG_CTRL2, val);
pm830_charger_enable(p, 1);
mdelay(10000);
pm830_charger_enable(p, 0);
pmic_reg_read(p, PM830_REG_VBAT_VAL1, &val);
vbat = val << 4;
pmic_reg_read(p, PM830_REG_VBAT_VAL2, &val);
vbat |= (val & 0xf);
/* if battery voltage is above 2.2V, it's dead battery case, enable battery short
* detect and start charging
* otherwise +/- pin is read short, stop charging
*/
if (vbat >= PM830_VBAT_2V2) {
/* dead battery case */
printf("dead battery case, enable short detect and start charging\n");
pmic_reg_read(p, PM830_REG_CHG_CTRL2, &val);
val |= PM830_BAT_SHRT_EN;
pmic_reg_write(p, PM830_REG_CHG_CTRL2, val);
} else {
printf("real shorted battery case, stop charging"
"vbat: 0x%x\n", vbat);
return -ENODEV;
}
} else
printf("battery not short, start charging\n");
return 0;
}
void charger_enable_bat_priority(void)
{
u32 val;
struct pmic *p = g_pmic;
if (pmic_probe(p)) {
printf("can't access charge pmic\n");
return;
}
pmic_reg_read(p, PM830_REG_PMIC_ID, &val);
/* BAT PRIO_EN should be enabled only on B2 chip */
if (val != PM830_B2_VERSION)
return;
pmic_reg_read(p, PM830_REG_CHG_CTRL3, &val);
/* enable it if not enabled yet */
if (!(val & PM830_BAT_PRTY_EN)) {
printf("enable bat prio\n");
val |= PM830_BAT_PRTY_EN;
pmic_reg_write(p, PM830_REG_CHG_CTRL3, val);
}
}
static int pm830_charger_state(struct pmic *p, int state, int current)
{
u32 val;
int ret;
if (pmic_probe(p))
return -EINVAL;
if (state == CHARGER_DISABLE) {
printf("Disable the charger.\n");
pm830_charger_enable(p, 0);
if (g_pmic->parent->pbat->bat->voltage_uV > BAT_PRIO_EN_THRESH)
charger_enable_bat_priority();
return 0;
}
/* if charging is ongoing, just return */
pmic_reg_read(p, PM830_REG_CHG_CTRL1, &val);
if (val & PM830_CHG_ENABLE) {
printf("Already start charging.\n");
return 0;
}
/* clear interrupt status and charger status */
pmic_reg_read(p, PM830_REG_INT1, &val);
pmic_reg_read(p, PM830_REG_INT2, &val);
pmic_reg_read(p, PM830_REG_INT3, &val);
pmic_reg_write(p, PM830_REG_CHG_STATUS1, 0xff);
pmic_reg_read(p, PM830_REG_CHG_STATUS2, &val);
val |= 0x3;
pmic_reg_write(p, PM830_REG_CHG_STATUS2, val);
/* do pre-charge if it's dead battery case */
ret = pm830_handle_dead_battery(p);
if (ret < 0) {
printf("+/- pin short, can't start charging\n");
return ret;
}
/* set fast charge current */
pmic_reg_read(p, PM830_REG_CHG_CTRL5, &val);
val &= ~PM830_ICHG_FAST_MASK;
val |= get_fastchg_cur(current);
pmic_reg_write(p, PM830_REG_CHG_CTRL5, val);
/* set chg limit current */
val = get_limit_cur(current) | 0x40;
pmic_reg_write(p, PM830_REG_CHG_CTRL8, val);
/* enable charging */
pm830_charger_enable(p, 1);
return 0;
}
static int pm830_charger_bat_present(struct pmic *p)
{
int ret, voltage;
/* static bool charger_bat_init; */
u32 buf[2];
unsigned int data, mask;
if (pmic_probe(p))
return -1;
/* set bias of 1uA for battery detection */
data = BIAS_GP0_SET(1);
mask = BIAS_GP0_MASK;
pmic_update_bits(p, PM830_REG_BIAS, mask, data);
/* enable GPADC0 bias */
mask = PM830_GPADC0_BIAS_EN | PM830_GPADC0_GP_BIAS_OUT;
pmic_update_bits(p, PM830_REG_BIAS_ENA, mask, mask);
/* enable GPADC measurement */
data = PM830_GPADC0_MEAS_EN;
mask = PM830_GPADC0_MEAS_EN;
pmic_update_bits(p, PM830_REG_MEAS_EN, mask, data);
/* wait for voltage to be stable */
udelay(20000);
/* read GPADC0 voltage */
pmic_reg_read(p, PM830_REG_MEAS1, &buf[0]);
pmic_reg_read(p, PM830_REG_MEAS2, &buf[1]);
data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f);
voltage = (data * 175) >> 9;
/* if (voltage < 700) it's battery mode */
ret = (voltage < 700) ? 1 : 0;
return ret;
}
int charger_get_vbus(unsigned int *level)
{
unsigned int val;
if (pmic_probe(g_pmic)) {
printf("%s: charger power seems lost\n", __func__);
*level = 0;
return -ENODEV;
}
pmic_reg_read(g_pmic, PM830_REG_STATUS, &val);
if (val & PM830_CHG_PRESENT) {
*level = 1;
debug("%s: USB cable is valid!\n", __func__);
} else {
*level = 0;
printf("%s: USB cable not valid!\n", __func__);
}
return 0;
}
static struct power_chrg power_chrg_ops = {
.chrg_type = pm830_charger_type,
.chrg_bat_present = pm830_charger_bat_present,
.chrg_state = pm830_charger_state,
};
int power_chrg_init(unsigned char bus)
{
static const char name[] = MARVELL_PMIC_CHARGE;
struct pmic *p = pmic_alloc();
if (!p) {
printf("%s: POWER allocation error!\n", __func__);
return -ENOMEM;
}
debug("Board PMIC init\n");
p->name = name;
p->interface = PMIC_I2C;
p->number_of_regs = PM830_REG_NUM_OF_REGS;
p->hw.i2c.addr = PM830_I2C_ADDR;
p->hw.i2c.tx_num = 1;
p->bus = bus;
p->chrg = &power_chrg_ops;
g_pmic = p;
/* ret = pmic_reg_read(p, 0x0, &buff); */
if (pmic_probe(p)) {
printf("%s: can't find 88pm830 charger!\n", __func__);
return -ENODEV;
}
return 0;
}