// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2022 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include "charger_class.h" #include "mtk_charger.h" static bool dbg_log_en; module_param(dbg_log_en, bool, 0644); #define mt_dbg(dev, fmt, ...) \ do { \ if (dbg_log_en) \ dev_info(dev, "%s " fmt, __func__, ##__VA_ARGS__); \ } while (0) #define RT9758_REG_DEVINFO 0x00 #define RT9758_REG_FLAG1 0x01 #define RT9758_REG_FLAG2 0x02 #define RT9758_REG_FLAG3 0x03 #define RT9758_REG_ICSTAT 0x04 #define RT9758_REG_STAT1 0x08 #define RT9758_REG_CTRL1 0x0A #define RT9758_REG_CTRL2 0x0B #define RT9758_REG_CTRL3 0x0C #define RT9758_REG_CTRL4 0x0D #define RT9758_REG_CTRL5 0x0E #define RT9758_REG_CTRL6 0x0F #define RT9758_REG_CTRL7 0x10 #define RT9758_REG_CTRL8 0x11 #define RT9758_REG_CTRL9 0x12 #define RT9758_REG_CTRL10 0x13 #define RT9758_DEVID_MASK GENMASK(3, 0) #define RT9758_WRXIN_MASK BIT(2) #define RT9758_DEVICE_ID 0x03 #define RT9758_RSTRG_VAL BIT(2) #define RT9758_RESET_WAITUS 1000 #define RT9758_Q0CTRL_OFF 0 #define RT9758_Q0CTRL_ON 2 enum { RT9758_SYNC_OFF = 0, RT9758_SYNC_MASTER, RT9758_SYNC_SLAVE = 3, RT9758_MAX_SYNC }; enum { RT9758_PRESENT_MODE = 0, RT9758_STANDBY_MODE, RT9758_FORWARD_DIV2_MODE, RT9758_FORWARD_BYPASS_MODE, RT9758_REVERSE_DIV2_MODE, RT9758_REVERSE_BYPASS_MODE, RT9758_CHARGE_FAULT, }; enum rt9758_fields { F_IC_STAT = 0, F_VLERR_STAT, F_WRXIN_STAT, F_SWITCH_STAT, F_VOUT_OVP, F_VBUS_OVP, F_IBUS_OCP, F_SYNC_MODE, F_Q0_CTRL, F_WDT, F_WDT_EN, F_CHG_EN, F_OP_MODE, F_ATEN, F_BA_WDT, F_TDIE_EN, F_MAX_FIELDS }; enum { RT9758_RANGE_VOUTOVP = 0, RT9758_RANGE_VBUSOVP, RT9758_RANGE_IBUSOCP, RT9758_MAX_RANGES }; enum rt9758_chg_dtprop_type { DTPROP_U32, DTPROP_BOOL, }; struct rt9758_priv { struct device *dev; struct regmap *regmap; struct regmap_field *rm_field[F_MAX_FIELDS]; struct gpio_desc *enable_gpio; struct power_supply *psy; struct power_supply_desc desc; struct charger_device *chg_dev; const char *chg_name; struct charger_properties chg_prop; bool charge_enabled; bool bypass_enabled; }; static struct reg_field rt9758_reg_fields[F_MAX_FIELDS] = { [F_IC_STAT] = REG_FIELD(RT9758_REG_ICSTAT, 0, 2), [F_VLERR_STAT] = REG_FIELD(RT9758_REG_STAT1, 3, 3), [F_WRXIN_STAT] = REG_FIELD(RT9758_REG_STAT1, 2, 2), [F_SWITCH_STAT] = REG_FIELD(RT9758_REG_STAT1, 1, 1), [F_VOUT_OVP] = REG_FIELD(RT9758_REG_CTRL1, 0, 2), [F_VBUS_OVP] = REG_FIELD(RT9758_REG_CTRL2, 0, 5), [F_IBUS_OCP] = REG_FIELD(RT9758_REG_CTRL3, 4, 7), [F_SYNC_MODE] = REG_FIELD(RT9758_REG_CTRL4, 2, 3), [F_Q0_CTRL] = REG_FIELD(RT9758_REG_CTRL4, 0, 1), [F_WDT] = REG_FIELD(RT9758_REG_CTRL5, 4, 6), [F_WDT_EN] = REG_FIELD(RT9758_REG_CTRL5, 3, 3), [F_CHG_EN] = REG_FIELD(RT9758_REG_CTRL6, 2, 2), [F_OP_MODE] = REG_FIELD(RT9758_REG_CTRL6, 0, 0), [F_ATEN] = REG_FIELD(RT9758_REG_CTRL7, 3, 3), [F_BA_WDT] = REG_FIELD(RT9758_REG_CTRL8, 0, 0), [F_TDIE_EN] = REG_FIELD(RT9758_REG_CTRL9, 5, 5) }; static const struct linear_range rt9758_ranges[RT9758_MAX_RANGES] = { [RT9758_RANGE_VOUTOVP] = { 7000000, 0, 7, 1000000 }, [RT9758_RANGE_VBUSOVP] = { 7250000, 0, 59, 250000 }, [RT9758_RANGE_IBUSOCP] = { 2000000, 2, 10, 500000 } }; static int rt9758_get_status(struct rt9758_priv *priv, union power_supply_propval *val) { unsigned int switching; int ret; ret = regmap_field_read(priv->rm_field[F_SWITCH_STAT], &switching); if (ret) return ret; if (switching) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; return 0; } static int rt9758_get_charge_type(struct rt9758_priv *priv, union power_supply_propval *val) { if (!priv->charge_enabled) val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else if (!priv->bypass_enabled) val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; else val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; return 0; } static int rt9758_get_online(struct rt9758_priv *priv, union power_supply_propval *val) { unsigned int wrxin_stat; int ret; ret = regmap_field_read(priv->rm_field[F_WRXIN_STAT], &wrxin_stat); if (ret) return ret; val->intval = wrxin_stat; return 0; } static int rt9758_get_const_charge_volt(struct rt9758_priv *priv, union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_VOUTOVP; unsigned int vout_ovp_sel, vout_ovp_val; int ret; ret = regmap_field_read(priv->rm_field[F_VOUT_OVP], &vout_ovp_sel); if (ret) return ret; ret = linear_range_get_value(range, vout_ovp_sel, &vout_ovp_val); if (ret) return ret; val->intval = vout_ovp_val; return 0; } static int rt9758_get_input_curr_lim(struct rt9758_priv *priv, union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_IBUSOCP; unsigned int ibus_ocp_sel, ibus_ocp_val; int ret; ret = regmap_field_read(priv->rm_field[F_IBUS_OCP], &ibus_ocp_sel); if (ret) return ret; ret = linear_range_get_value(range, ibus_ocp_sel, &ibus_ocp_val); if (ret) return ret; val->intval = ibus_ocp_val; return 0; } static int rt9758_get_input_volt_lim(struct rt9758_priv *priv, union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_VBUSOVP; unsigned int vbus_ovp_sel, vbus_ovp_val; int ret; ret = regmap_field_read(priv->rm_field[F_VBUS_OVP], &vbus_ovp_sel); if (ret) return ret; ret = linear_range_get_value(range, vbus_ovp_sel, &vbus_ovp_val); if (ret) return ret; val->intval = vbus_ovp_val; return 0; } static int rt9758_get_manufacturer(struct rt9758_priv *priv, union power_supply_propval *val) { val->strval = "Richtek Technology Corp."; return 0; } static int rt9758_set_charge_enable(struct rt9758_priv *priv, const union power_supply_propval *val) { unsigned int q0ctrl_val, chgen; int ret; chgen = !!val->intval; if (chgen) q0ctrl_val = RT9758_Q0CTRL_ON; else q0ctrl_val = RT9758_Q0CTRL_OFF; ret = regmap_field_write(priv->rm_field[F_Q0_CTRL], q0ctrl_val); if (ret) return ret; ret = regmap_field_write(priv->rm_field[F_CHG_EN], chgen); if (ret) return ret; ret = regmap_field_write(priv->rm_field[F_WDT_EN], chgen); if (ret) return ret; if (priv->enable_gpio) gpiod_set_value(priv->enable_gpio, !chgen); mdelay(1); priv->charge_enabled = chgen; return 0; } static int rt9758_set_bypass_enable(struct rt9758_priv *priv, const union power_supply_propval *val) { unsigned int bypass_enabled; int ret; bypass_enabled = !!val->intval; ret = regmap_field_write(priv->rm_field[F_OP_MODE], bypass_enabled); if (ret) return ret; priv->bypass_enabled = bypass_enabled; return regmap_field_write(priv->rm_field[F_WDT], 0); } static int rt9758_set_const_charge_volt(struct rt9758_priv *priv, const union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_VOUTOVP; unsigned int sel = 0; linear_range_get_selector_within(range, val->intval, &sel); return regmap_field_write(priv->rm_field[F_VOUT_OVP], sel); } static int rt9758_set_input_curr_lim(struct rt9758_priv *priv, const union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_IBUSOCP; unsigned int sel = 0; linear_range_get_selector_within(range, val->intval, &sel); return regmap_field_write(priv->rm_field[F_IBUS_OCP], sel); } static int rt9758_set_input_volt_lim(struct rt9758_priv *priv, const union power_supply_propval *val) { const struct linear_range *range = rt9758_ranges + RT9758_RANGE_VBUSOVP; unsigned int sel = 0; linear_range_get_selector_within(range, val->intval, &sel); return regmap_field_write(priv->rm_field[F_VBUS_OVP], sel); } static int rt9758_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rt9758_priv *priv = power_supply_get_drvdata(psy); if (!priv) return -ENODATA; switch (psp) { case POWER_SUPPLY_PROP_STATUS: return rt9758_get_status(priv, val); case POWER_SUPPLY_PROP_CHARGE_TYPE: return rt9758_get_charge_type(priv, val); case POWER_SUPPLY_PROP_ONLINE: return rt9758_get_online(priv, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: return rt9758_get_const_charge_volt(priv, val); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return rt9758_get_input_curr_lim(priv, val); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: return rt9758_get_input_volt_lim(priv, val); case POWER_SUPPLY_PROP_MANUFACTURER: return rt9758_get_manufacturer(priv, val); default: return -ENODATA; } } static int rt9758_charger_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct rt9758_priv *priv = power_supply_get_drvdata(psy); if (!priv) return -ENODATA; switch (psp) { case POWER_SUPPLY_PROP_STATUS: return rt9758_set_charge_enable(priv, val); case POWER_SUPPLY_PROP_CHARGE_TYPE: return rt9758_set_bypass_enable(priv, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: return rt9758_set_const_charge_volt(priv, val); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return rt9758_set_input_curr_lim(priv, val); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: return rt9758_set_input_volt_lim(priv, val); default: return -EINVAL; } } static const enum power_supply_property rt9758_charger_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, POWER_SUPPLY_PROP_MANUFACTURER }; static int rt9758_init_chg_properties(struct rt9758_priv *priv) { unsigned int sync_mode = RT9758_SYNC_OFF; int ret; ret = regmap_write(priv->regmap, RT9758_REG_CTRL9, RT9758_RSTRG_VAL); if (ret) return ret; usleep_range(RT9758_RESET_WAITUS, RT9758_RESET_WAITUS + 100); /* mediatek chgdev name */ ret = device_property_read_string(priv->dev, "chg_name", &priv->chg_name); if (ret) { dev_notice(priv->dev, "failed to get chg_name\n"); priv->chg_name = "hvdiv2_chg1"; } device_property_read_u32(priv->dev, "richtek,dv2-sync-mode", &sync_mode); if (sync_mode >= RT9758_MAX_SYNC) { dev_err(priv->dev, "Not valid mode [%d]\n", sync_mode); return -EINVAL; } ret = regmap_field_write(priv->rm_field[F_BA_WDT], 0); if (ret) return ret; ret = regmap_field_write(priv->rm_field[F_SYNC_MODE], sync_mode); if (ret) return ret; ret = regmap_field_write(priv->rm_field[F_Q0_CTRL], RT9758_Q0CTRL_OFF); if (ret) return ret; return regmap_field_write(priv->rm_field[F_CHG_EN], 0); } static int rt9758_check_device_info(struct rt9758_priv *priv) { unsigned int dev_info; int ret; ret = regmap_read(priv->regmap, RT9758_REG_DEVINFO, &dev_info); if (ret) return ret; if ((dev_info & RT9758_DEVID_MASK) != RT9758_DEVICE_ID) { dev_err(priv->dev, "Failed to match devid 0x%02x\n", dev_info); return -ENODEV; } dev_info(priv->dev, "devid = 0x%02X\n", dev_info); return 0; } static int rt9758_psy_set_prop(struct rt9758_priv *priv, enum power_supply_property psp, uint32_t data) { union power_supply_propval val; val.intval = data; return power_supply_set_property(priv->psy, psp, &val); } static int rt9758_enable_chg(struct charger_device *chg_dev, bool en) { uint32_t set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "%s %d\n", __func__, en); set_val = en; return rt9758_psy_set_prop(priv, POWER_SUPPLY_PROP_STATUS, set_val); } static int rt9758_is_chg_enabled(struct charger_device *chg_dev, bool *en) { int ret; unsigned int get_val; struct rt9758_priv *priv = charger_get_data(chg_dev); ret = regmap_field_read(priv->rm_field[F_CHG_EN], &get_val); if (ret) return ret; *en = get_val; dev_info(priv->dev, "%s %d\n", __func__, *en); return 0; } static int rt9758_init_chip(struct charger_device *chg_dev) { return 0; } static int rt9758_set_vbusovp(struct charger_device *chg_dev, uint32_t uV) { uint32_t set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "%s %d\n", __func__, uV); set_val = uV; return rt9758_psy_set_prop(priv, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, set_val); } static int rt9758_set_ibusocp(struct charger_device *chg_dev, uint32_t uA) { uint32_t set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "%s %d\n", __func__, uA); set_val = uA; return rt9758_psy_set_prop(priv, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, set_val); } static int rt9758_set_voutovp(struct charger_device *chg_dev, uint32_t uV) { uint32_t set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "%s %d\n", __func__, uV); set_val = uV; return rt9758_psy_set_prop(priv, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, set_val); } static int rt9758_is_vbuslowerr(struct charger_device *chg_dev, bool *err) { int ret; unsigned int get_val; struct rt9758_priv *priv = charger_get_data(chg_dev); ret = regmap_field_read(priv->rm_field[F_VLERR_STAT], &get_val); if (ret) return ret; *err = get_val; dev_info(priv->dev, "%s %d\n", __func__, *err); return 0; } static int rt9758_enable_auto_trans(struct charger_device *chg_dev, bool en) { uint32_t set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "%s %d\n", __func__, en); set_val = en; return regmap_field_write(priv->rm_field[F_ATEN], set_val); } static int rt9758_set_auto_trans(struct charger_device *chg_dev, uint32_t uV, bool en) { return 0; } static int rt9758_operation_mode_select(struct charger_device *chg_dev, bool div2) { unsigned int set_val; struct rt9758_priv *priv = charger_get_data(chg_dev); dev_info(priv->dev, "div2 = %d\n", div2); set_val = div2; return rt9758_psy_set_prop(priv, POWER_SUPPLY_PROP_CHARGE_TYPE, set_val); } #define DUMP_REG_BUF_SIZE 1024 static int rt9758_dump_registers(struct charger_device *chg_dev) { struct rt9758_priv *priv = charger_get_data(chg_dev); int ret, i; u32 val; char buf[DUMP_REG_BUF_SIZE] = "\0"; static const struct { const u8 reg; const char *name; } regs[] = { { .reg = RT9758_REG_FLAG1, .name = "FLAG1"}, { .reg = RT9758_REG_FLAG2, .name = "FLAG2"}, { .reg = RT9758_REG_FLAG3, .name = "FLAG3"}, { .reg = RT9758_REG_ICSTAT, .name = "IC_STAT"}, { .reg = RT9758_REG_CTRL1, .name = "CTRL1"}, { .reg = RT9758_REG_CTRL2, .name = "CTRL2"}, { .reg = RT9758_REG_CTRL3, .name = "CTRL3"}, { .reg = RT9758_REG_CTRL4, .name = "CTRL4"}, { .reg = RT9758_REG_CTRL5, .name = "CTRL5"}, { .reg = RT9758_REG_CTRL6, .name = "CTRL6"}, }; for (i = 0; i < ARRAY_SIZE(regs); i++) { ret = regmap_read(priv->regmap, regs[i].reg, &val); if (ret) { dev_err(priv->dev, "failed to get %s\n", regs[i].name); return ret; } if (i == ARRAY_SIZE(regs) - 1) scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE, "%s = 0x%02X\n", regs[i].name, val); else scnprintf(buf + strlen(buf), DUMP_REG_BUF_SIZE, "%s = 0x%02X, ", regs[i].name, val); } dev_info(priv->dev, "%s %s\n", __func__, buf); dev_info(priv->dev, "%s enable_gpio = %d\n", __func__, gpiod_get_value(priv->enable_gpio)); return 0; } static const struct charger_ops rt9758_chg_ops = { .enable = rt9758_enable_chg, .is_enabled = rt9758_is_chg_enabled, .init_chip = rt9758_init_chip, .set_vbusovp = rt9758_set_vbusovp, .set_ibusocp = rt9758_set_ibusocp, .set_vbatovp = rt9758_set_voutovp, .is_vbuslowerr = rt9758_is_vbuslowerr, .enable_auto_trans = rt9758_enable_auto_trans, .set_auto_trans = rt9758_set_auto_trans, .set_operation_mode = rt9758_operation_mode_select, .dump_registers = rt9758_dump_registers, }; static const struct regmap_config rt9758_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = RT9758_REG_CTRL10, }; static int rt9758_charger_probe(struct i2c_client *i2c) { struct rt9758_priv *priv; struct power_supply_config cfg = {}; int ret; priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = &i2c->dev; i2c_set_clientdata(i2c, priv); priv->regmap = devm_regmap_init_i2c(i2c, &rt9758_regmap_config); if (IS_ERR(priv->regmap)) { ret = PTR_ERR(priv->regmap); dev_err(&i2c->dev, "Failed to init regmap [%d]\n", ret); return ret; } ret = devm_regmap_field_bulk_alloc(&i2c->dev, priv->regmap, priv->rm_field, rt9758_reg_fields, ARRAY_SIZE(rt9758_reg_fields)); if (ret) { dev_err(&i2c->dev, "Failed to alloc regmap fields\n"); return ret; } ret = rt9758_check_device_info(priv); if (ret) { dev_err(&i2c->dev, "Failed to check device info\n"); return ret; } /* If specified, initial control 'enable' high to enter lowest IQ */ priv->enable_gpio = devm_gpiod_get_optional(&i2c->dev, "enable", GPIOD_OUT_HIGH); if (IS_ERR(priv->enable_gpio)) { dev_err(&i2c->dev, "Failed to init enable gpio\n"); return PTR_ERR(priv->enable_gpio); } ret = rt9758_init_chg_properties(priv); if (ret) { dev_err(&i2c->dev, "Failed to init charger properties\n"); return ret; } priv->desc.name = dev_name(&i2c->dev); priv->desc.type = POWER_SUPPLY_TYPE_UNKNOWN; priv->desc.properties = rt9758_charger_properties; priv->desc.num_properties = ARRAY_SIZE(rt9758_charger_properties); priv->desc.get_property = rt9758_charger_get_property; priv->desc.set_property = rt9758_charger_set_property; cfg.of_node = i2c->dev.of_node; cfg.drv_data = priv; priv->psy = devm_power_supply_register(&i2c->dev, &priv->desc, &cfg); if (IS_ERR(priv->psy)) { dev_err(&i2c->dev, "Failed to register psy\n"); return PTR_ERR(priv->psy); } priv->chg_prop.alias_name = priv->chg_name; priv->chg_dev = charger_device_register(priv->chg_name, priv->dev, priv, &rt9758_chg_ops, &priv->chg_prop); if (IS_ERR(priv->chg_dev)) { dev_err(&i2c->dev, "Failed to register chgdev\n"); return PTR_ERR(priv->chg_dev); } dev_info(&i2c->dev, "%s ok\n", __func__); return 0; } static int rt9758_charger_remove(struct i2c_client *i2c) { struct rt9758_priv *priv = i2c_get_clientdata(i2c); charger_device_unregister(priv->chg_dev); return 0; } static const struct of_device_id rt9758_charger_of_match_table[] = { { .compatible = "richtek,rt9758", }, { } }; MODULE_DEVICE_TABLE(of, rt9758_charger_of_match_table); static struct i2c_driver rt9758_charger_driver = { .driver = { .name = "rt9758-charger", .of_match_table = rt9758_charger_of_match_table, }, .probe_new = rt9758_charger_probe, .remove = rt9758_charger_remove, }; module_i2c_driver(rt9758_charger_driver); MODULE_DESCRIPTION("Richtek RT9758 charger driver"); MODULE_AUTHOR("ChiYuan Huang "); MODULE_LICENSE("GPL");