1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * db8500_thermal.c - DB8500 Thermal Management Implementation 4 * 5 * Copyright (C) 2012 ST-Ericsson 6 * Copyright (C) 2012-2019 Linaro Ltd. 7 * 8 * Authors: Hongbo Zhang, Linus Walleij 9 */ 10 11 #include <linux/cpu_cooling.h> 12 #include <linux/interrupt.h> 13 #include <linux/mfd/dbx500-prcmu.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/platform_device.h> 17 #include <linux/slab.h> 18 #include <linux/thermal.h> 19 20 #define PRCMU_DEFAULT_MEASURE_TIME 0xFFF 21 #define PRCMU_DEFAULT_LOW_TEMP 0 22 23 /** 24 * db8500_thermal_points - the interpolation points that trigger 25 * interrupts 26 */ 27 static const unsigned long db8500_thermal_points[] = { 28 15000, 29 20000, 30 25000, 31 30000, 32 35000, 33 40000, 34 45000, 35 50000, 36 55000, 37 60000, 38 65000, 39 70000, 40 75000, 41 80000, 42 /* 43 * This is where things start to get really bad for the 44 * SoC and the thermal zones should be set up to trigger 45 * critical temperature at 85000 mC so we don't get above 46 * this point. 47 */ 48 85000, 49 90000, 50 95000, 51 100000, 52 }; 53 54 struct db8500_thermal_zone { 55 struct thermal_zone_device *tz; 56 struct device *dev; 57 unsigned long interpolated_temp; 58 unsigned int cur_index; 59 }; 60 61 /* Callback to get current temperature */ 62 static int db8500_thermal_get_temp(struct thermal_zone_device *tz, int *temp) 63 { 64 struct db8500_thermal_zone *th = thermal_zone_device_priv(tz); 65 66 /* 67 * TODO: There is no PRCMU interface to get temperature data currently, 68 * so a pseudo temperature is returned , it works for thermal framework 69 * and this will be fixed when the PRCMU interface is available. 70 */ 71 *temp = th->interpolated_temp; 72 73 return 0; 74 } 75 76 static const struct thermal_zone_device_ops thdev_ops = { 77 .get_temp = db8500_thermal_get_temp, 78 }; 79 80 static void db8500_thermal_update_config(struct db8500_thermal_zone *th, 81 unsigned int idx, 82 unsigned long next_low, 83 unsigned long next_high) 84 { 85 prcmu_stop_temp_sense(); 86 87 th->cur_index = idx; 88 th->interpolated_temp = (next_low + next_high)/2; 89 90 /* 91 * The PRCMU accept absolute temperatures in celsius so divide 92 * down the millicelsius with 1000 93 */ 94 prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); 95 prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); 96 } 97 98 static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) 99 { 100 struct db8500_thermal_zone *th = irq_data; 101 unsigned int idx = th->cur_index; 102 unsigned long next_low, next_high; 103 104 if (idx == 0) 105 /* Meaningless for thermal management, ignoring it */ 106 return IRQ_HANDLED; 107 108 if (idx == 1) { 109 next_high = db8500_thermal_points[0]; 110 next_low = PRCMU_DEFAULT_LOW_TEMP; 111 } else { 112 next_high = db8500_thermal_points[idx - 1]; 113 next_low = db8500_thermal_points[idx - 2]; 114 } 115 idx -= 1; 116 117 db8500_thermal_update_config(th, idx, next_low, next_high); 118 dev_dbg(th->dev, 119 "PRCMU set max %ld, min %ld\n", next_high, next_low); 120 121 thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED); 122 123 return IRQ_HANDLED; 124 } 125 126 static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data) 127 { 128 struct db8500_thermal_zone *th = irq_data; 129 unsigned int idx = th->cur_index; 130 unsigned long next_low, next_high; 131 int num_points = ARRAY_SIZE(db8500_thermal_points); 132 133 if (idx < num_points - 1) { 134 next_high = db8500_thermal_points[idx+1]; 135 next_low = db8500_thermal_points[idx]; 136 idx += 1; 137 138 db8500_thermal_update_config(th, idx, next_low, next_high); 139 140 dev_dbg(th->dev, 141 "PRCMU set max %ld, min %ld\n", next_high, next_low); 142 } else if (idx == num_points - 1) 143 /* So we roof out 1 degree over the max point */ 144 th->interpolated_temp = db8500_thermal_points[idx] + 1; 145 146 thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED); 147 148 return IRQ_HANDLED; 149 } 150 151 static int db8500_thermal_probe(struct platform_device *pdev) 152 { 153 struct db8500_thermal_zone *th = NULL; 154 struct device *dev = &pdev->dev; 155 int low_irq, high_irq, ret = 0; 156 157 th = devm_kzalloc(dev, sizeof(*th), GFP_KERNEL); 158 if (!th) 159 return -ENOMEM; 160 161 th->dev = dev; 162 163 low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); 164 if (low_irq < 0) 165 return low_irq; 166 167 ret = devm_request_threaded_irq(dev, low_irq, NULL, 168 prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, 169 "dbx500_temp_low", th); 170 if (ret < 0) { 171 dev_err(dev, "failed to allocate temp low irq\n"); 172 return ret; 173 } 174 175 high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); 176 if (high_irq < 0) 177 return high_irq; 178 179 ret = devm_request_threaded_irq(dev, high_irq, NULL, 180 prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, 181 "dbx500_temp_high", th); 182 if (ret < 0) { 183 dev_err(dev, "failed to allocate temp high irq\n"); 184 return ret; 185 } 186 187 /* register of thermal sensor and get info from DT */ 188 th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops); 189 if (IS_ERR(th->tz)) { 190 dev_err(dev, "register thermal zone sensor failed\n"); 191 return PTR_ERR(th->tz); 192 } 193 dev_info(dev, "thermal zone sensor registered\n"); 194 195 /* Start measuring at the lowest point */ 196 db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP, 197 db8500_thermal_points[0]); 198 199 platform_set_drvdata(pdev, th); 200 201 return 0; 202 } 203 204 static int db8500_thermal_suspend(struct platform_device *pdev, 205 pm_message_t state) 206 { 207 prcmu_stop_temp_sense(); 208 209 return 0; 210 } 211 212 static int db8500_thermal_resume(struct platform_device *pdev) 213 { 214 struct db8500_thermal_zone *th = platform_get_drvdata(pdev); 215 216 /* Resume and start measuring at the lowest point */ 217 db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP, 218 db8500_thermal_points[0]); 219 220 return 0; 221 } 222 223 static const struct of_device_id db8500_thermal_match[] = { 224 { .compatible = "stericsson,db8500-thermal" }, 225 {}, 226 }; 227 MODULE_DEVICE_TABLE(of, db8500_thermal_match); 228 229 static struct platform_driver db8500_thermal_driver = { 230 .driver = { 231 .name = "db8500-thermal", 232 .of_match_table = db8500_thermal_match, 233 }, 234 .probe = db8500_thermal_probe, 235 .suspend = db8500_thermal_suspend, 236 .resume = db8500_thermal_resume, 237 }; 238 239 module_platform_driver(db8500_thermal_driver); 240 241 MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>"); 242 MODULE_DESCRIPTION("DB8500 thermal driver"); 243 MODULE_LICENSE("GPL"); 244