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