Hi,
On Tue, Apr 09, 2024 at 03:54:41PM +0200, Mike Looijmans wrote:
The LTC3350 is a backup power controller that can charge and monitorplease share output of
a series stack of one to four supercapacitors.
Signed-off-by: Mike Looijmans <mike.looijmans@xxxxxxxx>
---
./tools/testing/selftests/power_supply/test_power_supply_properties.sh
below the fold with your next submission. It's useful for verifying,
that you got the unit scaling correct for the standard properties :)
(no changes since v2)please keep custom uAPI consistent with the general uAPI and use µV.
Changes in v2:
Duplicate "vin_ov" and "vin_uv" attributes
drivers/power/supply/Kconfig | 10 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/ltc3350-charger.c | 718 +++++++++++++++++++++++++
3 files changed, 729 insertions(+)
create mode 100644 drivers/power/supply/ltc3350-charger.c
+/* Custom properties */
+
+static ssize_t ltc3350_attr_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ unsigned int reg, unsigned int scale)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(info->regmap, reg, ®val);
+ if (ret)
+ return ret;
+
+ regval *= scale; /* Scale is in 10 μV units */
+ regval /= 10;obviously also applied to writing.
+
+ return sprintf(buf, "%u\n", regval);
+}
+
+static ssize_t ltc3350_attr_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count,
+ unsigned int reg, unsigned int scale)
+{
+ struct power_supply *psy = to_power_supply(dev);
+ struct ltc3350_info *info = power_supply_get_drvdata(psy);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ val *= 10;
+ val = DIV_ROUND_CLOSEST(val, scale); /* Scale is in 10 μV units */
+I suppose it would be sensible to expose this as a second
+ ret = regmap_write(info->regmap, reg, val);
+ if (ret)
+ return ret;
+
+ /* When writing to one of the LVL registers, update the alarm mask */
+ if (reg >= LTC3350_REG_CAP_UV_LVL && reg <= LTC3350_REG_CAP_LO_LVL)
+ ltc3350_enable_alarm(info, reg);
+
+ return count;
+}
+
+#define LTC3350_DEVICE_ATTR_RO(_name, _reg, _scale) \
+static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return ltc3350_attr_show(dev, attr, buf, _reg, _scale); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+#define LTC3350_DEVICE_ATTR_RW(_name, _reg, _scale) \
+static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return ltc3350_attr_show(dev, attr, buf, _reg, _scale); \
+} \
+static ssize_t _name##_store(struct device *dev, struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return ltc3350_attr_store(dev, attr, buf, count, _reg, _scale); \
+} \
+static DEVICE_ATTR_RW(_name)
+
+/* Shunt voltage, 183.5μV per LSB */
+LTC3350_DEVICE_ATTR_RW(vshunt, LTC3350_REG_VSHUNT, 1835);
+
+/* Single capacitor voltages, 183.5μV per LSB */
+LTC3350_DEVICE_ATTR_RO(vcap1, LTC3350_REG_MEAS_VCAP1, 1835);
+LTC3350_DEVICE_ATTR_RO(vcap2, LTC3350_REG_MEAS_VCAP2, 1835);
+LTC3350_DEVICE_ATTR_RO(vcap3, LTC3350_REG_MEAS_VCAP3, 1835);
+LTC3350_DEVICE_ATTR_RO(vcap4, LTC3350_REG_MEAS_VCAP4, 1835);
+LTC3350_DEVICE_ATTR_RW(cap_uv, LTC3350_REG_CAP_UV_LVL, 1835);
+LTC3350_DEVICE_ATTR_RW(cap_ov, LTC3350_REG_CAP_OV_LVL, 1835);
+
+/* General purpose input, 183.5μV per LSB */
+LTC3350_DEVICE_ATTR_RO(gpi, LTC3350_REG_MEAS_GPI, 1835);
+LTC3350_DEVICE_ATTR_RW(gpi_uv, LTC3350_REG_GPI_UV_LVL, 1835);
+LTC3350_DEVICE_ATTR_RW(gpi_ov, LTC3350_REG_GPI_OV_LVL, 1835);
+
+/* Input voltage, 2.21mV per LSB */
+LTC3350_DEVICE_ATTR_RO(vin, LTC3350_REG_MEAS_VIN, 22100);
+LTC3350_DEVICE_ATTR_RW(vin_uv, LTC3350_REG_VIN_UV_LVL, 22100);
+LTC3350_DEVICE_ATTR_RW(vin_ov, LTC3350_REG_VIN_OV_LVL, 22100);
+
+/* Capacitor stack voltage, 1.476 mV per LSB */
+LTC3350_DEVICE_ATTR_RO(vcap, LTC3350_REG_MEAS_VCAP, 14760);
+LTC3350_DEVICE_ATTR_RW(vcap_uv, LTC3350_REG_VCAP_UV_LVL, 14760);
+LTC3350_DEVICE_ATTR_RW(vcap_ov, LTC3350_REG_VCAP_OV_LVL, 14760);
power_supply device of type battery with ltc3350_config.supplied_to
set to this second power_supply device.
Then you can map
LTC3350_REG_MEAS_VCAP -> VOLTAGE_NOW
LTC3350_REG_VCAP_UV_LVL -> VOLTAGE_MIN
LTC3350_REG_VCAP_OV_LVL -> VOLTAGE_MAX
LTC3350_REG_VSHUNT -> CURRENT_NOW
TECHNOLOGY = POWER_SUPPLY_TECHNOLOGY_CAPACITOR (new)
Also the single capacitor voltages are similar to per-cell voltage
information, so I think we should create generic properties for
that:
LTC3350_REG_NUM_CAPS -> NUMBER_OF_CELLS (new)
LTC3350_REG_MEAS_VCAP1 -> CELL1_VOLTAGE_NOW (new)
LTC3350_REG_MEAS_VCAP2 -> CELL2_VOLTAGE_NOW (new)
LTC3350_REG_MEAS_VCAP3 -> CELL3_VOLTAGE_NOW (new)
LTC3350_REG_MEAS_VCAP4 -> CELL4_VOLTAGE_NOW (new)
LTC3350_REG_CAP_UV_LVL -> CELL_VOLTAGE_MIN (new)
LTC3350_REG_CAP_OV_LVL -> CELL_VOLTAGE_MAX (new)
...
+return dev_err_probe(dev, -ENODEV, "No support for SMBUS_WORD_DATA\n");
+static int ltc3350_probe(struct i2c_client *client)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ struct device *dev = &client->dev;
+ struct ltc3350_info *info;
+ struct power_supply_config ltc3350_config = {};
+ int ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(dev, "No support for SMBUS_WORD_DATA\n");
+ return -ENODEV;
+ }
But I think this can just be dropped. devm_regmap_init_i2c() should
generate an error, if the i2c adapter requirements are not met.
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);dev_err_probe()
+ if (!info)
+ return -ENOMEM;
+
+ info->client = client;
+ i2c_set_clientdata(client, info);
+
+ info->regmap = devm_regmap_init_i2c(client, <c3350_regmap_config);
+ if (IS_ERR(info->regmap)) {
+ dev_err(dev, "Failed to initialize register map\n");
+ return PTR_ERR(info->regmap);
+ }
+dev_err_probe()
+ ret = device_property_read_u32(dev, "lltc,rsnsc-micro-ohms",
+ &info->rsnsc);
+ if (ret) {
+ dev_err(dev, "Missing lltc,rsnsc-micro-ohms property\n");
+ return ret;
+ }
+ if (!info->rsnsc)dev_err_probe()
+ 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)dev_err_probe()
+ return -EINVAL;
+
+ /* Clear and disable all interrupt sources*/
+ ret = regmap_write(info->regmap, LTC3350_REG_CLR_ALARMS, 0xFFFF);
+ if (ret) {
+ dev_err(dev, "Failed to write configuration\n");
+ return ret;
+ }
+ regmap_write(info->regmap, LTC3350_REG_MSK_ALARMS, 0);replace with
+ regmap_write(info->regmap, LTC3350_REG_MSK_MON_STATUS, 0);
+
+ ltc3350_config.of_node = dev->of_node;
ltc3350_config.fwnode = dev_fwnode(dev);
+ ltc3350_config.drv_data = info;dev_err_probe()
+ ltc3350_config.attr_grp = ltc3350_attr_groups;
+
+ info->charger = devm_power_supply_register(dev, <c3350_desc,
+ <c3350_config);
+ if (IS_ERR(info->charger)) {
+ dev_err(dev, "Failed to register charger\n");
+ return PTR_ERR(info->charger);
+ }
+Please check what of_match_ptr() does and then drop it :)
+ /* Enable interrupts on status changes */
+ regmap_write(info->regmap, LTC3350_REG_MSK_MON_STATUS,
+ LTC3350_MON_POWER_FAILED | LTC3350_MON_POWER_RETURNED);
+
+ return 0;
+}
+
+static void ltc3350_alert(struct i2c_client *client, enum i2c_alert_protocol type,
+ unsigned int flag)
+{
+ struct ltc3350_info *info = i2c_get_clientdata(client);
+
+ if (type != I2C_PROTOCOL_SMBUS_ALERT)
+ return;
+
+ power_supply_changed(info->charger);
+}
+
+static const struct i2c_device_id ltc3350_i2c_id_table[] = {
+ { "ltc3350", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ltc3350_i2c_id_table);
+
+static const struct of_device_id ltc3350_of_match[] = {
+ { .compatible = "lltc,ltc3350", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ltc3350_of_match);
+
+static struct i2c_driver ltc3350_driver = {
+ .probe = ltc3350_probe,
+ .alert = ltc3350_alert,
+ .id_table = ltc3350_i2c_id_table,
+ .driver = {
+ .name = "ltc3350-charger",
+ .of_match_table = of_match_ptr(ltc3350_of_match),
+ },Thanks for your patch,
+};
+module_i2c_driver(ltc3350_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mike Looijmans <mike.looijmans@xxxxxxxx>");
+MODULE_DESCRIPTION("LTC3350 charger driver");
-- Sebastian