On 6/8/21 3:29 PM, Thara Gopinath wrote: > Driver enabling various pieces of Limits Management Hardware(LMh) for cpu > cluster0 and cpu cluster1 namely kick starting monitoring of temperature, > current, battery current violations, enabling reliability algorithm and > setting up various temperature limits. > > The following has been explained in the cover letter. I am including this > here so that this remains in the commit message as well. > > LMh is a hardware infrastructure on some Qualcomm SoCs that can enforce > temperature and current limits as programmed by software for certain IPs > like CPU. On many newer SoCs LMh is configured by firmware/TZ and no > programming is needed from the kernel side. But on certain SoCs like sdm845 > the firmware does not do a complete programming of the h/w. On such SoCs > kernel software has to explicitly set up the temperature limits and turn on > various monitoring and enforcing algorithms on the hardware. > > Signed-off-by: Thara Gopinath <thara.gopinath@xxxxxxxxxx> > --- > drivers/thermal/qcom/Kconfig | 10 ++ > drivers/thermal/qcom/Makefile | 1 + > drivers/thermal/qcom/lmh.c | 244 ++++++++++++++++++++++++++++++++++ > 3 files changed, 255 insertions(+) > create mode 100644 drivers/thermal/qcom/lmh.c > > diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig > index 8d5ac2df26dc..c95b95e254d7 100644 > --- a/drivers/thermal/qcom/Kconfig > +++ b/drivers/thermal/qcom/Kconfig > @@ -31,3 +31,13 @@ config QCOM_SPMI_TEMP_ALARM > trip points. The temperature reported by the thermal sensor reflects the > real time die temperature if an ADC is present or an estimate of the > temperature based upon the over temperature stage value. > + > +config QCOM_LMH > + tristate "Qualcomm Limits Management Hardware" > + depends on ARCH_QCOM > + help > + This enables initialization of Qualcomm limits management > + hardware(LMh). LMh allows for h/w enforced mitigation for cpus based on hardware-enforced CPUs > + input from temperature and current sensors. On many newer Qualcomm SoCs > + LMH is configure in the firmware and this feature need not be enabled. LMh > + However, on certain SoCs like sdm845 LMH has to be configured from HLOS. LMh What is HLOS? > diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c > new file mode 100644 > index 000000000000..8741a36cb674 > --- /dev/null > +++ b/drivers/thermal/qcom/lmh.c > @@ -0,0 +1,244 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +/* > + * Copyright (C) 2021, Linaro Limited. All rights reserved. > + */ [snip] > +static int lmh_probe(struct platform_device *pdev) > +{ > + struct device *dev; > + struct device_node *np; > + struct lmh_hw_data *lmh_data; > + u32 node_id; > + int ret; > + > + dev = &pdev->dev; > + np = dev->of_node; > + if (!np) > + return -EINVAL; > + > + lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL); > + if (!lmh_data) > + return -ENOMEM; > + > + lmh_data->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(lmh_data->base)) > + return PTR_ERR(lmh_data->base); > + > + ret = of_property_read_u32(np, "qcom,lmh-cpu-id", &lmh_data->cpu_id); > + if (ret) > + return -ENODEV; > + > + /* > + * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed > + * for other platforms, revisit this to check if the <cpu-id, node-id> should be part > + * of a dt match table. > + */ > + if (lmh_data->cpu_id == 0) { > + node_id = LMH_CLUSTER0_NODE_ID; > + } else if (lmh_data->cpu_id == 4) { > + node_id = LMH_CLUSTER1_NODE_ID; > + } else { > + dev_err(dev, "Wrong cpu id associated with lmh node\n"); CPU LMh > + return -EINVAL; > + } > + > + /* Payload size is five bytes for now */ > + lmh_data->payload_size = 5 * sizeof(u32); > + > + platform_set_drvdata(pdev, lmh_data); > + > + if (!qcom_scm_lmh_dcvsh_available()) > + return -EINVAL; > + > + /* Enable Thermal Algorithm */ > + update_payload(lmh_data, LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error %d enabling thermal subfunction\n", ret); > + return ret; > + } > + > + /* Enable Current Sensing Algorithm */ > + update_payload(lmh_data, LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error %d enabling current subfunction\n", ret); > + return ret; > + } > + > + /* Enable Reliability Algorithm */ > + update_payload(lmh_data, LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error %d enabling reliability subfunction\n", ret); > + return ret; > + } > + > + /* Enable BCL Algorithm */ > + update_payload(lmh_data, LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error %d enabling BCL subfunction\n", ret); What is BCL? > + return ret; > + } > + > + ret = qcom_scm_lmh_profile_change(0x1); > + if (ret) { > + dev_err(dev, "Error %d changing profile\n", ret); > + return ret; > + } > + > + /* Set default thermal trips */ > + update_payload(lmh_data, LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, LMH_TH_ARM_TEMP); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error setting thermal ARM thershold%d\n", ret); threshold > + return ret; > + } > + > + update_payload(lmh_data, LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, LMH_TH_HI_TEMP); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error setting thermal HI thershold%d\n", ret); threshold > + return ret; > + } > + update_payload(lmh_data, LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, LMH_TH_LOW_TEMP); > + ret = qcom_scm_lmh_dcvsh(lmh_data->payload, lmh_data->payload_size, > + LMH_NODE_DCVS, node_id, 0); > + if (ret) { > + dev_err(dev, "Error setting thermal ARM thershold%d\n", ret); threshold > + return ret; > + } > + > + lmh_data->irq = platform_get_irq(pdev, 0); > + lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data); > + if (!lmh_data->domain) { > + dev_err(dev, "Error adding irq_domain\n"); > + return -EINVAL; > + } > + > + ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQF_NO_SUSPEND, > + "lmh-irq", lmh_data); > + if (ret) { > + dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); > + irq_domain_remove(lmh_data->domain); > + return ret; > + } > + return 0; > +} > + > +static const struct of_device_id lmh_table[] = { > + { .compatible = "qcom,msm-hw-limits", }, > + {}, > +}; > + > +static struct platform_driver lmh_driver = { > + .probe = lmh_probe, > + .driver = { > + .name = "qcom-lmh", > + .of_match_table = lmh_table, > + }, > +}; > +module_platform_driver(lmh_driver); > + > +MODULE_LICENSE("GPL v2"); > +MODULE_DESCRIPTION("QCOM LMH driver"); LMh thanks. -- ~Randy