11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
23e8c4d31SAmit Kucheria /*
33e8c4d31SAmit Kucheria  * Intel Broxton PMIC thermal driver
43e8c4d31SAmit Kucheria  *
53e8c4d31SAmit Kucheria  * Copyright (C) 2016 Intel Corporation. All rights reserved.
63e8c4d31SAmit Kucheria  */
73e8c4d31SAmit Kucheria 
83e8c4d31SAmit Kucheria #include <linux/module.h>
93e8c4d31SAmit Kucheria #include <linux/kernel.h>
103e8c4d31SAmit Kucheria #include <linux/slab.h>
113e8c4d31SAmit Kucheria #include <linux/delay.h>
123e8c4d31SAmit Kucheria #include <linux/interrupt.h>
133e8c4d31SAmit Kucheria #include <linux/device.h>
143e8c4d31SAmit Kucheria #include <linux/thermal.h>
153e8c4d31SAmit Kucheria #include <linux/platform_device.h>
163e8c4d31SAmit Kucheria #include <linux/sched.h>
173e8c4d31SAmit Kucheria #include <linux/mfd/intel_soc_pmic.h>
183e8c4d31SAmit Kucheria 
193e8c4d31SAmit Kucheria #define BXTWC_THRM0IRQ		0x4E04
203e8c4d31SAmit Kucheria #define BXTWC_THRM1IRQ		0x4E05
213e8c4d31SAmit Kucheria #define BXTWC_THRM2IRQ		0x4E06
223e8c4d31SAmit Kucheria #define BXTWC_MTHRM0IRQ		0x4E12
233e8c4d31SAmit Kucheria #define BXTWC_MTHRM1IRQ		0x4E13
243e8c4d31SAmit Kucheria #define BXTWC_MTHRM2IRQ		0x4E14
253e8c4d31SAmit Kucheria #define BXTWC_STHRM0IRQ		0x4F19
263e8c4d31SAmit Kucheria #define BXTWC_STHRM1IRQ		0x4F1A
273e8c4d31SAmit Kucheria #define BXTWC_STHRM2IRQ		0x4F1B
283e8c4d31SAmit Kucheria 
293e8c4d31SAmit Kucheria struct trip_config_map {
303e8c4d31SAmit Kucheria 	u16 irq_reg;
313e8c4d31SAmit Kucheria 	u16 irq_en;
323e8c4d31SAmit Kucheria 	u16 evt_stat;
333e8c4d31SAmit Kucheria 	u8 irq_mask;
343e8c4d31SAmit Kucheria 	u8 irq_en_mask;
353e8c4d31SAmit Kucheria 	u8 evt_mask;
363e8c4d31SAmit Kucheria 	u8 trip_num;
373e8c4d31SAmit Kucheria };
383e8c4d31SAmit Kucheria 
393e8c4d31SAmit Kucheria struct thermal_irq_map {
403e8c4d31SAmit Kucheria 	char handle[20];
413e8c4d31SAmit Kucheria 	int num_trips;
423e8c4d31SAmit Kucheria 	const struct trip_config_map *trip_config;
433e8c4d31SAmit Kucheria };
443e8c4d31SAmit Kucheria 
453e8c4d31SAmit Kucheria struct pmic_thermal_data {
463e8c4d31SAmit Kucheria 	const struct thermal_irq_map *maps;
473e8c4d31SAmit Kucheria 	int num_maps;
483e8c4d31SAmit Kucheria };
493e8c4d31SAmit Kucheria 
503e8c4d31SAmit Kucheria static const struct trip_config_map bxtwc_str0_trip_config[] = {
513e8c4d31SAmit Kucheria 	{
523e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
533e8c4d31SAmit Kucheria 		.irq_mask = 0x01,
543e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
553e8c4d31SAmit Kucheria 		.irq_en_mask = 0x01,
563e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
573e8c4d31SAmit Kucheria 		.evt_mask = 0x01,
583e8c4d31SAmit Kucheria 		.trip_num = 0
593e8c4d31SAmit Kucheria 	},
603e8c4d31SAmit Kucheria 	{
613e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
623e8c4d31SAmit Kucheria 		.irq_mask = 0x10,
633e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
643e8c4d31SAmit Kucheria 		.irq_en_mask = 0x10,
653e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
663e8c4d31SAmit Kucheria 		.evt_mask = 0x10,
673e8c4d31SAmit Kucheria 		.trip_num = 1
683e8c4d31SAmit Kucheria 	}
693e8c4d31SAmit Kucheria };
703e8c4d31SAmit Kucheria 
713e8c4d31SAmit Kucheria static const struct trip_config_map bxtwc_str1_trip_config[] = {
723e8c4d31SAmit Kucheria 	{
733e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
743e8c4d31SAmit Kucheria 		.irq_mask = 0x02,
753e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
763e8c4d31SAmit Kucheria 		.irq_en_mask = 0x02,
773e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
783e8c4d31SAmit Kucheria 		.evt_mask = 0x02,
793e8c4d31SAmit Kucheria 		.trip_num = 0
803e8c4d31SAmit Kucheria 	},
813e8c4d31SAmit Kucheria 	{
823e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
833e8c4d31SAmit Kucheria 		.irq_mask = 0x20,
843e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
853e8c4d31SAmit Kucheria 		.irq_en_mask = 0x20,
863e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
873e8c4d31SAmit Kucheria 		.evt_mask = 0x20,
883e8c4d31SAmit Kucheria 		.trip_num = 1
893e8c4d31SAmit Kucheria 	},
903e8c4d31SAmit Kucheria };
913e8c4d31SAmit Kucheria 
923e8c4d31SAmit Kucheria static const struct trip_config_map bxtwc_str2_trip_config[] = {
933e8c4d31SAmit Kucheria 	{
943e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
953e8c4d31SAmit Kucheria 		.irq_mask = 0x04,
963e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
973e8c4d31SAmit Kucheria 		.irq_en_mask = 0x04,
983e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
993e8c4d31SAmit Kucheria 		.evt_mask = 0x04,
1003e8c4d31SAmit Kucheria 		.trip_num = 0
1013e8c4d31SAmit Kucheria 	},
1023e8c4d31SAmit Kucheria 	{
1033e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM0IRQ,
1043e8c4d31SAmit Kucheria 		.irq_mask = 0x40,
1053e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM0IRQ,
1063e8c4d31SAmit Kucheria 		.irq_en_mask = 0x40,
1073e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM0IRQ,
1083e8c4d31SAmit Kucheria 		.evt_mask = 0x40,
1093e8c4d31SAmit Kucheria 		.trip_num = 1
1103e8c4d31SAmit Kucheria 	},
1113e8c4d31SAmit Kucheria };
1123e8c4d31SAmit Kucheria 
1133e8c4d31SAmit Kucheria static const struct trip_config_map bxtwc_str3_trip_config[] = {
1143e8c4d31SAmit Kucheria 	{
1153e8c4d31SAmit Kucheria 		.irq_reg = BXTWC_THRM2IRQ,
1163e8c4d31SAmit Kucheria 		.irq_mask = 0x10,
1173e8c4d31SAmit Kucheria 		.irq_en = BXTWC_MTHRM2IRQ,
1183e8c4d31SAmit Kucheria 		.irq_en_mask = 0x10,
1193e8c4d31SAmit Kucheria 		.evt_stat = BXTWC_STHRM2IRQ,
1203e8c4d31SAmit Kucheria 		.evt_mask = 0x10,
1213e8c4d31SAmit Kucheria 		.trip_num = 0
1223e8c4d31SAmit Kucheria 	},
1233e8c4d31SAmit Kucheria };
1243e8c4d31SAmit Kucheria 
1253e8c4d31SAmit Kucheria static const struct thermal_irq_map bxtwc_thermal_irq_map[] = {
1263e8c4d31SAmit Kucheria 	{
1273e8c4d31SAmit Kucheria 		.handle = "STR0",
1283e8c4d31SAmit Kucheria 		.trip_config = bxtwc_str0_trip_config,
1293e8c4d31SAmit Kucheria 		.num_trips = ARRAY_SIZE(bxtwc_str0_trip_config),
1303e8c4d31SAmit Kucheria 	},
1313e8c4d31SAmit Kucheria 	{
1323e8c4d31SAmit Kucheria 		.handle = "STR1",
1333e8c4d31SAmit Kucheria 		.trip_config = bxtwc_str1_trip_config,
1343e8c4d31SAmit Kucheria 		.num_trips = ARRAY_SIZE(bxtwc_str1_trip_config),
1353e8c4d31SAmit Kucheria 	},
1363e8c4d31SAmit Kucheria 	{
1373e8c4d31SAmit Kucheria 		.handle = "STR2",
1383e8c4d31SAmit Kucheria 		.trip_config = bxtwc_str2_trip_config,
1393e8c4d31SAmit Kucheria 		.num_trips = ARRAY_SIZE(bxtwc_str2_trip_config),
1403e8c4d31SAmit Kucheria 	},
1413e8c4d31SAmit Kucheria 	{
1423e8c4d31SAmit Kucheria 		.handle = "STR3",
1433e8c4d31SAmit Kucheria 		.trip_config = bxtwc_str3_trip_config,
1443e8c4d31SAmit Kucheria 		.num_trips = ARRAY_SIZE(bxtwc_str3_trip_config),
1453e8c4d31SAmit Kucheria 	},
1463e8c4d31SAmit Kucheria };
1473e8c4d31SAmit Kucheria 
1483e8c4d31SAmit Kucheria static const struct pmic_thermal_data bxtwc_thermal_data = {
1493e8c4d31SAmit Kucheria 	.maps = bxtwc_thermal_irq_map,
1503e8c4d31SAmit Kucheria 	.num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map),
1513e8c4d31SAmit Kucheria };
1523e8c4d31SAmit Kucheria 
pmic_thermal_irq_handler(int irq,void * data)1533e8c4d31SAmit Kucheria static irqreturn_t pmic_thermal_irq_handler(int irq, void *data)
1543e8c4d31SAmit Kucheria {
1553e8c4d31SAmit Kucheria 	struct platform_device *pdev = data;
1563e8c4d31SAmit Kucheria 	struct thermal_zone_device *tzd;
1573e8c4d31SAmit Kucheria 	struct pmic_thermal_data *td;
1583e8c4d31SAmit Kucheria 	struct intel_soc_pmic *pmic;
1593e8c4d31SAmit Kucheria 	struct regmap *regmap;
1603e8c4d31SAmit Kucheria 	u8 reg_val, mask, irq_stat;
1613e8c4d31SAmit Kucheria 	u16 reg, evt_stat_reg;
1623e8c4d31SAmit Kucheria 	int i, j, ret;
1633e8c4d31SAmit Kucheria 
1643e8c4d31SAmit Kucheria 	pmic = dev_get_drvdata(pdev->dev.parent);
1653e8c4d31SAmit Kucheria 	regmap = pmic->regmap;
1663e8c4d31SAmit Kucheria 	td = (struct pmic_thermal_data *)
1673e8c4d31SAmit Kucheria 		platform_get_device_id(pdev)->driver_data;
1683e8c4d31SAmit Kucheria 
1693e8c4d31SAmit Kucheria 	/* Resolve thermal irqs */
1703e8c4d31SAmit Kucheria 	for (i = 0; i < td->num_maps; i++) {
1713e8c4d31SAmit Kucheria 		for (j = 0; j < td->maps[i].num_trips; j++) {
1723e8c4d31SAmit Kucheria 			reg = td->maps[i].trip_config[j].irq_reg;
1733e8c4d31SAmit Kucheria 			mask = td->maps[i].trip_config[j].irq_mask;
1743e8c4d31SAmit Kucheria 			/*
1753e8c4d31SAmit Kucheria 			 * Read the irq register to resolve whether the
1763e8c4d31SAmit Kucheria 			 * interrupt was triggered for this sensor
1773e8c4d31SAmit Kucheria 			 */
1783e8c4d31SAmit Kucheria 			if (regmap_read(regmap, reg, &ret))
1793e8c4d31SAmit Kucheria 				return IRQ_HANDLED;
1803e8c4d31SAmit Kucheria 
1813e8c4d31SAmit Kucheria 			reg_val = (u8)ret;
1823e8c4d31SAmit Kucheria 			irq_stat = ((u8)ret & mask);
1833e8c4d31SAmit Kucheria 
1843e8c4d31SAmit Kucheria 			if (!irq_stat)
1853e8c4d31SAmit Kucheria 				continue;
1863e8c4d31SAmit Kucheria 
1873e8c4d31SAmit Kucheria 			/*
1883e8c4d31SAmit Kucheria 			 * Read the status register to find out what
1893e8c4d31SAmit Kucheria 			 * event occurred i.e a high or a low
1903e8c4d31SAmit Kucheria 			 */
1913e8c4d31SAmit Kucheria 			evt_stat_reg = td->maps[i].trip_config[j].evt_stat;
1923e8c4d31SAmit Kucheria 			if (regmap_read(regmap, evt_stat_reg, &ret))
1933e8c4d31SAmit Kucheria 				return IRQ_HANDLED;
1943e8c4d31SAmit Kucheria 
1953e8c4d31SAmit Kucheria 			tzd = thermal_zone_get_zone_by_name(td->maps[i].handle);
1963e8c4d31SAmit Kucheria 			if (!IS_ERR(tzd))
1973e8c4d31SAmit Kucheria 				thermal_zone_device_update(tzd,
1983e8c4d31SAmit Kucheria 						THERMAL_EVENT_UNSPECIFIED);
1993e8c4d31SAmit Kucheria 
2003e8c4d31SAmit Kucheria 			/* Clear the appropriate irq */
2013e8c4d31SAmit Kucheria 			regmap_write(regmap, reg, reg_val & mask);
2023e8c4d31SAmit Kucheria 		}
2033e8c4d31SAmit Kucheria 	}
2043e8c4d31SAmit Kucheria 
2053e8c4d31SAmit Kucheria 	return IRQ_HANDLED;
2063e8c4d31SAmit Kucheria }
2073e8c4d31SAmit Kucheria 
pmic_thermal_probe(struct platform_device * pdev)2083e8c4d31SAmit Kucheria static int pmic_thermal_probe(struct platform_device *pdev)
2093e8c4d31SAmit Kucheria {
2103e8c4d31SAmit Kucheria 	struct regmap_irq_chip_data *regmap_irq_chip;
2113e8c4d31SAmit Kucheria 	struct pmic_thermal_data *thermal_data;
2123e8c4d31SAmit Kucheria 	int ret, irq, virq, i, j, pmic_irq_count;
2133e8c4d31SAmit Kucheria 	struct intel_soc_pmic *pmic;
2143e8c4d31SAmit Kucheria 	struct regmap *regmap;
2153e8c4d31SAmit Kucheria 	struct device *dev;
2163e8c4d31SAmit Kucheria 	u16 reg;
2173e8c4d31SAmit Kucheria 	u8 mask;
2183e8c4d31SAmit Kucheria 
2193e8c4d31SAmit Kucheria 	dev = &pdev->dev;
2203e8c4d31SAmit Kucheria 	pmic = dev_get_drvdata(pdev->dev.parent);
2213e8c4d31SAmit Kucheria 	if (!pmic) {
2223e8c4d31SAmit Kucheria 		dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n");
2233e8c4d31SAmit Kucheria 		return -ENODEV;
2243e8c4d31SAmit Kucheria 	}
2253e8c4d31SAmit Kucheria 
2263e8c4d31SAmit Kucheria 	thermal_data = (struct pmic_thermal_data *)
2273e8c4d31SAmit Kucheria 				platform_get_device_id(pdev)->driver_data;
2283e8c4d31SAmit Kucheria 	if (!thermal_data) {
2293e8c4d31SAmit Kucheria 		dev_err(dev, "No thermal data initialized!!\n");
2303e8c4d31SAmit Kucheria 		return -ENODEV;
2313e8c4d31SAmit Kucheria 	}
2323e8c4d31SAmit Kucheria 
2333e8c4d31SAmit Kucheria 	regmap = pmic->regmap;
2343e8c4d31SAmit Kucheria 	regmap_irq_chip = pmic->irq_chip_data;
2353e8c4d31SAmit Kucheria 
2363e8c4d31SAmit Kucheria 	pmic_irq_count = 0;
2373e8c4d31SAmit Kucheria 	while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) {
2383e8c4d31SAmit Kucheria 		virq = regmap_irq_get_virq(regmap_irq_chip, irq);
2393e8c4d31SAmit Kucheria 		if (virq < 0) {
2403e8c4d31SAmit Kucheria 			dev_err(dev, "failed to get virq by irq %d\n", irq);
2413e8c4d31SAmit Kucheria 			return virq;
2423e8c4d31SAmit Kucheria 		}
2433e8c4d31SAmit Kucheria 
2443e8c4d31SAmit Kucheria 		ret = devm_request_threaded_irq(&pdev->dev, virq,
2453e8c4d31SAmit Kucheria 				NULL, pmic_thermal_irq_handler,
2463e8c4d31SAmit Kucheria 				IRQF_ONESHOT, "pmic_thermal", pdev);
2473e8c4d31SAmit Kucheria 
2483e8c4d31SAmit Kucheria 		if (ret) {
2493e8c4d31SAmit Kucheria 			dev_err(dev, "request irq(%d) failed: %d\n", virq, ret);
2503e8c4d31SAmit Kucheria 			return ret;
2513e8c4d31SAmit Kucheria 		}
2523e8c4d31SAmit Kucheria 		pmic_irq_count++;
2533e8c4d31SAmit Kucheria 	}
2543e8c4d31SAmit Kucheria 
2553e8c4d31SAmit Kucheria 	/* Enable thermal interrupts */
2563e8c4d31SAmit Kucheria 	for (i = 0; i < thermal_data->num_maps; i++) {
2573e8c4d31SAmit Kucheria 		for (j = 0; j < thermal_data->maps[i].num_trips; j++) {
2583e8c4d31SAmit Kucheria 			reg = thermal_data->maps[i].trip_config[j].irq_en;
2593e8c4d31SAmit Kucheria 			mask = thermal_data->maps[i].trip_config[j].irq_en_mask;
2603e8c4d31SAmit Kucheria 			ret = regmap_update_bits(regmap, reg, mask, 0x00);
2613e8c4d31SAmit Kucheria 			if (ret)
2623e8c4d31SAmit Kucheria 				return ret;
2633e8c4d31SAmit Kucheria 		}
2643e8c4d31SAmit Kucheria 	}
2653e8c4d31SAmit Kucheria 
2663e8c4d31SAmit Kucheria 	return 0;
2673e8c4d31SAmit Kucheria }
2683e8c4d31SAmit Kucheria 
2693e8c4d31SAmit Kucheria static const struct platform_device_id pmic_thermal_id_table[] = {
2703e8c4d31SAmit Kucheria 	{
2713e8c4d31SAmit Kucheria 		.name = "bxt_wcove_thermal",
2723e8c4d31SAmit Kucheria 		.driver_data = (kernel_ulong_t)&bxtwc_thermal_data,
2733e8c4d31SAmit Kucheria 	},
2743e8c4d31SAmit Kucheria 	{},
2753e8c4d31SAmit Kucheria };
2763e8c4d31SAmit Kucheria 
2773e8c4d31SAmit Kucheria static struct platform_driver pmic_thermal_driver = {
2783e8c4d31SAmit Kucheria 	.probe = pmic_thermal_probe,
2793e8c4d31SAmit Kucheria 	.driver = {
2803e8c4d31SAmit Kucheria 		.name = "pmic_thermal",
2813e8c4d31SAmit Kucheria 	},
2823e8c4d31SAmit Kucheria 	.id_table = pmic_thermal_id_table,
2833e8c4d31SAmit Kucheria };
2843e8c4d31SAmit Kucheria 
2853e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table);
2863e8c4d31SAmit Kucheria module_platform_driver(pmic_thermal_driver);
2873e8c4d31SAmit Kucheria 
2883e8c4d31SAmit Kucheria MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>");
2893e8c4d31SAmit Kucheria MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver");
2903e8c4d31SAmit Kucheria MODULE_LICENSE("GPL v2");
291