diff options
author | hotran <hotran@apm.com> | 2016-07-21 18:37:32 -0400 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2016-09-09 00:34:16 -0400 |
commit | ed42cfa881e1d8d9603b7cb872199e3c8e0d1b19 (patch) | |
tree | 2cb25d6d7b444c57fa2442c2136cb3a7c150a07b /drivers/hwmon/xgene-hwmon.c | |
parent | 86430c1a66fe490fc97b0970575c223e27dee49d (diff) |
hwmon: Add xgene hwmon driver
This patch adds hardware temperature and power reading support for
APM X-Gene SoC using the mailbox communication interface.
Signed-off-by: Hoan Tran <hotran@apm.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Diffstat (limited to 'drivers/hwmon/xgene-hwmon.c')
-rw-r--r-- | drivers/hwmon/xgene-hwmon.c | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c new file mode 100644 index 000000000000..bc78a5d10182 --- /dev/null +++ b/drivers/hwmon/xgene-hwmon.c | |||
@@ -0,0 +1,755 @@ | |||
1 | /* | ||
2 | * APM X-Gene SoC Hardware Monitoring Driver | ||
3 | * | ||
4 | * Copyright (c) 2016, Applied Micro Circuits Corporation | ||
5 | * Author: Loc Ho <lho@apm.com> | ||
6 | * Hoan Tran <hotran@apm.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License as | ||
10 | * published by the Free Software Foundation; either version 2 of | ||
11 | * the License, or (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
20 | * | ||
21 | * This driver provides the following features: | ||
22 | * - Retrieve CPU total power (uW) | ||
23 | * - Retrieve IO total power (uW) | ||
24 | * - Retrieve SoC temperature (milli-degree C) and alarm | ||
25 | */ | ||
26 | #include <linux/acpi.h> | ||
27 | #include <linux/dma-mapping.h> | ||
28 | #include <linux/hwmon.h> | ||
29 | #include <linux/hwmon-sysfs.h> | ||
30 | #include <linux/interrupt.h> | ||
31 | #include <linux/kfifo.h> | ||
32 | #include <linux/mailbox_controller.h> | ||
33 | #include <linux/mailbox_client.h> | ||
34 | #include <linux/module.h> | ||
35 | #include <linux/of.h> | ||
36 | #include <linux/platform_device.h> | ||
37 | #include <acpi/acpi_io.h> | ||
38 | #include <acpi/pcc.h> | ||
39 | |||
40 | /* SLIMpro message defines */ | ||
41 | #define MSG_TYPE_DBG 0 | ||
42 | #define MSG_TYPE_ERR 7 | ||
43 | #define MSG_TYPE_PWRMGMT 9 | ||
44 | |||
45 | #define MSG_TYPE(v) (((v) & 0xF0000000) >> 28) | ||
46 | #define MSG_TYPE_SET(v) (((v) << 28) & 0xF0000000) | ||
47 | #define MSG_SUBTYPE(v) (((v) & 0x0F000000) >> 24) | ||
48 | #define MSG_SUBTYPE_SET(v) (((v) << 24) & 0x0F000000) | ||
49 | |||
50 | #define DBG_SUBTYPE_SENSOR_READ 4 | ||
51 | #define SENSOR_RD_MSG 0x04FFE902 | ||
52 | #define SENSOR_RD_EN_ADDR(a) ((a) & 0x000FFFFF) | ||
53 | #define PMD_PWR_REG 0x20 | ||
54 | #define PMD_PWR_MW_REG 0x26 | ||
55 | #define SOC_PWR_REG 0x21 | ||
56 | #define SOC_PWR_MW_REG 0x27 | ||
57 | #define SOC_TEMP_REG 0x10 | ||
58 | |||
59 | #define TEMP_NEGATIVE_BIT 8 | ||
60 | #define SENSOR_INVALID_DATA BIT(15) | ||
61 | |||
62 | #define PWRMGMT_SUBTYPE_TPC 1 | ||
63 | #define TPC_ALARM 2 | ||
64 | #define TPC_GET_ALARM 3 | ||
65 | #define TPC_CMD(v) (((v) & 0x00FF0000) >> 16) | ||
66 | #define TPC_CMD_SET(v) (((v) << 16) & 0x00FF0000) | ||
67 | #define TPC_EN_MSG(hndl, cmd, type) \ | ||
68 | (MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \ | ||
69 | MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type) | ||
70 | |||
71 | /* PCC defines */ | ||
72 | #define PCC_SIGNATURE_MASK 0x50424300 | ||
73 | #define PCCC_GENERATE_DB_INT BIT(15) | ||
74 | #define PCCS_CMD_COMPLETE BIT(0) | ||
75 | #define PCCS_SCI_DOORBEL BIT(1) | ||
76 | #define PCCS_PLATFORM_NOTIFICATION BIT(3) | ||
77 | /* | ||
78 | * Arbitrary retries in case the remote processor is slow to respond | ||
79 | * to PCC commands | ||
80 | */ | ||
81 | #define PCC_NUM_RETRIES 500 | ||
82 | |||
83 | #define ASYNC_MSG_FIFO_SIZE 16 | ||
84 | #define MBOX_OP_TIMEOUTMS 1000 | ||
85 | |||
86 | #define WATT_TO_mWATT(x) ((x) * 1000) | ||
87 | #define mWATT_TO_uWATT(x) ((x) * 1000) | ||
88 | #define CELSIUS_TO_mCELSIUS(x) ((x) * 1000) | ||
89 | |||
90 | #define to_xgene_hwmon_dev(cl) \ | ||
91 | container_of(cl, struct xgene_hwmon_dev, mbox_client) | ||
92 | |||
93 | struct slimpro_resp_msg { | ||
94 | u32 msg; | ||
95 | u32 param1; | ||
96 | u32 param2; | ||
97 | } __packed; | ||
98 | |||
99 | struct xgene_hwmon_dev { | ||
100 | struct device *dev; | ||
101 | struct mbox_chan *mbox_chan; | ||
102 | struct mbox_client mbox_client; | ||
103 | int mbox_idx; | ||
104 | |||
105 | spinlock_t kfifo_lock; | ||
106 | struct mutex rd_mutex; | ||
107 | struct completion rd_complete; | ||
108 | int resp_pending; | ||
109 | struct slimpro_resp_msg sync_msg; | ||
110 | |||
111 | struct work_struct workq; | ||
112 | struct kfifo_rec_ptr_1 async_msg_fifo; | ||
113 | |||
114 | struct device *hwmon_dev; | ||
115 | bool temp_critical_alarm; | ||
116 | |||
117 | phys_addr_t comm_base_addr; | ||
118 | void *pcc_comm_addr; | ||
119 | u64 usecs_lat; | ||
120 | }; | ||
121 | |||
122 | /* | ||
123 | * This function tests and clears a bitmask then returns its old value | ||
124 | */ | ||
125 | static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask) | ||
126 | { | ||
127 | u16 ret, val; | ||
128 | |||
129 | val = readw_relaxed(addr); | ||
130 | ret = val & mask; | ||
131 | val &= ~mask; | ||
132 | writew_relaxed(val, addr); | ||
133 | |||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) | ||
138 | { | ||
139 | struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; | ||
140 | void *ptr = generic_comm_base + 1; | ||
141 | int rc, i; | ||
142 | u16 val; | ||
143 | |||
144 | mutex_lock(&ctx->rd_mutex); | ||
145 | init_completion(&ctx->rd_complete); | ||
146 | ctx->resp_pending = true; | ||
147 | |||
148 | /* Write signature for subspace */ | ||
149 | writel_relaxed(PCC_SIGNATURE_MASK | ctx->mbox_idx, | ||
150 | &generic_comm_base->signature); | ||
151 | |||
152 | /* Write to the shared command region */ | ||
153 | writew_relaxed(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT, | ||
154 | &generic_comm_base->command); | ||
155 | |||
156 | /* Flip CMD COMPLETE bit */ | ||
157 | val = readw_relaxed(&generic_comm_base->status); | ||
158 | val &= ~PCCS_CMD_COMPLETE; | ||
159 | writew_relaxed(val, &generic_comm_base->status); | ||
160 | |||
161 | /* Copy the message to the PCC comm space */ | ||
162 | for (i = 0; i < sizeof(struct slimpro_resp_msg) / 4; i++) | ||
163 | writel_relaxed(msg[i], ptr + i * 4); | ||
164 | |||
165 | /* Ring the doorbell */ | ||
166 | rc = mbox_send_message(ctx->mbox_chan, msg); | ||
167 | if (rc < 0) { | ||
168 | dev_err(ctx->dev, "Mailbox send error %d\n", rc); | ||
169 | goto err; | ||
170 | } | ||
171 | if (!wait_for_completion_timeout(&ctx->rd_complete, | ||
172 | usecs_to_jiffies(ctx->usecs_lat))) { | ||
173 | dev_err(ctx->dev, "Mailbox operation timed out\n"); | ||
174 | rc = -ETIMEDOUT; | ||
175 | goto err; | ||
176 | } | ||
177 | |||
178 | /* Check for error message */ | ||
179 | if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { | ||
180 | rc = -EINVAL; | ||
181 | goto err; | ||
182 | } | ||
183 | |||
184 | msg[0] = ctx->sync_msg.msg; | ||
185 | msg[1] = ctx->sync_msg.param1; | ||
186 | msg[2] = ctx->sync_msg.param2; | ||
187 | |||
188 | err: | ||
189 | mbox_chan_txdone(ctx->mbox_chan, 0); | ||
190 | ctx->resp_pending = false; | ||
191 | mutex_unlock(&ctx->rd_mutex); | ||
192 | return rc; | ||
193 | } | ||
194 | |||
195 | static int xgene_hwmon_rd(struct xgene_hwmon_dev *ctx, u32 *msg) | ||
196 | { | ||
197 | int rc; | ||
198 | |||
199 | mutex_lock(&ctx->rd_mutex); | ||
200 | init_completion(&ctx->rd_complete); | ||
201 | ctx->resp_pending = true; | ||
202 | |||
203 | rc = mbox_send_message(ctx->mbox_chan, msg); | ||
204 | if (rc < 0) { | ||
205 | dev_err(ctx->dev, "Mailbox send error %d\n", rc); | ||
206 | goto err; | ||
207 | } | ||
208 | |||
209 | if (!wait_for_completion_timeout(&ctx->rd_complete, | ||
210 | msecs_to_jiffies(MBOX_OP_TIMEOUTMS))) { | ||
211 | dev_err(ctx->dev, "Mailbox operation timed out\n"); | ||
212 | rc = -ETIMEDOUT; | ||
213 | goto err; | ||
214 | } | ||
215 | |||
216 | /* Check for error message */ | ||
217 | if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { | ||
218 | rc = -EINVAL; | ||
219 | goto err; | ||
220 | } | ||
221 | |||
222 | msg[0] = ctx->sync_msg.msg; | ||
223 | msg[1] = ctx->sync_msg.param1; | ||
224 | msg[2] = ctx->sync_msg.param2; | ||
225 | |||
226 | err: | ||
227 | ctx->resp_pending = false; | ||
228 | mutex_unlock(&ctx->rd_mutex); | ||
229 | return rc; | ||
230 | } | ||
231 | |||
232 | static int xgene_hwmon_reg_map_rd(struct xgene_hwmon_dev *ctx, u32 addr, | ||
233 | u32 *data) | ||
234 | { | ||
235 | u32 msg[3]; | ||
236 | int rc; | ||
237 | |||
238 | msg[0] = SENSOR_RD_MSG; | ||
239 | msg[1] = SENSOR_RD_EN_ADDR(addr); | ||
240 | msg[2] = 0; | ||
241 | |||
242 | if (acpi_disabled) | ||
243 | rc = xgene_hwmon_rd(ctx, msg); | ||
244 | else | ||
245 | rc = xgene_hwmon_pcc_rd(ctx, msg); | ||
246 | |||
247 | if (rc < 0) | ||
248 | return rc; | ||
249 | |||
250 | /* | ||
251 | * Check if sensor data is valid. | ||
252 | */ | ||
253 | if (msg[1] & SENSOR_INVALID_DATA) | ||
254 | return -ENODATA; | ||
255 | |||
256 | *data = msg[1]; | ||
257 | |||
258 | return rc; | ||
259 | } | ||
260 | |||
261 | static int xgene_hwmon_get_notification_msg(struct xgene_hwmon_dev *ctx, | ||
262 | u32 *amsg) | ||
263 | { | ||
264 | u32 msg[3]; | ||
265 | int rc; | ||
266 | |||
267 | msg[0] = TPC_EN_MSG(PWRMGMT_SUBTYPE_TPC, TPC_GET_ALARM, 0); | ||
268 | msg[1] = 0; | ||
269 | msg[2] = 0; | ||
270 | |||
271 | rc = xgene_hwmon_pcc_rd(ctx, msg); | ||
272 | if (rc < 0) | ||
273 | return rc; | ||
274 | |||
275 | amsg[0] = msg[0]; | ||
276 | amsg[1] = msg[1]; | ||
277 | amsg[2] = msg[2]; | ||
278 | |||
279 | return rc; | ||
280 | } | ||
281 | |||
282 | static int xgene_hwmon_get_cpu_pwr(struct xgene_hwmon_dev *ctx, u32 *val) | ||
283 | { | ||
284 | u32 watt, mwatt; | ||
285 | int rc; | ||
286 | |||
287 | rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_REG, &watt); | ||
288 | if (rc < 0) | ||
289 | return rc; | ||
290 | |||
291 | rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_MW_REG, &mwatt); | ||
292 | if (rc < 0) | ||
293 | return rc; | ||
294 | |||
295 | *val = WATT_TO_mWATT(watt) + mwatt; | ||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | static int xgene_hwmon_get_io_pwr(struct xgene_hwmon_dev *ctx, u32 *val) | ||
300 | { | ||
301 | u32 watt, mwatt; | ||
302 | int rc; | ||
303 | |||
304 | rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_REG, &watt); | ||
305 | if (rc < 0) | ||
306 | return rc; | ||
307 | |||
308 | rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_MW_REG, &mwatt); | ||
309 | if (rc < 0) | ||
310 | return rc; | ||
311 | |||
312 | *val = WATT_TO_mWATT(watt) + mwatt; | ||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | static int xgene_hwmon_get_temp(struct xgene_hwmon_dev *ctx, u32 *val) | ||
317 | { | ||
318 | return xgene_hwmon_reg_map_rd(ctx, SOC_TEMP_REG, val); | ||
319 | } | ||
320 | |||
321 | /* | ||
322 | * Sensor temperature/power functions | ||
323 | */ | ||
324 | static ssize_t temp1_input_show(struct device *dev, | ||
325 | struct device_attribute *attr, | ||
326 | char *buf) | ||
327 | { | ||
328 | struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); | ||
329 | int rc, temp; | ||
330 | u32 val; | ||
331 | |||
332 | rc = xgene_hwmon_get_temp(ctx, &val); | ||
333 | if (rc < 0) | ||
334 | return rc; | ||
335 | |||
336 | temp = sign_extend32(val, TEMP_NEGATIVE_BIT); | ||
337 | |||
338 | return snprintf(buf, PAGE_SIZE, "%d\n", CELSIUS_TO_mCELSIUS(temp)); | ||
339 | } | ||
340 | |||
341 | static ssize_t temp1_label_show(struct device *dev, | ||
342 | struct device_attribute *attr, | ||
343 | char *buf) | ||
344 | { | ||
345 | return snprintf(buf, PAGE_SIZE, "SoC Temperature\n"); | ||
346 | } | ||
347 | |||
348 | static ssize_t temp1_critical_alarm_show(struct device *dev, | ||
349 | struct device_attribute *devattr, | ||
350 | char *buf) | ||
351 | { | ||
352 | struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); | ||
353 | |||
354 | return snprintf(buf, PAGE_SIZE, "%d\n", ctx->temp_critical_alarm); | ||
355 | } | ||
356 | |||
357 | static ssize_t power1_label_show(struct device *dev, | ||
358 | struct device_attribute *attr, | ||
359 | char *buf) | ||
360 | { | ||
361 | return snprintf(buf, PAGE_SIZE, "CPU power\n"); | ||
362 | } | ||
363 | |||
364 | static ssize_t power2_label_show(struct device *dev, | ||
365 | struct device_attribute *attr, | ||
366 | char *buf) | ||
367 | { | ||
368 | return snprintf(buf, PAGE_SIZE, "IO power\n"); | ||
369 | } | ||
370 | |||
371 | static ssize_t power1_input_show(struct device *dev, | ||
372 | struct device_attribute *attr, | ||
373 | char *buf) | ||
374 | { | ||
375 | struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); | ||
376 | u32 val; | ||
377 | int rc; | ||
378 | |||
379 | rc = xgene_hwmon_get_cpu_pwr(ctx, &val); | ||
380 | if (rc < 0) | ||
381 | return rc; | ||
382 | |||
383 | return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); | ||
384 | } | ||
385 | |||
386 | static ssize_t power2_input_show(struct device *dev, | ||
387 | struct device_attribute *attr, | ||
388 | char *buf) | ||
389 | { | ||
390 | struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); | ||
391 | u32 val; | ||
392 | int rc; | ||
393 | |||
394 | rc = xgene_hwmon_get_io_pwr(ctx, &val); | ||
395 | if (rc < 0) | ||
396 | return rc; | ||
397 | |||
398 | return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); | ||
399 | } | ||
400 | |||
401 | static DEVICE_ATTR_RO(temp1_label); | ||
402 | static DEVICE_ATTR_RO(temp1_input); | ||
403 | static DEVICE_ATTR_RO(temp1_critical_alarm); | ||
404 | static DEVICE_ATTR_RO(power1_label); | ||
405 | static DEVICE_ATTR_RO(power1_input); | ||
406 | static DEVICE_ATTR_RO(power2_label); | ||
407 | static DEVICE_ATTR_RO(power2_input); | ||
408 | |||
409 | static struct attribute *xgene_hwmon_attrs[] = { | ||
410 | &dev_attr_temp1_label.attr, | ||
411 | &dev_attr_temp1_input.attr, | ||
412 | &dev_attr_temp1_critical_alarm.attr, | ||
413 | &dev_attr_power1_label.attr, | ||
414 | &dev_attr_power1_input.attr, | ||
415 | &dev_attr_power2_label.attr, | ||
416 | &dev_attr_power2_input.attr, | ||
417 | NULL, | ||
418 | }; | ||
419 | |||
420 | ATTRIBUTE_GROUPS(xgene_hwmon); | ||
421 | |||
422 | static int xgene_hwmon_tpc_alarm(struct xgene_hwmon_dev *ctx, | ||
423 | struct slimpro_resp_msg *amsg) | ||
424 | { | ||
425 | ctx->temp_critical_alarm = !!amsg->param2; | ||
426 | sysfs_notify(&ctx->dev->kobj, NULL, "temp1_critical_alarm"); | ||
427 | |||
428 | return 0; | ||
429 | } | ||
430 | |||
431 | static void xgene_hwmon_process_pwrmsg(struct xgene_hwmon_dev *ctx, | ||
432 | struct slimpro_resp_msg *amsg) | ||
433 | { | ||
434 | if ((MSG_SUBTYPE(amsg->msg) == PWRMGMT_SUBTYPE_TPC) && | ||
435 | (TPC_CMD(amsg->msg) == TPC_ALARM)) | ||
436 | xgene_hwmon_tpc_alarm(ctx, amsg); | ||
437 | } | ||
438 | |||
439 | /* | ||
440 | * This function is called to process async work queue | ||
441 | */ | ||
442 | static void xgene_hwmon_evt_work(struct work_struct *work) | ||
443 | { | ||
444 | struct slimpro_resp_msg amsg; | ||
445 | struct xgene_hwmon_dev *ctx; | ||
446 | int ret; | ||
447 | |||
448 | ctx = container_of(work, struct xgene_hwmon_dev, workq); | ||
449 | while (kfifo_out_spinlocked(&ctx->async_msg_fifo, &amsg, | ||
450 | sizeof(struct slimpro_resp_msg), | ||
451 | &ctx->kfifo_lock)) { | ||
452 | /* | ||
453 | * If PCC, send a consumer command to Platform to get info | ||
454 | * If Slimpro Mailbox, get message from specific FIFO | ||
455 | */ | ||
456 | if (!acpi_disabled) { | ||
457 | ret = xgene_hwmon_get_notification_msg(ctx, | ||
458 | (u32 *)&amsg); | ||
459 | if (ret < 0) | ||
460 | continue; | ||
461 | } | ||
462 | |||
463 | if (MSG_TYPE(amsg.msg) == MSG_TYPE_PWRMGMT) | ||
464 | xgene_hwmon_process_pwrmsg(ctx, &amsg); | ||
465 | } | ||
466 | } | ||
467 | |||
468 | /* | ||
469 | * This function is called when the SLIMpro Mailbox received a message | ||
470 | */ | ||
471 | static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) | ||
472 | { | ||
473 | struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); | ||
474 | struct slimpro_resp_msg amsg; | ||
475 | |||
476 | /* | ||
477 | * Response message format: | ||
478 | * msg[0] is the return code of the operation | ||
479 | * msg[1] is the first parameter word | ||
480 | * msg[2] is the second parameter word | ||
481 | * | ||
482 | * As message only supports dword size, just assign it. | ||
483 | */ | ||
484 | |||
485 | /* Check for sync query */ | ||
486 | if (ctx->resp_pending && | ||
487 | ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || | ||
488 | (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && | ||
489 | MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || | ||
490 | (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && | ||
491 | MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && | ||
492 | TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { | ||
493 | ctx->sync_msg.msg = ((u32 *)msg)[0]; | ||
494 | ctx->sync_msg.param1 = ((u32 *)msg)[1]; | ||
495 | ctx->sync_msg.param2 = ((u32 *)msg)[2]; | ||
496 | |||
497 | /* Operation waiting for response */ | ||
498 | complete(&ctx->rd_complete); | ||
499 | |||
500 | return; | ||
501 | } | ||
502 | |||
503 | amsg.msg = ((u32 *)msg)[0]; | ||
504 | amsg.param1 = ((u32 *)msg)[1]; | ||
505 | amsg.param2 = ((u32 *)msg)[2]; | ||
506 | |||
507 | /* Enqueue to the FIFO */ | ||
508 | kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, | ||
509 | sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); | ||
510 | /* Schedule the bottom handler */ | ||
511 | schedule_work(&ctx->workq); | ||
512 | } | ||
513 | |||
514 | /* | ||
515 | * This function is called when the PCC Mailbox received a message | ||
516 | */ | ||
517 | static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) | ||
518 | { | ||
519 | struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); | ||
520 | struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; | ||
521 | struct slimpro_resp_msg amsg; | ||
522 | |||
523 | msg = generic_comm_base + 1; | ||
524 | /* Check if platform sends interrupt */ | ||
525 | if (!xgene_word_tst_and_clr(&generic_comm_base->status, | ||
526 | PCCS_SCI_DOORBEL)) | ||
527 | return; | ||
528 | |||
529 | /* | ||
530 | * Response message format: | ||
531 | * msg[0] is the return code of the operation | ||
532 | * msg[1] is the first parameter word | ||
533 | * msg[2] is the second parameter word | ||
534 | * | ||
535 | * As message only supports dword size, just assign it. | ||
536 | */ | ||
537 | |||
538 | /* Check for sync query */ | ||
539 | if (ctx->resp_pending && | ||
540 | ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || | ||
541 | (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && | ||
542 | MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || | ||
543 | (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && | ||
544 | MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && | ||
545 | TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { | ||
546 | /* Check if platform completes command */ | ||
547 | if (xgene_word_tst_and_clr(&generic_comm_base->status, | ||
548 | PCCS_CMD_COMPLETE)) { | ||
549 | ctx->sync_msg.msg = ((u32 *)msg)[0]; | ||
550 | ctx->sync_msg.param1 = ((u32 *)msg)[1]; | ||
551 | ctx->sync_msg.param2 = ((u32 *)msg)[2]; | ||
552 | |||
553 | /* Operation waiting for response */ | ||
554 | complete(&ctx->rd_complete); | ||
555 | |||
556 | return; | ||
557 | } | ||
558 | } | ||
559 | |||
560 | /* | ||
561 | * Platform notifies interrupt to OSPM. | ||
562 | * OPSM schedules a consumer command to get this information | ||
563 | * in a workqueue. Platform must wait until OSPM has issued | ||
564 | * a consumer command that serves this notification. | ||
565 | */ | ||
566 | |||
567 | /* Enqueue to the FIFO */ | ||
568 | kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, | ||
569 | sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); | ||
570 | /* Schedule the bottom handler */ | ||
571 | schedule_work(&ctx->workq); | ||
572 | } | ||
573 | |||
574 | static void xgene_hwmon_tx_done(struct mbox_client *cl, void *msg, int ret) | ||
575 | { | ||
576 | if (ret) { | ||
577 | dev_dbg(cl->dev, "TX did not complete: CMD sent:%x, ret:%d\n", | ||
578 | *(u16 *)msg, ret); | ||
579 | } else { | ||
580 | dev_dbg(cl->dev, "TX completed. CMD sent:%x, ret:%d\n", | ||
581 | *(u16 *)msg, ret); | ||
582 | } | ||
583 | } | ||
584 | |||
585 | static int xgene_hwmon_probe(struct platform_device *pdev) | ||
586 | { | ||
587 | struct xgene_hwmon_dev *ctx; | ||
588 | struct mbox_client *cl; | ||
589 | int rc; | ||
590 | |||
591 | ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); | ||
592 | if (!ctx) | ||
593 | return -ENOMEM; | ||
594 | |||
595 | ctx->dev = &pdev->dev; | ||
596 | platform_set_drvdata(pdev, ctx); | ||
597 | cl = &ctx->mbox_client; | ||
598 | |||
599 | /* Request mailbox channel */ | ||
600 | cl->dev = &pdev->dev; | ||
601 | cl->tx_done = xgene_hwmon_tx_done; | ||
602 | cl->tx_block = false; | ||
603 | cl->tx_tout = MBOX_OP_TIMEOUTMS; | ||
604 | cl->knows_txdone = false; | ||
605 | if (acpi_disabled) { | ||
606 | cl->rx_callback = xgene_hwmon_rx_cb; | ||
607 | ctx->mbox_chan = mbox_request_channel(cl, 0); | ||
608 | if (IS_ERR(ctx->mbox_chan)) { | ||
609 | dev_err(&pdev->dev, | ||
610 | "SLIMpro mailbox channel request failed\n"); | ||
611 | return -ENODEV; | ||
612 | } | ||
613 | } else { | ||
614 | struct acpi_pcct_hw_reduced *cppc_ss; | ||
615 | |||
616 | if (device_property_read_u32(&pdev->dev, "pcc-channel", | ||
617 | &ctx->mbox_idx)) { | ||
618 | dev_err(&pdev->dev, "no pcc-channel property\n"); | ||
619 | return -ENODEV; | ||
620 | } | ||
621 | |||
622 | cl->rx_callback = xgene_hwmon_pcc_rx_cb; | ||
623 | ctx->mbox_chan = pcc_mbox_request_channel(cl, ctx->mbox_idx); | ||
624 | if (IS_ERR(ctx->mbox_chan)) { | ||
625 | dev_err(&pdev->dev, | ||
626 | "PPC channel request failed\n"); | ||
627 | return -ENODEV; | ||
628 | } | ||
629 | |||
630 | /* | ||
631 | * The PCC mailbox controller driver should | ||
632 | * have parsed the PCCT (global table of all | ||
633 | * PCC channels) and stored pointers to the | ||
634 | * subspace communication region in con_priv. | ||
635 | */ | ||
636 | cppc_ss = ctx->mbox_chan->con_priv; | ||
637 | if (!cppc_ss) { | ||
638 | dev_err(&pdev->dev, "PPC subspace not found\n"); | ||
639 | rc = -ENODEV; | ||
640 | goto out_mbox_free; | ||
641 | } | ||
642 | |||
643 | if (!ctx->mbox_chan->mbox->txdone_irq) { | ||
644 | dev_err(&pdev->dev, "PCC IRQ not supported\n"); | ||
645 | rc = -ENODEV; | ||
646 | goto out_mbox_free; | ||
647 | } | ||
648 | |||
649 | /* | ||
650 | * This is the shared communication region | ||
651 | * for the OS and Platform to communicate over. | ||
652 | */ | ||
653 | ctx->comm_base_addr = cppc_ss->base_address; | ||
654 | if (ctx->comm_base_addr) { | ||
655 | ctx->pcc_comm_addr = | ||
656 | acpi_os_ioremap(ctx->comm_base_addr, | ||
657 | cppc_ss->length); | ||
658 | } else { | ||
659 | dev_err(&pdev->dev, "Failed to get PCC comm region\n"); | ||
660 | rc = -ENODEV; | ||
661 | goto out_mbox_free; | ||
662 | } | ||
663 | |||
664 | if (!ctx->pcc_comm_addr) { | ||
665 | dev_err(&pdev->dev, | ||
666 | "Failed to ioremap PCC comm region\n"); | ||
667 | rc = -ENOMEM; | ||
668 | goto out_mbox_free; | ||
669 | } | ||
670 | |||
671 | /* | ||
672 | * cppc_ss->latency is just a Nominal value. In reality | ||
673 | * the remote processor could be much slower to reply. | ||
674 | * So add an arbitrary amount of wait on top of Nominal. | ||
675 | */ | ||
676 | ctx->usecs_lat = PCC_NUM_RETRIES * cppc_ss->latency; | ||
677 | } | ||
678 | |||
679 | spin_lock_init(&ctx->kfifo_lock); | ||
680 | mutex_init(&ctx->rd_mutex); | ||
681 | |||
682 | rc = kfifo_alloc(&ctx->async_msg_fifo, | ||
683 | sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE, | ||
684 | GFP_KERNEL); | ||
685 | if (rc) | ||
686 | goto out_mbox_free; | ||
687 | |||
688 | INIT_WORK(&ctx->workq, xgene_hwmon_evt_work); | ||
689 | |||
690 | ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev, | ||
691 | "apm_xgene", | ||
692 | ctx, | ||
693 | xgene_hwmon_groups); | ||
694 | if (IS_ERR(ctx->hwmon_dev)) { | ||
695 | dev_err(&pdev->dev, "Failed to register HW monitor device\n"); | ||
696 | rc = PTR_ERR(ctx->hwmon_dev); | ||
697 | goto out; | ||
698 | } | ||
699 | |||
700 | dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n"); | ||
701 | |||
702 | return 0; | ||
703 | |||
704 | out: | ||
705 | kfifo_free(&ctx->async_msg_fifo); | ||
706 | out_mbox_free: | ||
707 | if (acpi_disabled) | ||
708 | mbox_free_channel(ctx->mbox_chan); | ||
709 | else | ||
710 | pcc_mbox_free_channel(ctx->mbox_chan); | ||
711 | |||
712 | return rc; | ||
713 | } | ||
714 | |||
715 | static int xgene_hwmon_remove(struct platform_device *pdev) | ||
716 | { | ||
717 | struct xgene_hwmon_dev *ctx = platform_get_drvdata(pdev); | ||
718 | |||
719 | hwmon_device_unregister(ctx->hwmon_dev); | ||
720 | kfifo_free(&ctx->async_msg_fifo); | ||
721 | if (acpi_disabled) | ||
722 | mbox_free_channel(ctx->mbox_chan); | ||
723 | else | ||
724 | pcc_mbox_free_channel(ctx->mbox_chan); | ||
725 | |||
726 | return 0; | ||
727 | } | ||
728 | |||
729 | #ifdef CONFIG_ACPI | ||
730 | static const struct acpi_device_id xgene_hwmon_acpi_match[] = { | ||
731 | {"APMC0D29", 0}, | ||
732 | {}, | ||
733 | }; | ||
734 | MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match); | ||
735 | #endif | ||
736 | |||
737 | static const struct of_device_id xgene_hwmon_of_match[] = { | ||
738 | {.compatible = "apm,xgene-slimpro-hwmon"}, | ||
739 | {} | ||
740 | }; | ||
741 | MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match); | ||
742 | |||
743 | static struct platform_driver xgene_hwmon_driver __refdata = { | ||
744 | .probe = xgene_hwmon_probe, | ||
745 | .remove = xgene_hwmon_remove, | ||
746 | .driver = { | ||
747 | .name = "xgene-slimpro-hwmon", | ||
748 | .of_match_table = xgene_hwmon_of_match, | ||
749 | .acpi_match_table = ACPI_PTR(xgene_hwmon_acpi_match), | ||
750 | }, | ||
751 | }; | ||
752 | module_platform_driver(xgene_hwmon_driver); | ||
753 | |||
754 | MODULE_DESCRIPTION("APM X-Gene SoC hardware monitor"); | ||
755 | MODULE_LICENSE("GPL"); | ||