// SPDX-License-Identifier: GPL-2.0 // // Copyright (C) 2021 Samuel Holland #include #include #include #include #define IP5XXX_SYS_CTL0 0x01 #define IP5XXX_SYS_CTL0_WLED_DET_EN BIT(4) #define IP5XXX_SYS_CTL0_WLED_EN BIT(3) #define IP5XXX_SYS_CTL0_BOOST_EN BIT(2) #define IP5XXX_SYS_CTL0_CHARGER_EN BIT(1) #define IP5XXX_SYS_CTL1 0x02 #define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN BIT(1) #define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN BIT(0) #define IP5XXX_SYS_CTL2 0x0c #define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH GENMASK(7, 3) #define IP5XXX_SYS_CTL3 0x03 #define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL GENMASK(7, 6) #define IP5XXX_SYS_CTL3_BTN_SHDN_EN BIT(5) #define IP5XXX_SYS_CTL4 0x04 #define IP5XXX_SYS_CTL4_SHDN_TIME_SEL GENMASK(7, 6) #define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN BIT(5) #define IP5XXX_SYS_CTL5 0x07 #define IP5XXX_SYS_CTL5_NTC_DIS BIT(6) #define IP5XXX_SYS_CTL5_WLED_MODE_SEL BIT(1) #define IP5XXX_SYS_CTL5_BTN_SHDN_SEL BIT(0) #define IP5XXX_CHG_CTL1 0x22 #define IP5XXX_CHG_CTL1_BOOST_UVP_SEL GENMASK(3, 2) #define IP5XXX_CHG_CTL2 0x24 #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL GENMASK(6, 5) #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V (0x0 << 5) #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V (0x1 << 5) #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V (0x2 << 5) #define IP5XXX_CHG_CTL2_CONST_VOLT_SEL GENMASK(2, 1) #define IP5XXX_CHG_CTL4 0x26 #define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN BIT(6) #define IP5XXX_CHG_CTL4A 0x25 #define IP5XXX_CHG_CTL4A_CONST_CUR_SEL GENMASK(4, 0) #define IP5XXX_MFP_CTL0 0x51 #define IP5XXX_MFP_CTL1 0x52 #define IP5XXX_GPIO_CTL2 0x53 #define IP5XXX_GPIO_CTL2A 0x54 #define IP5XXX_GPIO_CTL3 0x55 #define IP5XXX_READ0 0x71 #define IP5XXX_READ0_CHG_STAT GENMASK(7, 5) #define IP5XXX_READ0_CHG_STAT_IDLE (0x0 << 5) #define IP5XXX_READ0_CHG_STAT_TRICKLE (0x1 << 5) #define IP5XXX_READ0_CHG_STAT_CONST_VOLT (0x2 << 5) #define IP5XXX_READ0_CHG_STAT_CONST_CUR (0x3 << 5) #define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP (0x4 << 5) #define IP5XXX_READ0_CHG_STAT_FULL (0x5 << 5) #define IP5XXX_READ0_CHG_STAT_TIMEOUT (0x6 << 5) #define IP5XXX_READ0_CHG_OP BIT(4) #define IP5XXX_READ0_CHG_END BIT(3) #define IP5XXX_READ0_CONST_VOLT_TIMEOUT BIT(2) #define IP5XXX_READ0_CHG_TIMEOUT BIT(1) #define IP5XXX_READ0_TRICKLE_TIMEOUT BIT(0) #define IP5XXX_READ0_TIMEOUT GENMASK(2, 0) #define IP5XXX_READ1 0x72 #define IP5XXX_READ1_WLED_PRESENT BIT(7) #define IP5XXX_READ1_LIGHT_LOAD BIT(6) #define IP5XXX_READ1_VIN_OVERVOLT BIT(5) #define IP5XXX_READ2 0x77 #define IP5XXX_READ2_BTN_PRESS BIT(3) #define IP5XXX_READ2_BTN_LONG_PRESS BIT(1) #define IP5XXX_READ2_BTN_SHORT_PRESS BIT(0) #define IP5XXX_BATVADC_DAT0 0xa2 #define IP5XXX_BATVADC_DAT1 0xa3 #define IP5XXX_BATIADC_DAT0 0xa4 #define IP5XXX_BATIADC_DAT1 0xa5 #define IP5XXX_BATOCV_DAT0 0xa8 #define IP5XXX_BATOCV_DAT1 0xa9 struct ip5xxx { struct regmap *regmap; bool initialized; }; /* * The IP5xxx charger only responds on I2C when it is "awake". The charger is * generally only awake when VIN is powered or when its boost converter is * enabled. Going into shutdown resets all register values. To handle this: * 1) When any bus error occurs, assume the charger has gone into shutdown. * 2) Attempt the initialization sequence on each subsequent register access * until it succeeds. */ static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg, unsigned int *val) { int ret; ret = regmap_read(ip5xxx->regmap, reg, val); if (ret) ip5xxx->initialized = false; return ret; } static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg, unsigned int mask, unsigned int val) { int ret; ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val); if (ret) ip5xxx->initialized = false; return ret; } static int ip5xxx_initialize(struct power_supply *psy) { struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); int ret; if (ip5xxx->initialized) return 0; /* * Disable shutdown under light load. * Enable power on when under load. */ ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1, IP5XXX_SYS_CTL1_LIGHT_SHDN_EN | IP5XXX_SYS_CTL1_LOAD_PWRUP_EN, IP5XXX_SYS_CTL1_LOAD_PWRUP_EN); if (ret) return ret; /* * Enable shutdown after a long button press (as configured below). */ ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3, IP5XXX_SYS_CTL3_BTN_SHDN_EN, IP5XXX_SYS_CTL3_BTN_SHDN_EN); if (ret) return ret; /* * Power on automatically when VIN is removed. */ ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4, IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN, IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN); if (ret) return ret; /* * Enable the NTC. * Configure the button for two presses => LED, long press => shutdown. */ ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5, IP5XXX_SYS_CTL5_NTC_DIS | IP5XXX_SYS_CTL5_WLED_MODE_SEL | IP5XXX_SYS_CTL5_BTN_SHDN_SEL, IP5XXX_SYS_CTL5_WLED_MODE_SEL | IP5XXX_SYS_CTL5_BTN_SHDN_SEL); if (ret) return ret; ip5xxx->initialized = true; dev_dbg(psy->dev.parent, "Initialized after power on\n"); return 0; } static const enum power_supply_property ip5xxx_battery_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, }; static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val) { unsigned int rval; int ret; ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); if (ret) return ret; switch (rval & IP5XXX_READ0_CHG_STAT) { case IP5XXX_READ0_CHG_STAT_IDLE: *val = POWER_SUPPLY_STATUS_DISCHARGING; break; case IP5XXX_READ0_CHG_STAT_TRICKLE: case IP5XXX_READ0_CHG_STAT_CONST_CUR: case IP5XXX_READ0_CHG_STAT_CONST_VOLT: *val = POWER_SUPPLY_STATUS_CHARGING; break; case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: case IP5XXX_READ0_CHG_STAT_FULL: *val = POWER_SUPPLY_STATUS_FULL; break; case IP5XXX_READ0_CHG_STAT_TIMEOUT: *val = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default: return -EINVAL; } return 0; } static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val) { unsigned int rval; int ret; ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); if (ret) return ret; switch (rval & IP5XXX_READ0_CHG_STAT) { case IP5XXX_READ0_CHG_STAT_IDLE: case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: case IP5XXX_READ0_CHG_STAT_FULL: case IP5XXX_READ0_CHG_STAT_TIMEOUT: *val = POWER_SUPPLY_CHARGE_TYPE_NONE; break; case IP5XXX_READ0_CHG_STAT_TRICKLE: *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case IP5XXX_READ0_CHG_STAT_CONST_CUR: case IP5XXX_READ0_CHG_STAT_CONST_VOLT: *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; break; default: return -EINVAL; } return 0; } static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val) { unsigned int rval; int ret; ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); if (ret) return ret; if (rval & IP5XXX_READ0_TIMEOUT) *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; else *val = POWER_SUPPLY_HEALTH_GOOD; return 0; } static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val) { unsigned int rval; int ret; ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); if (ret) return ret; /* * It is not clear what this will return if * IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set... */ switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) { case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V: *val = 4200000; break; case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V: *val = 4300000; break; case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V: *val = 4350000; break; default: return -EINVAL; } return 0; } static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx, u8 lo_reg, u8 hi_reg, int *val) { unsigned int hi, lo; int ret; ret = ip5xxx_read(ip5xxx, lo_reg, &lo); if (ret) return ret; ret = ip5xxx_read(ip5xxx, hi_reg, &hi); if (ret) return ret; *val = sign_extend32(hi << 8 | lo, 13); return 0; } static int ip5xxx_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); int raw, ret, vmax; unsigned int rval; ret = ip5xxx_initialize(psy); if (ret) return ret; switch (psp) { case POWER_SUPPLY_PROP_STATUS: return ip5xxx_battery_get_status(ip5xxx, &val->intval); case POWER_SUPPLY_PROP_CHARGE_TYPE: return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval); case POWER_SUPPLY_PROP_HEALTH: return ip5xxx_battery_get_health(ip5xxx, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0, IP5XXX_BATVADC_DAT1, &raw); val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_VOLTAGE_OCV: ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0, IP5XXX_BATOCV_DAT1, &raw); val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_CURRENT_NOW: ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0, IP5XXX_BATIADC_DAT1, &raw); val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200); return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval); if (ret) return ret; rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL; val->intval = 100000 * rval; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = 100000 * 0x1f; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); if (ret) return ret; ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); if (ret) return ret; rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL; val->intval = vmax + 14000 * (rval >> 1); return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); if (ret) return ret; val->intval = vmax + 14000 * 3; return 0; default: return -EINVAL; } } static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val) { unsigned int rval; int ret; switch (val) { case 4200000: rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V; break; case 4300000: rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V; break; case 4350000: rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V; break; default: return -EINVAL; } ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval); if (ret) return ret; ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4, IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN, IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN); if (ret) return ret; return 0; } static int ip5xxx_battery_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); unsigned int rval; int ret, vmax; ret = ip5xxx_initialize(psy); if (ret) return ret; switch (psp) { case POWER_SUPPLY_PROP_STATUS: switch (val->intval) { case POWER_SUPPLY_STATUS_CHARGING: rval = IP5XXX_SYS_CTL0_CHARGER_EN; break; case POWER_SUPPLY_STATUS_DISCHARGING: case POWER_SUPPLY_STATUS_NOT_CHARGING: rval = 0; break; default: return -EINVAL; } return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, IP5XXX_SYS_CTL0_CHARGER_EN, rval); case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: rval = val->intval / 100000; return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A, IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); if (ret) return ret; rval = ((val->intval - vmax) / 14000) << 1; return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval); default: return -EINVAL; } } static int ip5xxx_battery_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { return psp == POWER_SUPPLY_PROP_STATUS || psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE; } static const struct power_supply_desc ip5xxx_battery_desc = { .name = "ip5xxx-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = ip5xxx_battery_properties, .num_properties = ARRAY_SIZE(ip5xxx_battery_properties), .get_property = ip5xxx_battery_get_property, .set_property = ip5xxx_battery_set_property, .property_is_writeable = ip5xxx_battery_property_is_writeable, }; static const enum power_supply_property ip5xxx_boost_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, }; static int ip5xxx_boost_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); unsigned int rval; int ret; ret = ip5xxx_initialize(psy); if (ret) return ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval); if (ret) return ret; val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN); return 0; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval); if (ret) return ret; rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL; val->intval = 4530000 + 100000 * (rval >> 2); return 0; default: return -EINVAL; } } static int ip5xxx_boost_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); unsigned int rval; int ret; ret = ip5xxx_initialize(psy); if (ret) return ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0; return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, IP5XXX_SYS_CTL0_BOOST_EN, rval); case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: rval = ((val->intval - 4530000) / 100000) << 2; return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1, IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval); default: return -EINVAL; } } static int ip5xxx_boost_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { return true; } static const struct power_supply_desc ip5xxx_boost_desc = { .name = "ip5xxx-boost", .type = POWER_SUPPLY_TYPE_USB, .properties = ip5xxx_boost_properties, .num_properties = ARRAY_SIZE(ip5xxx_boost_properties), .get_property = ip5xxx_boost_get_property, .set_property = ip5xxx_boost_set_property, .property_is_writeable = ip5xxx_boost_property_is_writeable, }; static const struct regmap_config ip5xxx_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = IP5XXX_BATOCV_DAT1, }; static int ip5xxx_power_probe(struct i2c_client *client) { struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; struct power_supply *psy; struct ip5xxx *ip5xxx; ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL); if (!ip5xxx) return -ENOMEM; ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config); if (IS_ERR(ip5xxx->regmap)) return PTR_ERR(ip5xxx->regmap); psy_cfg.of_node = dev->of_node; psy_cfg.drv_data = ip5xxx; psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg); if (IS_ERR(psy)) return PTR_ERR(psy); psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg); if (IS_ERR(psy)) return PTR_ERR(psy); return 0; } static const struct of_device_id ip5xxx_power_of_match[] = { { .compatible = "injoinic,ip5108" }, { .compatible = "injoinic,ip5109" }, { .compatible = "injoinic,ip5207" }, { .compatible = "injoinic,ip5209" }, { } }; MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match); static struct i2c_driver ip5xxx_power_driver = { .probe_new = ip5xxx_power_probe, .driver = { .name = "ip5xxx-power", .of_match_table = ip5xxx_power_of_match, } }; module_i2c_driver(ip5xxx_power_driver); MODULE_AUTHOR("Samuel Holland "); MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver"); MODULE_LICENSE("GPL");