diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index 9842199..1f41abf 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -26,6 +26,7 @@ #include #include #include +#include #define AXP20X_OFF 0x80 @@ -606,6 +607,565 @@ static void axp20x_power_off(void) AXP20X_OFF); } +#define kobj_to_device(x) container_of(x, struct device, kobj) + +static ssize_t axp20x_read_special(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int ret = 0, i; + unsigned int res; + s32 lval1 = 0, lval2 = 0, lval; + struct axp20x_dev *axp; + const char *subsystem; + struct device *dev; + + subsystem = kobject_name(kobj); + dev = kobj_to_device(kobj->parent); + axp = dev_get_drvdata(dev); + + // DEBUG + //dev_info(axp->dev, "read_cumulative: reading attribute %s of object %s\n", attr->attr.name, subsystem); + + if (strcmp(subsystem, "battery") == 0) { + if (strcmp(attr->attr.name, "consumption") == 0) { + for (i = 0; i < 3; i++) { + ret |= regmap_read(axp->regmap, AXP20X_PWR_BATT_H + i, &res); + lval1 |= res << (8 * (2 - i)); + } + lval = 2 * lval1 * 1100 * 500 / 1000 / 1000; + } + else if (strcmp(attr->attr.name, "capacity") == 0) { + for (i = 0; i < 4; i++) { + ret |= regmap_read(axp->regmap, AXP20X_CHRG_CC_31_24 + i, &res); + lval1 |= res << (8 * (3 - i)); + } + for (i = 0; i < 4; i++) { + ret |= regmap_read(axp->regmap, AXP20X_DISCHRG_CC_31_24 + i, &res); + lval2 |= res << (8 * (3 - i)); + } + lval = 65536 * 500 * (lval1 - lval2) / 3600 / 100; + } + else if (strcmp(attr->attr.name, "percentage") == 0) { + ret = regmap_read(axp->regmap, AXP20X_FG_RES, &res); + lval = res & 0x7f; + } + else + return -EINVAL; + } + else + return -EINVAL; + + if (ret < 0) { + dev_warn(axp->dev, "Unable to read parameter: %d\n", ret); + return ret; + } + return sprintf(buf, "%d\n", lval); +} + +static ssize_t axp20x_read_status(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int val, ret; + unsigned int res, reg, bit; + struct device *dev; + struct axp20x_dev *axp; + const char *subsystem; + + subsystem = kobject_name(kobj); + dev = kobj_to_device(kobj->parent); + axp = dev_get_drvdata(dev); + + // DEBUG + //dev_info(axp->dev, "write_status: writing attribute %s of object %s\n", attr->attr.name, subsystem); + + if (strcmp(subsystem, "ac") == 0) { + if (strcmp(attr->attr.name, "connected") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 7; + } + else if (strcmp(attr->attr.name, "used") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 6; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "vbus") == 0) { + if (strcmp(attr->attr.name, "connected") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 5; + } + else if (strcmp(attr->attr.name, "used") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 4; + } + else if (strcmp(attr->attr.name, "is_strong") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 3; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "battery") == 0) { + if (strcmp(attr->attr.name, "connected") == 0) { + reg = AXP20X_PWR_OP_MODE; + bit = 5; + } + else if (strcmp(attr->attr.name, "charging") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 2; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "pmu") == 0) { + if (strcmp(attr->attr.name, "cold_boot") == 0) { + reg = AXP20X_PWR_INPUT_STATUS; + bit = 0; + } + else if (strcmp(attr->attr.name, "overheat") == 0) { + reg = AXP20X_PWR_OP_MODE; + bit = 7; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "charger") == 0) { + if (strcmp(attr->attr.name, "charge_in_progress") == 0) { + reg = AXP20X_PWR_OP_MODE; + bit = 6; + } + else if (strcmp(attr->attr.name, "activation_mode") == 0) { + reg = AXP20X_PWR_OP_MODE; + bit = 3; + } + else if (strcmp(attr->attr.name, "current_lowered") == 0) { + reg = AXP20X_PWR_OP_MODE; + bit = 2; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "control") == 0) { + if (strcmp(attr->attr.name, "set_vbus_direct_mode") == 0) { + reg = AXP20X_VBUS_IPSOUT_MGMT; + bit = 6; + } + else if (strcmp(attr->attr.name, "reset_coulomb_counter") == 0) { + reg = AXP20X_CC_CTRL; + bit = 5; + } + else + return -EINVAL; + } + else + return -EINVAL; + + ret = regmap_read(axp->regmap, reg, &res); + if (ret < 0) { + dev_warn(axp->dev, "Unable to read parameter: %d\n", ret); + return ret; + } + val = (res & BIT(bit)) == BIT(bit) ? 1 : 0; + return sprintf(buf, "%d\n", val); +} + +static ssize_t axp20x_write_status(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) +{ + int var, ret; + unsigned int reg, bit; + struct device *dev; + struct axp20x_dev *axp; + const char *subsystem; + + subsystem = kobject_name(kobj); + dev = kobj_to_device(kobj->parent); + axp = dev_get_drvdata(dev); + + // DEBUG + //dev_info(axp->dev, "write_status: writing attribute %s of object %s", attr->attr.name, subsystem); + + ret = kstrtoint(buf, 10, &var); + if (ret < 0) + return ret; + + if (strcmp(subsystem, "control") == 0) { + if (strcmp(attr->attr.name, "set_vbus_direct_mode") == 0) { + reg = AXP20X_VBUS_IPSOUT_MGMT; + bit = 6; + } + else if (strcmp(attr->attr.name, "reset_coulomb_counter") == 0) { + reg = AXP20X_CC_CTRL; + bit = 5; + } + else + return -EINVAL; + } + else + return -EINVAL; + + ret = regmap_update_bits(axp->regmap, reg, var ? BIT(bit) : 0, BIT(bit)); + if (ret) + dev_warn(axp->dev, "Unable to write value: %d", ret); + return count; +} + +static int axp20x_averaging_helper(struct regmap *reg_map, unsigned int reg_h, + unsigned int width) +{ + long acc = 0; + int ret, i; + + for (i = 0; i < 3; i++) { + ret = axp20x_read_variable_width(reg_map, reg_h, width); + if (ret < 0) + return ret; + acc += ret; + // For 100Hz sampling frequency + msleep(20); + } + return (int)(acc / 3); +} + +static ssize_t axp20x_read_volatile(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int val, ret, scale; + unsigned int reg, width = 12, offset = 0; + struct device *dev; + struct axp20x_dev *axp; + const char *subsystem; + + subsystem = kobject_name(kobj); + dev = kobj_to_device(kobj->parent); + axp = dev_get_drvdata(dev); + + // DEBUG + //dev_info(axp->dev, "read_volatile: reading attribute %s of object %s\n", attr->attr.name, subsystem); + + if (strcmp(subsystem, "ac") == 0) { + if (strcmp(attr->attr.name, "voltage") == 0) + { + reg = AXP20X_ACIN_V_ADC_H; + scale = 1700; + } + else if (strcmp(attr->attr.name, "amperage") == 0) + { + reg = AXP20X_ACIN_I_ADC_H; + scale = 625; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "vbus") == 0) { + if (strcmp(attr->attr.name, "voltage") == 0) + { + reg = AXP20X_VBUS_V_ADC_H; + scale = 1700; + } + else if (strcmp(attr->attr.name, "amperage") == 0) + { + reg = AXP20X_VBUS_I_ADC_H; + scale = 375; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "battery") == 0) { + if (strcmp(attr->attr.name, "voltage") == 0) + { + reg = AXP20X_BATT_V_H; + scale = 1100; + } + else if (strcmp(attr->attr.name, "discharge_current") == 0) + { + reg = AXP20X_BATT_DISCHRG_I_H; + scale = 500; + width = 13; + } + else if (strcmp(attr->attr.name, "ts_voltage") == 0) + { + reg = AXP20X_TS_IN_H; + scale = 800; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "pmu") == 0) { + if (strcmp(attr->attr.name, "internal_temp") == 0) + { + reg = AXP20X_TEMP_ADC_H; + scale = 100; + offset = 144700; + } + else if (strcmp(attr->attr.name, "output_voltage") == 0) + { + reg = AXP20X_IPSOUT_V_HIGH_H; + scale = 1400; + } + else + return -EINVAL; + } + else if (strcmp(subsystem, "charger") == 0) { + if (strcmp(attr->attr.name, "charge_current") == 0) + { + reg = AXP20X_BATT_CHRG_I_H; + scale = 500; + } + else + return -EINVAL; + } + else + return -EINVAL; + + ret = axp20x_averaging_helper(axp->regmap, reg, width); + + if (ret < 0) { + dev_warn(axp->dev, "Unable to read parameter: %d\n", ret); + return ret; + } + val = ret * scale - offset; + return sprintf(buf, "%d\n", val); +} + +/* + sysfs attributes +*/ +// AC IN +static struct kobj_attribute ac_in_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute ac_in_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute ac_in_connected = __ATTR(connected, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute ac_in_used = __ATTR(used, S_IRUGO, axp20x_read_status, NULL); + +static struct attribute *axp20x_attributes_ac[] = { + &ac_in_voltage.attr, + &ac_in_amperage.attr, + &ac_in_connected.attr, + &ac_in_used.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_ac = { + .attrs = axp20x_attributes_ac, +}; + +// VBUS +static struct kobj_attribute vbus_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute vbus_amperage = __ATTR(amperage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute vbus_connected = __ATTR(connected, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute vbus_used = __ATTR(used, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute vbus_is_strong = __ATTR(is_strong, S_IRUGO, axp20x_read_status, NULL); + +static struct attribute *axp20x_attributes_vbus[] = { + &vbus_voltage.attr, + &vbus_amperage.attr, + &vbus_connected.attr, + &vbus_used.attr, + &vbus_is_strong.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_vbus = { + .attrs = axp20x_attributes_vbus, +}; + +// Battery +static struct kobj_attribute batt_voltage = __ATTR(voltage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute batt_discharge_current = __ATTR(discharge_current, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute batt_ts_voltage = __ATTR(ts_voltage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute batt_consumption = __ATTR(consumption, S_IRUGO, axp20x_read_special, NULL); +static struct kobj_attribute batt_percentage = __ATTR(percentage, S_IRUGO, axp20x_read_special, NULL); +static struct kobj_attribute batt_capacity = __ATTR(capacity, S_IRUGO, axp20x_read_special, NULL); +static struct kobj_attribute batt_connected = __ATTR(connected, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute batt_charging = __ATTR(charging, S_IRUGO, axp20x_read_status, NULL); + +static struct attribute *axp20x_attributes_battery[] = { + &batt_voltage.attr, + &batt_discharge_current.attr, + &batt_ts_voltage.attr, + &batt_consumption.attr, + &batt_percentage.attr, + &batt_capacity.attr, + &batt_connected.attr, + &batt_charging.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_battery = { + .attrs = axp20x_attributes_battery, +}; + +// PMU +static struct kobj_attribute pmu_internal_temp = __ATTR(internal_temp, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute pmu_output_voltage = __ATTR(output_voltage, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute pmu_cold_boot = __ATTR(cold_boot, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute pmu_overheat = __ATTR(overheat, S_IRUGO, axp20x_read_status, NULL); + +static struct attribute *axp20x_attributes_pmu[] = { + &pmu_internal_temp.attr, + &pmu_output_voltage.attr, + &pmu_cold_boot.attr, + &pmu_overheat.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_pmu = { + .attrs = axp20x_attributes_pmu, +}; + +// Charger +static struct kobj_attribute batt_charge_current = __ATTR(charge_current, S_IRUGO, axp20x_read_volatile, NULL); +static struct kobj_attribute batt_charge_in_progress = __ATTR(charge_in_progress, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute batt_charge_activation_mode = __ATTR(activation_mode, S_IRUGO, axp20x_read_status, NULL); +static struct kobj_attribute batt_charge_current_lowered = __ATTR(current_lowered, S_IRUGO, axp20x_read_status, NULL); + +static struct attribute *axp20x_attributes_charger[] = { + &batt_charge_current.attr, + &batt_charge_in_progress.attr, + &batt_charge_activation_mode.attr, + &batt_charge_current_lowered.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_charger = { + .attrs = axp20x_attributes_charger, +}; + +// Control (writeable) +static struct kobj_attribute control_vbus_direct_mode = __ATTR(set_vbus_direct_mode, S_IRUGO | S_IWUSR, + axp20x_read_status, axp20x_write_status); +static struct kobj_attribute control_reset_coulomb_counter = __ATTR(reset_coulomb_counter, S_IRUGO | S_IWUSR, + axp20x_read_status, axp20x_write_status); + +static struct attribute *axp20x_attributes_control[] = { + &control_vbus_direct_mode.attr, + &control_reset_coulomb_counter.attr, + NULL, +}; + +static const struct attribute_group axp20x_sysfs_attr_group_control = { + .attrs = axp20x_attributes_control, +}; + +/* + sysfs register/inregister functions +*/ + +static struct { + struct kobject *ac; + struct kobject *vbus; + struct kobject *battery; + struct kobject *pmu; + struct kobject *charger; + struct kobject *control; +} subsystems; + +static int axp20x_sysfs_init(struct axp20x_dev *axp) +{ + int ret; + unsigned int res; + + // Enable all ADC channels in first register + ret = regmap_write(axp->regmap, AXP20X_ADC_EN1, 0xFF); + if (ret) + dev_warn(axp->dev, "Unable to enable ADC: %d", ret); + + // Set ADC sampling frequency to 100Hz (default is 25) + // Always measure battery temperature (default: only when charging) + ret = regmap_update_bits(axp->regmap, AXP20X_ADC_RATE, 0xC3, 0x83); + if (ret) + dev_warn(axp->dev, "Unable to set ADC frequency and TS current output: %d", ret); + + // Enable fuel gauge and coulomb counter + ret = regmap_update_bits(axp->regmap, AXP20X_FG_RES, 0x80, 0x80); + if (ret) + dev_warn(axp->dev, "Unable to enable battery fuel gauge: %d", ret); + ret = regmap_update_bits(axp->regmap, AXP20X_CC_CTRL, 0x80, 0x80); + if (ret) + dev_warn(axp->dev, "Unable to enable battery coulomb counter: %d", ret); + + // Enable battery detection + ret = regmap_read(axp->regmap, AXP20X_OFF_CTRL, &res); + if (ret == 0) { + if ((res & 0x40) != 0x40) { + dev_info(axp->dev, "Battery detection is disabled, enabling"); + ret = regmap_update_bits(axp->regmap, AXP20X_OFF_CTRL, 0x40, 0x40); + if (ret) + dev_warn(axp->dev, "Unable to enable battery detection: %d", ret); + } + } + else + dev_warn(axp->dev, "Unable to read register AXP20X_OFF_CTRL: %d", ret); + + subsystems.ac = kobject_create_and_add("ac", &axp->dev->kobj); + if (subsystems.ac != NULL) { + ret = sysfs_create_group(subsystems.ac, &axp20x_sysfs_attr_group_ac); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: ac: %d", ret); + kobject_put(subsystems.ac); + } + } + subsystems.vbus = kobject_create_and_add("vbus", &axp->dev->kobj); + if (subsystems.ac != NULL) { + ret = sysfs_create_group(subsystems.vbus, &axp20x_sysfs_attr_group_vbus); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: vbus: %d", ret); + kobject_put(subsystems.vbus); + } + } + subsystems.battery = kobject_create_and_add("battery", &axp->dev->kobj); + if (subsystems.ac != NULL) { + ret = sysfs_create_group(subsystems.battery, &axp20x_sysfs_attr_group_battery); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: battery: %d", ret); + kobject_put(subsystems.battery); + } + } + subsystems.pmu = kobject_create_and_add("pmu", &axp->dev->kobj); + if (subsystems.ac != NULL) { + ret = sysfs_create_group(subsystems.pmu, &axp20x_sysfs_attr_group_pmu); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: pmu: %d", ret); + kobject_put(subsystems.pmu); + } + } + subsystems.charger = kobject_create_and_add("charger", &axp->dev->kobj); + if (subsystems.ac != NULL) { + ret = sysfs_create_group(subsystems.charger, &axp20x_sysfs_attr_group_charger); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: charger: %d", ret); + kobject_put(subsystems.charger); + } + } + subsystems.control = kobject_create_and_add("control", &axp->dev->kobj); + if (subsystems.control != NULL) { + ret = sysfs_create_group(subsystems.control, &axp20x_sysfs_attr_group_control); + if (ret) { + dev_warn(axp->dev, "Unable to register sysfs group: control: %d", ret); + kobject_put(subsystems.control); + } + } + + ret = sysfs_create_link_nowarn(power_kobj, &axp->dev->kobj, "axp_pmu"); + if (ret) + dev_warn(axp->dev, "Unable to create sysfs symlink: %d", ret); + return ret; +} + +static void axp20x_sysfs_exit(struct axp20x_dev *axp) +{ + sysfs_delete_link(power_kobj, &axp->dev->kobj, "axp_pmu"); + sysfs_remove_group(subsystems.control, &axp20x_sysfs_attr_group_control); + sysfs_remove_group(subsystems.charger, &axp20x_sysfs_attr_group_charger); + sysfs_remove_group(subsystems.pmu, &axp20x_sysfs_attr_group_pmu); + sysfs_remove_group(subsystems.battery, &axp20x_sysfs_attr_group_battery); + sysfs_remove_group(subsystems.vbus, &axp20x_sysfs_attr_group_vbus); + sysfs_remove_group(subsystems.ac, &axp20x_sysfs_attr_group_ac); + kobject_put(subsystems.control); + kobject_put(subsystems.charger); + kobject_put(subsystems.pmu); + kobject_put(subsystems.battery); + kobject_put(subsystems.vbus); + kobject_put(subsystems.ac); +} + static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev) { const struct acpi_device_id *acpi_id; @@ -711,6 +1271,10 @@ static int axp20x_i2c_probe(struct i2c_client *i2c, pm_power_off = axp20x_power_off; } + if (axp20x->variant == AXP209_ID || axp20x->variant == AXP202_ID) { + axp20x_sysfs_init(axp20x); + } + dev_info(&i2c->dev, "AXP20X driver loaded\n"); return 0; @@ -720,6 +1284,10 @@ static int axp20x_i2c_remove(struct i2c_client *i2c) { struct axp20x_dev *axp20x = i2c_get_clientdata(i2c); + if (axp20x->variant == AXP209_ID || axp20x->variant == AXP202_ID) { + axp20x_sysfs_exit(axp20x); + } + if (axp20x == axp20x_pm_power_off) { axp20x_pm_power_off = NULL; pm_power_off = NULL;