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