1 // SPDX-License-Identifier: GPL-2.0-only 2 3 /* 4 * Copyright (C) 2021, Linaro Limited. All rights reserved. 5 */ 6 #include <linux/module.h> 7 #include <linux/interrupt.h> 8 #include <linux/irqdomain.h> 9 #include <linux/err.h> 10 #include <linux/platform_device.h> 11 #include <linux/of_platform.h> 12 #include <linux/slab.h> 13 #include <linux/qcom_scm.h> 14 15 #define LMH_NODE_DCVS 0x44435653 16 #define LMH_CLUSTER0_NODE_ID 0x6370302D 17 #define LMH_CLUSTER1_NODE_ID 0x6370312D 18 19 #define LMH_SUB_FN_THERMAL 0x54484D4C 20 #define LMH_SUB_FN_CRNT 0x43524E54 21 #define LMH_SUB_FN_REL 0x52454C00 22 #define LMH_SUB_FN_BCL 0x42434C00 23 24 #define LMH_ALGO_MODE_ENABLE 0x454E424C 25 #define LMH_TH_HI_THRESHOLD 0x48494748 26 #define LMH_TH_LOW_THRESHOLD 0x4C4F5700 27 #define LMH_TH_ARM_THRESHOLD 0x41524D00 28 29 #define LMH_REG_DCVS_INTR_CLR 0x8 30 31 struct lmh_hw_data { 32 void __iomem *base; 33 struct irq_domain *domain; 34 int irq; 35 }; 36 37 static irqreturn_t lmh_handle_irq(int hw_irq, void *data) 38 { 39 struct lmh_hw_data *lmh_data = data; 40 int irq = irq_find_mapping(lmh_data->domain, 0); 41 42 /* Call the cpufreq driver to handle the interrupt */ 43 if (irq) 44 generic_handle_irq(irq); 45 46 return 0; 47 } 48 49 static void lmh_enable_interrupt(struct irq_data *d) 50 { 51 struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d); 52 53 /* Clear the existing interrupt */ 54 writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR); 55 enable_irq(lmh_data->irq); 56 } 57 58 static void lmh_disable_interrupt(struct irq_data *d) 59 { 60 struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d); 61 62 disable_irq_nosync(lmh_data->irq); 63 } 64 65 static struct irq_chip lmh_irq_chip = { 66 .name = "lmh", 67 .irq_enable = lmh_enable_interrupt, 68 .irq_disable = lmh_disable_interrupt 69 }; 70 71 static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) 72 { 73 struct lmh_hw_data *lmh_data = d->host_data; 74 75 irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq); 76 irq_set_chip_data(irq, lmh_data); 77 78 return 0; 79 } 80 81 static const struct irq_domain_ops lmh_irq_ops = { 82 .map = lmh_irq_map, 83 .xlate = irq_domain_xlate_onecell, 84 }; 85 86 static int lmh_probe(struct platform_device *pdev) 87 { 88 struct device *dev = &pdev->dev; 89 struct device_node *np = dev->of_node; 90 struct device_node *cpu_node; 91 struct lmh_hw_data *lmh_data; 92 int temp_low, temp_high, temp_arm, cpu_id, ret; 93 u32 node_id; 94 95 lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL); 96 if (!lmh_data) 97 return -ENOMEM; 98 99 lmh_data->base = devm_platform_ioremap_resource(pdev, 0); 100 if (IS_ERR(lmh_data->base)) 101 return PTR_ERR(lmh_data->base); 102 103 cpu_node = of_parse_phandle(np, "cpus", 0); 104 if (!cpu_node) 105 return -EINVAL; 106 cpu_id = of_cpu_node_to_id(cpu_node); 107 of_node_put(cpu_node); 108 109 ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high); 110 if (ret) { 111 dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n"); 112 return ret; 113 } 114 115 ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low); 116 if (ret) { 117 dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n"); 118 return ret; 119 } 120 121 ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm); 122 if (ret) { 123 dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n"); 124 return ret; 125 } 126 127 /* 128 * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed 129 * for other platforms, revisit this to check if the <cpu-id, node-id> should be part 130 * of a dt match table. 131 */ 132 if (cpu_id == 0) { 133 node_id = LMH_CLUSTER0_NODE_ID; 134 } else if (cpu_id == 4) { 135 node_id = LMH_CLUSTER1_NODE_ID; 136 } else { 137 dev_err(dev, "Wrong CPU id associated with LMh node\n"); 138 return -EINVAL; 139 } 140 141 if (!qcom_scm_lmh_dcvsh_available()) 142 return -EINVAL; 143 144 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1, 145 LMH_NODE_DCVS, node_id, 0); 146 if (ret) 147 dev_err(dev, "Error %d enabling current subfunction\n", ret); 148 149 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1, 150 LMH_NODE_DCVS, node_id, 0); 151 if (ret) 152 dev_err(dev, "Error %d enabling reliability subfunction\n", ret); 153 154 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1, 155 LMH_NODE_DCVS, node_id, 0); 156 if (ret) 157 dev_err(dev, "Error %d enabling BCL subfunction\n", ret); 158 159 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1, 160 LMH_NODE_DCVS, node_id, 0); 161 if (ret) { 162 dev_err(dev, "Error %d enabling thermal subfunction\n", ret); 163 return ret; 164 } 165 166 ret = qcom_scm_lmh_profile_change(0x1); 167 if (ret) { 168 dev_err(dev, "Error %d changing profile\n", ret); 169 return ret; 170 } 171 172 /* Set default thermal trips */ 173 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm, 174 LMH_NODE_DCVS, node_id, 0); 175 if (ret) { 176 dev_err(dev, "Error setting thermal ARM threshold%d\n", ret); 177 return ret; 178 } 179 180 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high, 181 LMH_NODE_DCVS, node_id, 0); 182 if (ret) { 183 dev_err(dev, "Error setting thermal HI threshold%d\n", ret); 184 return ret; 185 } 186 187 ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low, 188 LMH_NODE_DCVS, node_id, 0); 189 if (ret) { 190 dev_err(dev, "Error setting thermal ARM threshold%d\n", ret); 191 return ret; 192 } 193 194 lmh_data->irq = platform_get_irq(pdev, 0); 195 lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data); 196 if (!lmh_data->domain) { 197 dev_err(dev, "Error adding irq_domain\n"); 198 return -EINVAL; 199 } 200 201 /* Disable the irq and let cpufreq enable it when ready to handle the interrupt */ 202 irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN); 203 ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq, 204 IRQF_ONESHOT | IRQF_NO_SUSPEND, 205 "lmh-irq", lmh_data); 206 if (ret) { 207 dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); 208 irq_domain_remove(lmh_data->domain); 209 return ret; 210 } 211 212 return 0; 213 } 214 215 static const struct of_device_id lmh_table[] = { 216 { .compatible = "qcom,sdm845-lmh", }, 217 {} 218 }; 219 MODULE_DEVICE_TABLE(of, lmh_table); 220 221 static struct platform_driver lmh_driver = { 222 .probe = lmh_probe, 223 .driver = { 224 .name = "qcom-lmh", 225 .of_match_table = lmh_table, 226 .suppress_bind_attrs = true, 227 }, 228 }; 229 module_platform_driver(lmh_driver); 230 231 MODULE_LICENSE("GPL v2"); 232 MODULE_DESCRIPTION("QCOM LMh driver"); 233