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