// SPDX-License-Identifier: GPL-2.0 /* * Battery driver for the Ingenic JZ47xx SoCs * Copyright (c) 2019 Artur Rojek * * based on drivers/power/supply/jz4740-battery.c */ #include #include #include #include #include #include struct ingenic_battery { struct device *dev; struct iio_channel *channel; struct power_supply_desc desc; struct power_supply *battery; struct power_supply_battery_info info; }; static int ingenic_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ingenic_battery *bat = power_supply_get_drvdata(psy); struct power_supply_battery_info *info = &bat->info; int ret; switch (psp) { case POWER_SUPPLY_PROP_HEALTH: ret = iio_read_channel_processed(bat->channel, &val->intval); val->intval *= 1000; if (val->intval < info->voltage_min_design_uv) val->intval = POWER_SUPPLY_HEALTH_DEAD; else if (val->intval > info->voltage_max_design_uv) val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else val->intval = POWER_SUPPLY_HEALTH_GOOD; return ret; case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = iio_read_channel_processed(bat->channel, &val->intval); val->intval *= 1000; return ret; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = info->voltage_min_design_uv; return 0; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = info->voltage_max_design_uv; return 0; default: return -EINVAL; }; } /* Set the most appropriate IIO channel voltage reference scale * based on the battery's max voltage. */ static int ingenic_battery_set_scale(struct ingenic_battery *bat) { const int *scale_raw; int scale_len, scale_type, best_idx = -1, best_mV, max_raw, i, ret; u64 max_mV; ret = iio_read_max_channel_raw(bat->channel, &max_raw); if (ret) { dev_err(bat->dev, "Unable to read max raw channel value\n"); return ret; } ret = iio_read_avail_channel_attribute(bat->channel, &scale_raw, &scale_type, &scale_len, IIO_CHAN_INFO_SCALE); if (ret < 0) { dev_err(bat->dev, "Unable to read channel avail scale\n"); return ret; } if (ret != IIO_AVAIL_LIST || scale_type != IIO_VAL_FRACTIONAL_LOG2) return -EINVAL; max_mV = bat->info.voltage_max_design_uv / 1000; for (i = 0; i < scale_len; i += 2) { u64 scale_mV = (max_raw * scale_raw[i]) >> scale_raw[i + 1]; if (scale_mV < max_mV) continue; if (best_idx >= 0 && scale_mV > best_mV) continue; best_mV = scale_mV; best_idx = i; } if (best_idx < 0) { dev_err(bat->dev, "Unable to find matching voltage scale\n"); return -EINVAL; } /* Only set scale if there is more than one (fractional) entry */ if (scale_len > 2) { ret = iio_write_channel_attribute(bat->channel, scale_raw[best_idx], scale_raw[best_idx + 1], IIO_CHAN_INFO_SCALE); if (ret) return ret; } return 0; } static enum power_supply_property ingenic_battery_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, }; static int ingenic_battery_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ingenic_battery *bat; struct power_supply_config psy_cfg = {}; struct power_supply_desc *desc; int ret; bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL); if (!bat) return -ENOMEM; bat->dev = dev; bat->channel = devm_iio_channel_get(dev, "battery"); if (IS_ERR(bat->channel)) return PTR_ERR(bat->channel); desc = &bat->desc; desc->name = "jz-battery"; desc->type = POWER_SUPPLY_TYPE_BATTERY; desc->properties = ingenic_battery_properties; desc->num_properties = ARRAY_SIZE(ingenic_battery_properties); desc->get_property = ingenic_battery_get_property; psy_cfg.drv_data = bat; psy_cfg.of_node = dev->of_node; bat->battery = devm_power_supply_register(dev, desc, &psy_cfg); if (IS_ERR(bat->battery)) { if (PTR_ERR(bat->battery) != -EPROBE_DEFER) dev_err(dev, "Unable to register battery\n"); return PTR_ERR(bat->battery); } ret = power_supply_get_battery_info(bat->battery, &bat->info); if (ret) { dev_err(dev, "Unable to get battery info: %d\n", ret); return ret; } if (bat->info.voltage_min_design_uv < 0) { dev_err(dev, "Unable to get voltage min design\n"); return bat->info.voltage_min_design_uv; } if (bat->info.voltage_max_design_uv < 0) { dev_err(dev, "Unable to get voltage max design\n"); return bat->info.voltage_max_design_uv; } return ingenic_battery_set_scale(bat); } #ifdef CONFIG_OF static const struct of_device_id ingenic_battery_of_match[] = { { .compatible = "ingenic,jz4740-battery", }, { }, }; MODULE_DEVICE_TABLE(of, ingenic_battery_of_match); #endif static struct platform_driver ingenic_battery_driver = { .driver = { .name = "ingenic-battery", .of_match_table = of_match_ptr(ingenic_battery_of_match), }, .probe = ingenic_battery_probe, }; module_platform_driver(ingenic_battery_driver); MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs"); MODULE_AUTHOR("Artur Rojek "); MODULE_LICENSE("GPL");