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
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 = 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
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 = 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
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 = 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
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 = 0
122	},
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 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
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