266 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * ADM1177 Hot Swap Controller and Digital Power Monitor with Soft Start Pin4 *5 * Copyright 2015-2019 Analog Devices Inc.6 */7 8#include <linux/bits.h>9#include <linux/device.h>10#include <linux/hwmon.h>11#include <linux/i2c.h>12#include <linux/init.h>13#include <linux/module.h>14#include <linux/regulator/consumer.h>15 16/* Command Byte Operations */17#define ADM1177_CMD_V_CONT BIT(0)18#define ADM1177_CMD_I_CONT BIT(2)19#define ADM1177_CMD_VRANGE BIT(4)20 21/* Extended Register */22#define ADM1177_REG_ALERT_TH 223 24#define ADM1177_BITS 1225 26/**27 * struct adm1177_state - driver instance specific data28 * @client: pointer to i2c client29 * @r_sense_uohm: current sense resistor value30 * @alert_threshold_ua: current limit for shutdown31 * @vrange_high: internal voltage divider32 */33struct adm1177_state {34 struct i2c_client *client;35 u32 r_sense_uohm;36 u32 alert_threshold_ua;37 bool vrange_high;38};39 40static int adm1177_read_raw(struct adm1177_state *st, u8 num, u8 *data)41{42 return i2c_master_recv(st->client, data, num);43}44 45static int adm1177_write_cmd(struct adm1177_state *st, u8 cmd)46{47 return i2c_smbus_write_byte(st->client, cmd);48}49 50static int adm1177_write_alert_thr(struct adm1177_state *st,51 u32 alert_threshold_ua)52{53 u64 val;54 int ret;55 56 val = 0xFFULL * alert_threshold_ua * st->r_sense_uohm;57 val = div_u64(val, 105840000U);58 val = div_u64(val, 1000U);59 if (val > 0xFF)60 val = 0xFF;61 62 ret = i2c_smbus_write_byte_data(st->client, ADM1177_REG_ALERT_TH,63 val);64 if (ret)65 return ret;66 67 st->alert_threshold_ua = alert_threshold_ua;68 return 0;69}70 71static int adm1177_read(struct device *dev, enum hwmon_sensor_types type,72 u32 attr, int channel, long *val)73{74 struct adm1177_state *st = dev_get_drvdata(dev);75 u8 data[3];76 long dummy;77 int ret;78 79 switch (type) {80 case hwmon_curr:81 switch (attr) {82 case hwmon_curr_input:83 ret = adm1177_read_raw(st, 3, data);84 if (ret < 0)85 return ret;86 dummy = (data[1] << 4) | (data[2] & 0xF);87 /*88 * convert to milliamperes89 * ((105.84mV / 4096) x raw) / senseResistor(ohm)90 */91 *val = div_u64((105840000ull * dummy),92 4096 * st->r_sense_uohm);93 return 0;94 case hwmon_curr_max_alarm:95 *val = st->alert_threshold_ua;96 return 0;97 default:98 return -EOPNOTSUPP;99 }100 case hwmon_in:101 ret = adm1177_read_raw(st, 3, data);102 if (ret < 0)103 return ret;104 dummy = (data[0] << 4) | (data[2] >> 4);105 /*106 * convert to millivolts based on resistor devision107 * (V_fullscale / 4096) * raw108 */109 if (st->vrange_high)110 dummy *= 26350;111 else112 dummy *= 6650;113 114 *val = DIV_ROUND_CLOSEST(dummy, 4096);115 return 0;116 default:117 return -EOPNOTSUPP;118 }119}120 121static int adm1177_write(struct device *dev, enum hwmon_sensor_types type,122 u32 attr, int channel, long val)123{124 struct adm1177_state *st = dev_get_drvdata(dev);125 126 switch (type) {127 case hwmon_curr:128 switch (attr) {129 case hwmon_curr_max_alarm:130 adm1177_write_alert_thr(st, val);131 return 0;132 default:133 return -EOPNOTSUPP;134 }135 default:136 return -EOPNOTSUPP;137 }138}139 140static umode_t adm1177_is_visible(const void *data,141 enum hwmon_sensor_types type,142 u32 attr, int channel)143{144 const struct adm1177_state *st = data;145 146 switch (type) {147 case hwmon_in:148 switch (attr) {149 case hwmon_in_input:150 return 0444;151 }152 break;153 case hwmon_curr:154 switch (attr) {155 case hwmon_curr_input:156 if (st->r_sense_uohm)157 return 0444;158 return 0;159 case hwmon_curr_max_alarm:160 if (st->r_sense_uohm)161 return 0644;162 return 0;163 }164 break;165 default:166 break;167 }168 return 0;169}170 171static const struct hwmon_channel_info * const adm1177_info[] = {172 HWMON_CHANNEL_INFO(curr,173 HWMON_C_INPUT | HWMON_C_MAX_ALARM),174 HWMON_CHANNEL_INFO(in,175 HWMON_I_INPUT),176 NULL177};178 179static const struct hwmon_ops adm1177_hwmon_ops = {180 .is_visible = adm1177_is_visible,181 .read = adm1177_read,182 .write = adm1177_write,183};184 185static const struct hwmon_chip_info adm1177_chip_info = {186 .ops = &adm1177_hwmon_ops,187 .info = adm1177_info,188};189 190static int adm1177_probe(struct i2c_client *client)191{192 struct device *dev = &client->dev;193 struct device *hwmon_dev;194 struct adm1177_state *st;195 u32 alert_threshold_ua;196 int ret;197 198 st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);199 if (!st)200 return -ENOMEM;201 202 st->client = client;203 204 ret = devm_regulator_get_enable_optional(&client->dev, "vref");205 if (ret == -EPROBE_DEFER)206 return -EPROBE_DEFER;207 208 if (device_property_read_u32(dev, "shunt-resistor-micro-ohms",209 &st->r_sense_uohm))210 st->r_sense_uohm = 0;211 if (device_property_read_u32(dev, "adi,shutdown-threshold-microamp",212 &alert_threshold_ua)) {213 if (st->r_sense_uohm)214 /*215 * set maximum default value from datasheet based on216 * shunt-resistor217 */218 alert_threshold_ua = div_u64(105840000000,219 st->r_sense_uohm);220 else221 alert_threshold_ua = 0;222 }223 st->vrange_high = device_property_read_bool(dev,224 "adi,vrange-high-enable");225 if (alert_threshold_ua && st->r_sense_uohm)226 adm1177_write_alert_thr(st, alert_threshold_ua);227 228 ret = adm1177_write_cmd(st, ADM1177_CMD_V_CONT |229 ADM1177_CMD_I_CONT |230 (st->vrange_high ? 0 : ADM1177_CMD_VRANGE));231 if (ret)232 return ret;233 234 hwmon_dev =235 devm_hwmon_device_register_with_info(dev, client->name, st,236 &adm1177_chip_info, NULL);237 return PTR_ERR_OR_ZERO(hwmon_dev);238}239 240static const struct i2c_device_id adm1177_id[] = {241 {"adm1177"},242 {}243};244MODULE_DEVICE_TABLE(i2c, adm1177_id);245 246static const struct of_device_id adm1177_dt_ids[] = {247 { .compatible = "adi,adm1177" },248 {},249};250MODULE_DEVICE_TABLE(of, adm1177_dt_ids);251 252static struct i2c_driver adm1177_driver = {253 .driver = {254 .name = "adm1177",255 .of_match_table = adm1177_dt_ids,256 },257 .probe = adm1177_probe,258 .id_table = adm1177_id,259};260module_i2c_driver(adm1177_driver);261 262MODULE_AUTHOR("Beniamin Bia <beniamin.bia@analog.com>");263MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");264MODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver");265MODULE_LICENSE("GPL v2");266