1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * uniphier_thermal.c - Socionext UniPhier thermal driver 4 * Copyright 2014 Panasonic Corporation 5 * Copyright 2016-2017 Socionext Inc. 6 * Author: 7 * Kunihiko Hayashi <hayashi.kunihiko@socionext.com> 8 */ 9 10 #include <linux/bitops.h> 11 #include <linux/interrupt.h> 12 #include <linux/mfd/syscon.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/of_device.h> 16 #include <linux/platform_device.h> 17 #include <linux/regmap.h> 18 #include <linux/thermal.h> 19 20 /* 21 * block registers 22 * addresses are the offset from .block_base 23 */ 24 #define PVTCTLEN 0x0000 25 #define PVTCTLEN_EN BIT(0) 26 27 #define PVTCTLMODE 0x0004 28 #define PVTCTLMODE_MASK 0xf 29 #define PVTCTLMODE_TEMPMON 0x5 30 31 #define EMONREPEAT 0x0040 32 #define EMONREPEAT_ENDLESS BIT(24) 33 #define EMONREPEAT_PERIOD GENMASK(3, 0) 34 #define EMONREPEAT_PERIOD_1000000 0x9 35 36 /* 37 * common registers 38 * addresses are the offset from .map_base 39 */ 40 #define PVTCTLSEL 0x0900 41 #define PVTCTLSEL_MASK GENMASK(2, 0) 42 #define PVTCTLSEL_MONITOR 0 43 44 #define SETALERT0 0x0910 45 #define SETALERT1 0x0914 46 #define SETALERT2 0x0918 47 #define SETALERT_TEMP_OVF (GENMASK(7, 0) << 16) 48 #define SETALERT_TEMP_OVF_VALUE(val) (((val) & GENMASK(7, 0)) << 16) 49 #define SETALERT_EN BIT(0) 50 51 #define PMALERTINTCTL 0x0920 52 #define PMALERTINTCTL_CLR(ch) BIT(4 * (ch) + 2) 53 #define PMALERTINTCTL_SET(ch) BIT(4 * (ch) + 1) 54 #define PMALERTINTCTL_EN(ch) BIT(4 * (ch) + 0) 55 #define PMALERTINTCTL_MASK (GENMASK(10, 8) | GENMASK(6, 4) | \ 56 GENMASK(2, 0)) 57 58 #define TMOD 0x0928 59 #define TMOD_WIDTH 9 60 61 #define TMODCOEF 0x0e5c 62 63 #define TMODSETUP0_EN BIT(30) 64 #define TMODSETUP0_VAL(val) (((val) & GENMASK(13, 0)) << 16) 65 #define TMODSETUP1_EN BIT(15) 66 #define TMODSETUP1_VAL(val) ((val) & GENMASK(14, 0)) 67 68 /* SoC critical temperature */ 69 #define CRITICAL_TEMP_LIMIT (120 * 1000) 70 71 /* Max # of alert channels */ 72 #define ALERT_CH_NUM 3 73 74 /* SoC specific thermal sensor data */ 75 struct uniphier_tm_soc_data { 76 u32 map_base; 77 u32 block_base; 78 u32 tmod_setup_addr; 79 }; 80 81 struct uniphier_tm_dev { 82 struct regmap *regmap; 83 struct device *dev; 84 bool alert_en[ALERT_CH_NUM]; 85 struct thermal_zone_device *tz_dev; 86 const struct uniphier_tm_soc_data *data; 87 }; 88 89 static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev) 90 { 91 struct regmap *map = tdev->regmap; 92 u32 val; 93 u32 tmod_calib[2]; 94 int ret; 95 96 /* stop PVT */ 97 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN, 98 PVTCTLEN_EN, 0); 99 100 /* 101 * Since SoC has a calibrated value that was set in advance, 102 * TMODCOEF shows non-zero and PVT refers the value internally. 103 * 104 * If TMODCOEF shows zero, the boards don't have the calibrated 105 * value, and the driver has to set default value from DT. 106 */ 107 ret = regmap_read(map, tdev->data->map_base + TMODCOEF, &val); 108 if (ret) 109 return ret; 110 if (!val) { 111 /* look for the default values in DT */ 112 ret = of_property_read_u32_array(tdev->dev->of_node, 113 "socionext,tmod-calibration", 114 tmod_calib, 115 ARRAY_SIZE(tmod_calib)); 116 if (ret) 117 return ret; 118 119 regmap_write(map, tdev->data->tmod_setup_addr, 120 TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) | 121 TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1])); 122 } 123 124 /* select temperature mode */ 125 regmap_write_bits(map, tdev->data->block_base + PVTCTLMODE, 126 PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON); 127 128 /* set monitoring period */ 129 regmap_write_bits(map, tdev->data->block_base + EMONREPEAT, 130 EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD, 131 EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000); 132 133 /* set monitor mode */ 134 regmap_write_bits(map, tdev->data->map_base + PVTCTLSEL, 135 PVTCTLSEL_MASK, PVTCTLSEL_MONITOR); 136 137 return 0; 138 } 139 140 static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch, 141 u32 temp) 142 { 143 struct regmap *map = tdev->regmap; 144 145 /* set alert temperature */ 146 regmap_write_bits(map, tdev->data->map_base + SETALERT0 + (ch << 2), 147 SETALERT_EN | SETALERT_TEMP_OVF, 148 SETALERT_EN | 149 SETALERT_TEMP_OVF_VALUE(temp / 1000)); 150 } 151 152 static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev) 153 { 154 struct regmap *map = tdev->regmap; 155 int i; 156 u32 bits = 0; 157 158 for (i = 0; i < ALERT_CH_NUM; i++) 159 if (tdev->alert_en[i]) 160 bits |= PMALERTINTCTL_EN(i); 161 162 /* enable alert interrupt */ 163 regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL, 164 PMALERTINTCTL_MASK, bits); 165 166 /* start PVT */ 167 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN, 168 PVTCTLEN_EN, PVTCTLEN_EN); 169 170 usleep_range(700, 1500); /* The spec note says at least 700us */ 171 } 172 173 static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev) 174 { 175 struct regmap *map = tdev->regmap; 176 177 /* disable alert interrupt */ 178 regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL, 179 PMALERTINTCTL_MASK, 0); 180 181 /* stop PVT */ 182 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN, 183 PVTCTLEN_EN, 0); 184 185 usleep_range(1000, 2000); /* The spec note says at least 1ms */ 186 } 187 188 static int uniphier_tm_get_temp(struct thermal_zone_device *tz, int *out_temp) 189 { 190 struct uniphier_tm_dev *tdev = thermal_zone_device_priv(tz); 191 struct regmap *map = tdev->regmap; 192 int ret; 193 u32 temp; 194 195 ret = regmap_read(map, tdev->data->map_base + TMOD, &temp); 196 if (ret) 197 return ret; 198 199 /* MSB of the TMOD field is a sign bit */ 200 *out_temp = sign_extend32(temp, TMOD_WIDTH - 1) * 1000; 201 202 return 0; 203 } 204 205 static const struct thermal_zone_device_ops uniphier_of_thermal_ops = { 206 .get_temp = uniphier_tm_get_temp, 207 }; 208 209 static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev) 210 { 211 u32 mask = 0, bits = 0; 212 int i; 213 214 for (i = 0; i < ALERT_CH_NUM; i++) { 215 mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i)); 216 bits |= PMALERTINTCTL_CLR(i); 217 } 218 219 /* clear alert interrupt */ 220 regmap_write_bits(tdev->regmap, 221 tdev->data->map_base + PMALERTINTCTL, mask, bits); 222 } 223 224 static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev) 225 { 226 struct uniphier_tm_dev *tdev = _tdev; 227 228 disable_irq_nosync(irq); 229 uniphier_tm_irq_clear(tdev); 230 231 return IRQ_WAKE_THREAD; 232 } 233 234 static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev) 235 { 236 struct uniphier_tm_dev *tdev = _tdev; 237 238 thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED); 239 240 return IRQ_HANDLED; 241 } 242 243 static int uniphier_tm_probe(struct platform_device *pdev) 244 { 245 struct device *dev = &pdev->dev; 246 struct regmap *regmap; 247 struct device_node *parent; 248 struct uniphier_tm_dev *tdev; 249 int i, ret, irq, crit_temp = INT_MAX; 250 251 tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL); 252 if (!tdev) 253 return -ENOMEM; 254 tdev->dev = dev; 255 256 tdev->data = of_device_get_match_data(dev); 257 if (WARN_ON(!tdev->data)) 258 return -EINVAL; 259 260 irq = platform_get_irq(pdev, 0); 261 if (irq < 0) 262 return irq; 263 264 /* get regmap from syscon node */ 265 parent = of_get_parent(dev->of_node); /* parent should be syscon node */ 266 regmap = syscon_node_to_regmap(parent); 267 of_node_put(parent); 268 if (IS_ERR(regmap)) { 269 dev_err(dev, "failed to get regmap (error %ld)\n", 270 PTR_ERR(regmap)); 271 return PTR_ERR(regmap); 272 } 273 tdev->regmap = regmap; 274 275 ret = uniphier_tm_initialize_sensor(tdev); 276 if (ret) { 277 dev_err(dev, "failed to initialize sensor\n"); 278 return ret; 279 } 280 281 ret = devm_request_threaded_irq(dev, irq, uniphier_tm_alarm_irq, 282 uniphier_tm_alarm_irq_thread, 283 0, "thermal", tdev); 284 if (ret) 285 return ret; 286 287 platform_set_drvdata(pdev, tdev); 288 289 tdev->tz_dev = devm_thermal_of_zone_register(dev, 0, tdev, 290 &uniphier_of_thermal_ops); 291 if (IS_ERR(tdev->tz_dev)) { 292 dev_err(dev, "failed to register sensor device\n"); 293 return PTR_ERR(tdev->tz_dev); 294 } 295 296 /* set alert temperatures */ 297 for (i = 0; i < thermal_zone_get_num_trips(tdev->tz_dev); i++) { 298 struct thermal_trip trip; 299 300 ret = thermal_zone_get_trip(tdev->tz_dev, i, &trip); 301 if (ret) 302 return ret; 303 304 if (trip.type == THERMAL_TRIP_CRITICAL && 305 trip.temperature < crit_temp) 306 crit_temp = trip.temperature; 307 uniphier_tm_set_alert(tdev, i, trip.temperature); 308 tdev->alert_en[i] = true; 309 } 310 if (crit_temp > CRITICAL_TEMP_LIMIT) { 311 dev_err(dev, "critical trip is over limit(>%d), or not set\n", 312 CRITICAL_TEMP_LIMIT); 313 return -EINVAL; 314 } 315 316 uniphier_tm_enable_sensor(tdev); 317 318 return 0; 319 } 320 321 static int uniphier_tm_remove(struct platform_device *pdev) 322 { 323 struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev); 324 325 /* disable sensor */ 326 uniphier_tm_disable_sensor(tdev); 327 328 return 0; 329 } 330 331 static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = { 332 .map_base = 0xe000, 333 .block_base = 0xe000, 334 .tmod_setup_addr = 0xe904, 335 }; 336 337 static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = { 338 .map_base = 0xe000, 339 .block_base = 0xe800, 340 .tmod_setup_addr = 0xe938, 341 }; 342 343 static const struct of_device_id uniphier_tm_dt_ids[] = { 344 { 345 .compatible = "socionext,uniphier-pxs2-thermal", 346 .data = &uniphier_pxs2_tm_data, 347 }, 348 { 349 .compatible = "socionext,uniphier-ld20-thermal", 350 .data = &uniphier_ld20_tm_data, 351 }, 352 { 353 .compatible = "socionext,uniphier-pxs3-thermal", 354 .data = &uniphier_ld20_tm_data, 355 }, 356 { 357 .compatible = "socionext,uniphier-nx1-thermal", 358 .data = &uniphier_ld20_tm_data, 359 }, 360 { /* sentinel */ } 361 }; 362 MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids); 363 364 static struct platform_driver uniphier_tm_driver = { 365 .probe = uniphier_tm_probe, 366 .remove = uniphier_tm_remove, 367 .driver = { 368 .name = "uniphier-thermal", 369 .of_match_table = uniphier_tm_dt_ids, 370 }, 371 }; 372 module_platform_driver(uniphier_tm_driver); 373 374 MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>"); 375 MODULE_DESCRIPTION("UniPhier thermal driver"); 376 MODULE_LICENSE("GPL v2"); 377