| /* |
| * Copyright (C) 2014 Marvell International Ltd. |
| * Xuhong Gao <gaoxhong@marvell.com> |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <power/pmic.h> |
| #include <power/battery.h> |
| #include <power/88pm801.h> |
| #ifdef CONFIG_BAT_PM802 |
| #include <power/pm802.h> |
| #endif |
| #ifdef CONFIG_CHRG_BQ2419X |
| #include <power/bq2419x.h> |
| #endif |
| #ifdef CONFIG_CHRG_ETA6005 |
| #include <power/eta6005.h> |
| #endif |
| #include <errno.h> |
| #include <power/power_chrg.h> |
| #include <linux/usb/mv-phy.h> |
| |
| static u32 onkey_status; |
| static struct battery pm801_bat; |
| __weak void oled_show_bat_charging(u16 percent) {} |
| __weak void lcd_show_bat_charging(int percent) {} |
| __weak void lcd_panel_on(int on_off) {} |
| |
| static struct pmic *g_pmic; |
| |
| 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, 0x01, &val); |
| debug("pm801 base_reg01: 0x%02x\n", val); |
| |
| if (val & (0x1<<1)) { |
| *level = 1; |
| printf("%s: USB cable is valid!\n", __func__); |
| } else { |
| *level = 0; |
| printf("%s: USB cable not valid!\n", __func__); |
| } |
| |
| return 0; |
| } |
| |
| static int pm801_battery_charge(struct pmic *bat) |
| { |
| struct power_battery *p_bat = bat->pbat; |
| struct battery *battery = p_bat->bat; |
| int i, k, current, chg_type, j, t, ret = CHG_STAT_NORMAL; |
| int mv_discharge, mv_charge = 0, mv_offset = 0; |
| static int chg_time = 0, chg_full_flag = 0; |
| u32 long_det_mask = (0x1 << LONG_ONKEY_BOOT) - 1; |
| static u32 backlight_on_time = BACKLIGHT_INIT_ON_SEC, backlight_off = 0; |
| |
| onkey_status = 0; |
| chg_type = bat->chrg->chrg_type(p_bat->chrg); |
| switch (chg_type) { |
| case NULL_CHARGER: |
| printf("%s: no charger, power off...\n", __func__); |
| ret = CHG_STAT_CHGR_LOST; |
| goto out; |
| break; |
| case DCP_CHARGER: |
| case CDP_CHARGER: |
| /* |
| * charger spec supports 1500mA, |
| * but need to set as battery requirement |
| */ |
| current = DCP_FAST_CHG_CURRENT; |
| break; |
| case SDP_CHARGER: |
| case NONE_STANDARD_CHARGER: |
| case DEFAULT_CHARGER: |
| current = SDP_FAST_CHG_CURRENT; |
| break; |
| default: |
| current = SDP_FAST_CHG_CURRENT; |
| } |
| |
| printf("chg_current: %dmA\n", current); |
| if (bat->pbat->bat->voltage_uV > BAT_DISCHARGE_MIN_UVOLT) { |
| bat->chrg->chrg_state(p_bat->chrg, CHARGER_DISABLE, current); |
| mdelay(50); |
| mv_discharge = bat->fg->fg_vbat_vol(p_bat->fg, bat); |
| if (bat->chrg->chrg_state(p_bat->chrg, CHARGER_ENABLE, current)) { |
| ret = CHG_STAT_ABNORMAL; |
| goto out; |
| } |
| mdelay(50); |
| mv_charge = bat->fg->fg_vbat_vol(p_bat->fg, bat); |
| mv_offset = mv_charge - mv_discharge; |
| printf("chgmv: %d, dischgmv: %d\n", mv_charge, mv_discharge); |
| if (mv_offset < 0) |
| mv_offset = 0; |
| if (mv_offset > 400) |
| mv_offset = 400; |
| } |
| /* loop until battery voltage raise up to 3.6V */ |
| for (k = 0;; k++) { |
| if (!bat->chrg->chrg_bat_present(p_bat->chrg)) { |
| ret = CHG_STAT_BAT_LOST; |
| goto out; |
| } |
| |
| if (!bat->chrg->chrg_type(p_bat->chrg)) { |
| ret = CHG_STAT_CHGR_LOST; |
| goto out; |
| } |
| |
| i = UDELAY_INTERVAL / ONKEY_CHECK_INTERVAL_USEC; |
| while (i--) { |
| if (marvell88pm_get_onkey()) { |
| if (!backlight_on_time) { |
| backlight_on_time = BACKLIGHT_ON_SEC; |
| if (backlight_off) { |
| lcd_panel_on(1); |
| backlight_off = 0; |
| printf("turn on backlight\n"); |
| } |
| } else { |
| backlight_on_time = BACKLIGHT_ON_SEC; |
| printf("push backlight off time\n"); |
| } |
| } |
| |
| udelay(ONKEY_CHECK_INTERVAL_USEC); |
| } |
| if (backlight_on_time) |
| backlight_on_time--; |
| if (!backlight_on_time) { |
| if (!backlight_off) { |
| lcd_panel_on(0); |
| backlight_off = 1; |
| printf("turn off backlight\n"); |
| } |
| } |
| |
| chg_time++; |
| printf("charge_time: %d\n", chg_time); |
| |
| onkey_status = onkey_status << 1; |
| onkey_status |= (marvell88pm_get_onkey() & 0x1); |
| if ((onkey_status & long_det_mask) == long_det_mask) { |
| printf("long onkey detected, boot...\n"); |
| onkey_status = 0; |
| ret = CHG_STAT_ONKEY_BOOT; |
| goto out; |
| } |
| |
| bat->fg->fg_battery_update(p_bat->fg, bat, mv_offset); |
| |
| if (p_bat->bat->state_of_chrg == 100) |
| chg_full_flag = 1; |
| |
| if (chg_full_flag) { |
| oled_show_bat_charging(100); |
| if (!backlight_off) |
| lcd_show_bat_charging(100); |
| } else { |
| oled_show_bat_charging(p_bat->bat->state_of_chrg); |
| if (!backlight_off) |
| lcd_show_bat_charging(p_bat->bat->state_of_chrg); |
| } |
| |
| if (k <= CHARGE_ONE_ROUND_SECS) { |
| printf(" %d [uV]\n", battery->voltage_uV); |
| puts("\n"); |
| } else |
| /* return to have chance to check voltage */ |
| goto out; |
| |
| if (ctrlc()) { |
| printf("Charging disabled on request.\n"); |
| goto exit; |
| } |
| } |
| exit: |
| bat->chrg->chrg_state(p_bat->chrg, CHARGER_DISABLE, 0); |
| out: |
| return ret; |
| } |
| |
| static int pm801_battery_init(struct pmic *bat_, |
| struct pmic *fg_, |
| struct pmic *chrg_, |
| struct pmic *muic_) |
| { |
| struct pmic *p = chrg_; |
| int ret; |
| u32 data, id; |
| |
| if (!p) { |
| printf("Charger invalid\n"); |
| return -1; |
| } |
| |
| bat_->pbat->fg = fg_; |
| bat_->pbat->chrg = chrg_; |
| bat_->pbat->muic = muic_; |
| |
| if (fg_) |
| bat_->fg = fg_->fg; |
| |
| if (chrg_) |
| bat_->chrg = chrg_->chrg; |
| |
| if (muic_) |
| bat_->chrg->chrg_type = muic_->chrg->chrg_type; |
| |
| #ifndef CONFIG_CHRG_ETA6005 |
| /* init PM801 */ |
| if (pmic_probe(p)) { |
| printf("Can't find 88pm801 battery\n"); |
| return -1; |
| } |
| #endif |
| |
| #ifdef CONFIG_CHRG_BQ2419X |
| /* disable hiz, input: 4.36v, 500mA */ |
| data = 0x32; |
| pmic_reg_write(p, BQ2419X_REG_00, data); |
| /* |
| * reg not reset, watchdog no reset, chg disable, |
| * sys: 3.3V, BOOST LIM: 1.3A |
| */ |
| data = 0x07; |
| pmic_reg_write(p, BQ2419X_REG_01, data); |
| /* fastchg: 512mA */ |
| data = 0x00; |
| pmic_reg_write(p, BQ2419X_REG_02, data); |
| /*prechg: 128mA, term: 128mA */ |
| data = 0x00; |
| pmic_reg_write(p, BQ2419X_REG_03, data); |
| /* Fast chg: 4.208v, prechg2fastchg: 3v */ |
| data = 0xB2; |
| pmic_reg_write(p, BQ2419X_REG_04, data); |
| /* Enable term, disable safety timer, disable watchdog, 8hrs, 50%jeita*/ |
| data = 0x82; |
| pmic_reg_write(p, BQ2419X_REG_05, data); |
| /* thermal regulation: 120C */ |
| data = 0x03; |
| pmic_reg_write(p, BQ2419X_REG_06, data); |
| /* Jeita 4.2v, int on fault, safety timer slowed by 2x */ |
| data = 0x4B; |
| pmic_reg_write(p, BQ2419X_REG_07, data); |
| #endif |
| |
| #ifdef CONFIG_BAT_PM803 |
| if (pmic_is_pm803()) { |
| /* do nothing now */ |
| } |
| #endif |
| |
| #ifdef CONFIG_BAT_PM801 |
| if (pmic_is_pm801() || pmic_is_pm812()) { |
| /* enable vinldo meas */ |
| pmic_reg_read(fg_, 0x1, &data); |
| data |= (1 << 2); |
| pmic_reg_write(fg_, 0x01, data); |
| |
| /* enable gpadc0/1 meas */ |
| pmic_reg_read(fg_, 0x2, &data); |
| data |= ((1 << 2) | (1 << 3)); |
| pmic_reg_write(fg_, 0x02, data); |
| |
| /* enable meas, none stop mode, duty cycle mode*/ |
| data = 0x33; |
| pmic_reg_write(fg_, 0x06, data); |
| } |
| #endif |
| return 0; |
| } |
| |
| static struct power_battery power_bat_ops = { |
| .bat = &pm801_bat, |
| .battery_init = pm801_battery_init, |
| .battery_charge = pm801_battery_charge, |
| }; |
| |
| int power_bat_init(unsigned char bus) |
| { |
| static const char name[] = MARVELL_PMIC_BATT; |
| struct pmic *p = pmic_alloc(); |
| |
| if (!p) { |
| printf("%s: POWER allocation error!\n", __func__); |
| return -ENOMEM; |
| } |
| |
| debug("Board BAT init\n"); |
| |
| p->name = name; |
| p->interface = PMIC_I2C; |
| p->number_of_regs = PM801_BAT_REG_NUM; |
| p->hw.i2c.addr = PM801_BAT_I2C_ADDR; |
| p->hw.i2c.tx_num = 1; |
| p->bus = bus; |
| |
| p->pbat = &power_bat_ops; |
| g_pmic = p; |
| |
| return 0; |
| } |