18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ADM1177 Hot Swap Controller and Digital Power Monitor with Soft Start Pin 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2015-2019 Analog Devices Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/bits.h> 98c2ecf20Sopenharmony_ci#include <linux/device.h> 108c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 118c2ecf20Sopenharmony_ci#include <linux/i2c.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* Command Byte Operations */ 178c2ecf20Sopenharmony_ci#define ADM1177_CMD_V_CONT BIT(0) 188c2ecf20Sopenharmony_ci#define ADM1177_CMD_I_CONT BIT(2) 198c2ecf20Sopenharmony_ci#define ADM1177_CMD_VRANGE BIT(4) 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* Extended Register */ 228c2ecf20Sopenharmony_ci#define ADM1177_REG_ALERT_TH 2 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define ADM1177_BITS 12 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/** 278c2ecf20Sopenharmony_ci * struct adm1177_state - driver instance specific data 288c2ecf20Sopenharmony_ci * @client pointer to i2c client 298c2ecf20Sopenharmony_ci * @reg regulator info for the the power supply of the device 308c2ecf20Sopenharmony_ci * @r_sense_uohm current sense resistor value 318c2ecf20Sopenharmony_ci * @alert_threshold_ua current limit for shutdown 328c2ecf20Sopenharmony_ci * @vrange_high internal voltage divider 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_cistruct adm1177_state { 358c2ecf20Sopenharmony_ci struct i2c_client *client; 368c2ecf20Sopenharmony_ci struct regulator *reg; 378c2ecf20Sopenharmony_ci u32 r_sense_uohm; 388c2ecf20Sopenharmony_ci u32 alert_threshold_ua; 398c2ecf20Sopenharmony_ci bool vrange_high; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int adm1177_read_raw(struct adm1177_state *st, u8 num, u8 *data) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci return i2c_master_recv(st->client, data, num); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic int adm1177_write_cmd(struct adm1177_state *st, u8 cmd) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci return i2c_smbus_write_byte(st->client, cmd); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int adm1177_write_alert_thr(struct adm1177_state *st, 538c2ecf20Sopenharmony_ci u32 alert_threshold_ua) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci u64 val; 568c2ecf20Sopenharmony_ci int ret; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci val = 0xFFULL * alert_threshold_ua * st->r_sense_uohm; 598c2ecf20Sopenharmony_ci val = div_u64(val, 105840000U); 608c2ecf20Sopenharmony_ci val = div_u64(val, 1000U); 618c2ecf20Sopenharmony_ci if (val > 0xFF) 628c2ecf20Sopenharmony_ci val = 0xFF; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci ret = i2c_smbus_write_byte_data(st->client, ADM1177_REG_ALERT_TH, 658c2ecf20Sopenharmony_ci val); 668c2ecf20Sopenharmony_ci if (ret) 678c2ecf20Sopenharmony_ci return ret; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci st->alert_threshold_ua = alert_threshold_ua; 708c2ecf20Sopenharmony_ci return 0; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int adm1177_read(struct device *dev, enum hwmon_sensor_types type, 748c2ecf20Sopenharmony_ci u32 attr, int channel, long *val) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct adm1177_state *st = dev_get_drvdata(dev); 778c2ecf20Sopenharmony_ci u8 data[3]; 788c2ecf20Sopenharmony_ci long dummy; 798c2ecf20Sopenharmony_ci int ret; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci switch (type) { 828c2ecf20Sopenharmony_ci case hwmon_curr: 838c2ecf20Sopenharmony_ci switch (attr) { 848c2ecf20Sopenharmony_ci case hwmon_curr_input: 858c2ecf20Sopenharmony_ci ret = adm1177_read_raw(st, 3, data); 868c2ecf20Sopenharmony_ci if (ret < 0) 878c2ecf20Sopenharmony_ci return ret; 888c2ecf20Sopenharmony_ci dummy = (data[1] << 4) | (data[2] & 0xF); 898c2ecf20Sopenharmony_ci /* 908c2ecf20Sopenharmony_ci * convert to milliamperes 918c2ecf20Sopenharmony_ci * ((105.84mV / 4096) x raw) / senseResistor(ohm) 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ci *val = div_u64((105840000ull * dummy), 948c2ecf20Sopenharmony_ci 4096 * st->r_sense_uohm); 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci case hwmon_curr_max_alarm: 978c2ecf20Sopenharmony_ci *val = st->alert_threshold_ua; 988c2ecf20Sopenharmony_ci return 0; 998c2ecf20Sopenharmony_ci default: 1008c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci case hwmon_in: 1038c2ecf20Sopenharmony_ci ret = adm1177_read_raw(st, 3, data); 1048c2ecf20Sopenharmony_ci if (ret < 0) 1058c2ecf20Sopenharmony_ci return ret; 1068c2ecf20Sopenharmony_ci dummy = (data[0] << 4) | (data[2] >> 4); 1078c2ecf20Sopenharmony_ci /* 1088c2ecf20Sopenharmony_ci * convert to millivolts based on resistor devision 1098c2ecf20Sopenharmony_ci * (V_fullscale / 4096) * raw 1108c2ecf20Sopenharmony_ci */ 1118c2ecf20Sopenharmony_ci if (st->vrange_high) 1128c2ecf20Sopenharmony_ci dummy *= 26350; 1138c2ecf20Sopenharmony_ci else 1148c2ecf20Sopenharmony_ci dummy *= 6650; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci *val = DIV_ROUND_CLOSEST(dummy, 4096); 1178c2ecf20Sopenharmony_ci return 0; 1188c2ecf20Sopenharmony_ci default: 1198c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int adm1177_write(struct device *dev, enum hwmon_sensor_types type, 1248c2ecf20Sopenharmony_ci u32 attr, int channel, long val) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct adm1177_state *st = dev_get_drvdata(dev); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci switch (type) { 1298c2ecf20Sopenharmony_ci case hwmon_curr: 1308c2ecf20Sopenharmony_ci switch (attr) { 1318c2ecf20Sopenharmony_ci case hwmon_curr_max_alarm: 1328c2ecf20Sopenharmony_ci adm1177_write_alert_thr(st, val); 1338c2ecf20Sopenharmony_ci return 0; 1348c2ecf20Sopenharmony_ci default: 1358c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci default: 1388c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic umode_t adm1177_is_visible(const void *data, 1438c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, 1448c2ecf20Sopenharmony_ci u32 attr, int channel) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci const struct adm1177_state *st = data; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci switch (type) { 1498c2ecf20Sopenharmony_ci case hwmon_in: 1508c2ecf20Sopenharmony_ci switch (attr) { 1518c2ecf20Sopenharmony_ci case hwmon_in_input: 1528c2ecf20Sopenharmony_ci return 0444; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci break; 1558c2ecf20Sopenharmony_ci case hwmon_curr: 1568c2ecf20Sopenharmony_ci switch (attr) { 1578c2ecf20Sopenharmony_ci case hwmon_curr_input: 1588c2ecf20Sopenharmony_ci if (st->r_sense_uohm) 1598c2ecf20Sopenharmony_ci return 0444; 1608c2ecf20Sopenharmony_ci return 0; 1618c2ecf20Sopenharmony_ci case hwmon_curr_max_alarm: 1628c2ecf20Sopenharmony_ci if (st->r_sense_uohm) 1638c2ecf20Sopenharmony_ci return 0644; 1648c2ecf20Sopenharmony_ci return 0; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci break; 1678c2ecf20Sopenharmony_ci default: 1688c2ecf20Sopenharmony_ci break; 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic const struct hwmon_channel_info *adm1177_info[] = { 1748c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(curr, 1758c2ecf20Sopenharmony_ci HWMON_C_INPUT | HWMON_C_MAX_ALARM), 1768c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(in, 1778c2ecf20Sopenharmony_ci HWMON_I_INPUT), 1788c2ecf20Sopenharmony_ci NULL 1798c2ecf20Sopenharmony_ci}; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic const struct hwmon_ops adm1177_hwmon_ops = { 1828c2ecf20Sopenharmony_ci .is_visible = adm1177_is_visible, 1838c2ecf20Sopenharmony_ci .read = adm1177_read, 1848c2ecf20Sopenharmony_ci .write = adm1177_write, 1858c2ecf20Sopenharmony_ci}; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic const struct hwmon_chip_info adm1177_chip_info = { 1888c2ecf20Sopenharmony_ci .ops = &adm1177_hwmon_ops, 1898c2ecf20Sopenharmony_ci .info = adm1177_info, 1908c2ecf20Sopenharmony_ci}; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic void adm1177_remove(void *data) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct adm1177_state *st = data; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci regulator_disable(st->reg); 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic int adm1177_probe(struct i2c_client *client) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 2028c2ecf20Sopenharmony_ci struct device *hwmon_dev; 2038c2ecf20Sopenharmony_ci struct adm1177_state *st; 2048c2ecf20Sopenharmony_ci u32 alert_threshold_ua; 2058c2ecf20Sopenharmony_ci int ret; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); 2088c2ecf20Sopenharmony_ci if (!st) 2098c2ecf20Sopenharmony_ci return -ENOMEM; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci st->client = client; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci st->reg = devm_regulator_get_optional(&client->dev, "vref"); 2148c2ecf20Sopenharmony_ci if (IS_ERR(st->reg)) { 2158c2ecf20Sopenharmony_ci if (PTR_ERR(st->reg) == -EPROBE_DEFER) 2168c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci st->reg = NULL; 2198c2ecf20Sopenharmony_ci } else { 2208c2ecf20Sopenharmony_ci ret = regulator_enable(st->reg); 2218c2ecf20Sopenharmony_ci if (ret) 2228c2ecf20Sopenharmony_ci return ret; 2238c2ecf20Sopenharmony_ci ret = devm_add_action_or_reset(&client->dev, adm1177_remove, 2248c2ecf20Sopenharmony_ci st); 2258c2ecf20Sopenharmony_ci if (ret) 2268c2ecf20Sopenharmony_ci return ret; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "shunt-resistor-micro-ohms", 2308c2ecf20Sopenharmony_ci &st->r_sense_uohm)) 2318c2ecf20Sopenharmony_ci st->r_sense_uohm = 0; 2328c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "adi,shutdown-threshold-microamp", 2338c2ecf20Sopenharmony_ci &alert_threshold_ua)) { 2348c2ecf20Sopenharmony_ci if (st->r_sense_uohm) 2358c2ecf20Sopenharmony_ci /* 2368c2ecf20Sopenharmony_ci * set maximum default value from datasheet based on 2378c2ecf20Sopenharmony_ci * shunt-resistor 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ci alert_threshold_ua = div_u64(105840000000, 2408c2ecf20Sopenharmony_ci st->r_sense_uohm); 2418c2ecf20Sopenharmony_ci else 2428c2ecf20Sopenharmony_ci alert_threshold_ua = 0; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci st->vrange_high = device_property_read_bool(dev, 2458c2ecf20Sopenharmony_ci "adi,vrange-high-enable"); 2468c2ecf20Sopenharmony_ci if (alert_threshold_ua && st->r_sense_uohm) 2478c2ecf20Sopenharmony_ci adm1177_write_alert_thr(st, alert_threshold_ua); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci ret = adm1177_write_cmd(st, ADM1177_CMD_V_CONT | 2508c2ecf20Sopenharmony_ci ADM1177_CMD_I_CONT | 2518c2ecf20Sopenharmony_ci (st->vrange_high ? 0 : ADM1177_CMD_VRANGE)); 2528c2ecf20Sopenharmony_ci if (ret) 2538c2ecf20Sopenharmony_ci return ret; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci hwmon_dev = 2568c2ecf20Sopenharmony_ci devm_hwmon_device_register_with_info(dev, client->name, st, 2578c2ecf20Sopenharmony_ci &adm1177_chip_info, NULL); 2588c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(hwmon_dev); 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic const struct i2c_device_id adm1177_id[] = { 2628c2ecf20Sopenharmony_ci {"adm1177", 0}, 2638c2ecf20Sopenharmony_ci {} 2648c2ecf20Sopenharmony_ci}; 2658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, adm1177_id); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic const struct of_device_id adm1177_dt_ids[] = { 2688c2ecf20Sopenharmony_ci { .compatible = "adi,adm1177" }, 2698c2ecf20Sopenharmony_ci {}, 2708c2ecf20Sopenharmony_ci}; 2718c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, adm1177_dt_ids); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic struct i2c_driver adm1177_driver = { 2748c2ecf20Sopenharmony_ci .class = I2C_CLASS_HWMON, 2758c2ecf20Sopenharmony_ci .driver = { 2768c2ecf20Sopenharmony_ci .name = "adm1177", 2778c2ecf20Sopenharmony_ci .of_match_table = adm1177_dt_ids, 2788c2ecf20Sopenharmony_ci }, 2798c2ecf20Sopenharmony_ci .probe_new = adm1177_probe, 2808c2ecf20Sopenharmony_ci .id_table = adm1177_id, 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_cimodule_i2c_driver(adm1177_driver); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ciMODULE_AUTHOR("Beniamin Bia <beniamin.bia@analog.com>"); 2858c2ecf20Sopenharmony_ciMODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); 2868c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver"); 2878c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 288