Commit | Line | Data |
---|---|---|
09b08ac9 BB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * ADM1177 Hot Swap Controller and Digital Power Monitor with Soft Start Pin | |
4 | * | |
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 2 | |
23 | ||
24 | #define ADM1177_BITS 12 | |
25 | ||
26 | /** | |
27 | * struct adm1177_state - driver instance specific data | |
3478c269 | 28 | * @client: pointer to i2c client |
3478c269 LJ |
29 | * @r_sense_uohm: current sense resistor value |
30 | * @alert_threshold_ua: current limit for shutdown | |
31 | * @vrange_high: internal voltage divider | |
09b08ac9 BB |
32 | */ |
33 | struct adm1177_state { | |
34 | struct i2c_client *client; | |
09b08ac9 BB |
35 | u32 r_sense_uohm; |
36 | u32 alert_threshold_ua; | |
37 | bool vrange_high; | |
38 | }; | |
39 | ||
40 | static int adm1177_read_raw(struct adm1177_state *st, u8 num, u8 *data) | |
41 | { | |
42 | return i2c_master_recv(st->client, data, num); | |
43 | } | |
44 | ||
45 | static int adm1177_write_cmd(struct adm1177_state *st, u8 cmd) | |
46 | { | |
47 | return i2c_smbus_write_byte(st->client, cmd); | |
48 | } | |
49 | ||
50 | static 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 | ||
71 | static 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 milliamperes | |
89 | * ((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 devision | |
107 | * (V_fullscale / 4096) * raw | |
108 | */ | |
109 | if (st->vrange_high) | |
110 | dummy *= 26350; | |
111 | else | |
112 | dummy *= 6650; | |
113 | ||
114 | *val = DIV_ROUND_CLOSEST(dummy, 4096); | |
115 | return 0; | |
116 | default: | |
117 | return -EOPNOTSUPP; | |
118 | } | |
119 | } | |
120 | ||
121 | static 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 | ||
140 | static 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 | ||
171 | static const struct hwmon_channel_info *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 | NULL | |
177 | }; | |
178 | ||
179 | static const struct hwmon_ops adm1177_hwmon_ops = { | |
180 | .is_visible = adm1177_is_visible, | |
181 | .read = adm1177_read, | |
182 | .write = adm1177_write, | |
183 | }; | |
184 | ||
185 | static const struct hwmon_chip_info adm1177_chip_info = { | |
186 | .ops = &adm1177_hwmon_ops, | |
187 | .info = adm1177_info, | |
188 | }; | |
189 | ||
a391adfa | 190 | static int adm1177_probe(struct i2c_client *client) |
09b08ac9 BB |
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 | ||
bba63de0 MV |
204 | ret = devm_regulator_get_enable_optional(&client->dev, "vref"); |
205 | if (ret == -EPROBE_DEFER) | |
206 | return -EPROBE_DEFER; | |
09b08ac9 BB |
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 on | |
216 | * shunt-resistor | |
217 | */ | |
218 | alert_threshold_ua = div_u64(105840000000, | |
219 | st->r_sense_uohm); | |
220 | else | |
221 | 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 | ||
240 | static const struct i2c_device_id adm1177_id[] = { | |
241 | {"adm1177", 0}, | |
242 | {} | |
243 | }; | |
244 | MODULE_DEVICE_TABLE(i2c, adm1177_id); | |
245 | ||
246 | static const struct of_device_id adm1177_dt_ids[] = { | |
247 | { .compatible = "adi,adm1177" }, | |
248 | {}, | |
249 | }; | |
250 | MODULE_DEVICE_TABLE(of, adm1177_dt_ids); | |
251 | ||
252 | static struct i2c_driver adm1177_driver = { | |
253 | .class = I2C_CLASS_HWMON, | |
254 | .driver = { | |
255 | .name = "adm1177", | |
256 | .of_match_table = adm1177_dt_ids, | |
257 | }, | |
a391adfa | 258 | .probe_new = adm1177_probe, |
09b08ac9 BB |
259 | .id_table = adm1177_id, |
260 | }; | |
261 | module_i2c_driver(adm1177_driver); | |
262 | ||
263 | MODULE_AUTHOR("Beniamin Bia <beniamin.bia@analog.com>"); | |
264 | MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); | |
265 | MODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver"); | |
266 | MODULE_LICENSE("GPL v2"); |