Integrate X-Gene upstream HWMon driver and bug fixes. Signed-off-by: Duc Dang <dhdang at apm.com> --- ...on-dtb-xgene-Add-hwmon-dts-binding-docume.patch | 39 + SOURCES/1005-hwmon-Add-xgene-hwmon-driver.patch | 850 +++++++++++++++++++++ ...-Fix-crash-when-alarm-occurs-before-drive.patch | 158 ++++ SOURCES/config-centos-sig | 1 + SPECS/kernel-aarch64.spec | 7 + 5 files changed, 1055 insertions(+) create mode 100644 SOURCES/1004-Documentation-dtb-xgene-Add-hwmon-dts-binding-docume.patch create mode 100644 SOURCES/1005-hwmon-Add-xgene-hwmon-driver.patch create mode 100644 SOURCES/1006-hwmon-xgene-Fix-crash-when-alarm-occurs-before-drive.patch diff --git a/SOURCES/1004-Documentation-dtb-xgene-Add-hwmon-dts-binding-docume.patch b/SOURCES/1004-Documentation-dtb-xgene-Add-hwmon-dts-binding-docume.patch new file mode 100644 index 0000000..f042588 --- /dev/null +++ b/SOURCES/1004-Documentation-dtb-xgene-Add-hwmon-dts-binding-docume.patch @@ -0,0 +1,39 @@ +From 5ba8581eadf6b596e835392181e289d3a209569c Mon Sep 17 00:00:00 2001 +From: hotran <hotran at apm.com> +Date: Thu, 21 Jul 2016 13:55:55 -0700 +Subject: [PATCH 1004/1018] Documentation: dtb: xgene: Add hwmon dts binding + documentation + +This patch adds the APM X-Gene hwmon device tree node documentation. + +Signed-off-by: Hoan Tran <hotran at apm.com> +Acked-by: Rob Herring <robh at kernel.org> +Signed-off-by: Guenter Roeck <linux at roeck-us.net> +--- + .../devicetree/bindings/hwmon/apm-xgene-hwmon.txt | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + create mode 100644 Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt + +diff --git a/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt b/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt +new file mode 100644 +index 0000000..59b3855 +--- /dev/null ++++ b/Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt +@@ -0,0 +1,14 @@ ++APM X-Gene hwmon driver ++ ++APM X-Gene SOC sensors are accessed over the "SLIMpro" mailbox. ++ ++Required properties : ++ - compatible : should be "apm,xgene-slimpro-hwmon" ++ - mboxes : use the label reference for the mailbox as the first parameter. ++ The second parameter is the channel number. ++ ++Example : ++ hwmonslimpro { ++ compatible = "apm,xgene-slimpro-hwmon"; ++ mboxes = <&mailbox 7>; ++ }; +-- +1.8.3.1 + diff --git a/SOURCES/1005-hwmon-Add-xgene-hwmon-driver.patch b/SOURCES/1005-hwmon-Add-xgene-hwmon-driver.patch new file mode 100644 index 0000000..3025592 --- /dev/null +++ b/SOURCES/1005-hwmon-Add-xgene-hwmon-driver.patch @@ -0,0 +1,850 @@ +From 54bffda1db3651556ce0ebc492d47154bd5b5d2d Mon Sep 17 00:00:00 2001 +From: hotran <hotran at apm.com> +Date: Thu, 21 Jul 2016 15:37:32 -0700 +Subject: [PATCH 1005/1018] 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 at apm.com> +Reviewed-by: Guenter Roeck <linux at roeck-us.net> +Signed-off-by: Guenter Roeck <linux at roeck-us.net> +--- + Documentation/hwmon/xgene-hwmon | 30 ++ + drivers/hwmon/Kconfig | 7 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/xgene-hwmon.c | 755 ++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 793 insertions(+) + create mode 100644 Documentation/hwmon/xgene-hwmon + create mode 100644 drivers/hwmon/xgene-hwmon.c + +diff --git a/Documentation/hwmon/xgene-hwmon b/Documentation/hwmon/xgene-hwmon +new file mode 100644 +index 0000000..6ec50ed +--- /dev/null ++++ b/Documentation/hwmon/xgene-hwmon +@@ -0,0 +1,30 @@ ++Kernel driver xgene-hwmon ++======================== ++ ++Supported chips: ++ * APM X-Gene SoC ++ ++Description ++----------- ++ ++This driver adds hardware temperature and power reading support for ++APM X-Gene SoC using the mailbox communication interface. ++For device tree, it is the standard DT mailbox. ++For ACPI, it is the PCC mailbox. ++ ++The following sensors are supported ++ ++ * Temperature ++ - SoC on-die temperature in milli-degree C ++ - Alarm when high/over temperature occurs ++ * Power ++ - CPU power in uW ++ - IO power in uW ++ ++sysfs-Interface ++--------------- ++ ++temp0_input - SoC on-die temperature (milli-degree C) ++temp0_critical_alarm - An 1 would indicates on-die temperature exceeded threshold ++power0_input - CPU power in (uW) ++power1_input - IO power in (uW) +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 60fb80b..293dbc6 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1747,6 +1747,13 @@ config SENSORS_ULTRA45 + This driver provides support for the Ultra45 workstation environmental + sensors. + ++config SENSORS_XGENE ++ tristate "APM X-Gene SoC hardware monitoring driver" ++ depends on XGENE_SLIMPRO_MBOX || PCC ++ help ++ If you say yes here you get support for the temperature ++ and power sensors for APM X-Gene SoC. ++ + if ACPI + + comment "ACPI drivers" +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index 30c94df..c43aa07 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -159,6 +159,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o + obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o + obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o + obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o ++obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o + + obj-$(CONFIG_PMBUS) += pmbus/ + +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +new file mode 100644 +index 0000000..bc78a5d +--- /dev/null ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -0,0 +1,755 @@ ++/* ++ * APM X-Gene SoC Hardware Monitoring Driver ++ * ++ * Copyright (c) 2016, Applied Micro Circuits Corporation ++ * Author: Loc Ho <lho at apm.com> ++ * Hoan Tran <hotran at apm.com> ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of ++ * the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see <http://www.gnu.org/licenses/>. ++ * ++ * This driver provides the following features: ++ * - Retrieve CPU total power (uW) ++ * - Retrieve IO total power (uW) ++ * - Retrieve SoC temperature (milli-degree C) and alarm ++ */ ++#include <linux/acpi.h> ++#include <linux/dma-mapping.h> ++#include <linux/hwmon.h> ++#include <linux/hwmon-sysfs.h> ++#include <linux/interrupt.h> ++#include <linux/kfifo.h> ++#include <linux/mailbox_controller.h> ++#include <linux/mailbox_client.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <acpi/acpi_io.h> ++#include <acpi/pcc.h> ++ ++/* SLIMpro message defines */ ++#define MSG_TYPE_DBG 0 ++#define MSG_TYPE_ERR 7 ++#define MSG_TYPE_PWRMGMT 9 ++ ++#define MSG_TYPE(v) (((v) & 0xF0000000) >> 28) ++#define MSG_TYPE_SET(v) (((v) << 28) & 0xF0000000) ++#define MSG_SUBTYPE(v) (((v) & 0x0F000000) >> 24) ++#define MSG_SUBTYPE_SET(v) (((v) << 24) & 0x0F000000) ++ ++#define DBG_SUBTYPE_SENSOR_READ 4 ++#define SENSOR_RD_MSG 0x04FFE902 ++#define SENSOR_RD_EN_ADDR(a) ((a) & 0x000FFFFF) ++#define PMD_PWR_REG 0x20 ++#define PMD_PWR_MW_REG 0x26 ++#define SOC_PWR_REG 0x21 ++#define SOC_PWR_MW_REG 0x27 ++#define SOC_TEMP_REG 0x10 ++ ++#define TEMP_NEGATIVE_BIT 8 ++#define SENSOR_INVALID_DATA BIT(15) ++ ++#define PWRMGMT_SUBTYPE_TPC 1 ++#define TPC_ALARM 2 ++#define TPC_GET_ALARM 3 ++#define TPC_CMD(v) (((v) & 0x00FF0000) >> 16) ++#define TPC_CMD_SET(v) (((v) << 16) & 0x00FF0000) ++#define TPC_EN_MSG(hndl, cmd, type) \ ++ (MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \ ++ MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type) ++ ++/* PCC defines */ ++#define PCC_SIGNATURE_MASK 0x50424300 ++#define PCCC_GENERATE_DB_INT BIT(15) ++#define PCCS_CMD_COMPLETE BIT(0) ++#define PCCS_SCI_DOORBEL BIT(1) ++#define PCCS_PLATFORM_NOTIFICATION BIT(3) ++/* ++ * Arbitrary retries in case the remote processor is slow to respond ++ * to PCC commands ++ */ ++#define PCC_NUM_RETRIES 500 ++ ++#define ASYNC_MSG_FIFO_SIZE 16 ++#define MBOX_OP_TIMEOUTMS 1000 ++ ++#define WATT_TO_mWATT(x) ((x) * 1000) ++#define mWATT_TO_uWATT(x) ((x) * 1000) ++#define CELSIUS_TO_mCELSIUS(x) ((x) * 1000) ++ ++#define to_xgene_hwmon_dev(cl) \ ++ container_of(cl, struct xgene_hwmon_dev, mbox_client) ++ ++struct slimpro_resp_msg { ++ u32 msg; ++ u32 param1; ++ u32 param2; ++} __packed; ++ ++struct xgene_hwmon_dev { ++ struct device *dev; ++ struct mbox_chan *mbox_chan; ++ struct mbox_client mbox_client; ++ int mbox_idx; ++ ++ spinlock_t kfifo_lock; ++ struct mutex rd_mutex; ++ struct completion rd_complete; ++ int resp_pending; ++ struct slimpro_resp_msg sync_msg; ++ ++ struct work_struct workq; ++ struct kfifo_rec_ptr_1 async_msg_fifo; ++ ++ struct device *hwmon_dev; ++ bool temp_critical_alarm; ++ ++ phys_addr_t comm_base_addr; ++ void *pcc_comm_addr; ++ u64 usecs_lat; ++}; ++ ++/* ++ * This function tests and clears a bitmask then returns its old value ++ */ ++static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask) ++{ ++ u16 ret, val; ++ ++ val = readw_relaxed(addr); ++ ret = val & mask; ++ val &= ~mask; ++ writew_relaxed(val, addr); ++ ++ return ret; ++} ++ ++static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) ++{ ++ struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; ++ void *ptr = generic_comm_base + 1; ++ int rc, i; ++ u16 val; ++ ++ mutex_lock(&ctx->rd_mutex); ++ init_completion(&ctx->rd_complete); ++ ctx->resp_pending = true; ++ ++ /* Write signature for subspace */ ++ writel_relaxed(PCC_SIGNATURE_MASK | ctx->mbox_idx, ++ &generic_comm_base->signature); ++ ++ /* Write to the shared command region */ ++ writew_relaxed(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT, ++ &generic_comm_base->command); ++ ++ /* Flip CMD COMPLETE bit */ ++ val = readw_relaxed(&generic_comm_base->status); ++ val &= ~PCCS_CMD_COMPLETE; ++ writew_relaxed(val, &generic_comm_base->status); ++ ++ /* Copy the message to the PCC comm space */ ++ for (i = 0; i < sizeof(struct slimpro_resp_msg) / 4; i++) ++ writel_relaxed(msg[i], ptr + i * 4); ++ ++ /* Ring the doorbell */ ++ rc = mbox_send_message(ctx->mbox_chan, msg); ++ if (rc < 0) { ++ dev_err(ctx->dev, "Mailbox send error %d\n", rc); ++ goto err; ++ } ++ if (!wait_for_completion_timeout(&ctx->rd_complete, ++ usecs_to_jiffies(ctx->usecs_lat))) { ++ dev_err(ctx->dev, "Mailbox operation timed out\n"); ++ rc = -ETIMEDOUT; ++ goto err; ++ } ++ ++ /* Check for error message */ ++ if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { ++ rc = -EINVAL; ++ goto err; ++ } ++ ++ msg[0] = ctx->sync_msg.msg; ++ msg[1] = ctx->sync_msg.param1; ++ msg[2] = ctx->sync_msg.param2; ++ ++err: ++ mbox_chan_txdone(ctx->mbox_chan, 0); ++ ctx->resp_pending = false; ++ mutex_unlock(&ctx->rd_mutex); ++ return rc; ++} ++ ++static int xgene_hwmon_rd(struct xgene_hwmon_dev *ctx, u32 *msg) ++{ ++ int rc; ++ ++ mutex_lock(&ctx->rd_mutex); ++ init_completion(&ctx->rd_complete); ++ ctx->resp_pending = true; ++ ++ rc = mbox_send_message(ctx->mbox_chan, msg); ++ if (rc < 0) { ++ dev_err(ctx->dev, "Mailbox send error %d\n", rc); ++ goto err; ++ } ++ ++ if (!wait_for_completion_timeout(&ctx->rd_complete, ++ msecs_to_jiffies(MBOX_OP_TIMEOUTMS))) { ++ dev_err(ctx->dev, "Mailbox operation timed out\n"); ++ rc = -ETIMEDOUT; ++ goto err; ++ } ++ ++ /* Check for error message */ ++ if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { ++ rc = -EINVAL; ++ goto err; ++ } ++ ++ msg[0] = ctx->sync_msg.msg; ++ msg[1] = ctx->sync_msg.param1; ++ msg[2] = ctx->sync_msg.param2; ++ ++err: ++ ctx->resp_pending = false; ++ mutex_unlock(&ctx->rd_mutex); ++ return rc; ++} ++ ++static int xgene_hwmon_reg_map_rd(struct xgene_hwmon_dev *ctx, u32 addr, ++ u32 *data) ++{ ++ u32 msg[3]; ++ int rc; ++ ++ msg[0] = SENSOR_RD_MSG; ++ msg[1] = SENSOR_RD_EN_ADDR(addr); ++ msg[2] = 0; ++ ++ if (acpi_disabled) ++ rc = xgene_hwmon_rd(ctx, msg); ++ else ++ rc = xgene_hwmon_pcc_rd(ctx, msg); ++ ++ if (rc < 0) ++ return rc; ++ ++ /* ++ * Check if sensor data is valid. ++ */ ++ if (msg[1] & SENSOR_INVALID_DATA) ++ return -ENODATA; ++ ++ *data = msg[1]; ++ ++ return rc; ++} ++ ++static int xgene_hwmon_get_notification_msg(struct xgene_hwmon_dev *ctx, ++ u32 *amsg) ++{ ++ u32 msg[3]; ++ int rc; ++ ++ msg[0] = TPC_EN_MSG(PWRMGMT_SUBTYPE_TPC, TPC_GET_ALARM, 0); ++ msg[1] = 0; ++ msg[2] = 0; ++ ++ rc = xgene_hwmon_pcc_rd(ctx, msg); ++ if (rc < 0) ++ return rc; ++ ++ amsg[0] = msg[0]; ++ amsg[1] = msg[1]; ++ amsg[2] = msg[2]; ++ ++ return rc; ++} ++ ++static int xgene_hwmon_get_cpu_pwr(struct xgene_hwmon_dev *ctx, u32 *val) ++{ ++ u32 watt, mwatt; ++ int rc; ++ ++ rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_REG, &watt); ++ if (rc < 0) ++ return rc; ++ ++ rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_MW_REG, &mwatt); ++ if (rc < 0) ++ return rc; ++ ++ *val = WATT_TO_mWATT(watt) + mwatt; ++ return 0; ++} ++ ++static int xgene_hwmon_get_io_pwr(struct xgene_hwmon_dev *ctx, u32 *val) ++{ ++ u32 watt, mwatt; ++ int rc; ++ ++ rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_REG, &watt); ++ if (rc < 0) ++ return rc; ++ ++ rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_MW_REG, &mwatt); ++ if (rc < 0) ++ return rc; ++ ++ *val = WATT_TO_mWATT(watt) + mwatt; ++ return 0; ++} ++ ++static int xgene_hwmon_get_temp(struct xgene_hwmon_dev *ctx, u32 *val) ++{ ++ return xgene_hwmon_reg_map_rd(ctx, SOC_TEMP_REG, val); ++} ++ ++/* ++ * Sensor temperature/power functions ++ */ ++static ssize_t temp1_input_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); ++ int rc, temp; ++ u32 val; ++ ++ rc = xgene_hwmon_get_temp(ctx, &val); ++ if (rc < 0) ++ return rc; ++ ++ temp = sign_extend32(val, TEMP_NEGATIVE_BIT); ++ ++ return snprintf(buf, PAGE_SIZE, "%d\n", CELSIUS_TO_mCELSIUS(temp)); ++} ++ ++static ssize_t temp1_label_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return snprintf(buf, PAGE_SIZE, "SoC Temperature\n"); ++} ++ ++static ssize_t temp1_critical_alarm_show(struct device *dev, ++ struct device_attribute *devattr, ++ char *buf) ++{ ++ struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); ++ ++ return snprintf(buf, PAGE_SIZE, "%d\n", ctx->temp_critical_alarm); ++} ++ ++static ssize_t power1_label_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return snprintf(buf, PAGE_SIZE, "CPU power\n"); ++} ++ ++static ssize_t power2_label_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return snprintf(buf, PAGE_SIZE, "IO power\n"); ++} ++ ++static ssize_t power1_input_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); ++ u32 val; ++ int rc; ++ ++ rc = xgene_hwmon_get_cpu_pwr(ctx, &val); ++ if (rc < 0) ++ return rc; ++ ++ return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); ++} ++ ++static ssize_t power2_input_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); ++ u32 val; ++ int rc; ++ ++ rc = xgene_hwmon_get_io_pwr(ctx, &val); ++ if (rc < 0) ++ return rc; ++ ++ return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); ++} ++ ++static DEVICE_ATTR_RO(temp1_label); ++static DEVICE_ATTR_RO(temp1_input); ++static DEVICE_ATTR_RO(temp1_critical_alarm); ++static DEVICE_ATTR_RO(power1_label); ++static DEVICE_ATTR_RO(power1_input); ++static DEVICE_ATTR_RO(power2_label); ++static DEVICE_ATTR_RO(power2_input); ++ ++static struct attribute *xgene_hwmon_attrs[] = { ++ &dev_attr_temp1_label.attr, ++ &dev_attr_temp1_input.attr, ++ &dev_attr_temp1_critical_alarm.attr, ++ &dev_attr_power1_label.attr, ++ &dev_attr_power1_input.attr, ++ &dev_attr_power2_label.attr, ++ &dev_attr_power2_input.attr, ++ NULL, ++}; ++ ++ATTRIBUTE_GROUPS(xgene_hwmon); ++ ++static int xgene_hwmon_tpc_alarm(struct xgene_hwmon_dev *ctx, ++ struct slimpro_resp_msg *amsg) ++{ ++ ctx->temp_critical_alarm = !!amsg->param2; ++ sysfs_notify(&ctx->dev->kobj, NULL, "temp1_critical_alarm"); ++ ++ return 0; ++} ++ ++static void xgene_hwmon_process_pwrmsg(struct xgene_hwmon_dev *ctx, ++ struct slimpro_resp_msg *amsg) ++{ ++ if ((MSG_SUBTYPE(amsg->msg) == PWRMGMT_SUBTYPE_TPC) && ++ (TPC_CMD(amsg->msg) == TPC_ALARM)) ++ xgene_hwmon_tpc_alarm(ctx, amsg); ++} ++ ++/* ++ * This function is called to process async work queue ++ */ ++static void xgene_hwmon_evt_work(struct work_struct *work) ++{ ++ struct slimpro_resp_msg amsg; ++ struct xgene_hwmon_dev *ctx; ++ int ret; ++ ++ ctx = container_of(work, struct xgene_hwmon_dev, workq); ++ while (kfifo_out_spinlocked(&ctx->async_msg_fifo, &amsg, ++ sizeof(struct slimpro_resp_msg), ++ &ctx->kfifo_lock)) { ++ /* ++ * If PCC, send a consumer command to Platform to get info ++ * If Slimpro Mailbox, get message from specific FIFO ++ */ ++ if (!acpi_disabled) { ++ ret = xgene_hwmon_get_notification_msg(ctx, ++ (u32 *)&amsg); ++ if (ret < 0) ++ continue; ++ } ++ ++ if (MSG_TYPE(amsg.msg) == MSG_TYPE_PWRMGMT) ++ xgene_hwmon_process_pwrmsg(ctx, &amsg); ++ } ++} ++ ++/* ++ * This function is called when the SLIMpro Mailbox received a message ++ */ ++static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) ++{ ++ struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); ++ struct slimpro_resp_msg amsg; ++ ++ /* ++ * Response message format: ++ * msg[0] is the return code of the operation ++ * msg[1] is the first parameter word ++ * msg[2] is the second parameter word ++ * ++ * As message only supports dword size, just assign it. ++ */ ++ ++ /* Check for sync query */ ++ if (ctx->resp_pending && ++ ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || ++ (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && ++ MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || ++ (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && ++ MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && ++ TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { ++ ctx->sync_msg.msg = ((u32 *)msg)[0]; ++ ctx->sync_msg.param1 = ((u32 *)msg)[1]; ++ ctx->sync_msg.param2 = ((u32 *)msg)[2]; ++ ++ /* Operation waiting for response */ ++ complete(&ctx->rd_complete); ++ ++ return; ++ } ++ ++ amsg.msg = ((u32 *)msg)[0]; ++ amsg.param1 = ((u32 *)msg)[1]; ++ amsg.param2 = ((u32 *)msg)[2]; ++ ++ /* Enqueue to the FIFO */ ++ kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, ++ sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); ++ /* Schedule the bottom handler */ ++ schedule_work(&ctx->workq); ++} ++ ++/* ++ * This function is called when the PCC Mailbox received a message ++ */ ++static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) ++{ ++ struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); ++ struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; ++ struct slimpro_resp_msg amsg; ++ ++ msg = generic_comm_base + 1; ++ /* Check if platform sends interrupt */ ++ if (!xgene_word_tst_and_clr(&generic_comm_base->status, ++ PCCS_SCI_DOORBEL)) ++ return; ++ ++ /* ++ * Response message format: ++ * msg[0] is the return code of the operation ++ * msg[1] is the first parameter word ++ * msg[2] is the second parameter word ++ * ++ * As message only supports dword size, just assign it. ++ */ ++ ++ /* Check for sync query */ ++ if (ctx->resp_pending && ++ ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || ++ (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && ++ MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || ++ (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && ++ MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && ++ TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { ++ /* Check if platform completes command */ ++ if (xgene_word_tst_and_clr(&generic_comm_base->status, ++ PCCS_CMD_COMPLETE)) { ++ ctx->sync_msg.msg = ((u32 *)msg)[0]; ++ ctx->sync_msg.param1 = ((u32 *)msg)[1]; ++ ctx->sync_msg.param2 = ((u32 *)msg)[2]; ++ ++ /* Operation waiting for response */ ++ complete(&ctx->rd_complete); ++ ++ return; ++ } ++ } ++ ++ /* ++ * Platform notifies interrupt to OSPM. ++ * OPSM schedules a consumer command to get this information ++ * in a workqueue. Platform must wait until OSPM has issued ++ * a consumer command that serves this notification. ++ */ ++ ++ /* Enqueue to the FIFO */ ++ kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, ++ sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); ++ /* Schedule the bottom handler */ ++ schedule_work(&ctx->workq); ++} ++ ++static void xgene_hwmon_tx_done(struct mbox_client *cl, void *msg, int ret) ++{ ++ if (ret) { ++ dev_dbg(cl->dev, "TX did not complete: CMD sent:%x, ret:%d\n", ++ *(u16 *)msg, ret); ++ } else { ++ dev_dbg(cl->dev, "TX completed. CMD sent:%x, ret:%d\n", ++ *(u16 *)msg, ret); ++ } ++} ++ ++static int xgene_hwmon_probe(struct platform_device *pdev) ++{ ++ struct xgene_hwmon_dev *ctx; ++ struct mbox_client *cl; ++ int rc; ++ ++ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ++ ctx->dev = &pdev->dev; ++ platform_set_drvdata(pdev, ctx); ++ cl = &ctx->mbox_client; ++ ++ /* Request mailbox channel */ ++ cl->dev = &pdev->dev; ++ cl->tx_done = xgene_hwmon_tx_done; ++ cl->tx_block = false; ++ cl->tx_tout = MBOX_OP_TIMEOUTMS; ++ cl->knows_txdone = false; ++ if (acpi_disabled) { ++ cl->rx_callback = xgene_hwmon_rx_cb; ++ ctx->mbox_chan = mbox_request_channel(cl, 0); ++ if (IS_ERR(ctx->mbox_chan)) { ++ dev_err(&pdev->dev, ++ "SLIMpro mailbox channel request failed\n"); ++ return -ENODEV; ++ } ++ } else { ++ struct acpi_pcct_hw_reduced *cppc_ss; ++ ++ if (device_property_read_u32(&pdev->dev, "pcc-channel", ++ &ctx->mbox_idx)) { ++ dev_err(&pdev->dev, "no pcc-channel property\n"); ++ return -ENODEV; ++ } ++ ++ cl->rx_callback = xgene_hwmon_pcc_rx_cb; ++ ctx->mbox_chan = pcc_mbox_request_channel(cl, ctx->mbox_idx); ++ if (IS_ERR(ctx->mbox_chan)) { ++ dev_err(&pdev->dev, ++ "PPC channel request failed\n"); ++ return -ENODEV; ++ } ++ ++ /* ++ * The PCC mailbox controller driver should ++ * have parsed the PCCT (global table of all ++ * PCC channels) and stored pointers to the ++ * subspace communication region in con_priv. ++ */ ++ cppc_ss = ctx->mbox_chan->con_priv; ++ if (!cppc_ss) { ++ dev_err(&pdev->dev, "PPC subspace not found\n"); ++ rc = -ENODEV; ++ goto out_mbox_free; ++ } ++ ++ if (!ctx->mbox_chan->mbox->txdone_irq) { ++ dev_err(&pdev->dev, "PCC IRQ not supported\n"); ++ rc = -ENODEV; ++ goto out_mbox_free; ++ } ++ ++ /* ++ * This is the shared communication region ++ * for the OS and Platform to communicate over. ++ */ ++ ctx->comm_base_addr = cppc_ss->base_address; ++ if (ctx->comm_base_addr) { ++ ctx->pcc_comm_addr = ++ acpi_os_ioremap(ctx->comm_base_addr, ++ cppc_ss->length); ++ } else { ++ dev_err(&pdev->dev, "Failed to get PCC comm region\n"); ++ rc = -ENODEV; ++ goto out_mbox_free; ++ } ++ ++ if (!ctx->pcc_comm_addr) { ++ dev_err(&pdev->dev, ++ "Failed to ioremap PCC comm region\n"); ++ rc = -ENOMEM; ++ goto out_mbox_free; ++ } ++ ++ /* ++ * cppc_ss->latency is just a Nominal value. In reality ++ * the remote processor could be much slower to reply. ++ * So add an arbitrary amount of wait on top of Nominal. ++ */ ++ ctx->usecs_lat = PCC_NUM_RETRIES * cppc_ss->latency; ++ } ++ ++ spin_lock_init(&ctx->kfifo_lock); ++ mutex_init(&ctx->rd_mutex); ++ ++ rc = kfifo_alloc(&ctx->async_msg_fifo, ++ sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE, ++ GFP_KERNEL); ++ if (rc) ++ goto out_mbox_free; ++ ++ INIT_WORK(&ctx->workq, xgene_hwmon_evt_work); ++ ++ ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev, ++ "apm_xgene", ++ ctx, ++ xgene_hwmon_groups); ++ if (IS_ERR(ctx->hwmon_dev)) { ++ dev_err(&pdev->dev, "Failed to register HW monitor device\n"); ++ rc = PTR_ERR(ctx->hwmon_dev); ++ goto out; ++ } ++ ++ dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n"); ++ ++ return 0; ++ ++out: ++ kfifo_free(&ctx->async_msg_fifo); ++out_mbox_free: ++ if (acpi_disabled) ++ mbox_free_channel(ctx->mbox_chan); ++ else ++ pcc_mbox_free_channel(ctx->mbox_chan); ++ ++ return rc; ++} ++ ++static int xgene_hwmon_remove(struct platform_device *pdev) ++{ ++ struct xgene_hwmon_dev *ctx = platform_get_drvdata(pdev); ++ ++ hwmon_device_unregister(ctx->hwmon_dev); ++ kfifo_free(&ctx->async_msg_fifo); ++ if (acpi_disabled) ++ mbox_free_channel(ctx->mbox_chan); ++ else ++ pcc_mbox_free_channel(ctx->mbox_chan); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_ACPI ++static const struct acpi_device_id xgene_hwmon_acpi_match[] = { ++ {"APMC0D29", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match); ++#endif ++ ++static const struct of_device_id xgene_hwmon_of_match[] = { ++ {.compatible = "apm,xgene-slimpro-hwmon"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match); ++ ++static struct platform_driver xgene_hwmon_driver __refdata = { ++ .probe = xgene_hwmon_probe, ++ .remove = xgene_hwmon_remove, ++ .driver = { ++ .name = "xgene-slimpro-hwmon", ++ .of_match_table = xgene_hwmon_of_match, ++ .acpi_match_table = ACPI_PTR(xgene_hwmon_acpi_match), ++ }, ++}; ++module_platform_driver(xgene_hwmon_driver); ++ ++MODULE_DESCRIPTION("APM X-Gene SoC hardware monitor"); ++MODULE_LICENSE("GPL"); +-- +1.8.3.1 + diff --git a/SOURCES/1006-hwmon-xgene-Fix-crash-when-alarm-occurs-before-drive.patch b/SOURCES/1006-hwmon-xgene-Fix-crash-when-alarm-occurs-before-drive.patch new file mode 100644 index 0000000..b009667 --- /dev/null +++ b/SOURCES/1006-hwmon-xgene-Fix-crash-when-alarm-occurs-before-drive.patch @@ -0,0 +1,158 @@ +From 5b839513ff79400d9f52c2f5969a428a519d7abd Mon Sep 17 00:00:00 2001 +From: hotran <hotran at apm.com> +Date: Thu, 8 Sep 2016 09:33:10 -0700 +Subject: [PATCH 1006/1018] hwmon: (xgene) Fix crash when alarm occurs before + driver probe + +The system crashes during probing xgene-hwmon driver when temperature +alarm interrupt occurs before. +It's because + - xgene_hwmon_probe() requests mailbox channel which also enables + the mailbox interrupt. + - As temperature alarm interrupt is pending, ISR runs and crashes when + accesses into invalid resourse as unmapped PCC shared memory. + +This patch fixes this issue by saving this alarm message and scheduling a +bottom handler after xgene_hwmon_probe() finish. + +Signed-off-by: Hoan Tran <hotran at apm.com> +Reported-by: Itaru Kitayama <itaru.kitayama at riken.jp> +Signed-off-by: Guenter Roeck <linux at roeck-us.net> +--- + drivers/hwmon/xgene-hwmon.c | 69 ++++++++++++++++++++++++++++++++------------- + 1 file changed, 50 insertions(+), 19 deletions(-) + +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +index bc78a5d..aa44579 100644 +--- a/drivers/hwmon/xgene-hwmon.c ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -465,13 +465,34 @@ static void xgene_hwmon_evt_work(struct work_struct *work) + } + } + ++static int xgene_hwmon_rx_ready(struct xgene_hwmon_dev *ctx, void *msg) ++{ ++ if (IS_ERR_OR_NULL(ctx->hwmon_dev) && !ctx->resp_pending) { ++ /* Enqueue to the FIFO */ ++ kfifo_in_spinlocked(&ctx->async_msg_fifo, msg, ++ sizeof(struct slimpro_resp_msg), ++ &ctx->kfifo_lock); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ + /* + * This function is called when the SLIMpro Mailbox received a message + */ + static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) + { + struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); +- struct slimpro_resp_msg amsg; ++ ++ /* ++ * While the driver registers with the mailbox framework, an interrupt ++ * can be pending before the probe function completes its ++ * initialization. If such condition occurs, just queue up the message ++ * as the driver is not ready for servicing the callback. ++ */ ++ if (xgene_hwmon_rx_ready(ctx, msg) < 0) ++ return; + + /* + * Response message format: +@@ -500,12 +521,8 @@ static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) + return; + } + +- amsg.msg = ((u32 *)msg)[0]; +- amsg.param1 = ((u32 *)msg)[1]; +- amsg.param2 = ((u32 *)msg)[2]; +- + /* Enqueue to the FIFO */ +- kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, ++ kfifo_in_spinlocked(&ctx->async_msg_fifo, msg, + sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); + /* Schedule the bottom handler */ + schedule_work(&ctx->workq); +@@ -520,6 +537,15 @@ static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) + struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; + struct slimpro_resp_msg amsg; + ++ /* ++ * While the driver registers with the mailbox framework, an interrupt ++ * can be pending before the probe function completes its ++ * initialization. If such condition occurs, just queue up the message ++ * as the driver is not ready for servicing the callback. ++ */ ++ if (xgene_hwmon_rx_ready(ctx, &amsg) < 0) ++ return; ++ + msg = generic_comm_base + 1; + /* Check if platform sends interrupt */ + if (!xgene_word_tst_and_clr(&generic_comm_base->status, +@@ -596,6 +622,17 @@ static int xgene_hwmon_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, ctx); + cl = &ctx->mbox_client; + ++ spin_lock_init(&ctx->kfifo_lock); ++ mutex_init(&ctx->rd_mutex); ++ ++ rc = kfifo_alloc(&ctx->async_msg_fifo, ++ sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE, ++ GFP_KERNEL); ++ if (rc) ++ goto out_mbox_free; ++ ++ INIT_WORK(&ctx->workq, xgene_hwmon_evt_work); ++ + /* Request mailbox channel */ + cl->dev = &pdev->dev; + cl->tx_done = xgene_hwmon_tx_done; +@@ -676,17 +713,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev) + ctx->usecs_lat = PCC_NUM_RETRIES * cppc_ss->latency; + } + +- spin_lock_init(&ctx->kfifo_lock); +- mutex_init(&ctx->rd_mutex); +- +- rc = kfifo_alloc(&ctx->async_msg_fifo, +- sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE, +- GFP_KERNEL); +- if (rc) +- goto out_mbox_free; +- +- INIT_WORK(&ctx->workq, xgene_hwmon_evt_work); +- + ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev, + "apm_xgene", + ctx, +@@ -697,17 +723,22 @@ static int xgene_hwmon_probe(struct platform_device *pdev) + goto out; + } + ++ /* ++ * Schedule the bottom handler if there is a pending message. ++ */ ++ schedule_work(&ctx->workq); ++ + dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n"); + + return 0; + + out: +- kfifo_free(&ctx->async_msg_fifo); +-out_mbox_free: + if (acpi_disabled) + mbox_free_channel(ctx->mbox_chan); + else + pcc_mbox_free_channel(ctx->mbox_chan); ++out_mbox_free: ++ kfifo_free(&ctx->async_msg_fifo); + + return rc; + } +-- +1.8.3.1 + diff --git a/SOURCES/config-centos-sig b/SOURCES/config-centos-sig index f8fab63..9d17fb7 100644 --- a/SOURCES/config-centos-sig +++ b/SOURCES/config-centos-sig @@ -4,3 +4,4 @@ CONFIG_QCOM_FALKOR_ERRATUM_E1003=y CONFIG_QCOM_FALKOR_ERRATUM_E1009=y CONFIG_ACPI_CPPC_CPUFREQ=y +CONFIG_SENSORS_XGENE=y diff --git a/SPECS/kernel-aarch64.spec b/SPECS/kernel-aarch64.spec index 1a5c885..2dcb885 100644 --- a/SPECS/kernel-aarch64.spec +++ b/SPECS/kernel-aarch64.spec @@ -328,6 +328,9 @@ Source60: config-centos-sig Patch1001: 1001-cpufreq-CPPC-Avoid-overflow-when-calculating-desired.patch Patch1002: 1002-cpufreq-CPPC-Correct-desired_perf-calculation.patch Patch1003: 1003-mailbox-PCC-Fix-return-value-of-pcc_mbox_request_cha.patch +Patch1004: 1004-Documentation-dtb-xgene-Add-hwmon-dts-binding-docume.patch +Patch1005: 1005-hwmon-Add-xgene-hwmon-driver.patch +Patch1006: 1006-hwmon-xgene-Fix-crash-when-alarm-occurs-before-drive.patch # QDF2400 Patches Patch4000: 4000-arm64-Define-Qualcomm-Technologies-ARMv8-CPU.patch @@ -666,6 +669,9 @@ fi git am %{PATCH1001} git am %{PATCH1002} git am %{PATCH1003} +git am %{PATCH1004} +git am %{PATCH1005} +git am %{PATCH1006} # Apply QDF2400 patches git am %{PATCH4000} @@ -1445,6 +1451,7 @@ fi %changelog * Thu Nov 10 2016 Duc Dang <dhdang at apm.com> [4.5.0-17.el7] +- Add X-Gene HWMon support - mailbox: PCC: Fix return value of pcc_mbox_request_channel - Add cpufreq CPPC bug fixes -- 1.8.3.1