1fb24ccfbSArtur Rojek // SPDX-License-Identifier: GPL-2.0 2fb24ccfbSArtur Rojek /* 3fb24ccfbSArtur Rojek * Battery driver for the Ingenic JZ47xx SoCs 4fb24ccfbSArtur Rojek * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu> 5fb24ccfbSArtur Rojek * 6fb24ccfbSArtur Rojek * based on drivers/power/supply/jz4740-battery.c 7fb24ccfbSArtur Rojek */ 8fb24ccfbSArtur Rojek 9fb24ccfbSArtur Rojek #include <linux/iio/consumer.h> 10fb24ccfbSArtur Rojek #include <linux/module.h> 11fb24ccfbSArtur Rojek #include <linux/of.h> 12fb24ccfbSArtur Rojek #include <linux/platform_device.h> 13fb24ccfbSArtur Rojek #include <linux/power_supply.h> 14fb24ccfbSArtur Rojek #include <linux/property.h> 15fb24ccfbSArtur Rojek 16fb24ccfbSArtur Rojek struct ingenic_battery { 17fb24ccfbSArtur Rojek struct device *dev; 18fb24ccfbSArtur Rojek struct iio_channel *channel; 19fb24ccfbSArtur Rojek struct power_supply_desc desc; 20fb24ccfbSArtur Rojek struct power_supply *battery; 21fb24ccfbSArtur Rojek struct power_supply_battery_info info; 22fb24ccfbSArtur Rojek }; 23fb24ccfbSArtur Rojek 24fb24ccfbSArtur Rojek static int ingenic_battery_get_property(struct power_supply *psy, 25fb24ccfbSArtur Rojek enum power_supply_property psp, 26fb24ccfbSArtur Rojek union power_supply_propval *val) 27fb24ccfbSArtur Rojek { 28fb24ccfbSArtur Rojek struct ingenic_battery *bat = power_supply_get_drvdata(psy); 29fb24ccfbSArtur Rojek struct power_supply_battery_info *info = &bat->info; 30fb24ccfbSArtur Rojek int ret; 31fb24ccfbSArtur Rojek 32fb24ccfbSArtur Rojek switch (psp) { 33fb24ccfbSArtur Rojek case POWER_SUPPLY_PROP_HEALTH: 34fb24ccfbSArtur Rojek ret = iio_read_channel_processed(bat->channel, &val->intval); 35fb24ccfbSArtur Rojek val->intval *= 1000; 36fb24ccfbSArtur Rojek if (val->intval < info->voltage_min_design_uv) 37fb24ccfbSArtur Rojek val->intval = POWER_SUPPLY_HEALTH_DEAD; 38fb24ccfbSArtur Rojek else if (val->intval > info->voltage_max_design_uv) 39fb24ccfbSArtur Rojek val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 40fb24ccfbSArtur Rojek else 41fb24ccfbSArtur Rojek val->intval = POWER_SUPPLY_HEALTH_GOOD; 42fb24ccfbSArtur Rojek return ret; 43fb24ccfbSArtur Rojek case POWER_SUPPLY_PROP_VOLTAGE_NOW: 44fb24ccfbSArtur Rojek ret = iio_read_channel_processed(bat->channel, &val->intval); 45fb24ccfbSArtur Rojek val->intval *= 1000; 46fb24ccfbSArtur Rojek return ret; 47fb24ccfbSArtur Rojek case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 48fb24ccfbSArtur Rojek val->intval = info->voltage_min_design_uv; 49fb24ccfbSArtur Rojek return 0; 50fb24ccfbSArtur Rojek case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 51fb24ccfbSArtur Rojek val->intval = info->voltage_max_design_uv; 52fb24ccfbSArtur Rojek return 0; 53fb24ccfbSArtur Rojek default: 54fb24ccfbSArtur Rojek return -EINVAL; 55fb24ccfbSArtur Rojek }; 56fb24ccfbSArtur Rojek } 57fb24ccfbSArtur Rojek 58fb24ccfbSArtur Rojek /* Set the most appropriate IIO channel voltage reference scale 59fb24ccfbSArtur Rojek * based on the battery's max voltage. 60fb24ccfbSArtur Rojek */ 61fb24ccfbSArtur Rojek static int ingenic_battery_set_scale(struct ingenic_battery *bat) 62fb24ccfbSArtur Rojek { 63fb24ccfbSArtur Rojek const int *scale_raw; 64fb24ccfbSArtur Rojek int scale_len, scale_type, best_idx = -1, best_mV, max_raw, i, ret; 65fb24ccfbSArtur Rojek u64 max_mV; 66fb24ccfbSArtur Rojek 67fb24ccfbSArtur Rojek ret = iio_read_max_channel_raw(bat->channel, &max_raw); 68fb24ccfbSArtur Rojek if (ret) { 69fb24ccfbSArtur Rojek dev_err(bat->dev, "Unable to read max raw channel value\n"); 70fb24ccfbSArtur Rojek return ret; 71fb24ccfbSArtur Rojek } 72fb24ccfbSArtur Rojek 73fb24ccfbSArtur Rojek ret = iio_read_avail_channel_attribute(bat->channel, &scale_raw, 74fb24ccfbSArtur Rojek &scale_type, &scale_len, 75fb24ccfbSArtur Rojek IIO_CHAN_INFO_SCALE); 76fb24ccfbSArtur Rojek if (ret < 0) { 77fb24ccfbSArtur Rojek dev_err(bat->dev, "Unable to read channel avail scale\n"); 78fb24ccfbSArtur Rojek return ret; 79fb24ccfbSArtur Rojek } 80fb24ccfbSArtur Rojek if (ret != IIO_AVAIL_LIST || scale_type != IIO_VAL_FRACTIONAL_LOG2) 81fb24ccfbSArtur Rojek return -EINVAL; 82fb24ccfbSArtur Rojek 83fb24ccfbSArtur Rojek max_mV = bat->info.voltage_max_design_uv / 1000; 84fb24ccfbSArtur Rojek 85fb24ccfbSArtur Rojek for (i = 0; i < scale_len; i += 2) { 86fb24ccfbSArtur Rojek u64 scale_mV = (max_raw * scale_raw[i]) >> scale_raw[i + 1]; 87fb24ccfbSArtur Rojek 88fb24ccfbSArtur Rojek if (scale_mV < max_mV) 89fb24ccfbSArtur Rojek continue; 90fb24ccfbSArtur Rojek 91fb24ccfbSArtur Rojek if (best_idx >= 0 && scale_mV > best_mV) 92fb24ccfbSArtur Rojek continue; 93fb24ccfbSArtur Rojek 94fb24ccfbSArtur Rojek best_mV = scale_mV; 95fb24ccfbSArtur Rojek best_idx = i; 96fb24ccfbSArtur Rojek } 97fb24ccfbSArtur Rojek 98fb24ccfbSArtur Rojek if (best_idx < 0) { 99fb24ccfbSArtur Rojek dev_err(bat->dev, "Unable to find matching voltage scale\n"); 100fb24ccfbSArtur Rojek return -EINVAL; 101fb24ccfbSArtur Rojek } 102fb24ccfbSArtur Rojek 10386b9182dSPaul Cercueil /* Only set scale if there is more than one (fractional) entry */ 10486b9182dSPaul Cercueil if (scale_len > 2) { 10586b9182dSPaul Cercueil ret = iio_write_channel_attribute(bat->channel, 106fb24ccfbSArtur Rojek scale_raw[best_idx], 107fb24ccfbSArtur Rojek scale_raw[best_idx + 1], 108fb24ccfbSArtur Rojek IIO_CHAN_INFO_SCALE); 10986b9182dSPaul Cercueil if (ret) 11086b9182dSPaul Cercueil return ret; 11186b9182dSPaul Cercueil } 11286b9182dSPaul Cercueil 11386b9182dSPaul Cercueil return 0; 114fb24ccfbSArtur Rojek } 115fb24ccfbSArtur Rojek 116fb24ccfbSArtur Rojek static enum power_supply_property ingenic_battery_properties[] = { 117fb24ccfbSArtur Rojek POWER_SUPPLY_PROP_HEALTH, 118fb24ccfbSArtur Rojek POWER_SUPPLY_PROP_VOLTAGE_NOW, 119fb24ccfbSArtur Rojek POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 120fb24ccfbSArtur Rojek POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 121fb24ccfbSArtur Rojek }; 122fb24ccfbSArtur Rojek 123fb24ccfbSArtur Rojek static int ingenic_battery_probe(struct platform_device *pdev) 124fb24ccfbSArtur Rojek { 125fb24ccfbSArtur Rojek struct device *dev = &pdev->dev; 126fb24ccfbSArtur Rojek struct ingenic_battery *bat; 127fb24ccfbSArtur Rojek struct power_supply_config psy_cfg = {}; 128fb24ccfbSArtur Rojek struct power_supply_desc *desc; 129fb24ccfbSArtur Rojek int ret; 130fb24ccfbSArtur Rojek 131fb24ccfbSArtur Rojek bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL); 132fb24ccfbSArtur Rojek if (!bat) 133fb24ccfbSArtur Rojek return -ENOMEM; 134fb24ccfbSArtur Rojek 135fb24ccfbSArtur Rojek bat->dev = dev; 136fb24ccfbSArtur Rojek bat->channel = devm_iio_channel_get(dev, "battery"); 137fb24ccfbSArtur Rojek if (IS_ERR(bat->channel)) 138fb24ccfbSArtur Rojek return PTR_ERR(bat->channel); 139fb24ccfbSArtur Rojek 140fb24ccfbSArtur Rojek desc = &bat->desc; 141fb24ccfbSArtur Rojek desc->name = "jz-battery"; 142fb24ccfbSArtur Rojek desc->type = POWER_SUPPLY_TYPE_BATTERY; 143fb24ccfbSArtur Rojek desc->properties = ingenic_battery_properties; 144fb24ccfbSArtur Rojek desc->num_properties = ARRAY_SIZE(ingenic_battery_properties); 145fb24ccfbSArtur Rojek desc->get_property = ingenic_battery_get_property; 146fb24ccfbSArtur Rojek psy_cfg.drv_data = bat; 147fb24ccfbSArtur Rojek psy_cfg.of_node = dev->of_node; 148fb24ccfbSArtur Rojek 149fb24ccfbSArtur Rojek bat->battery = devm_power_supply_register(dev, desc, &psy_cfg); 150fb24ccfbSArtur Rojek if (IS_ERR(bat->battery)) { 151fb24ccfbSArtur Rojek dev_err(dev, "Unable to register battery\n"); 152fb24ccfbSArtur Rojek return PTR_ERR(bat->battery); 153fb24ccfbSArtur Rojek } 154fb24ccfbSArtur Rojek 155fb24ccfbSArtur Rojek ret = power_supply_get_battery_info(bat->battery, &bat->info); 156fb24ccfbSArtur Rojek if (ret) { 157fb24ccfbSArtur Rojek dev_err(dev, "Unable to get battery info: %d\n", ret); 158fb24ccfbSArtur Rojek return ret; 159fb24ccfbSArtur Rojek } 160fb24ccfbSArtur Rojek if (bat->info.voltage_min_design_uv < 0) { 161fb24ccfbSArtur Rojek dev_err(dev, "Unable to get voltage min design\n"); 162fb24ccfbSArtur Rojek return bat->info.voltage_min_design_uv; 163fb24ccfbSArtur Rojek } 164fb24ccfbSArtur Rojek if (bat->info.voltage_max_design_uv < 0) { 165fb24ccfbSArtur Rojek dev_err(dev, "Unable to get voltage max design\n"); 166fb24ccfbSArtur Rojek return bat->info.voltage_max_design_uv; 167fb24ccfbSArtur Rojek } 168fb24ccfbSArtur Rojek 169fb24ccfbSArtur Rojek return ingenic_battery_set_scale(bat); 170fb24ccfbSArtur Rojek } 171fb24ccfbSArtur Rojek 172fb24ccfbSArtur Rojek #ifdef CONFIG_OF 173fb24ccfbSArtur Rojek static const struct of_device_id ingenic_battery_of_match[] = { 174fb24ccfbSArtur Rojek { .compatible = "ingenic,jz4740-battery", }, 175fb24ccfbSArtur Rojek { }, 176fb24ccfbSArtur Rojek }; 177fb24ccfbSArtur Rojek MODULE_DEVICE_TABLE(of, ingenic_battery_of_match); 178fb24ccfbSArtur Rojek #endif 179fb24ccfbSArtur Rojek 180fb24ccfbSArtur Rojek static struct platform_driver ingenic_battery_driver = { 181fb24ccfbSArtur Rojek .driver = { 182fb24ccfbSArtur Rojek .name = "ingenic-battery", 183fb24ccfbSArtur Rojek .of_match_table = of_match_ptr(ingenic_battery_of_match), 184fb24ccfbSArtur Rojek }, 185fb24ccfbSArtur Rojek .probe = ingenic_battery_probe, 186fb24ccfbSArtur Rojek }; 187fb24ccfbSArtur Rojek module_platform_driver(ingenic_battery_driver); 188fb24ccfbSArtur Rojek 189fb24ccfbSArtur Rojek MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs"); 190fb24ccfbSArtur Rojek MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>"); 191fb24ccfbSArtur Rojek MODULE_LICENSE("GPL"); 192