331 lines
7.6 KiB
C
331 lines
7.6 KiB
C
|
/*
|
||
|
* (C) Copyright 2022 Rockchip Electronics Co., Ltd
|
||
|
*
|
||
|
* SPDX-License-Identifier: GPL-2.0+
|
||
|
*/
|
||
|
|
||
|
#include <common.h>
|
||
|
#include <dm.h>
|
||
|
#include <dm/device.h>
|
||
|
#include <dm/uclass.h>
|
||
|
#include <power/fuel_gauge.h>
|
||
|
#include <power/pmic.h>
|
||
|
#include <power/power_delivery/power_delivery.h>
|
||
|
#include <linux/usb/phy-rockchip-usb2.h>
|
||
|
|
||
|
DECLARE_GLOBAL_DATA_PTR;
|
||
|
|
||
|
#define BQ25890_CHARGE_CURRENT_1500MA 1500
|
||
|
#define BQ25890_SDP_INPUT_CURRENT_500MA 0x45
|
||
|
#define BQ25890_DCP_INPUT_CURRENT_1500MA 0x4f
|
||
|
#define BQ25890_DCP_INPUT_CURRENT_2000MA 0x54
|
||
|
#define BQ25890_DCP_INPUT_CURRENT_3000MA 0x5e
|
||
|
|
||
|
#define WATCHDOG_ENSABLE (0x03 << 4)
|
||
|
|
||
|
#define BQ25890_CHARGEOPTION0_REG 0x07
|
||
|
#define BQ25890_CHARGECURREN_REG 0x04
|
||
|
#define BQ25890_CHARGERSTAUS_REG 0x0B
|
||
|
#define BQ25890_INPUTVOLTAGE_REG 0x0D
|
||
|
#define BQ25890_INPUTCURREN_REG 0x00
|
||
|
#define BQ25890_AUTO_DPDM_REG 0x02
|
||
|
|
||
|
/*
|
||
|
* Most of the val -> idx conversions can be computed, given the minimum,
|
||
|
* maximum and the step between values. For the rest of conversions, we use
|
||
|
* lookup tables.
|
||
|
*/
|
||
|
enum bq25890_table_ids {
|
||
|
/* range tables */
|
||
|
TBL_ICHG,
|
||
|
TBL_ITERM,
|
||
|
TBL_IPRECHG,
|
||
|
TBL_VREG,
|
||
|
TBL_BATCMP,
|
||
|
TBL_VCLAMP,
|
||
|
TBL_BOOSTV,
|
||
|
TBL_SYSVMIN,
|
||
|
TBL_VINDPM,
|
||
|
TBL_IINLIM,
|
||
|
|
||
|
/* lookup tables */
|
||
|
TBL_TREG,
|
||
|
TBL_BOOSTI,
|
||
|
};
|
||
|
|
||
|
struct bq25890 {
|
||
|
struct udevice *dev;
|
||
|
u32 ichg;
|
||
|
u32 chip_id;
|
||
|
struct udevice *pd;
|
||
|
bool pd_online;
|
||
|
};
|
||
|
|
||
|
/* Thermal Regulation Threshold lookup table, in degrees Celsius */
|
||
|
static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 };
|
||
|
|
||
|
#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl)
|
||
|
|
||
|
/* Boost mode current limit lookup table, in uA */
|
||
|
static const u32 bq25890_boosti_tbl[] = {
|
||
|
500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000
|
||
|
};
|
||
|
|
||
|
#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl)
|
||
|
|
||
|
struct bq25890_range {
|
||
|
u32 min;
|
||
|
u32 max;
|
||
|
u32 step;
|
||
|
};
|
||
|
|
||
|
struct bq25890_lookup {
|
||
|
const u32 *tbl;
|
||
|
u32 size;
|
||
|
};
|
||
|
|
||
|
static const union {
|
||
|
struct bq25890_range rt;
|
||
|
struct bq25890_lookup lt;
|
||
|
} bq25890_tables[] = {
|
||
|
/* range tables */
|
||
|
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
|
||
|
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
|
||
|
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
|
||
|
[TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */
|
||
|
[TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */
|
||
|
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
|
||
|
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
|
||
|
[TBL_VINDPM] = { .rt = {2600000, 15300000, 100000} }, /* uV */
|
||
|
[TBL_IINLIM] = { .rt = {100, 3250000, 100000} }, /* uA */
|
||
|
|
||
|
/* lookup tables */
|
||
|
[TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
|
||
|
[TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} }
|
||
|
};
|
||
|
|
||
|
static int bq25890_read(struct bq25890 *charger, uint reg)
|
||
|
{
|
||
|
u16 val;
|
||
|
int ret;
|
||
|
|
||
|
ret = dm_i2c_read(charger->dev, reg, (u8 *)&val, 1);
|
||
|
if (ret) {
|
||
|
printf("bq25890: read %#x error, ret=%d", reg, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static int bq25890_write(struct bq25890 *charger, uint reg, u16 val)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = dm_i2c_write(charger->dev, reg, (u8 *)&val, 1);
|
||
|
if (ret) {
|
||
|
printf("bq25890: write %#x error, ret=%d", reg, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id)
|
||
|
{
|
||
|
u8 idx;
|
||
|
|
||
|
if (id >= TBL_TREG) {
|
||
|
const u32 *tbl = bq25890_tables[id].lt.tbl;
|
||
|
u32 tbl_size = bq25890_tables[id].lt.size;
|
||
|
|
||
|
for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
|
||
|
;
|
||
|
} else {
|
||
|
const struct bq25890_range *rtbl = &bq25890_tables[id].rt;
|
||
|
u8 rtbl_size;
|
||
|
|
||
|
rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
|
||
|
|
||
|
for (idx = 1;
|
||
|
idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
|
||
|
idx++)
|
||
|
;
|
||
|
}
|
||
|
|
||
|
return idx - 1;
|
||
|
}
|
||
|
|
||
|
static bool bq25890_charger_status(struct bq25890 *charger)
|
||
|
{
|
||
|
int state_of_charger;
|
||
|
u16 value;
|
||
|
int i = 0;
|
||
|
|
||
|
__retry:
|
||
|
value = bq25890_read(charger, BQ25890_CHARGERSTAUS_REG);
|
||
|
state_of_charger = value & 0x04;
|
||
|
if (!state_of_charger && charger->pd_online) {
|
||
|
if (i < 3) {
|
||
|
i++;
|
||
|
mdelay(20);
|
||
|
goto __retry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return state_of_charger;
|
||
|
}
|
||
|
|
||
|
static bool bq257xx_charger_status(struct udevice *dev)
|
||
|
{
|
||
|
struct bq25890 *charger = dev_get_priv(dev);
|
||
|
|
||
|
return bq25890_charger_status(charger);
|
||
|
}
|
||
|
|
||
|
static int bq25890_charger_capability(struct udevice *dev)
|
||
|
{
|
||
|
return FG_CAP_CHARGER;
|
||
|
}
|
||
|
|
||
|
static int bq25890_get_usb_type(void)
|
||
|
{
|
||
|
#ifdef CONFIG_PHY_ROCKCHIP_INNO_USB2
|
||
|
return rockchip_chg_get_type();
|
||
|
#else
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static int bq25890_get_pd_output_val(struct bq25890 *charger,
|
||
|
int *vol, int *cur)
|
||
|
{
|
||
|
struct power_delivery_data pd_data;
|
||
|
int ret;
|
||
|
|
||
|
if (!charger->pd)
|
||
|
return -EINVAL;
|
||
|
|
||
|
memset(&pd_data, 0, sizeof(pd_data));
|
||
|
ret = power_delivery_get_data(charger->pd, &pd_data);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
if (!pd_data.online || !pd_data.voltage || !pd_data.current)
|
||
|
return -EINVAL;
|
||
|
|
||
|
*vol = pd_data.voltage;
|
||
|
*cur = pd_data.current;
|
||
|
charger->pd_online = pd_data.online;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void bq25890_set_auto_dpdm_detect(struct bq25890 *charger, bool enable)
|
||
|
{
|
||
|
u8 value;
|
||
|
|
||
|
value = bq25890_read(charger, BQ25890_AUTO_DPDM_REG);
|
||
|
value &= 0xfe;
|
||
|
value |= enable;
|
||
|
bq25890_write(charger, BQ25890_AUTO_DPDM_REG, value);
|
||
|
}
|
||
|
|
||
|
static void bq25890_charger_current_init(struct bq25890 *charger)
|
||
|
{
|
||
|
u8 charge_current = bq25890_find_idx(BQ25890_CHARGE_CURRENT_1500MA * 1000, TBL_ICHG);
|
||
|
u8 sdp_inputcurrent = BQ25890_SDP_INPUT_CURRENT_500MA;
|
||
|
u8 dcp_inputcurrent = BQ25890_DCP_INPUT_CURRENT_1500MA;
|
||
|
int pd_inputvol, pd_inputcurrent;
|
||
|
u16 vol_idx = 0, cur_idx;
|
||
|
u8 temp;
|
||
|
|
||
|
temp = bq25890_read(charger, BQ25890_CHARGEOPTION0_REG);
|
||
|
temp &= (~WATCHDOG_ENSABLE);
|
||
|
bq25890_write(charger, BQ25890_CHARGEOPTION0_REG, temp);
|
||
|
|
||
|
if (!bq25890_get_pd_output_val(charger, &pd_inputvol,
|
||
|
&pd_inputcurrent)) {
|
||
|
printf("bq25890: pd charge %duV, %duA\n", pd_inputvol, pd_inputcurrent);
|
||
|
if (pd_inputvol > 5000000) {
|
||
|
vol_idx = bq25890_find_idx((pd_inputvol - 1280000 - 3200000),
|
||
|
TBL_VINDPM);
|
||
|
vol_idx = vol_idx << 6;
|
||
|
}
|
||
|
cur_idx = bq25890_find_idx(pd_inputcurrent,
|
||
|
TBL_IINLIM);
|
||
|
cur_idx = cur_idx << 8;
|
||
|
if (pd_inputcurrent != 0) {
|
||
|
bq25890_set_auto_dpdm_detect(charger, false);
|
||
|
bq25890_write(charger, BQ25890_INPUTCURREN_REG,
|
||
|
cur_idx);
|
||
|
if (vol_idx)
|
||
|
bq25890_write(charger, BQ25890_INPUTVOLTAGE_REG,
|
||
|
vol_idx);
|
||
|
charge_current = bq25890_find_idx(charger->ichg,
|
||
|
TBL_ICHG);
|
||
|
}
|
||
|
} else {
|
||
|
if (bq25890_get_usb_type() > 1)
|
||
|
bq25890_write(charger, BQ25890_INPUTCURREN_REG,
|
||
|
dcp_inputcurrent);
|
||
|
else
|
||
|
bq25890_write(charger, BQ25890_INPUTCURREN_REG,
|
||
|
sdp_inputcurrent);
|
||
|
}
|
||
|
|
||
|
if (bq25890_charger_status(charger))
|
||
|
bq25890_write(charger, BQ25890_CHARGECURREN_REG,
|
||
|
charge_current);
|
||
|
}
|
||
|
|
||
|
static int bq25890_ofdata_to_platdata(struct udevice *dev)
|
||
|
{
|
||
|
struct bq25890 *charger = dev_get_priv(dev);
|
||
|
|
||
|
charger->dev = dev;
|
||
|
charger->ichg = dev_read_u32_default(dev, "ti,charge-current", 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int bq25890_probe(struct udevice *dev)
|
||
|
{
|
||
|
struct bq25890 *charger = dev_get_priv(dev);
|
||
|
int ret;
|
||
|
|
||
|
ret = uclass_get_device(UCLASS_PD, 0, &charger->pd);
|
||
|
if (ret) {
|
||
|
if (ret == -ENODEV)
|
||
|
printf("Can't find PD\n");
|
||
|
else
|
||
|
printf("Get UCLASS PD failed: %d\n", ret);
|
||
|
|
||
|
charger->pd = NULL;
|
||
|
}
|
||
|
|
||
|
bq25890_charger_current_init(charger);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct udevice_id charger_ids[] = {
|
||
|
{ .compatible = "ti,bq25890" },
|
||
|
{ .compatible = "sy,sy6970" },
|
||
|
{ },
|
||
|
};
|
||
|
|
||
|
static struct dm_fuel_gauge_ops charger_ops = {
|
||
|
.get_chrg_online = bq257xx_charger_status,
|
||
|
.capability = bq25890_charger_capability,
|
||
|
};
|
||
|
|
||
|
U_BOOT_DRIVER(bq25890_charger) = {
|
||
|
.name = "bq25890_charger",
|
||
|
.id = UCLASS_FG,
|
||
|
.probe = bq25890_probe,
|
||
|
.of_match = charger_ids,
|
||
|
.ops = &charger_ops,
|
||
|
.ofdata_to_platdata = bq25890_ofdata_to_platdata,
|
||
|
.priv_auto_alloc_size = sizeof(struct bq25890),
|
||
|
};
|