1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Intel Broxton PMIC thermal driver 4 * 5 * Copyright (C) 2016 Intel Corporation. All rights reserved. 6 */ 7 8 #include <linux/module.h> 9 #include <linux/kernel.h> 10 #include <linux/slab.h> 11 #include <linux/delay.h> 12 #include <linux/interrupt.h> 13 #include <linux/device.h> 14 #include <linux/thermal.h> 15 #include <linux/platform_device.h> 16 #include <linux/sched.h> 17 #include <linux/mfd/intel_soc_pmic.h> 18 19 #define BXTWC_THRM0IRQ 0x4E04 20 #define BXTWC_THRM1IRQ 0x4E05 21 #define BXTWC_THRM2IRQ 0x4E06 22 #define BXTWC_MTHRM0IRQ 0x4E12 23 #define BXTWC_MTHRM1IRQ 0x4E13 24 #define BXTWC_MTHRM2IRQ 0x4E14 25 #define BXTWC_STHRM0IRQ 0x4F19 26 #define BXTWC_STHRM1IRQ 0x4F1A 27 #define BXTWC_STHRM2IRQ 0x4F1B 28 29 struct trip_config_map { 30 u16 irq_reg; 31 u16 irq_en; 32 u16 evt_stat; 33 u8 irq_mask; 34 u8 irq_en_mask; 35 u8 evt_mask; 36 u8 trip_num; 37 }; 38 39 struct thermal_irq_map { 40 char handle[20]; 41 int num_trips; 42 const struct trip_config_map *trip_config; 43 }; 44 45 struct pmic_thermal_data { 46 const struct thermal_irq_map *maps; 47 int num_maps; 48 }; 49 50 static const struct trip_config_map bxtwc_str0_trip_config[] = { 51 { 52 .irq_reg = BXTWC_THRM0IRQ, 53 .irq_mask = 0x01, 54 .irq_en = BXTWC_MTHRM0IRQ, 55 .irq_en_mask = 0x01, 56 .evt_stat = BXTWC_STHRM0IRQ, 57 .evt_mask = 0x01, 58 .trip_num = 0 59 }, 60 { 61 .irq_reg = BXTWC_THRM0IRQ, 62 .irq_mask = 0x10, 63 .irq_en = BXTWC_MTHRM0IRQ, 64 .irq_en_mask = 0x10, 65 .evt_stat = BXTWC_STHRM0IRQ, 66 .evt_mask = 0x10, 67 .trip_num = 1 68 } 69 }; 70 71 static const struct trip_config_map bxtwc_str1_trip_config[] = { 72 { 73 .irq_reg = BXTWC_THRM0IRQ, 74 .irq_mask = 0x02, 75 .irq_en = BXTWC_MTHRM0IRQ, 76 .irq_en_mask = 0x02, 77 .evt_stat = BXTWC_STHRM0IRQ, 78 .evt_mask = 0x02, 79 .trip_num = 0 80 }, 81 { 82 .irq_reg = BXTWC_THRM0IRQ, 83 .irq_mask = 0x20, 84 .irq_en = BXTWC_MTHRM0IRQ, 85 .irq_en_mask = 0x20, 86 .evt_stat = BXTWC_STHRM0IRQ, 87 .evt_mask = 0x20, 88 .trip_num = 1 89 }, 90 }; 91 92 static const struct trip_config_map bxtwc_str2_trip_config[] = { 93 { 94 .irq_reg = BXTWC_THRM0IRQ, 95 .irq_mask = 0x04, 96 .irq_en = BXTWC_MTHRM0IRQ, 97 .irq_en_mask = 0x04, 98 .evt_stat = BXTWC_STHRM0IRQ, 99 .evt_mask = 0x04, 100 .trip_num = 0 101 }, 102 { 103 .irq_reg = BXTWC_THRM0IRQ, 104 .irq_mask = 0x40, 105 .irq_en = BXTWC_MTHRM0IRQ, 106 .irq_en_mask = 0x40, 107 .evt_stat = BXTWC_STHRM0IRQ, 108 .evt_mask = 0x40, 109 .trip_num = 1 110 }, 111 }; 112 113 static const struct trip_config_map bxtwc_str3_trip_config[] = { 114 { 115 .irq_reg = BXTWC_THRM2IRQ, 116 .irq_mask = 0x10, 117 .irq_en = BXTWC_MTHRM2IRQ, 118 .irq_en_mask = 0x10, 119 .evt_stat = BXTWC_STHRM2IRQ, 120 .evt_mask = 0x10, 121 .trip_num = 0 122 }, 123 }; 124 125 static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { 126 { 127 .handle = "STR0", 128 .trip_config = bxtwc_str0_trip_config, 129 .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), 130 }, 131 { 132 .handle = "STR1", 133 .trip_config = bxtwc_str1_trip_config, 134 .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), 135 }, 136 { 137 .handle = "STR2", 138 .trip_config = bxtwc_str2_trip_config, 139 .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), 140 }, 141 { 142 .handle = "STR3", 143 .trip_config = bxtwc_str3_trip_config, 144 .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), 145 }, 146 }; 147 148 static const struct pmic_thermal_data bxtwc_thermal_data = { 149 .maps = bxtwc_thermal_irq_map, 150 .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), 151 }; 152 153 static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) 154 { 155 struct platform_device *pdev = data; 156 struct thermal_zone_device *tzd; 157 struct pmic_thermal_data *td; 158 struct intel_soc_pmic *pmic; 159 struct regmap *regmap; 160 u8 reg_val, mask, irq_stat; 161 u16 reg, evt_stat_reg; 162 int i, j, ret; 163 164 pmic = dev_get_drvdata(pdev->dev.parent); 165 regmap = pmic->regmap; 166 td = (struct pmic_thermal_data *) 167 platform_get_device_id(pdev)->driver_data; 168 169 /* Resolve thermal irqs */ 170 for (i = 0; i < td->num_maps; i++) { 171 for (j = 0; j < td->maps[i].num_trips; j++) { 172 reg = td->maps[i].trip_config[j].irq_reg; 173 mask = td->maps[i].trip_config[j].irq_mask; 174 /* 175 * Read the irq register to resolve whether the 176 * interrupt was triggered for this sensor 177 */ 178 if (regmap_read(regmap, reg, &ret)) 179 return IRQ_HANDLED; 180 181 reg_val = (u8)ret; 182 irq_stat = ((u8)ret & mask); 183 184 if (!irq_stat) 185 continue; 186 187 /* 188 * Read the status register to find out what 189 * event occurred i.e a high or a low 190 */ 191 evt_stat_reg = td->maps[i].trip_config[j].evt_stat; 192 if (regmap_read(regmap, evt_stat_reg, &ret)) 193 return IRQ_HANDLED; 194 195 tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); 196 if (!IS_ERR(tzd)) 197 thermal_zone_device_update(tzd, 198 THERMAL_EVENT_UNSPECIFIED); 199 200 /* Clear the appropriate irq */ 201 regmap_write(regmap, reg, reg_val & mask); 202 } 203 } 204 205 return IRQ_HANDLED; 206 } 207 208 static int pmic_thermal_probe(struct platform_device *pdev) 209 { 210 struct regmap_irq_chip_data *regmap_irq_chip; 211 struct pmic_thermal_data *thermal_data; 212 int ret, irq, virq, i, j, pmic_irq_count; 213 struct intel_soc_pmic *pmic; 214 struct regmap *regmap; 215 struct device *dev; 216 u16 reg; 217 u8 mask; 218 219 dev = &pdev->dev; 220 pmic = dev_get_drvdata(pdev->dev.parent); 221 if (!pmic) { 222 dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n"); 223 return -ENODEV; 224 } 225 226 thermal_data = (struct pmic_thermal_data *) 227 platform_get_device_id(pdev)->driver_data; 228 if (!thermal_data) { 229 dev_err(dev, "No thermal data initialized!!\n"); 230 return -ENODEV; 231 } 232 233 regmap = pmic->regmap; 234 regmap_irq_chip = pmic->irq_chip_data; 235 236 pmic_irq_count = 0; 237 while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { 238 virq = regmap_irq_get_virq(regmap_irq_chip, irq); 239 if (virq < 0) { 240 dev_err(dev, "failed to get virq by irq %d\n", irq); 241 return virq; 242 } 243 244 ret = devm_request_threaded_irq(&pdev->dev, virq, 245 NULL, pmic_thermal_irq_handler, 246 IRQF_ONESHOT, "pmic_thermal", pdev); 247 248 if (ret) { 249 dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); 250 return ret; 251 } 252 pmic_irq_count++; 253 } 254 255 /* Enable thermal interrupts */ 256 for (i = 0; i < thermal_data->num_maps; i++) { 257 for (j = 0; j < thermal_data->maps[i].num_trips; j++) { 258 reg = thermal_data->maps[i].trip_config[j].irq_en; 259 mask = thermal_data->maps[i].trip_config[j].irq_en_mask; 260 ret = regmap_update_bits(regmap, reg, mask, 0x00); 261 if (ret) 262 return ret; 263 } 264 } 265 266 return 0; 267 } 268 269 static const struct platform_device_id pmic_thermal_id_table[] = { 270 { 271 .name = "bxt_wcove_thermal", 272 .driver_data = (kernel_ulong_t)&bxtwc_thermal_data, 273 }, 274 {}, 275 }; 276 277 static struct platform_driver pmic_thermal_driver = { 278 .probe = pmic_thermal_probe, 279 .driver = { 280 .name = "pmic_thermal", 281 }, 282 .id_table = pmic_thermal_id_table, 283 }; 284 285 MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); 286 module_platform_driver(pmic_thermal_driver); 287 288 MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>"); 289 MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver"); 290 MODULE_LICENSE("GPL v2"); 291