// SPDX-License-Identifier: GPL-2.0 // // Copyright (C) 2021 Samuel Holland #include #include #include #include #define IP5XXX_BAT_TYPE_4_2V 0x0 #define IP5XXX_BAT_TYPE_4_3V 0x1 #define IP5XXX_BAT_TYPE_4_35V 0x2 #define IP5XXX_BAT_TYPE_4_4V 0x3 #define IP5XXX_CHG_STAT_IDLE 0x0 #define IP5XXX_CHG_STAT_TRICKLE 0x1 #define IP5XXX_CHG_STAT_CONST_VOLT 0x2 #define IP5XXX_CHG_STAT_CONST_CUR 0x3 #define IP5XXX_CHG_STAT_CONST_VOLT_STOP 0x4 #define IP5XXX_CHG_STAT_FULL 0x5 #define IP5XXX_CHG_STAT_TIMEOUT 0x6 struct ip5xxx { struct regmap *regmap; bool initialized; struct { struct { /* Charger enable */ struct regmap_field *enable; /* Constant voltage value */ struct regmap_field *const_volt_sel; /* Constant current value */ struct regmap_field *const_curr_sel; /* Charger status */ struct regmap_field *status; /* Charging ended flag */ struct regmap_field *chg_end; /* Timeout flags (CV, charge, trickle) */ struct regmap_field *timeout; /* Overvoltage limit */ struct regmap_field *vin_overvolt; } charger; struct { /* Boost converter enable */ struct regmap_field *enable; struct { /* Light load shutdown enable */ struct regmap_field *enable; /* Light load shutdown current limit */ struct regmap_field *i_limit; } light_load_shutdown; /* Automatic powerup on increased load */ struct regmap_field *load_powerup_en; /* Automatic powerup on VIN pull-out */ struct regmap_field *vin_pullout_en; /* Undervoltage limit */ struct regmap_field *undervolt_limit; /* Light load status flag */ struct regmap_field *light_load_status; } boost; struct { /* NTC disable */ struct regmap_field *ntc_dis; /* Battery voltage type */ struct regmap_field *type; /* Battery voltage autoset from Vset pin */ struct regmap_field *vset_en; struct { /* Battery measurement registers */ struct ip5xxx_battery_adc_regs { struct regmap_field *low; struct regmap_field *high; } volt, curr, open_volt; } adc; } battery; struct { /* Double/long press shutdown enable */ struct regmap_field *shdn_enable; /* WLED activation: double press or long press */ struct regmap_field *wled_mode; /* Shutdown activation: double press or long press */ struct regmap_field *shdn_mode; /* Long press time */ struct regmap_field *long_press_time; /* Button pressed */ struct regmap_field *pressed; /* Button long-pressed */ struct regmap_field *long_pressed; /* Button short-pressed */ struct regmap_field *short_pressed; } btn; struct { /* WLED enable */ struct regmap_field *enable; /* WLED detect */ struct regmap_field *detect_en; /* WLED present */ struct regmap_field *present; } wled; } regs; /* Maximum supported battery voltage (via regs.battery.type) */ int vbat_max; /* Scaling constants for regs.boost.undervolt_limit */ struct { int setpoint; int microvolts_per_bit; } boost_undervolt; /* Scaling constants for regs.charger.const_curr_sel */ struct { int setpoint; } const_curr; /* Whether regs.charger.chg_end is inverted */ u8 chg_end_inverted; }; #define REG_FIELD_UNSUPPORTED { .lsb = 1 } /* Register fields layout. Unsupported registers marked as { .lsb = 1 } */ struct ip5xxx_regfield_config { const struct reg_field charger_enable; const struct reg_field charger_const_volt_sel; const struct reg_field charger_const_curr_sel; const struct reg_field charger_status; const struct reg_field charger_chg_end; const struct reg_field charger_timeout; const struct reg_field charger_vin_overvolt; const struct reg_field boost_enable; const struct reg_field boost_llshdn_enable; const struct reg_field boost_llshdn_i_limit; const struct reg_field boost_load_powerup_en; const struct reg_field boost_vin_pullout_en; const struct reg_field boost_undervolt_limit; const struct reg_field boost_light_load_status; const struct reg_field battery_ntc_dis; const struct reg_field battery_type; const struct reg_field battery_vset_en; const struct reg_field battery_adc_volt_low; const struct reg_field battery_adc_volt_high; const struct reg_field battery_adc_curr_low; const struct reg_field battery_adc_curr_high; const struct reg_field battery_adc_ovolt_low; const struct reg_field battery_adc_ovolt_high; const struct reg_field btn_shdn_enable; const struct reg_field btn_wled_mode; const struct reg_field btn_shdn_mode; const struct reg_field btn_long_press_time; const struct reg_field btn_pressed; const struct reg_field btn_long_pressed; const struct reg_field btn_short_pressed; const struct reg_field wled_enable; const struct reg_field wled_detect_en; const struct reg_field wled_present; int vbat_max; int boost_undervolt_setpoint; int boost_undervolt_uv_per_bit; int const_curr_setpoint; u8 chg_end_inverted; }; /* * 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, struct regmap_field *field, unsigned int *val) { int ret; if (!field) return -EOPNOTSUPP; ret = regmap_field_read(field, val); if (ret) ip5xxx->initialized = false; return ret; } static int ip5xxx_write(struct ip5xxx *ip5xxx, struct regmap_field *field, unsigned int val) { int ret; if (!field) return -EOPNOTSUPP; ret = regmap_field_write(field, 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. */ if (ip5xxx->regs.boost.light_load_shutdown.enable) { ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.light_load_shutdown.enable, 0); if (ret) return ret; } ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.load_powerup_en, 1); if (ret) return ret; /* * Enable shutdown after a long button press (as configured below). */ ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_enable, 1); if (ret) return ret; /* * Power on automatically when VIN is removed. */ ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.vin_pullout_en, 1); if (ret) return ret; /* * Enable the NTC. * Configure the button for two presses => LED, long press => shutdown. */ if (ip5xxx->regs.battery.ntc_dis) { ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.ntc_dis, 0); if (ret) return ret; } ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.wled_mode, 1); if (ret) return ret; ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_mode, 1); 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; if (!ip5xxx->regs.charger.status) { // Fall-back to Charging Ended bit ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.chg_end, &rval); if (ret) return ret; if (rval == ip5xxx->chg_end_inverted) *val = POWER_SUPPLY_STATUS_CHARGING; else *val = POWER_SUPPLY_STATUS_NOT_CHARGING; return 0; } ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.status, &rval); if (ret) return ret; switch (rval) { case IP5XXX_CHG_STAT_IDLE: *val = POWER_SUPPLY_STATUS_DISCHARGING; break; case IP5XXX_CHG_STAT_TRICKLE: case IP5XXX_CHG_STAT_CONST_CUR: case IP5XXX_CHG_STAT_CONST_VOLT: *val = POWER_SUPPLY_STATUS_CHARGING; break; case IP5XXX_CHG_STAT_CONST_VOLT_STOP: case IP5XXX_CHG_STAT_FULL: *val = POWER_SUPPLY_STATUS_FULL; break; case IP5XXX_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->regs.charger.status, &rval); if (ret) return ret; switch (rval) { case IP5XXX_CHG_STAT_IDLE: case IP5XXX_CHG_STAT_CONST_VOLT_STOP: case IP5XXX_CHG_STAT_FULL: case IP5XXX_CHG_STAT_TIMEOUT: *val = POWER_SUPPLY_CHARGE_TYPE_NONE; break; case IP5XXX_CHG_STAT_TRICKLE: *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case IP5XXX_CHG_STAT_CONST_CUR: case IP5XXX_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->regs.charger.timeout, &rval); if (ret) return ret; if (rval) *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->regs.battery.type, &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) { case IP5XXX_BAT_TYPE_4_2V: *val = 4200000; break; case IP5XXX_BAT_TYPE_4_3V: *val = 4300000; break; case IP5XXX_BAT_TYPE_4_35V: *val = 4350000; break; case IP5XXX_BAT_TYPE_4_4V: *val = 4400000; break; default: return -EINVAL; } return 0; } static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx, struct ip5xxx_battery_adc_regs *regs, int *val) { unsigned int hi, lo; int ret; ret = ip5xxx_read(ip5xxx, regs->low, &lo); if (ret) return ret; ret = ip5xxx_read(ip5xxx, regs->high, &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->regs.battery.adc.volt, &raw); if (ret) return ret; val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_VOLTAGE_OCV: ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.open_volt, &raw); if (ret) return ret; val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); return 0; case POWER_SUPPLY_PROP_CURRENT_NOW: ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.curr, &raw); if (ret) return ret; val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200); return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.const_curr_sel, &rval); if (ret) return ret; val->intval = ip5xxx->const_curr.setpoint + 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->regs.charger.const_volt_sel, &rval); if (ret) return ret; val->intval = vmax + 14000 * rval; 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; if (val > ip5xxx->vbat_max) return -EINVAL; switch (val) { case 4200000: rval = IP5XXX_BAT_TYPE_4_2V; break; case 4300000: rval = IP5XXX_BAT_TYPE_4_3V; break; case 4350000: rval = IP5XXX_BAT_TYPE_4_35V; break; case 4400000: rval = IP5XXX_BAT_TYPE_4_4V; break; default: return -EINVAL; } ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.type, rval); if (ret) return ret; /* Don't try to auto-detect battery type, even if the IC could */ if (ip5xxx->regs.battery.vset_en) { ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.vset_en, 1); 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 = 1; break; case POWER_SUPPLY_STATUS_DISCHARGING: case POWER_SUPPLY_STATUS_NOT_CHARGING: rval = 0; break; default: return -EINVAL; } return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.enable, 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 - ip5xxx->const_curr.setpoint) / 100000; return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.const_curr_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; return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.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->regs.boost.enable, &rval); if (ret) return ret; val->intval = !!rval; return 0; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ret = ip5xxx_read(ip5xxx, ip5xxx->regs.boost.undervolt_limit, &rval); if (ret) return ret; val->intval = ip5xxx->boost_undervolt.setpoint + ip5xxx->boost_undervolt.microvolts_per_bit * rval; 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: return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.enable, !!val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: rval = (val->intval - ip5xxx->boost_undervolt.setpoint) / ip5xxx->boost_undervolt.microvolts_per_bit; return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.undervolt_limit, 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 = 0xa9, }; static struct ip5xxx_regfield_config ip51xx_fields = { .charger_enable = REG_FIELD(0x01, 1, 1), .charger_const_volt_sel = REG_FIELD(0x24, 1, 2), .charger_const_curr_sel = REG_FIELD(0x25, 0, 4), .charger_status = REG_FIELD(0x71, 5, 7), .charger_chg_end = REG_FIELD(0x71, 3, 3), .charger_timeout = REG_FIELD(0x71, 0, 2), .charger_vin_overvolt = REG_FIELD(0x72, 5, 5), .boost_enable = REG_FIELD(0x01, 2, 2), .boost_llshdn_enable = REG_FIELD(0x02, 1, 1), .boost_llshdn_i_limit = REG_FIELD(0x0c, 3, 7), .boost_load_powerup_en = REG_FIELD(0x02, 0, 0), .boost_vin_pullout_en = REG_FIELD(0x04, 5, 5), .boost_undervolt_limit = REG_FIELD(0x22, 2, 3), .boost_light_load_status = REG_FIELD(0x72, 6, 6), .battery_ntc_dis = REG_FIELD(0x07, 6, 6), .battery_type = REG_FIELD(0x24, 5, 6), .battery_vset_en = REG_FIELD(0x26, 6, 6), .battery_adc_volt_low = REG_FIELD(0xa2, 0, 7), .battery_adc_volt_high = REG_FIELD(0xa3, 0, 5), .battery_adc_curr_low = REG_FIELD(0xa4, 0, 7), .battery_adc_curr_high = REG_FIELD(0xa5, 0, 5), .battery_adc_ovolt_low = REG_FIELD(0xa8, 0, 7), .battery_adc_ovolt_high = REG_FIELD(0xa9, 0, 5), .btn_shdn_enable = REG_FIELD(0x03, 5, 5), .btn_wled_mode = REG_FIELD(0x07, 1, 1), .btn_shdn_mode = REG_FIELD(0x07, 0, 0), .btn_long_press_time = REG_FIELD(0x03, 6, 7), .btn_pressed = REG_FIELD(0x77, 3, 3), .btn_long_pressed = REG_FIELD(0x77, 1, 1), .btn_short_pressed = REG_FIELD(0x77, 0, 0), .wled_enable = REG_FIELD(0x01, 3, 3), .wled_detect_en = REG_FIELD(0x01, 4, 4), .wled_present = REG_FIELD(0x72, 7, 7), .vbat_max = 4350000, .boost_undervolt_setpoint = 4530000, .boost_undervolt_uv_per_bit = 100000, }; static struct ip5xxx_regfield_config ip5306_fields = { .charger_enable = REG_FIELD(0x00, 4, 4), .charger_const_volt_sel = REG_FIELD(0x22, 0, 1), .charger_const_curr_sel = REG_FIELD(0x24, 0, 4), .charger_status = REG_FIELD_UNSUPPORTED, // other bits... .charger_chg_end = REG_FIELD(0x71, 3, 3), .charger_timeout = REG_FIELD_UNSUPPORTED, .charger_vin_overvolt = REG_FIELD_UNSUPPORTED, .boost_enable = REG_FIELD(0x00, 5, 5), .boost_llshdn_enable = REG_FIELD_UNSUPPORTED, .boost_llshdn_i_limit = REG_FIELD_UNSUPPORTED, .boost_load_powerup_en = REG_FIELD(0x00, 2, 2), .boost_vin_pullout_en = REG_FIELD(0x01, 2, 2), .boost_undervolt_limit = REG_FIELD(0x21, 2, 4), .boost_light_load_status = REG_FIELD(0x72, 2, 2), .battery_ntc_dis = REG_FIELD_UNSUPPORTED, .battery_type = REG_FIELD(0x22, 2, 3), .battery_vset_en = REG_FIELD_UNSUPPORTED, .battery_adc_volt_low = REG_FIELD_UNSUPPORTED, .battery_adc_volt_high = REG_FIELD_UNSUPPORTED, .battery_adc_curr_low = REG_FIELD_UNSUPPORTED, .battery_adc_curr_high = REG_FIELD_UNSUPPORTED, .battery_adc_ovolt_low = REG_FIELD_UNSUPPORTED, .battery_adc_ovolt_high = REG_FIELD_UNSUPPORTED, .btn_shdn_enable = REG_FIELD(0x00, 0, 0), .btn_wled_mode = REG_FIELD(0x01, 6, 6), .btn_shdn_mode = REG_FIELD(0x01, 7, 7), .btn_long_press_time = REG_FIELD(0x02, 4, 4), // +1s .btn_pressed = REG_FIELD_UNSUPPORTED, /* TODO: double press */ .btn_long_pressed = REG_FIELD(0x77, 1, 1), .btn_short_pressed = REG_FIELD(0x77, 0, 0), .wled_enable = REG_FIELD_UNSUPPORTED, .wled_detect_en = REG_FIELD_UNSUPPORTED, .wled_present = REG_FIELD_UNSUPPORTED, .vbat_max = 4400000, .boost_undervolt_setpoint = 4450000, .boost_undervolt_uv_per_bit = 50000, .const_curr_setpoint = 50000, .chg_end_inverted = 1, }; #define ip5xxx_setup_reg(_field, _reg) \ do { \ if (likely(cfg->_field.lsb <= cfg->_field.msb)) { \ struct regmap_field *_tmp = devm_regmap_field_alloc(dev, \ ip5xxx->regmap, cfg->_field); \ if (!IS_ERR(_tmp)) \ ip5xxx->regs._reg = _tmp; \ } \ } while (0) static void ip5xxx_setup_regs(struct device *dev, struct ip5xxx *ip5xxx, const struct ip5xxx_regfield_config *cfg) { ip5xxx_setup_reg(charger_enable, charger.enable); ip5xxx_setup_reg(charger_const_volt_sel, charger.const_volt_sel); ip5xxx_setup_reg(charger_const_curr_sel, charger.const_curr_sel); ip5xxx_setup_reg(charger_status, charger.status); ip5xxx_setup_reg(charger_chg_end, charger.chg_end); ip5xxx_setup_reg(charger_timeout, charger.timeout); ip5xxx_setup_reg(charger_vin_overvolt, charger.vin_overvolt); ip5xxx_setup_reg(boost_enable, boost.enable); ip5xxx_setup_reg(boost_llshdn_enable, boost.light_load_shutdown.enable); ip5xxx_setup_reg(boost_llshdn_i_limit, boost.light_load_shutdown.i_limit); ip5xxx_setup_reg(boost_load_powerup_en, boost.load_powerup_en); ip5xxx_setup_reg(boost_vin_pullout_en, boost.vin_pullout_en); ip5xxx_setup_reg(boost_undervolt_limit, boost.undervolt_limit); ip5xxx_setup_reg(boost_light_load_status, boost.light_load_status); ip5xxx_setup_reg(battery_ntc_dis, battery.ntc_dis); ip5xxx_setup_reg(battery_type, battery.type); ip5xxx_setup_reg(battery_vset_en, battery.vset_en); ip5xxx_setup_reg(battery_adc_volt_low, battery.adc.volt.low); ip5xxx_setup_reg(battery_adc_volt_high, battery.adc.volt.high); ip5xxx_setup_reg(battery_adc_curr_low, battery.adc.curr.low); ip5xxx_setup_reg(battery_adc_curr_high, battery.adc.curr.high); ip5xxx_setup_reg(battery_adc_ovolt_low, battery.adc.open_volt.low); ip5xxx_setup_reg(battery_adc_ovolt_high, battery.adc.open_volt.high); ip5xxx_setup_reg(btn_shdn_enable, btn.shdn_enable); ip5xxx_setup_reg(btn_wled_mode, btn.wled_mode); ip5xxx_setup_reg(btn_shdn_mode, btn.shdn_mode); ip5xxx_setup_reg(btn_long_press_time, btn.long_press_time); ip5xxx_setup_reg(btn_pressed, btn.pressed); ip5xxx_setup_reg(btn_long_pressed, btn.long_pressed); ip5xxx_setup_reg(btn_short_pressed, btn.short_pressed); ip5xxx_setup_reg(wled_enable, wled.enable); ip5xxx_setup_reg(wled_detect_en, wled.detect_en); ip5xxx_setup_reg(wled_present, wled.present); ip5xxx->vbat_max = cfg->vbat_max; ip5xxx->boost_undervolt.setpoint = cfg->boost_undervolt_setpoint; ip5xxx->boost_undervolt.microvolts_per_bit = cfg->boost_undervolt_uv_per_bit; ip5xxx->const_curr.setpoint = cfg->const_curr_setpoint; ip5xxx->chg_end_inverted = cfg->chg_end_inverted; } static int ip5xxx_power_probe(struct i2c_client *client) { const struct ip5xxx_regfield_config *fields = &ip51xx_fields; struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; const struct of_device_id *of_id; 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); of_id = i2c_of_match_device(dev->driver->of_match_table, client); if (of_id) fields = (const struct ip5xxx_regfield_config *)of_id->data; ip5xxx_setup_regs(dev, ip5xxx, fields); 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", .data = &ip51xx_fields }, { .compatible = "injoinic,ip5109", .data = &ip51xx_fields }, { .compatible = "injoinic,ip5207", .data = &ip51xx_fields }, { .compatible = "injoinic,ip5209", .data = &ip51xx_fields }, { .compatible = "injoinic,ip5306", .data = &ip5306_fields }, { } }; MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match); static struct i2c_driver ip5xxx_power_driver = { .probe = 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");