// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for Analog Devices (Linear Technology) LTC4162-L charger IC. * Copyright (C) 2020, Topic Embedded Products */ #include #include #include #include #include #include #include /* Registers (names based on what datasheet uses) */ #define LTC4162L_EN_LIMIT_ALERTS_REG 0x0D #define LTC4162L_EN_CHARGER_STATE_ALERTS_REG 0x0E #define LTC4162L_EN_CHARGE_STATUS_ALERTS_REG 0x0F #define LTC4162L_CONFIG_BITS_REG 0x14 #define LTC4162L_IIN_LIMIT_TARGET 0x15 #define LTC4162L_ARM_SHIP_MODE 0x19 #define LTC4162L_CHARGE_CURRENT_SETTING 0X1A #define LTC4162L_VCHARGE_SETTING 0X1B #define LTC4162L_C_OVER_X_THRESHOLD 0x1C #define LTC4162L_MAX_CV_TIME 0X1D #define LTC4162L_MAX_CHARGE_TIME 0X1E #define LTC4162L_CHARGER_CONFIG_BITS 0x29 #define LTC4162L_CHARGER_STATE 0x34 #define LTC4162L_CHARGE_STATUS 0x35 #define LTC4162L_LIMIT_ALERTS_REG 0x36 #define LTC4162L_CHARGER_STATE_ALERTS_REG 0x37 #define LTC4162L_CHARGE_STATUS_ALERTS_REG 0x38 #define LTC4162L_SYSTEM_STATUS_REG 0x39 #define LTC4162L_VBAT 0x3A #define LTC4162L_VIN 0x3B #define LTC4162L_VOUT 0x3C #define LTC4162L_IBAT 0x3D #define LTC4162L_IIN 0x3E #define LTC4162L_DIE_TEMPERATURE 0x3F #define LTC4162L_THERMISTOR_VOLTAGE 0x40 #define LTC4162L_BSR 0x41 #define LTC4162L_JEITA_REGION 0x42 #define LTC4162L_CHEM_CELLS_REG 0x43 #define LTC4162L_ICHARGE_DAC 0x44 #define LTC4162L_VCHARGE_DAC 0x45 #define LTC4162L_IIN_LIMIT_DAC 0x46 #define LTC4162L_VBAT_FILT 0x47 #define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B /* Enumeration as in datasheet. Individual bits are mutually exclusive. */ enum ltc4162l_state { battery_detection = 2048, charger_suspended = 256, precharge = 128, /* trickle on low bat voltage */ cc_cv_charge = 64, /* normal charge */ ntc_pause = 32, timer_term = 16, c_over_x_term = 8, /* battery is full */ max_charge_time_fault = 4, bat_missing_fault = 2, bat_short_fault = 1 }; /* Individual bits are mutually exclusive. Only active in charging states.*/ enum ltc4162l_charge_status { ilim_reg_active = 32, thermal_reg_active = 16, vin_uvcl_active = 8, iin_limit_active = 4, constant_current = 2, constant_voltage = 1, charger_off = 0 }; /* Magic number to write to ARM_SHIP_MODE register */ #define LTC4162L_ARM_SHIP_MODE_MAGIC 21325 struct ltc4162l_info { struct i2c_client *client; struct regmap *regmap; struct power_supply *charger; u32 rsnsb; /* Series resistor that sets charge current, microOhm */ u32 rsnsi; /* Series resistor to measure input current, microOhm */ u8 cell_count; /* Number of connected cells, 0 while unknown */ }; static u8 ltc4162l_get_cell_count(struct ltc4162l_info *info) { int ret; unsigned int val; /* Once read successfully */ if (info->cell_count) return info->cell_count; ret = regmap_read(info->regmap, LTC4162L_CHEM_CELLS_REG, &val); if (ret) return 0; /* Lower 4 bits is the cell count, or 0 if the chip doesn't know yet */ val &= 0x0f; if (!val) return 0; /* Once determined, keep the value */ info->cell_count = val; return val; }; /* Convert enum value to POWER_SUPPLY_STATUS value */ static int ltc4162l_state_decode(enum ltc4162l_state value) { switch (value) { case precharge: case cc_cv_charge: return POWER_SUPPLY_STATUS_CHARGING; case c_over_x_term: return POWER_SUPPLY_STATUS_FULL; case bat_missing_fault: case bat_short_fault: return POWER_SUPPLY_STATUS_UNKNOWN; default: return POWER_SUPPLY_STATUS_NOT_CHARGING; } }; static int ltc4162l_get_status(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, ®val); if (ret) { dev_err(&info->client->dev, "Failed to read CHARGER_STATE\n"); return ret; } val->intval = ltc4162l_state_decode(regval); return 0; } static int ltc4162l_charge_status_decode(enum ltc4162l_charge_status value) { if (!value) return POWER_SUPPLY_CHARGE_TYPE_NONE; /* constant voltage/current and input_current limit are "fast" modes */ if (value <= iin_limit_active) return POWER_SUPPLY_CHARGE_TYPE_FAST; /* Anything that's not fast we'll return as trickle */ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; } static int ltc4162l_get_charge_type(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, ®val); if (ret) return ret; val->intval = ltc4162l_charge_status_decode(regval); return 0; } static int ltc4162l_state_to_health(enum ltc4162l_state value) { switch (value) { case ntc_pause: return POWER_SUPPLY_HEALTH_OVERHEAT; case timer_term: return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; case max_charge_time_fault: return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; case bat_missing_fault: return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; case bat_short_fault: return POWER_SUPPLY_HEALTH_DEAD; default: return POWER_SUPPLY_HEALTH_GOOD; } } static int ltc4162l_get_health(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, ®val); if (ret) return ret; val->intval = ltc4162l_state_to_health(regval); return 0; } static int ltc4162l_get_online(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_SYSTEM_STATUS_REG, ®val); if (ret) return ret; /* BIT(2) indicates if input voltage is sufficient to charge */ val->intval = !!(regval & BIT(2)); return 0; } static int ltc4162l_get_vbat(struct ltc4162l_info *info, unsigned int reg, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, reg, ®val); if (ret) return ret; /* cell_count × 192.4μV/LSB */ regval *= 1924; regval *= ltc4162l_get_cell_count(info); regval /= 10; val->intval = regval; return 0; } static int ltc4162l_get_ibat(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_IBAT, ®val); if (ret) return ret; /* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */ ret = (s16)(regval & 0xFFFF); val->intval = 100 * mult_frac(ret, 14660, (int)info->rsnsb); return 0; } static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_VIN, ®val); if (ret) return ret; /* 1.649mV/LSB */ val->intval = regval * 1694; return 0; } static int ltc4162l_get_input_current(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_IIN, ®val); if (ret) return ret; /* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */ ret = (s16)(regval & 0xFFFF); ret *= 14660; ret /= info->rsnsi; ret *= 100; val->intval = ret; return 0; } static int ltc4162l_get_icharge(struct ltc4162l_info *info, unsigned int reg, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, reg, ®val); if (ret) return ret; regval &= BIT(6) - 1; /* Only the lower 5 bits */ /* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */ ++regval; val->intval = 10000u * mult_frac(regval, 100000u, info->rsnsb); return 0; } static int ltc4162l_set_icharge(struct ltc4162l_info *info, unsigned int reg, unsigned int value) { value = mult_frac(value, info->rsnsb, 100000u); value /= 10000u; /* Round to lowest possible */ if (value) --value; if (value > 31) return -EINVAL; return regmap_write(info->regmap, reg, value); } static int ltc4162l_get_vcharge(struct ltc4162l_info *info, unsigned int reg, union power_supply_propval *val) { unsigned int regval; int ret; u32 voltage; ret = regmap_read(info->regmap, reg, ®val); if (ret) return ret; regval &= BIT(6) - 1; /* Only the lower 5 bits */ /* * charge voltage setting can be computed from * cell_count × (vcharge_setting × 12.5mV + 3.8125V) * where vcharge_setting ranges from 0 to 31 (4.2V max). */ voltage = 3812500 + (regval * 12500); voltage *= ltc4162l_get_cell_count(info); val->intval = voltage; return 0; } static int ltc4162l_set_vcharge(struct ltc4162l_info *info, unsigned int reg, unsigned int value) { u8 cell_count = ltc4162l_get_cell_count(info); if (!cell_count) return -EBUSY; /* Not available yet, try again later */ value /= cell_count; if (value < 3812500) return -EINVAL; value -= 3812500; value /= 12500; if (value > 31) return -EINVAL; return regmap_write(info->regmap, reg, value); } static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_IIN_LIMIT_DAC, ®val); if (ret) return ret; regval &= BIT(6) - 1; /* Only 6 bits */ /* (iin_limit_dac + 1) × 500μV / RSNSI */ ++regval; regval *= 5000000u; regval /= info->rsnsi; val->intval = 100u * regval; return 0; } static int ltc4162l_set_iin_limit(struct ltc4162l_info *info, unsigned int value) { unsigned int regval; regval = mult_frac(value, info->rsnsi, 50000u); regval /= 10000u; if (regval) --regval; if (regval > 63) regval = 63; return regmap_write(info->regmap, LTC4162L_IIN_LIMIT_TARGET, regval); } static int ltc4162l_get_die_temp(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_DIE_TEMPERATURE, ®val); if (ret) return ret; /* die_temp × 0.0215°C/LSB - 264.4°C */ ret = (s16)(regval & 0xFFFF); ret *= 215; ret /= 100; /* Centidegrees scale */ ret -= 26440; val->intval = ret; return 0; } static int ltc4162l_get_term_current(struct ltc4162l_info *info, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, ®val); if (ret) return ret; /* Check if C_OVER_X_THRESHOLD is enabled */ if (!(regval & BIT(2))) { val->intval = 0; return 0; } ret = regmap_read(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, ®val); if (ret) return ret; /* 1.466μV / RSNSB amperes/LSB */ regval *= 14660u; regval /= info->rsnsb; val->intval = 100 * regval; return 0; } static int ltc4162l_set_term_current(struct ltc4162l_info *info, unsigned int value) { int ret; unsigned int regval; if (!value) { /* Disable en_c_over_x_term when set to zero */ return regmap_update_bits(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, BIT(2), 0); } regval = mult_frac(value, info->rsnsb, 14660u); regval /= 100u; ret = regmap_write(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, regval); if (ret) return ret; /* Set en_c_over_x_term after changing the threshold value */ return regmap_update_bits(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, BIT(2), BIT(2)); } /* Custom properties */ static const char * const ltc4162l_charge_status_name[] = { "ilim_reg_active", /* 32 */ "thermal_reg_active", "vin_uvcl_active", "iin_limit_active", "constant_current", "constant_voltage", "charger_off" /* 0 */ }; static ssize_t charge_status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); const char *result = ltc4162l_charge_status_name[ ARRAY_SIZE(ltc4162l_charge_status_name) - 1]; unsigned int regval; unsigned int mask; unsigned int index; int ret; ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, ®val); if (ret) return ret; /* Only one bit is set according to datasheet, let's be safe here */ for (mask = 32, index = 0; mask != 0; mask >>= 1, ++index) { if (regval & mask) { result = ltc4162l_charge_status_name[index]; break; } } return sprintf(buf, "%s\n", result); } static DEVICE_ATTR_RO(charge_status); static ssize_t vbat_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); union power_supply_propval val; int ret; ret = ltc4162l_get_vbat(info, LTC4162L_VBAT, &val); if (ret) return ret; return sprintf(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(vbat); static ssize_t vbat_avg_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); union power_supply_propval val; int ret; ret = ltc4162l_get_vbat(info, LTC4162L_VBAT_FILT, &val); if (ret) return ret; return sprintf(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(vbat_avg); static ssize_t ibat_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); union power_supply_propval val; int ret; ret = ltc4162l_get_ibat(info, &val); if (ret) return ret; return sprintf(buf, "%d\n", val.intval); } static DEVICE_ATTR_RO(ibat); static ssize_t force_telemetry_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_CONFIG_BITS_REG, ®val); if (ret) return ret; return sprintf(buf, "%u\n", regval & BIT(2) ? 1 : 0); } static ssize_t force_telemetry_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); int ret; unsigned int value; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; ret = regmap_update_bits(info->regmap, LTC4162L_CONFIG_BITS_REG, BIT(2), value ? BIT(2) : 0); if (ret < 0) return ret; return count; } static DEVICE_ATTR_RW(force_telemetry); static ssize_t arm_ship_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); unsigned int regval; int ret; ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, ®val); if (ret) return ret; return sprintf(buf, "%u\n", regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0); } static ssize_t arm_ship_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); int ret; unsigned int value; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; ret = regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE, value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0); if (ret < 0) return ret; return count; } static DEVICE_ATTR_RW(arm_ship_mode); static struct attribute *ltc4162l_sysfs_entries[] = { &dev_attr_charge_status.attr, &dev_attr_ibat.attr, &dev_attr_vbat.attr, &dev_attr_vbat_avg.attr, &dev_attr_force_telemetry.attr, &dev_attr_arm_ship_mode.attr, NULL, }; static const struct attribute_group ltc4162l_attr_group = { .name = NULL, /* put in device directory */ .attrs = ltc4162l_sysfs_entries, }; static const struct attribute_group *ltc4162l_attr_groups[] = { <c4162l_attr_group, NULL, }; static int ltc4162l_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ltc4162l_info *info = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: return ltc4162l_get_status(info, val); case POWER_SUPPLY_PROP_CHARGE_TYPE: return ltc4162l_get_charge_type(info, val); case POWER_SUPPLY_PROP_HEALTH: return ltc4162l_get_health(info, val); case POWER_SUPPLY_PROP_ONLINE: return ltc4162l_get_online(info, val); case POWER_SUPPLY_PROP_VOLTAGE_NOW: return ltc4162l_get_input_voltage(info, val); case POWER_SUPPLY_PROP_CURRENT_NOW: return ltc4162l_get_input_current(info, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return ltc4162l_get_icharge(info, LTC4162L_ICHARGE_DAC, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return ltc4162l_get_icharge(info, LTC4162L_CHARGE_CURRENT_SETTING, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: return ltc4162l_get_vcharge(info, LTC4162L_VCHARGE_DAC, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: return ltc4162l_get_vcharge(info, LTC4162L_VCHARGE_SETTING, val); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return ltc4162l_get_iin_limit_dac(info, val); case POWER_SUPPLY_PROP_TEMP: return ltc4162l_get_die_temp(info, val); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return ltc4162l_get_term_current(info, val); default: return -EINVAL; } } static int ltc4162l_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct ltc4162l_info *info = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return ltc4162l_set_icharge(info, LTC4162L_CHARGE_CURRENT_SETTING, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: return ltc4162l_set_vcharge(info, LTC4162L_VCHARGE_SETTING, val->intval); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return ltc4162l_set_iin_limit(info, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return ltc4162l_set_term_current(info, val->intval); default: return -EINVAL; } } static int ltc4162l_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return 1; default: return 0; } } /* Charger power supply property routines */ static enum power_supply_property ltc4162l_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, 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, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, }; static const struct power_supply_desc ltc4162l_desc = { .name = "ltc4162-l", .type = POWER_SUPPLY_TYPE_MAINS, .properties = ltc4162l_properties, .num_properties = ARRAY_SIZE(ltc4162l_properties), .get_property = ltc4162l_get_property, .set_property = ltc4162l_set_property, .property_is_writeable = ltc4162l_property_is_writeable, }; static bool ltc4162l_is_writeable_reg(struct device *dev, unsigned int reg) { /* all registers up to this one are writeable */ if (reg <= LTC4162L_CHARGER_CONFIG_BITS) return true; /* The ALERTS registers can be written to clear alerts */ if (reg >= LTC4162L_LIMIT_ALERTS_REG && reg <= LTC4162L_CHARGE_STATUS_ALERTS_REG) return true; return false; } static bool ltc4162l_is_volatile_reg(struct device *dev, unsigned int reg) { /* all registers after this one are read-only status registers */ return reg > LTC4162L_CHARGER_CONFIG_BITS; } static const struct regmap_config ltc4162l_regmap_config = { .reg_bits = 8, .val_bits = 16, .val_format_endian = REGMAP_ENDIAN_LITTLE, .writeable_reg = ltc4162l_is_writeable_reg, .volatile_reg = ltc4162l_is_volatile_reg, .max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC, .cache_type = REGCACHE_RBTREE, }; static void ltc4162l_clear_interrupts(struct ltc4162l_info *info) { /* Acknowledge interrupt to chip by clearing all events */ regmap_write(info->regmap, LTC4162L_LIMIT_ALERTS_REG, 0); regmap_write(info->regmap, LTC4162L_CHARGER_STATE_ALERTS_REG, 0); regmap_write(info->regmap, LTC4162L_CHARGE_STATUS_ALERTS_REG, 0); } static int ltc4162l_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = client->adapter; struct device *dev = &client->dev; struct ltc4162l_info *info; struct power_supply_config ltc4162l_config = {}; u32 value; int ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { dev_err(dev, "No support for SMBUS_WORD_DATA\n"); return -ENODEV; } info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; info->client = client; i2c_set_clientdata(client, info); info->regmap = devm_regmap_init_i2c(client, <c4162l_regmap_config); if (IS_ERR(info->regmap)) { dev_err(dev, "Failed to initialize register map\n"); return PTR_ERR(info->regmap); } ret = device_property_read_u32(dev, "lltc,rsnsb-micro-ohms", &info->rsnsb); if (ret) { dev_err(dev, "Missing lltc,rsnsb-micro-ohms property\n"); return ret; } if (!info->rsnsb) return -EINVAL; ret = device_property_read_u32(dev, "lltc,rsnsi-micro-ohms", &info->rsnsi); if (ret) { dev_err(dev, "Missing lltc,rsnsi-micro-ohms property\n"); return ret; } if (!info->rsnsi) return -EINVAL; if (!device_property_read_u32(dev, "lltc,cell-count", &value)) info->cell_count = value; ltc4162l_config.of_node = dev->of_node; ltc4162l_config.drv_data = info; ltc4162l_config.attr_grp = ltc4162l_attr_groups; info->charger = devm_power_supply_register(dev, <c4162l_desc, <c4162l_config); if (IS_ERR(info->charger)) { dev_err(dev, "Failed to register charger\n"); return PTR_ERR(info->charger); } /* Disable the threshold alerts, we're not using them */ regmap_write(info->regmap, LTC4162L_EN_LIMIT_ALERTS_REG, 0); /* Enable interrupts on all status changes */ regmap_write(info->regmap, LTC4162L_EN_CHARGER_STATE_ALERTS_REG, 0x1fff); regmap_write(info->regmap, LTC4162L_EN_CHARGE_STATUS_ALERTS_REG, 0x1f); ltc4162l_clear_interrupts(info); return 0; } static void ltc4162l_alert(struct i2c_client *client, enum i2c_alert_protocol type, unsigned int flag) { struct ltc4162l_info *info = i2c_get_clientdata(client); if (type != I2C_PROTOCOL_SMBUS_ALERT) return; ltc4162l_clear_interrupts(info); power_supply_changed(info->charger); } static const struct i2c_device_id ltc4162l_i2c_id_table[] = { { "ltc4162-l", 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table); static const struct of_device_id ltc4162l_of_match[] = { { .compatible = "lltc,ltc4162-l", }, { }, }; MODULE_DEVICE_TABLE(of, ltc4162l_of_match); static struct i2c_driver ltc4162l_driver = { .probe = ltc4162l_probe, .alert = ltc4162l_alert, .id_table = ltc4162l_i2c_id_table, .driver = { .name = "ltc4162-l-charger", .of_match_table = of_match_ptr(ltc4162l_of_match), }, }; module_i2c_driver(ltc4162l_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mike Looijmans "); MODULE_DESCRIPTION("LTC4162-L charger driver");