Loading Documentation/devicetree/bindings/thermal/qcom-lmh-dcvs.txt 0 → 100644 +64 −0 Original line number Diff line number Diff line Limits Management Hardware - DCVS The LMH-DCVS block is a hardware IP for every CPU cluster, to handle quick changes in thermal limits. The hardware responds to thermal variation amongst the CPUs in the cluster by requesting limits on the clock frequency and voltage on the OSM hardware. The LMH DCVS driver exports a virtual sensor that can be used to set the thermal limits on the hardware. LMH DCVS driver can be a platform CPU Cooling device, which registers with the CPU cooling device interface. All CPU device nodes should reference the corresponding LMH DCVS hardware in device tree. CPUs referencing the same LMH DCVS node will be associated with the corresponding cooling device as related CPUs. Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,msm-hw-limits" - interrupts: Usage: required Value type: <interrupt_type interrupt_number interrupt_trigger_type> Definition: Should specify interrupt information about the debug interrupt generated by the LMH DCVSh hardware. LMH DCVSh hardware will generate this interrupt whenever it makes a new cpu DCVS decision. - qcom,affinity: Usage: Required Value type: <u32> Definition: Should specify the cluster affinity this hardware corresponds to. - isens_vref-supply: Usage: optional Value type: <phandle> Definition: Should specify the phandle of the vref regulator used by the isens hardware. This active only regulator will be enabled by LMH DCVSh. - isens-vref-settings: Usage: optional Value type: <u32 array> Definition: Should specify the min voltage(uV), max voltage(uV) and max load(uA) for the isens vref regulator. This property is valid only if there is valid entry for isens_vref-supply. Example: lmh_dcvs0: qcom,limits-dcvs@0 { compatible = "qcom,msm-hw-limits"; interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>; qcom,affinity = <0>; isens_vref-supply = <&pm8998_l1_ao>; isens-vref-settings = <880000 880000 36000>; }; CPU0: cpu@0 { device_type = "cpu"; compatible = "arm,armv8"; reg = <0x0 0x0>; qcom,lmh-dcvs = <&lmh_dcvs0>;; }; Documentation/devicetree/bindings/thermal/qti-rpmh-reg-cdev.txt 0 → 100644 +44 −0 Original line number Diff line number Diff line RPMh regulator cooling device. The RPMh regulator cooling device, will be used to place a voltage floor restriction on a rail. This cooling device will use a QMP AOP mail box to send the message to apply and clear voltage floor restriction. The cooling device node should be a child of the regulator devicetree node, which it is trying to place the floor restriction. Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,rpmh-reg-cdev" - qcom,reg-resource-name: Usage: required Value type: <string> Definition: The regulator resource name to be used for communicating with RPMh. This value should be any of the below resource name, cx -> For CX rail, mx -> For MX rail, ebi -> For EBI rail. - mboxes: Usage: required Value type: <phandle> Definition: A phandle to the QMP AOP mail box, that needs to be used for sending the floor restriction message. - #cooling-cells: Must be 2. Please refer to <devicetree/bindings/thermal/thermal.txt> for more details. Example: vdd_cx: rpmh-cx-regulator-cdev { compatible = "qcom,rpmh-reg-cdev"; mboxes = <&qmp_aop 0>; qcom,reg-resource-name = "cx"; #cooling-cells = <2>; }; drivers/thermal/qcom/Kconfig +20 −0 Original line number Diff line number Diff line Loading @@ -9,3 +9,23 @@ config QCOM_TSENS thermal zone device via the mode file results in disabling the sensor. Also able to set threshold temperature for both hot and cold and update when a threshold is reached. config QTI_THERMAL_LIMITS_DCVS bool "QTI LMH DCVS Driver" depends on THERMAL_OF depends on CPU_THERMAL help This enables the driver for Limits Management Hardware - DCVS block for the application processors. The h/w block that is available for each cluster can be used to perform quick thermal mitigations by tracking temperatures of the CPUs and taking thermal action in the hardware without s/w intervention. config QTI_AOP_REG_COOLING_DEVICE bool "QTI AOP Regulator cooling device" depends on THERMAL_OF && MSM_QMP help This enables the AOP based Regulator cooling device. This cooling device will be used by QTI chipset to place a floor voltage restriction at low temperatures. The cooling device will message the AOP using mail box to establish the floor voltage. drivers/thermal/qcom/Makefile +2 −0 Original line number Diff line number Diff line obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.o drivers/thermal/qcom/lmh_dbg.c 0 → 100644 +568 −0 Original line number Diff line number Diff line /* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include <linux/interrupt.h> #include <linux/err.h> #include <linux/mutex.h> #include <linux/slab.h> #include <asm/cacheflush.h> #include <soc/qcom/scm.h> #include <linux/dma-mapping.h> #include <linux/uaccess.h> #include <linux/debugfs.h> #include "lmh_dbg.h" #define LMH_MON_NAME "lmh_monitor" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_SCM_PAYLOAD_SIZE 10 #define LMH_READ_LINE_LENGTH 10 #define LMH_DEBUG_READ_TYPE 0x0 #define LMH_DEBUG_CONFIG_TYPE 0x1 #define LMH_DEBUG_SET 0x08 #define LMH_DEBUG_READ_BUF_SIZE 0x09 #define LMH_DEBUG_READ 0x0A #define LMH_DEBUG_GET_TYPE 0x0B struct lmh_driver_data { struct device *dev; uint32_t *read_type; uint32_t *config_type; uint32_t read_type_count; uint32_t config_type_count; struct dentry *debugfs_parent; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; }; enum lmh_read_type { LMH_READ_TYPE = 0, LMH_CONFIG_TYPE, }; static struct lmh_driver_data *lmh_data; static int lmh_debug_read(uint32_t **buf) { int ret = 0, size = 0, tz_ret = 0; static uint32_t curr_size; struct scm_desc desc_arg; static uint32_t *payload; desc_arg.arginfo = SCM_ARGS(0); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE), &desc_arg); size = desc_arg.ret[0]; if (ret) { pr_err("Error in SCM get debug buffer size call. err:%d\n", ret); goto get_dbg_exit; } if (!size) { pr_err("No Debug data to read\n"); ret = -ENODEV; goto get_dbg_exit; } size = SCM_BUFFER_SIZE(uint32_t) * size * LMH_READ_LINE_LENGTH; if (curr_size != size) { if (payload) devm_kfree(lmh_data->dev, payload); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto get_dbg_exit; } curr_size = size; } /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = curr_size; desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); dmac_flush_range(payload, (void *)payload + curr_size); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ), &desc_arg); dmac_inv_range(payload, (void *)payload + curr_size); tz_ret = desc_arg.ret[0]; /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in get debug read. err:%d\n", ret); goto get_dbg_exit; } if (tz_ret) { pr_err("TZ API returned error. err:%d\n", tz_ret); ret = tz_ret; goto get_dbg_exit; } get_dbg_exit: if (ret && payload) { devm_kfree(lmh_data->dev, payload); payload = NULL; curr_size = 0; } *buf = payload; return (ret < 0) ? ret : curr_size; } static int lmh_debug_config_write(uint32_t cmd_id, uint32_t *buf, int size) { int ret = 0, size_bytes = 0; struct scm_desc desc_arg; uint32_t *payload = NULL; size_bytes = (size - 3) * sizeof(uint32_t); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size_bytes), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto set_cfg_exit; } memcpy(payload, &buf[3], size_bytes); /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = size_bytes; desc_arg.args[2] = buf[0]; desc_arg.args[3] = buf[1]; desc_arg.args[4] = buf[2]; desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL); dmac_flush_range(payload, (void *)payload + size_bytes); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, cmd_id), &desc_arg); /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in config debug read. err:%d\n", ret); goto set_cfg_exit; } set_cfg_exit: return ret; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_READ_TYPE: case LMH_CONFIG_TYPE: ret = lmh_debug_config_write(LMH_DEBUG_SET, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { static uint32_t *read_buf; static int read_buf_size; int idx = 0, ret = 0; if (!read_buf_size) { ret = lmh_debug_read(&read_buf); if (ret <= 0) goto dfs_read_exit; if (!read_buf || ret < sizeof(uint32_t)) { ret = -EINVAL; goto dfs_read_exit; } read_buf_size = ret; ret = 0; } do { seq_printf(seq_fp, "0x%x ", read_buf[idx]); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } idx++; if ((idx % LMH_READ_LINE_LENGTH) == 0) { seq_puts(seq_fp, "\n"); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } } } while (idx < (read_buf_size / sizeof(uint32_t))); read_buf_size = 0; read_buf = NULL; dfs_read_exit: return ret; } static int lmh_get_recurssive_data(struct scm_desc *desc_arg, uint32_t cmd_idx, uint32_t *payload, uint32_t *size, uint32_t **dest_buf) { int idx = 0, ret = 0; uint32_t next = 0; do { desc_arg->args[cmd_idx] = next; dmac_flush_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE), desc_arg); dmac_inv_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); *size = desc_arg->ret[0]; /* Have barrier before reading from TZ data */ mb(); if (ret) { pr_err("Error in SCM get type. cmd:%x err:%d\n", LMH_DEBUG_GET_TYPE, ret); return ret; } if (!*size) { pr_err("No LMH device supported\n"); return -ENODEV; } if (!*dest_buf) { *dest_buf = devm_kcalloc(lmh_data->dev, *size, sizeof(**dest_buf), GFP_KERNEL); if (!*dest_buf) return -ENOMEM; } for (idx = next; idx < min((next + LMH_SCM_PAYLOAD_SIZE), *size); idx++) (*dest_buf)[idx] = payload[idx - next]; next += LMH_SCM_PAYLOAD_SIZE; } while (next < *size); return ret; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_debug_get_types(bool is_read, uint32_t **buf) { int ret = 0; uint32_t size = 0; struct scm_desc desc_arg; uint32_t *payload = NULL, *dest_buf = NULL; if (is_read && lmh_data->read_type) { *buf = lmh_data->read_type; return lmh_data->read_type_count; } else if (!is_read && lmh_data->config_type) { *buf = lmh_data->config_type; return lmh_data->config_type_count; } payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(LMH_SCM_PAYLOAD_SIZE * sizeof(*payload)), GFP_KERNEL); if (!payload) return -ENOMEM; /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = SCM_BUFFER_SIZE(uint32_t) * LMH_SCM_PAYLOAD_SIZE; desc_arg.args[2] = (is_read) ? LMH_DEBUG_READ_TYPE : LMH_DEBUG_CONFIG_TYPE; desc_arg.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL); ret = lmh_get_recurssive_data(&desc_arg, 3, payload, &size, &dest_buf); if (ret) goto get_type_exit; pr_debug("Total %s types:%d\n", (is_read) ? "read" : "config", size); if (is_read) { lmh_data->read_type = *buf = dest_buf; lmh_data->read_type_count = size; } else { lmh_data->config_type = *buf = dest_buf; lmh_data->config_type_count = size; } get_type_exit: if (ret) { if (lmh_data->read_type_count) { devm_kfree(lmh_data->dev, lmh_data->read_type); lmh_data->read_type_count = 0; } if (lmh_data->config_type_count) { devm_kfree(lmh_data->dev, lmh_data->config_type); lmh_data->config_type_count = 0; } } if (payload) devm_kfree(lmh_data->dev, payload); return (ret) ? ret : size; } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_READ_TYPE: ret = lmh_debug_get_types(true, &type_list); break; case LMH_CONFIG_TYPE: ret = lmh_debug_get_types(false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int lmh_check_tz_debug_cmds(void) { if (!scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_SET) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE)) { pr_debug("LMH debug scm not available\n"); return -ENODEV; } return 0; } static int lmh_debug_init(void) { int ret = 0; if (lmh_check_tz_debug_cmds()) { pr_debug("Debug commands not available\n"); return -ENODEV; } lmh_data->debugfs_parent = debugfs_create_dir(LMH_MON_NAME, NULL); if (IS_ERR(lmh_data->debugfs_parent)) { ret = PTR_ERR(lmh_data->debugfs_parent); pr_debug("Error creating debugfs dir:%s. err:%d\n", LMH_MON_NAME, ret); return ret; } lmh_data->debug_read = debugfs_create_file(LMH_DBGFS_READ, 0600, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_fops); if (IS_ERR(lmh_data->debug_read)) { pr_err("Error creating" LMH_DBGFS_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_read); goto dbg_reg_exit; } lmh_data->debug_config = debugfs_create_file(LMH_DBGFS_CONFIG_READ, 0200, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_fops); if (IS_ERR(lmh_data->debug_config)) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_config); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file(LMH_DBGFS_READ_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_type_fops); if (IS_ERR(lmh_data->debug_read_type)) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_read_type); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file( LMH_DBGFS_CONFIG_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_type_fops); if (IS_ERR(lmh_data->debug_config_type)) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_config_type); goto dbg_reg_exit; } dbg_reg_exit: if (ret) /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_data->debugfs_parent); return ret; } int lmh_debug_register(struct platform_device *pdev) { int ret = 0; if (lmh_data) { pr_debug("Reinitializing lmh hardware driver\n"); return -EEXIST; } lmh_data = devm_kzalloc(&pdev->dev, sizeof(*lmh_data), GFP_KERNEL); if (!lmh_data) return -ENOMEM; lmh_data->dev = &pdev->dev; ret = lmh_debug_init(); if (ret) { pr_debug("LMH debug init failed. err:%d\n", ret); goto probe_exit; } return ret; probe_exit: lmh_data = NULL; return ret; } EXPORT_SYMBOL(lmh_debug_register); Loading
Documentation/devicetree/bindings/thermal/qcom-lmh-dcvs.txt 0 → 100644 +64 −0 Original line number Diff line number Diff line Limits Management Hardware - DCVS The LMH-DCVS block is a hardware IP for every CPU cluster, to handle quick changes in thermal limits. The hardware responds to thermal variation amongst the CPUs in the cluster by requesting limits on the clock frequency and voltage on the OSM hardware. The LMH DCVS driver exports a virtual sensor that can be used to set the thermal limits on the hardware. LMH DCVS driver can be a platform CPU Cooling device, which registers with the CPU cooling device interface. All CPU device nodes should reference the corresponding LMH DCVS hardware in device tree. CPUs referencing the same LMH DCVS node will be associated with the corresponding cooling device as related CPUs. Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,msm-hw-limits" - interrupts: Usage: required Value type: <interrupt_type interrupt_number interrupt_trigger_type> Definition: Should specify interrupt information about the debug interrupt generated by the LMH DCVSh hardware. LMH DCVSh hardware will generate this interrupt whenever it makes a new cpu DCVS decision. - qcom,affinity: Usage: Required Value type: <u32> Definition: Should specify the cluster affinity this hardware corresponds to. - isens_vref-supply: Usage: optional Value type: <phandle> Definition: Should specify the phandle of the vref regulator used by the isens hardware. This active only regulator will be enabled by LMH DCVSh. - isens-vref-settings: Usage: optional Value type: <u32 array> Definition: Should specify the min voltage(uV), max voltage(uV) and max load(uA) for the isens vref regulator. This property is valid only if there is valid entry for isens_vref-supply. Example: lmh_dcvs0: qcom,limits-dcvs@0 { compatible = "qcom,msm-hw-limits"; interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>; qcom,affinity = <0>; isens_vref-supply = <&pm8998_l1_ao>; isens-vref-settings = <880000 880000 36000>; }; CPU0: cpu@0 { device_type = "cpu"; compatible = "arm,armv8"; reg = <0x0 0x0>; qcom,lmh-dcvs = <&lmh_dcvs0>;; };
Documentation/devicetree/bindings/thermal/qti-rpmh-reg-cdev.txt 0 → 100644 +44 −0 Original line number Diff line number Diff line RPMh regulator cooling device. The RPMh regulator cooling device, will be used to place a voltage floor restriction on a rail. This cooling device will use a QMP AOP mail box to send the message to apply and clear voltage floor restriction. The cooling device node should be a child of the regulator devicetree node, which it is trying to place the floor restriction. Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,rpmh-reg-cdev" - qcom,reg-resource-name: Usage: required Value type: <string> Definition: The regulator resource name to be used for communicating with RPMh. This value should be any of the below resource name, cx -> For CX rail, mx -> For MX rail, ebi -> For EBI rail. - mboxes: Usage: required Value type: <phandle> Definition: A phandle to the QMP AOP mail box, that needs to be used for sending the floor restriction message. - #cooling-cells: Must be 2. Please refer to <devicetree/bindings/thermal/thermal.txt> for more details. Example: vdd_cx: rpmh-cx-regulator-cdev { compatible = "qcom,rpmh-reg-cdev"; mboxes = <&qmp_aop 0>; qcom,reg-resource-name = "cx"; #cooling-cells = <2>; };
drivers/thermal/qcom/Kconfig +20 −0 Original line number Diff line number Diff line Loading @@ -9,3 +9,23 @@ config QCOM_TSENS thermal zone device via the mode file results in disabling the sensor. Also able to set threshold temperature for both hot and cold and update when a threshold is reached. config QTI_THERMAL_LIMITS_DCVS bool "QTI LMH DCVS Driver" depends on THERMAL_OF depends on CPU_THERMAL help This enables the driver for Limits Management Hardware - DCVS block for the application processors. The h/w block that is available for each cluster can be used to perform quick thermal mitigations by tracking temperatures of the CPUs and taking thermal action in the hardware without s/w intervention. config QTI_AOP_REG_COOLING_DEVICE bool "QTI AOP Regulator cooling device" depends on THERMAL_OF && MSM_QMP help This enables the AOP based Regulator cooling device. This cooling device will be used by QTI chipset to place a floor voltage restriction at low temperatures. The cooling device will message the AOP using mail box to establish the floor voltage.
drivers/thermal/qcom/Makefile +2 −0 Original line number Diff line number Diff line obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.o
drivers/thermal/qcom/lmh_dbg.c 0 → 100644 +568 −0 Original line number Diff line number Diff line /* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include <linux/interrupt.h> #include <linux/err.h> #include <linux/mutex.h> #include <linux/slab.h> #include <asm/cacheflush.h> #include <soc/qcom/scm.h> #include <linux/dma-mapping.h> #include <linux/uaccess.h> #include <linux/debugfs.h> #include "lmh_dbg.h" #define LMH_MON_NAME "lmh_monitor" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_SCM_PAYLOAD_SIZE 10 #define LMH_READ_LINE_LENGTH 10 #define LMH_DEBUG_READ_TYPE 0x0 #define LMH_DEBUG_CONFIG_TYPE 0x1 #define LMH_DEBUG_SET 0x08 #define LMH_DEBUG_READ_BUF_SIZE 0x09 #define LMH_DEBUG_READ 0x0A #define LMH_DEBUG_GET_TYPE 0x0B struct lmh_driver_data { struct device *dev; uint32_t *read_type; uint32_t *config_type; uint32_t read_type_count; uint32_t config_type_count; struct dentry *debugfs_parent; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; }; enum lmh_read_type { LMH_READ_TYPE = 0, LMH_CONFIG_TYPE, }; static struct lmh_driver_data *lmh_data; static int lmh_debug_read(uint32_t **buf) { int ret = 0, size = 0, tz_ret = 0; static uint32_t curr_size; struct scm_desc desc_arg; static uint32_t *payload; desc_arg.arginfo = SCM_ARGS(0); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE), &desc_arg); size = desc_arg.ret[0]; if (ret) { pr_err("Error in SCM get debug buffer size call. err:%d\n", ret); goto get_dbg_exit; } if (!size) { pr_err("No Debug data to read\n"); ret = -ENODEV; goto get_dbg_exit; } size = SCM_BUFFER_SIZE(uint32_t) * size * LMH_READ_LINE_LENGTH; if (curr_size != size) { if (payload) devm_kfree(lmh_data->dev, payload); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto get_dbg_exit; } curr_size = size; } /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = curr_size; desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); dmac_flush_range(payload, (void *)payload + curr_size); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ), &desc_arg); dmac_inv_range(payload, (void *)payload + curr_size); tz_ret = desc_arg.ret[0]; /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in get debug read. err:%d\n", ret); goto get_dbg_exit; } if (tz_ret) { pr_err("TZ API returned error. err:%d\n", tz_ret); ret = tz_ret; goto get_dbg_exit; } get_dbg_exit: if (ret && payload) { devm_kfree(lmh_data->dev, payload); payload = NULL; curr_size = 0; } *buf = payload; return (ret < 0) ? ret : curr_size; } static int lmh_debug_config_write(uint32_t cmd_id, uint32_t *buf, int size) { int ret = 0, size_bytes = 0; struct scm_desc desc_arg; uint32_t *payload = NULL; size_bytes = (size - 3) * sizeof(uint32_t); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size_bytes), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto set_cfg_exit; } memcpy(payload, &buf[3], size_bytes); /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = size_bytes; desc_arg.args[2] = buf[0]; desc_arg.args[3] = buf[1]; desc_arg.args[4] = buf[2]; desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL); dmac_flush_range(payload, (void *)payload + size_bytes); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, cmd_id), &desc_arg); /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in config debug read. err:%d\n", ret); goto set_cfg_exit; } set_cfg_exit: return ret; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_READ_TYPE: case LMH_CONFIG_TYPE: ret = lmh_debug_config_write(LMH_DEBUG_SET, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { static uint32_t *read_buf; static int read_buf_size; int idx = 0, ret = 0; if (!read_buf_size) { ret = lmh_debug_read(&read_buf); if (ret <= 0) goto dfs_read_exit; if (!read_buf || ret < sizeof(uint32_t)) { ret = -EINVAL; goto dfs_read_exit; } read_buf_size = ret; ret = 0; } do { seq_printf(seq_fp, "0x%x ", read_buf[idx]); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } idx++; if ((idx % LMH_READ_LINE_LENGTH) == 0) { seq_puts(seq_fp, "\n"); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } } } while (idx < (read_buf_size / sizeof(uint32_t))); read_buf_size = 0; read_buf = NULL; dfs_read_exit: return ret; } static int lmh_get_recurssive_data(struct scm_desc *desc_arg, uint32_t cmd_idx, uint32_t *payload, uint32_t *size, uint32_t **dest_buf) { int idx = 0, ret = 0; uint32_t next = 0; do { desc_arg->args[cmd_idx] = next; dmac_flush_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE), desc_arg); dmac_inv_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); *size = desc_arg->ret[0]; /* Have barrier before reading from TZ data */ mb(); if (ret) { pr_err("Error in SCM get type. cmd:%x err:%d\n", LMH_DEBUG_GET_TYPE, ret); return ret; } if (!*size) { pr_err("No LMH device supported\n"); return -ENODEV; } if (!*dest_buf) { *dest_buf = devm_kcalloc(lmh_data->dev, *size, sizeof(**dest_buf), GFP_KERNEL); if (!*dest_buf) return -ENOMEM; } for (idx = next; idx < min((next + LMH_SCM_PAYLOAD_SIZE), *size); idx++) (*dest_buf)[idx] = payload[idx - next]; next += LMH_SCM_PAYLOAD_SIZE; } while (next < *size); return ret; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_debug_get_types(bool is_read, uint32_t **buf) { int ret = 0; uint32_t size = 0; struct scm_desc desc_arg; uint32_t *payload = NULL, *dest_buf = NULL; if (is_read && lmh_data->read_type) { *buf = lmh_data->read_type; return lmh_data->read_type_count; } else if (!is_read && lmh_data->config_type) { *buf = lmh_data->config_type; return lmh_data->config_type_count; } payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(LMH_SCM_PAYLOAD_SIZE * sizeof(*payload)), GFP_KERNEL); if (!payload) return -ENOMEM; /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = SCM_BUFFER_SIZE(uint32_t) * LMH_SCM_PAYLOAD_SIZE; desc_arg.args[2] = (is_read) ? LMH_DEBUG_READ_TYPE : LMH_DEBUG_CONFIG_TYPE; desc_arg.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL); ret = lmh_get_recurssive_data(&desc_arg, 3, payload, &size, &dest_buf); if (ret) goto get_type_exit; pr_debug("Total %s types:%d\n", (is_read) ? "read" : "config", size); if (is_read) { lmh_data->read_type = *buf = dest_buf; lmh_data->read_type_count = size; } else { lmh_data->config_type = *buf = dest_buf; lmh_data->config_type_count = size; } get_type_exit: if (ret) { if (lmh_data->read_type_count) { devm_kfree(lmh_data->dev, lmh_data->read_type); lmh_data->read_type_count = 0; } if (lmh_data->config_type_count) { devm_kfree(lmh_data->dev, lmh_data->config_type); lmh_data->config_type_count = 0; } } if (payload) devm_kfree(lmh_data->dev, payload); return (ret) ? ret : size; } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_READ_TYPE: ret = lmh_debug_get_types(true, &type_list); break; case LMH_CONFIG_TYPE: ret = lmh_debug_get_types(false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int lmh_check_tz_debug_cmds(void) { if (!scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_SET) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE)) { pr_debug("LMH debug scm not available\n"); return -ENODEV; } return 0; } static int lmh_debug_init(void) { int ret = 0; if (lmh_check_tz_debug_cmds()) { pr_debug("Debug commands not available\n"); return -ENODEV; } lmh_data->debugfs_parent = debugfs_create_dir(LMH_MON_NAME, NULL); if (IS_ERR(lmh_data->debugfs_parent)) { ret = PTR_ERR(lmh_data->debugfs_parent); pr_debug("Error creating debugfs dir:%s. err:%d\n", LMH_MON_NAME, ret); return ret; } lmh_data->debug_read = debugfs_create_file(LMH_DBGFS_READ, 0600, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_fops); if (IS_ERR(lmh_data->debug_read)) { pr_err("Error creating" LMH_DBGFS_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_read); goto dbg_reg_exit; } lmh_data->debug_config = debugfs_create_file(LMH_DBGFS_CONFIG_READ, 0200, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_fops); if (IS_ERR(lmh_data->debug_config)) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_config); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file(LMH_DBGFS_READ_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_type_fops); if (IS_ERR(lmh_data->debug_read_type)) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_read_type); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file( LMH_DBGFS_CONFIG_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_type_fops); if (IS_ERR(lmh_data->debug_config_type)) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_config_type); goto dbg_reg_exit; } dbg_reg_exit: if (ret) /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_data->debugfs_parent); return ret; } int lmh_debug_register(struct platform_device *pdev) { int ret = 0; if (lmh_data) { pr_debug("Reinitializing lmh hardware driver\n"); return -EEXIST; } lmh_data = devm_kzalloc(&pdev->dev, sizeof(*lmh_data), GFP_KERNEL); if (!lmh_data) return -ENOMEM; lmh_data->dev = &pdev->dev; ret = lmh_debug_init(); if (ret) { pr_debug("LMH debug init failed. err:%d\n", ret); goto probe_exit; } return ret; probe_exit: lmh_data = NULL; return ret; } EXPORT_SYMBOL(lmh_debug_register);