1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Windfarm PowerMac thermal control. AD7417 sensors |
4 | * |
5 | * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. |
6 | */ |
7 | |
8 | #include <linux/types.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/init.h> |
14 | #include <linux/wait.h> |
15 | #include <linux/i2c.h> |
16 | |
17 | #include <asm/machdep.h> |
18 | #include <asm/io.h> |
19 | #include <asm/sections.h> |
20 | |
21 | #include "windfarm.h" |
22 | #include "windfarm_mpu.h" |
23 | |
24 | #define VERSION "1.0" |
25 | |
26 | struct wf_ad7417_priv { |
27 | struct kref ref; |
28 | struct i2c_client *i2c; |
29 | u8 config; |
30 | u8 cpu; |
31 | const struct mpu_data *mpu; |
32 | struct wf_sensor sensors[5]; |
33 | struct mutex lock; |
34 | }; |
35 | |
36 | static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value) |
37 | { |
38 | struct wf_ad7417_priv *pv = sr->priv; |
39 | u8 buf[2]; |
40 | s16 raw; |
41 | int rc; |
42 | |
43 | *value = 0; |
44 | mutex_lock(&pv->lock); |
45 | |
46 | /* Read temp register */ |
47 | buf[0] = 0; |
48 | rc = i2c_master_send(client: pv->i2c, buf, count: 1); |
49 | if (rc < 0) |
50 | goto error; |
51 | rc = i2c_master_recv(client: pv->i2c, buf, count: 2); |
52 | if (rc < 0) |
53 | goto error; |
54 | |
55 | /* Read a a 16-bit signed value */ |
56 | raw = be16_to_cpup(p: (__le16 *)buf); |
57 | |
58 | /* Convert 8.8-bit to 16.16 fixed point */ |
59 | *value = ((s32)raw) << 8; |
60 | |
61 | mutex_unlock(lock: &pv->lock); |
62 | return 0; |
63 | |
64 | error: |
65 | mutex_unlock(lock: &pv->lock); |
66 | return -1; |
67 | } |
68 | |
69 | /* |
70 | * Scaling factors for the AD7417 ADC converters (except |
71 | * for the CPU diode which is obtained from the EEPROM). |
72 | * Those values are obtained from the property list of |
73 | * the darwin driver |
74 | */ |
75 | #define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */ |
76 | #define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */ |
77 | #define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */ |
78 | |
79 | static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv, |
80 | int chan, s32 raw, s32 *value) |
81 | { |
82 | switch(chan) { |
83 | case 1: /* Diode */ |
84 | *value = (raw * (s32)pv->mpu->mdiode + |
85 | ((s32)pv->mpu->bdiode << 12)) >> 2; |
86 | break; |
87 | case 2: /* 12v current */ |
88 | *value = raw * ADC_12V_CURRENT_SCALE; |
89 | break; |
90 | case 3: /* core voltage */ |
91 | *value = raw * ADC_CPU_VOLTAGE_SCALE; |
92 | break; |
93 | case 4: /* core current */ |
94 | *value = raw * ADC_CPU_CURRENT_SCALE; |
95 | break; |
96 | } |
97 | } |
98 | |
99 | static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value) |
100 | { |
101 | struct wf_ad7417_priv *pv = sr->priv; |
102 | int chan = sr - pv->sensors; |
103 | int i, rc; |
104 | u8 buf[2]; |
105 | u16 raw; |
106 | |
107 | *value = 0; |
108 | mutex_lock(&pv->lock); |
109 | for (i = 0; i < 10; i++) { |
110 | /* Set channel */ |
111 | buf[0] = 1; |
112 | buf[1] = (pv->config & 0x1f) | (chan << 5); |
113 | rc = i2c_master_send(client: pv->i2c, buf, count: 2); |
114 | if (rc < 0) |
115 | goto error; |
116 | |
117 | /* Wait for conversion */ |
118 | msleep(msecs: 1); |
119 | |
120 | /* Switch to data register */ |
121 | buf[0] = 4; |
122 | rc = i2c_master_send(client: pv->i2c, buf, count: 1); |
123 | if (rc < 0) |
124 | goto error; |
125 | |
126 | /* Read result */ |
127 | rc = i2c_master_recv(client: pv->i2c, buf, count: 2); |
128 | if (rc < 0) |
129 | goto error; |
130 | |
131 | /* Read a a 16-bit signed value */ |
132 | raw = be16_to_cpup(p: (__le16 *)buf) >> 6; |
133 | wf_ad7417_adc_convert(pv, chan, raw, value); |
134 | |
135 | dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]" |
136 | " raw value: 0x%x, conv to: 0x%08x\n" , |
137 | chan, sr->name, raw, *value); |
138 | |
139 | mutex_unlock(lock: &pv->lock); |
140 | return 0; |
141 | |
142 | error: |
143 | dev_dbg(&pv->i2c->dev, |
144 | "Error reading ADC, try %d...\n" , i); |
145 | if (i < 9) |
146 | msleep(msecs: 10); |
147 | } |
148 | mutex_unlock(lock: &pv->lock); |
149 | return -1; |
150 | } |
151 | |
152 | static void wf_ad7417_release(struct kref *ref) |
153 | { |
154 | struct wf_ad7417_priv *pv = container_of(ref, |
155 | struct wf_ad7417_priv, ref); |
156 | kfree(objp: pv); |
157 | } |
158 | |
159 | static void wf_ad7417_sensor_release(struct wf_sensor *sr) |
160 | { |
161 | struct wf_ad7417_priv *pv = sr->priv; |
162 | |
163 | kfree(objp: sr->name); |
164 | kref_put(kref: &pv->ref, release: wf_ad7417_release); |
165 | } |
166 | |
167 | static const struct wf_sensor_ops wf_ad7417_temp_ops = { |
168 | .get_value = wf_ad7417_temp_get, |
169 | .release = wf_ad7417_sensor_release, |
170 | .owner = THIS_MODULE, |
171 | }; |
172 | |
173 | static const struct wf_sensor_ops wf_ad7417_adc_ops = { |
174 | .get_value = wf_ad7417_adc_get, |
175 | .release = wf_ad7417_sensor_release, |
176 | .owner = THIS_MODULE, |
177 | }; |
178 | |
179 | static void wf_ad7417_add_sensor(struct wf_ad7417_priv *pv, |
180 | int index, const char *name, |
181 | const struct wf_sensor_ops *ops) |
182 | { |
183 | pv->sensors[index].name = kasprintf(GFP_KERNEL, fmt: "%s-%d" , name, pv->cpu); |
184 | pv->sensors[index].priv = pv; |
185 | pv->sensors[index].ops = ops; |
186 | if (!wf_register_sensor(sr: &pv->sensors[index])) |
187 | kref_get(kref: &pv->ref); |
188 | } |
189 | |
190 | static void wf_ad7417_init_chip(struct wf_ad7417_priv *pv) |
191 | { |
192 | int rc; |
193 | u8 buf[2]; |
194 | u8 config = 0; |
195 | |
196 | /* |
197 | * Read ADC the configuration register and cache it. We |
198 | * also make sure Config2 contains proper values, I've seen |
199 | * cases where we got stale grabage in there, thus preventing |
200 | * proper reading of conv. values |
201 | */ |
202 | |
203 | /* Clear Config2 */ |
204 | buf[0] = 5; |
205 | buf[1] = 0; |
206 | i2c_master_send(client: pv->i2c, buf, count: 2); |
207 | |
208 | /* Read & cache Config1 */ |
209 | buf[0] = 1; |
210 | rc = i2c_master_send(client: pv->i2c, buf, count: 1); |
211 | if (rc > 0) { |
212 | rc = i2c_master_recv(client: pv->i2c, buf, count: 1); |
213 | if (rc > 0) { |
214 | config = buf[0]; |
215 | |
216 | dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n" , |
217 | config); |
218 | |
219 | /* Disable shutdown mode */ |
220 | config &= 0xfe; |
221 | buf[0] = 1; |
222 | buf[1] = config; |
223 | rc = i2c_master_send(client: pv->i2c, buf, count: 2); |
224 | } |
225 | } |
226 | if (rc <= 0) |
227 | dev_err(&pv->i2c->dev, "Error reading ADC config\n" ); |
228 | |
229 | pv->config = config; |
230 | } |
231 | |
232 | static int wf_ad7417_probe(struct i2c_client *client) |
233 | { |
234 | struct wf_ad7417_priv *pv; |
235 | const struct mpu_data *mpu; |
236 | const char *loc; |
237 | int cpu_nr; |
238 | |
239 | loc = of_get_property(node: client->dev.of_node, name: "hwsensor-location" , NULL); |
240 | if (!loc) { |
241 | dev_warn(&client->dev, "Missing hwsensor-location property!\n" ); |
242 | return -ENXIO; |
243 | } |
244 | |
245 | /* |
246 | * Identify which CPU we belong to by looking at the first entry |
247 | * in the hwsensor-location list |
248 | */ |
249 | if (!strncmp(loc, "CPU A" , 5)) |
250 | cpu_nr = 0; |
251 | else if (!strncmp(loc, "CPU B" , 5)) |
252 | cpu_nr = 1; |
253 | else { |
254 | pr_err("wf_ad7417: Can't identify location %s\n" , loc); |
255 | return -ENXIO; |
256 | } |
257 | mpu = wf_get_mpu(cpu: cpu_nr); |
258 | if (!mpu) { |
259 | dev_err(&client->dev, "Failed to retrieve MPU data\n" ); |
260 | return -ENXIO; |
261 | } |
262 | |
263 | pv = kzalloc(size: sizeof(struct wf_ad7417_priv), GFP_KERNEL); |
264 | if (pv == NULL) |
265 | return -ENODEV; |
266 | |
267 | kref_init(kref: &pv->ref); |
268 | mutex_init(&pv->lock); |
269 | pv->i2c = client; |
270 | pv->cpu = cpu_nr; |
271 | pv->mpu = mpu; |
272 | dev_set_drvdata(dev: &client->dev, data: pv); |
273 | |
274 | /* Initialize the chip */ |
275 | wf_ad7417_init_chip(pv); |
276 | |
277 | /* |
278 | * We cannot rely on Apple device-tree giving us child |
279 | * node with the names of the individual sensors so we |
280 | * just hard code what we know about them |
281 | */ |
282 | wf_ad7417_add_sensor(pv, index: 0, name: "cpu-amb-temp" , ops: &wf_ad7417_temp_ops); |
283 | wf_ad7417_add_sensor(pv, index: 1, name: "cpu-diode-temp" , ops: &wf_ad7417_adc_ops); |
284 | wf_ad7417_add_sensor(pv, index: 2, name: "cpu-12v-current" , ops: &wf_ad7417_adc_ops); |
285 | wf_ad7417_add_sensor(pv, index: 3, name: "cpu-voltage" , ops: &wf_ad7417_adc_ops); |
286 | wf_ad7417_add_sensor(pv, index: 4, name: "cpu-current" , ops: &wf_ad7417_adc_ops); |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | static void wf_ad7417_remove(struct i2c_client *client) |
292 | { |
293 | struct wf_ad7417_priv *pv = dev_get_drvdata(dev: &client->dev); |
294 | int i; |
295 | |
296 | /* Mark client detached */ |
297 | pv->i2c = NULL; |
298 | |
299 | /* Release sensor */ |
300 | for (i = 0; i < 5; i++) |
301 | wf_unregister_sensor(sr: &pv->sensors[i]); |
302 | |
303 | kref_put(kref: &pv->ref, release: wf_ad7417_release); |
304 | } |
305 | |
306 | static const struct i2c_device_id wf_ad7417_id[] = { |
307 | { "MAC,ad7417" , 0 }, |
308 | { } |
309 | }; |
310 | MODULE_DEVICE_TABLE(i2c, wf_ad7417_id); |
311 | |
312 | static const struct of_device_id wf_ad7417_of_id[] = { |
313 | { .compatible = "ad7417" , }, |
314 | { } |
315 | }; |
316 | MODULE_DEVICE_TABLE(of, wf_ad7417_of_id); |
317 | |
318 | static struct i2c_driver wf_ad7417_driver = { |
319 | .driver = { |
320 | .name = "wf_ad7417" , |
321 | .of_match_table = wf_ad7417_of_id, |
322 | }, |
323 | .probe = wf_ad7417_probe, |
324 | .remove = wf_ad7417_remove, |
325 | .id_table = wf_ad7417_id, |
326 | }; |
327 | |
328 | static int wf_ad7417_init(void) |
329 | { |
330 | /* This is only supported on these machines */ |
331 | if (!of_machine_is_compatible(compat: "PowerMac7,2" ) && |
332 | !of_machine_is_compatible(compat: "PowerMac7,3" ) && |
333 | !of_machine_is_compatible(compat: "RackMac3,1" )) |
334 | return -ENODEV; |
335 | |
336 | return i2c_add_driver(&wf_ad7417_driver); |
337 | } |
338 | |
339 | static void wf_ad7417_exit(void) |
340 | { |
341 | i2c_del_driver(driver: &wf_ad7417_driver); |
342 | } |
343 | |
344 | module_init(wf_ad7417_init); |
345 | module_exit(wf_ad7417_exit); |
346 | |
347 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>" ); |
348 | MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs" ); |
349 | MODULE_LICENSE("GPL" ); |
350 | |
351 | |