/* * 1-Wire implementation for Maxim Semiconductor * MAX7211/MAX17215 standalone fuel gauge chip * * Copyright (C) 2017 Radioavionica Corporation * Author: Alex A. Mihaylov * * Use consistent with the GNU GPL is permitted, * provided that this copyright notice is * preserved in its entirety in all copies and derived works. * */ #include #include #include #include #include #define W1_MAX1721X_FAMILY_ID 0x26 #define DEF_DEV_NAME_MAX17211 "MAX17211" #define DEF_DEV_NAME_MAX17215 "MAX17215" #define DEF_DEV_NAME_UNKNOWN "UNKNOWN" #define DEF_MFG_NAME "MAXIM" #define PSY_MAX_NAME_LEN 32 /* Number of valid register addresses in W1 mode */ #define MAX1721X_MAX_REG_NR 0x1EF /* Factory settings (nonvolatile registers) (W1 specific) */ #define MAX1721X_REG_NRSENSE 0x1CF /* RSense in 10^-5 Ohm */ /* Strings */ #define MAX1721X_REG_MFG_STR 0x1CC #define MAX1721X_REG_MFG_NUMB 3 #define MAX1721X_REG_DEV_STR 0x1DB #define MAX1721X_REG_DEV_NUMB 5 /* HEX Strings */ #define MAX1721X_REG_SER_HEX 0x1D8 /* MAX172XX Output Registers for W1 chips */ #define MAX172XX_REG_STATUS 0x000 /* status reg */ #define MAX172XX_BAT_PRESENT (1<<4) /* battery connected bit */ #define MAX172XX_REG_DEVNAME 0x021 /* chip config */ #define MAX172XX_DEV_MASK 0x000F /* chip type mask */ #define MAX172X1_DEV 0x0001 #define MAX172X5_DEV 0x0005 #define MAX172XX_REG_TEMP 0x008 /* Temperature */ #define MAX172XX_REG_BATT 0x0DA /* Battery voltage */ #define MAX172XX_REG_CURRENT 0x00A /* Actual current */ #define MAX172XX_REG_AVGCURRENT 0x00B /* Average current */ #define MAX172XX_REG_REPSOC 0x006 /* Percentage of charge */ #define MAX172XX_REG_DESIGNCAP 0x018 /* Design capacity */ #define MAX172XX_REG_REPCAP 0x005 /* Average capacity */ #define MAX172XX_REG_TTE 0x011 /* Time to empty */ #define MAX172XX_REG_TTF 0x020 /* Time to full */ struct max17211_device_info { char name[PSY_MAX_NAME_LEN]; struct power_supply *bat; struct power_supply_desc bat_desc; struct device *w1_dev; struct regmap *regmap; /* battery design format */ unsigned int rsense; /* in tenths uOhm */ char DeviceName[2 * MAX1721X_REG_DEV_NUMB + 1]; char ManufacturerName[2 * MAX1721X_REG_MFG_NUMB + 1]; char SerialNumber[13]; /* see get_sn_str() later for comment */ }; /* Convert regs value to power_supply units */ static inline int max172xx_time_to_ps(unsigned int reg) { return reg * 5625 / 1000; /* in sec. */ } static inline int max172xx_percent_to_ps(unsigned int reg) { return reg / 256; /* in percent from 0 to 100 */ } static inline int max172xx_voltage_to_ps(unsigned int reg) { return reg * 1250; /* in uV */ } static inline int max172xx_capacity_to_ps(unsigned int reg) { return reg * 500; /* in uAh */ } /* * Current and temperature is signed values, so unsigned regs * value must be converted to signed type */ static inline int max172xx_temperature_to_ps(unsigned int reg) { int val = (int16_t)(reg); return val * 10 / 256; /* in tenths of deg. C */ } /* * Calculating current registers resolution: * * RSense stored in 10^-5 Ohm, so measurement voltage must be * in 10^-11 Volts for get current in uA. * 16 bit current reg fullscale +/-51.2mV is 102400 uV. * So: 102400 / 65535 * 10^5 = 156252 */ static inline int max172xx_current_to_voltage(unsigned int reg) { int val = (int16_t)(reg); return val * 156252; } static inline struct max17211_device_info * to_device_info(struct power_supply *psy) { return power_supply_get_drvdata(psy); } static int max1721x_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max17211_device_info *info = to_device_info(psy); unsigned int reg = 0; int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: /* * POWER_SUPPLY_PROP_PRESENT will always readable via * sysfs interface. Value return 0 if battery not * present or unaccessible via W1. */ val->intval = regmap_read(info->regmap, MAX172XX_REG_STATUS, ®) ? 0 : !(reg & MAX172XX_BAT_PRESENT); break; case POWER_SUPPLY_PROP_CAPACITY: ret = regmap_read(info->regmap, MAX172XX_REG_REPSOC, ®); val->intval = max172xx_percent_to_ps(reg); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = regmap_read(info->regmap, MAX172XX_REG_BATT, ®); val->intval = max172xx_voltage_to_ps(reg); break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ret = regmap_read(info->regmap, MAX172XX_REG_DESIGNCAP, ®); val->intval = max172xx_capacity_to_ps(reg); break; case POWER_SUPPLY_PROP_CHARGE_AVG: ret = regmap_read(info->regmap, MAX172XX_REG_REPCAP, ®); val->intval = max172xx_capacity_to_ps(reg); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: ret = regmap_read(info->regmap, MAX172XX_REG_TTE, ®); val->intval = max172xx_time_to_ps(reg); break; case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: ret = regmap_read(info->regmap, MAX172XX_REG_TTF, ®); val->intval = max172xx_time_to_ps(reg); break; case POWER_SUPPLY_PROP_TEMP: ret = regmap_read(info->regmap, MAX172XX_REG_TEMP, ®); val->intval = max172xx_temperature_to_ps(reg); break; /* We need signed current, so must cast info->rsense to signed type */ case POWER_SUPPLY_PROP_CURRENT_NOW: ret = regmap_read(info->regmap, MAX172XX_REG_CURRENT, ®); val->intval = max172xx_current_to_voltage(reg) / (int)info->rsense; break; case POWER_SUPPLY_PROP_CURRENT_AVG: ret = regmap_read(info->regmap, MAX172XX_REG_AVGCURRENT, ®); val->intval = max172xx_current_to_voltage(reg) / (int)info->rsense; break; /* * Strings already received and inited by probe. * We do dummy read for check battery still available. */ case POWER_SUPPLY_PROP_MODEL_NAME: ret = regmap_read(info->regmap, MAX1721X_REG_DEV_STR, ®); val->strval = info->DeviceName; break; case POWER_SUPPLY_PROP_MANUFACTURER: ret = regmap_read(info->regmap, MAX1721X_REG_MFG_STR, ®); val->strval = info->ManufacturerName; break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = regmap_read(info->regmap, MAX1721X_REG_SER_HEX, ®); val->strval = info->SerialNumber; break; default: ret = -EINVAL; } return ret; } static enum power_supply_property max1721x_battery_props[] = { /* int */ POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, /* strings */ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, }; static int get_string(struct max17211_device_info *info, uint16_t reg, uint8_t nr, char *str) { unsigned int val; if (!str || !(reg == MAX1721X_REG_MFG_STR || reg == MAX1721X_REG_DEV_STR)) return -EFAULT; while (nr--) { if (regmap_read(info->regmap, reg++, &val)) return -EFAULT; *str++ = val>>8 & 0x00FF; *str++ = val & 0x00FF; } return 0; } /* Maxim say: Serial number is a hex string up to 12 hex characters */ static int get_sn_string(struct max17211_device_info *info, char *str) { unsigned int val[3]; if (!str) return -EFAULT; if (regmap_read(info->regmap, MAX1721X_REG_SER_HEX, &val[0])) return -EFAULT; if (regmap_read(info->regmap, MAX1721X_REG_SER_HEX + 1, &val[1])) return -EFAULT; if (regmap_read(info->regmap, MAX1721X_REG_SER_HEX + 2, &val[2])) return -EFAULT; snprintf(str, 13, "%04X%04X%04X", val[0], val[1], val[2]); return 0; } /* * MAX1721x registers description for w1-regmap */ static const struct regmap_range max1721x_allow_range[] = { regmap_reg_range(0, 0xDF), /* volatile data */ regmap_reg_range(0x180, 0x1DF), /* non-volatile memory */ regmap_reg_range(0x1E0, 0x1EF), /* non-volatile history (unused) */ }; static const struct regmap_range max1721x_deny_range[] = { /* volatile data unused registers */ regmap_reg_range(0x24, 0x26), regmap_reg_range(0x30, 0x31), regmap_reg_range(0x33, 0x34), regmap_reg_range(0x37, 0x37), regmap_reg_range(0x3B, 0x3C), regmap_reg_range(0x40, 0x41), regmap_reg_range(0x43, 0x44), regmap_reg_range(0x47, 0x49), regmap_reg_range(0x4B, 0x4C), regmap_reg_range(0x4E, 0xAF), regmap_reg_range(0xB1, 0xB3), regmap_reg_range(0xB5, 0xB7), regmap_reg_range(0xBF, 0xD0), regmap_reg_range(0xDB, 0xDB), /* hole between volatile and non-volatile registers */ regmap_reg_range(0xE0, 0x17F), }; static const struct regmap_access_table max1721x_regs = { .yes_ranges = max1721x_allow_range, .n_yes_ranges = ARRAY_SIZE(max1721x_allow_range), .no_ranges = max1721x_deny_range, .n_no_ranges = ARRAY_SIZE(max1721x_deny_range), }; /* * Model Gauge M5 Algorithm output register * Volatile data (must not be cached) */ static const struct regmap_range max1721x_volatile_allow[] = { regmap_reg_range(0, 0xDF), }; static const struct regmap_access_table max1721x_volatile_regs = { .yes_ranges = max1721x_volatile_allow, .n_yes_ranges = ARRAY_SIZE(max1721x_volatile_allow), }; /* * W1-regmap config */ static const struct regmap_config max1721x_regmap_w1_config = { .reg_bits = 16, .val_bits = 16, .rd_table = &max1721x_regs, .volatile_table = &max1721x_volatile_regs, .max_register = MAX1721X_MAX_REG_NR, }; static int devm_w1_max1721x_add_device(struct w1_slave *sl) { struct power_supply_config psy_cfg = {}; struct max17211_device_info *info; info = devm_kzalloc(&sl->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; sl->family_data = (void *)info; info->w1_dev = &sl->dev; /* * power_supply class battery name translated from W1 slave device * unique ID (look like 26-0123456789AB) to "max1721x-0123456789AB\0" * so, 26 (device family) correspond to max1721x devices. * Device name still unique for any number of connected devices. */ snprintf(info->name, sizeof(info->name), "max1721x-%012X", (unsigned int)sl->reg_num.id); info->bat_desc.name = info->name; /* * FixMe: battery device name exceed max len for thermal_zone device * name and translation to thermal_zone must be disabled. */ info->bat_desc.no_thermal = true; info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; info->bat_desc.properties = max1721x_battery_props; info->bat_desc.num_properties = ARRAY_SIZE(max1721x_battery_props); info->bat_desc.get_property = max1721x_battery_get_property; psy_cfg.drv_data = info; /* regmap init */ info->regmap = devm_regmap_init_w1(info->w1_dev, &max1721x_regmap_w1_config); if (IS_ERR(info->regmap)) { int err = PTR_ERR(info->regmap); dev_err(info->w1_dev, "Failed to allocate register map: %d\n", err); return err; } /* rsense init */ info->rsense = 0; if (regmap_read(info->regmap, MAX1721X_REG_NRSENSE, &info->rsense)) { dev_err(info->w1_dev, "Can't read RSense. Hardware error.\n"); return -ENODEV; } if (!info->rsense) { dev_warn(info->w1_dev, "RSense not calibrated, set 10 mOhms!\n"); info->rsense = 1000; /* in regs in 10^-5 */ } dev_info(info->w1_dev, "RSense: %d mOhms.\n", info->rsense / 100); if (get_string(info, MAX1721X_REG_MFG_STR, MAX1721X_REG_MFG_NUMB, info->ManufacturerName)) { dev_err(info->w1_dev, "Can't read manufacturer. Hardware error.\n"); return -ENODEV; } if (!info->ManufacturerName[0]) strncpy(info->ManufacturerName, DEF_MFG_NAME, 2 * MAX1721X_REG_MFG_NUMB); if (get_string(info, MAX1721X_REG_DEV_STR, MAX1721X_REG_DEV_NUMB, info->DeviceName)) { dev_err(info->w1_dev, "Can't read device. Hardware error.\n"); return -ENODEV; } if (!info->DeviceName[0]) { unsigned int dev_name; if (regmap_read(info->regmap, MAX172XX_REG_DEVNAME, &dev_name)) { dev_err(info->w1_dev, "Can't read device name reg.\n"); return -ENODEV; } switch (dev_name & MAX172XX_DEV_MASK) { case MAX172X1_DEV: strncpy(info->DeviceName, DEF_DEV_NAME_MAX17211, 2 * MAX1721X_REG_DEV_NUMB); break; case MAX172X5_DEV: strncpy(info->DeviceName, DEF_DEV_NAME_MAX17215, 2 * MAX1721X_REG_DEV_NUMB); break; default: strncpy(info->DeviceName, DEF_DEV_NAME_UNKNOWN, 2 * MAX1721X_REG_DEV_NUMB); } } if (get_sn_string(info, info->SerialNumber)) { dev_err(info->w1_dev, "Can't read serial. Hardware error.\n"); return -ENODEV; } info->bat = devm_power_supply_register(&sl->dev, &info->bat_desc, &psy_cfg); if (IS_ERR(info->bat)) { dev_err(info->w1_dev, "failed to register battery\n"); return PTR_ERR(info->bat); } return 0; } static const struct w1_family_ops w1_max1721x_fops = { .add_slave = devm_w1_max1721x_add_device, }; static struct w1_family w1_max1721x_family = { .fid = W1_MAX1721X_FAMILY_ID, .fops = &w1_max1721x_fops, }; module_w1_family(w1_max1721x_family); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex A. Mihaylov "); MODULE_DESCRIPTION("Maxim MAX17211/MAX17215 Fuel Gauage IC driver"); MODULE_ALIAS("w1-family-" __stringify(W1_MAX1721X_FAMILY_ID));