| /* |
| * 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; |
| } |