291 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Intel Broxton PMIC thermal driver4 *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 0x4E0420#define BXTWC_THRM1IRQ 0x4E0521#define BXTWC_THRM2IRQ 0x4E0622#define BXTWC_MTHRM0IRQ 0x4E1223#define BXTWC_MTHRM1IRQ 0x4E1324#define BXTWC_MTHRM2IRQ 0x4E1425#define BXTWC_STHRM0IRQ 0x4F1926#define BXTWC_STHRM1IRQ 0x4F1A27#define BXTWC_STHRM2IRQ 0x4F1B28 29struct 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 39struct thermal_irq_map {40 char handle[20];41 int num_trips;42 const struct trip_config_map *trip_config;43};44 45struct pmic_thermal_data {46 const struct thermal_irq_map *maps;47 int num_maps;48};49 50static 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 = 059 },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 = 168 }69};70 71static 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 = 080 },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 = 189 },90};91 92static 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 = 0101 },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 = 1110 },111};112 113static 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 = 0122 },123};124 125static 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 148static 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 153static 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 the176 * interrupt was triggered for this sensor177 */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 what189 * event occurred i.e a high or a low190 */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 208static 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 269static 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 277static 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 285MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table);286module_platform_driver(pmic_thermal_driver);287 288MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>");289MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver");290MODULE_LICENSE("GPL v2");291