18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Nokia RX-51 battery driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2012 Pali Rohár <pali@kernel.org> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/param.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/iio/consumer.h> 148c2ecf20Sopenharmony_ci#include <linux/of.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct rx51_device_info { 178c2ecf20Sopenharmony_ci struct device *dev; 188c2ecf20Sopenharmony_ci struct power_supply *bat; 198c2ecf20Sopenharmony_ci struct power_supply_desc bat_desc; 208c2ecf20Sopenharmony_ci struct iio_channel *channel_temp; 218c2ecf20Sopenharmony_ci struct iio_channel *channel_bsi; 228c2ecf20Sopenharmony_ci struct iio_channel *channel_vbat; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* 268c2ecf20Sopenharmony_ci * Read ADCIN channel value, code copied from maemo kernel 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistatic int rx51_battery_read_adc(struct iio_channel *channel) 298c2ecf20Sopenharmony_ci{ 308c2ecf20Sopenharmony_ci int val, err; 318c2ecf20Sopenharmony_ci err = iio_read_channel_average_raw(channel, &val); 328c2ecf20Sopenharmony_ci if (err < 0) 338c2ecf20Sopenharmony_ci return err; 348c2ecf20Sopenharmony_ci return val; 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* 388c2ecf20Sopenharmony_ci * Read ADCIN channel 12 (voltage) and convert RAW value to micro voltage 398c2ecf20Sopenharmony_ci * This conversion formula was extracted from maemo program bsi-read 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_cistatic int rx51_battery_read_voltage(struct rx51_device_info *di) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci int voltage = rx51_battery_read_adc(di->channel_vbat); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (voltage < 0) { 468c2ecf20Sopenharmony_ci dev_err(di->dev, "Could not read ADC: %d\n", voltage); 478c2ecf20Sopenharmony_ci return voltage; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci return 1000 * (10000 * voltage / 1705); 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* 548c2ecf20Sopenharmony_ci * Temperature look-up tables 558c2ecf20Sopenharmony_ci * TEMP = (1/(t1 + 1/298) - 273.15) 568c2ecf20Sopenharmony_ci * Where t1 = (1/B) * ln((RAW_ADC_U * 2.5)/(R * I * 255)) 578c2ecf20Sopenharmony_ci * Formula is based on experimental data, RX-51 CAL data, maemo program bme 588c2ecf20Sopenharmony_ci * and formula from da9052 driver with values R = 100, B = 3380, I = 0.00671 598c2ecf20Sopenharmony_ci */ 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* 628c2ecf20Sopenharmony_ci * Table1 (temperature for first 25 RAW values) 638c2ecf20Sopenharmony_ci * Usage: TEMP = rx51_temp_table1[RAW] 648c2ecf20Sopenharmony_ci * RAW is between 1 and 24 658c2ecf20Sopenharmony_ci * TEMP is between 201 C and 55 C 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_cistatic u8 rx51_temp_table1[] = { 688c2ecf20Sopenharmony_ci 255, 201, 159, 138, 124, 114, 106, 99, 94, 89, 85, 82, 78, 75, 698c2ecf20Sopenharmony_ci 73, 70, 68, 66, 64, 62, 61, 59, 57, 56, 55 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* 738c2ecf20Sopenharmony_ci * Table2 (lowest RAW value for temperature) 748c2ecf20Sopenharmony_ci * Usage: RAW = rx51_temp_table2[TEMP-rx51_temp_table2_first] 758c2ecf20Sopenharmony_ci * TEMP is between 53 C and -32 C 768c2ecf20Sopenharmony_ci * RAW is between 25 and 993 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_ci#define rx51_temp_table2_first 53 798c2ecf20Sopenharmony_cistatic u16 rx51_temp_table2[] = { 808c2ecf20Sopenharmony_ci 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 818c2ecf20Sopenharmony_ci 40, 41, 43, 44, 46, 48, 49, 51, 53, 55, 57, 59, 61, 64, 828c2ecf20Sopenharmony_ci 66, 69, 71, 74, 77, 80, 83, 86, 90, 94, 97, 101, 106, 110, 838c2ecf20Sopenharmony_ci 115, 119, 125, 130, 136, 141, 148, 154, 161, 168, 176, 184, 202, 211, 848c2ecf20Sopenharmony_ci 221, 231, 242, 254, 266, 279, 293, 308, 323, 340, 357, 375, 395, 415, 858c2ecf20Sopenharmony_ci 437, 460, 485, 511, 539, 568, 600, 633, 669, 706, 747, 790, 836, 885, 868c2ecf20Sopenharmony_ci 937, 993, 1024 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci/* 908c2ecf20Sopenharmony_ci * Read ADCIN channel 0 (battery temp) and convert value to tenths of Celsius 918c2ecf20Sopenharmony_ci * Use Temperature look-up tables for conversation 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_cistatic int rx51_battery_read_temperature(struct rx51_device_info *di) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci int min = 0; 968c2ecf20Sopenharmony_ci int max = ARRAY_SIZE(rx51_temp_table2) - 1; 978c2ecf20Sopenharmony_ci int raw = rx51_battery_read_adc(di->channel_temp); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (raw < 0) 1008c2ecf20Sopenharmony_ci dev_err(di->dev, "Could not read ADC: %d\n", raw); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Zero and negative values are undefined */ 1038c2ecf20Sopenharmony_ci if (raw <= 0) 1048c2ecf20Sopenharmony_ci return INT_MAX; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* ADC channels are 10 bit, higher value are undefined */ 1078c2ecf20Sopenharmony_ci if (raw >= (1 << 10)) 1088c2ecf20Sopenharmony_ci return INT_MIN; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* First check for temperature in first direct table */ 1118c2ecf20Sopenharmony_ci if (raw < ARRAY_SIZE(rx51_temp_table1)) 1128c2ecf20Sopenharmony_ci return rx51_temp_table1[raw] * 10; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* Binary search RAW value in second inverse table */ 1158c2ecf20Sopenharmony_ci while (max - min > 1) { 1168c2ecf20Sopenharmony_ci int mid = (max + min) / 2; 1178c2ecf20Sopenharmony_ci if (rx51_temp_table2[mid] <= raw) 1188c2ecf20Sopenharmony_ci min = mid; 1198c2ecf20Sopenharmony_ci else if (rx51_temp_table2[mid] > raw) 1208c2ecf20Sopenharmony_ci max = mid; 1218c2ecf20Sopenharmony_ci if (rx51_temp_table2[mid] == raw) 1228c2ecf20Sopenharmony_ci break; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return (rx51_temp_table2_first - min) * 10; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci/* 1298c2ecf20Sopenharmony_ci * Read ADCIN channel 4 (BSI) and convert RAW value to micro Ah 1308c2ecf20Sopenharmony_ci * This conversion formula was extracted from maemo program bsi-read 1318c2ecf20Sopenharmony_ci */ 1328c2ecf20Sopenharmony_cistatic int rx51_battery_read_capacity(struct rx51_device_info *di) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci int capacity = rx51_battery_read_adc(di->channel_bsi); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (capacity < 0) { 1378c2ecf20Sopenharmony_ci dev_err(di->dev, "Could not read ADC: %d\n", capacity); 1388c2ecf20Sopenharmony_ci return capacity; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return 1280 * (1200 * capacity)/(1024 - capacity); 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci/* 1458c2ecf20Sopenharmony_ci * Return power_supply property 1468c2ecf20Sopenharmony_ci */ 1478c2ecf20Sopenharmony_cistatic int rx51_battery_get_property(struct power_supply *psy, 1488c2ecf20Sopenharmony_ci enum power_supply_property psp, 1498c2ecf20Sopenharmony_ci union power_supply_propval *val) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci struct rx51_device_info *di = power_supply_get_drvdata(psy); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci switch (psp) { 1548c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 1558c2ecf20Sopenharmony_ci val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 1568c2ecf20Sopenharmony_ci break; 1578c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 1588c2ecf20Sopenharmony_ci val->intval = 4200000; 1598c2ecf20Sopenharmony_ci break; 1608c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 1618c2ecf20Sopenharmony_ci val->intval = rx51_battery_read_voltage(di) ? 1 : 0; 1628c2ecf20Sopenharmony_ci break; 1638c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 1648c2ecf20Sopenharmony_ci val->intval = rx51_battery_read_voltage(di); 1658c2ecf20Sopenharmony_ci break; 1668c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_TEMP: 1678c2ecf20Sopenharmony_ci val->intval = rx51_battery_read_temperature(di); 1688c2ecf20Sopenharmony_ci break; 1698c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 1708c2ecf20Sopenharmony_ci val->intval = rx51_battery_read_capacity(di); 1718c2ecf20Sopenharmony_ci break; 1728c2ecf20Sopenharmony_ci default: 1738c2ecf20Sopenharmony_ci return -EINVAL; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (val->intval == INT_MAX || val->intval == INT_MIN) 1778c2ecf20Sopenharmony_ci return -EINVAL; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic enum power_supply_property rx51_battery_props[] = { 1838c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_TECHNOLOGY, 1848c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 1858c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 1868c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 1878c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_TEMP, 1888c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 1898c2ecf20Sopenharmony_ci}; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic int rx51_battery_probe(struct platform_device *pdev) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci struct power_supply_config psy_cfg = {}; 1948c2ecf20Sopenharmony_ci struct rx51_device_info *di; 1958c2ecf20Sopenharmony_ci int ret; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); 1988c2ecf20Sopenharmony_ci if (!di) 1998c2ecf20Sopenharmony_ci return -ENOMEM; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, di); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci di->dev = &pdev->dev; 2048c2ecf20Sopenharmony_ci di->bat_desc.name = "rx51-battery"; 2058c2ecf20Sopenharmony_ci di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; 2068c2ecf20Sopenharmony_ci di->bat_desc.properties = rx51_battery_props; 2078c2ecf20Sopenharmony_ci di->bat_desc.num_properties = ARRAY_SIZE(rx51_battery_props); 2088c2ecf20Sopenharmony_ci di->bat_desc.get_property = rx51_battery_get_property; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci psy_cfg.drv_data = di; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci di->channel_temp = iio_channel_get(di->dev, "temp"); 2138c2ecf20Sopenharmony_ci if (IS_ERR(di->channel_temp)) { 2148c2ecf20Sopenharmony_ci ret = PTR_ERR(di->channel_temp); 2158c2ecf20Sopenharmony_ci goto error; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci di->channel_bsi = iio_channel_get(di->dev, "bsi"); 2198c2ecf20Sopenharmony_ci if (IS_ERR(di->channel_bsi)) { 2208c2ecf20Sopenharmony_ci ret = PTR_ERR(di->channel_bsi); 2218c2ecf20Sopenharmony_ci goto error_channel_temp; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci di->channel_vbat = iio_channel_get(di->dev, "vbat"); 2258c2ecf20Sopenharmony_ci if (IS_ERR(di->channel_vbat)) { 2268c2ecf20Sopenharmony_ci ret = PTR_ERR(di->channel_vbat); 2278c2ecf20Sopenharmony_ci goto error_channel_bsi; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci di->bat = power_supply_register(di->dev, &di->bat_desc, &psy_cfg); 2318c2ecf20Sopenharmony_ci if (IS_ERR(di->bat)) { 2328c2ecf20Sopenharmony_ci ret = PTR_ERR(di->bat); 2338c2ecf20Sopenharmony_ci goto error_channel_vbat; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return 0; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cierror_channel_vbat: 2398c2ecf20Sopenharmony_ci iio_channel_release(di->channel_vbat); 2408c2ecf20Sopenharmony_cierror_channel_bsi: 2418c2ecf20Sopenharmony_ci iio_channel_release(di->channel_bsi); 2428c2ecf20Sopenharmony_cierror_channel_temp: 2438c2ecf20Sopenharmony_ci iio_channel_release(di->channel_temp); 2448c2ecf20Sopenharmony_cierror: 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return ret; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic int rx51_battery_remove(struct platform_device *pdev) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci struct rx51_device_info *di = platform_get_drvdata(pdev); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci power_supply_unregister(di->bat); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci iio_channel_release(di->channel_vbat); 2568c2ecf20Sopenharmony_ci iio_channel_release(di->channel_bsi); 2578c2ecf20Sopenharmony_ci iio_channel_release(di->channel_temp); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 2638c2ecf20Sopenharmony_cistatic const struct of_device_id n900_battery_of_match[] = { 2648c2ecf20Sopenharmony_ci {.compatible = "nokia,n900-battery", }, 2658c2ecf20Sopenharmony_ci { }, 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, n900_battery_of_match); 2688c2ecf20Sopenharmony_ci#endif 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic struct platform_driver rx51_battery_driver = { 2718c2ecf20Sopenharmony_ci .probe = rx51_battery_probe, 2728c2ecf20Sopenharmony_ci .remove = rx51_battery_remove, 2738c2ecf20Sopenharmony_ci .driver = { 2748c2ecf20Sopenharmony_ci .name = "rx51-battery", 2758c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(n900_battery_of_match), 2768c2ecf20Sopenharmony_ci }, 2778c2ecf20Sopenharmony_ci}; 2788c2ecf20Sopenharmony_cimodule_platform_driver(rx51_battery_driver); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:rx51-battery"); 2818c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 2828c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Nokia RX-51 battery driver"); 2838c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 284