xref: /openbmc/linux/drivers/acpi/pmic/intel_pmic.c (revision ac586e2d)
1b1eea857SAaron Lu /*
2b1eea857SAaron Lu  * intel_pmic.c - Intel PMIC operation region driver
3b1eea857SAaron Lu  *
4b1eea857SAaron Lu  * Copyright (C) 2014 Intel Corporation. All rights reserved.
5b1eea857SAaron Lu  *
6b1eea857SAaron Lu  * This program is free software; you can redistribute it and/or
7b1eea857SAaron Lu  * modify it under the terms of the GNU General Public License version
8b1eea857SAaron Lu  * 2 as published by the Free Software Foundation.
9b1eea857SAaron Lu  *
10b1eea857SAaron Lu  * This program is distributed in the hope that it will be useful,
11b1eea857SAaron Lu  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12b1eea857SAaron Lu  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13b1eea857SAaron Lu  * GNU General Public License for more details.
14b1eea857SAaron Lu  */
15b1eea857SAaron Lu 
16b1eea857SAaron Lu #include <linux/module.h>
17b1eea857SAaron Lu #include <linux/acpi.h>
18b1eea857SAaron Lu #include <linux/regmap.h>
19ac586e2dSSrinivas Pandruvada #include <acpi/acpi_lpat.h>
20b1eea857SAaron Lu #include "intel_pmic.h"
21b1eea857SAaron Lu 
22b1eea857SAaron Lu #define PMIC_POWER_OPREGION_ID		0x8d
23b1eea857SAaron Lu #define PMIC_THERMAL_OPREGION_ID	0x8c
24b1eea857SAaron Lu 
25b1eea857SAaron Lu struct intel_pmic_opregion {
26b1eea857SAaron Lu 	struct mutex lock;
27ac586e2dSSrinivas Pandruvada 	struct acpi_lpat_conversion_table *lpat_table;
28b1eea857SAaron Lu 	struct regmap *regmap;
29b1eea857SAaron Lu 	struct intel_pmic_opregion_data *data;
30b1eea857SAaron Lu };
31b1eea857SAaron Lu 
32b1eea857SAaron Lu static int pmic_get_reg_bit(int address, struct pmic_table *table,
33b1eea857SAaron Lu 			    int count, int *reg, int *bit)
34b1eea857SAaron Lu {
35b1eea857SAaron Lu 	int i;
36b1eea857SAaron Lu 
37b1eea857SAaron Lu 	for (i = 0; i < count; i++) {
38b1eea857SAaron Lu 		if (table[i].address == address) {
39b1eea857SAaron Lu 			*reg = table[i].reg;
40b1eea857SAaron Lu 			if (bit)
41b1eea857SAaron Lu 				*bit = table[i].bit;
42b1eea857SAaron Lu 			return 0;
43b1eea857SAaron Lu 		}
44b1eea857SAaron Lu 	}
45b1eea857SAaron Lu 	return -ENOENT;
46b1eea857SAaron Lu }
47b1eea857SAaron Lu 
48b1eea857SAaron Lu static acpi_status intel_pmic_power_handler(u32 function,
49b1eea857SAaron Lu 		acpi_physical_address address, u32 bits, u64 *value64,
50b1eea857SAaron Lu 		void *handler_context, void *region_context)
51b1eea857SAaron Lu {
52b1eea857SAaron Lu 	struct intel_pmic_opregion *opregion = region_context;
53b1eea857SAaron Lu 	struct regmap *regmap = opregion->regmap;
54b1eea857SAaron Lu 	struct intel_pmic_opregion_data *d = opregion->data;
55b1eea857SAaron Lu 	int reg, bit, result;
56b1eea857SAaron Lu 
57b1eea857SAaron Lu 	if (bits != 32 || !value64)
58b1eea857SAaron Lu 		return AE_BAD_PARAMETER;
59b1eea857SAaron Lu 
60b1eea857SAaron Lu 	if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1))
61b1eea857SAaron Lu 		return AE_BAD_PARAMETER;
62b1eea857SAaron Lu 
63b1eea857SAaron Lu 	result = pmic_get_reg_bit(address, d->power_table,
64b1eea857SAaron Lu 				  d->power_table_count, &reg, &bit);
65b1eea857SAaron Lu 	if (result == -ENOENT)
66b1eea857SAaron Lu 		return AE_BAD_PARAMETER;
67b1eea857SAaron Lu 
68b1eea857SAaron Lu 	mutex_lock(&opregion->lock);
69b1eea857SAaron Lu 
70b1eea857SAaron Lu 	result = function == ACPI_READ ?
71b1eea857SAaron Lu 		d->get_power(regmap, reg, bit, value64) :
72b1eea857SAaron Lu 		d->update_power(regmap, reg, bit, *value64 == 1);
73b1eea857SAaron Lu 
74b1eea857SAaron Lu 	mutex_unlock(&opregion->lock);
75b1eea857SAaron Lu 
76b1eea857SAaron Lu 	return result ? AE_ERROR : AE_OK;
77b1eea857SAaron Lu }
78b1eea857SAaron Lu 
79b1eea857SAaron Lu static int pmic_read_temp(struct intel_pmic_opregion *opregion,
80b1eea857SAaron Lu 			  int reg, u64 *value)
81b1eea857SAaron Lu {
82b1eea857SAaron Lu 	int raw_temp, temp;
83b1eea857SAaron Lu 
84b1eea857SAaron Lu 	if (!opregion->data->get_raw_temp)
85b1eea857SAaron Lu 		return -ENXIO;
86b1eea857SAaron Lu 
87b1eea857SAaron Lu 	raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg);
88b1eea857SAaron Lu 	if (raw_temp < 0)
89b1eea857SAaron Lu 		return raw_temp;
90b1eea857SAaron Lu 
91ac586e2dSSrinivas Pandruvada 	if (!opregion->lpat_table) {
92b1eea857SAaron Lu 		*value = raw_temp;
93b1eea857SAaron Lu 		return 0;
94b1eea857SAaron Lu 	}
95b1eea857SAaron Lu 
96ac586e2dSSrinivas Pandruvada 	temp = acpi_lpat_raw_to_temp(opregion->lpat_table, raw_temp);
97b1eea857SAaron Lu 	if (temp < 0)
98b1eea857SAaron Lu 		return temp;
99b1eea857SAaron Lu 
100b1eea857SAaron Lu 	*value = temp;
101b1eea857SAaron Lu 	return 0;
102b1eea857SAaron Lu }
103b1eea857SAaron Lu 
104b1eea857SAaron Lu static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg,
105b1eea857SAaron Lu 			     u32 function, u64 *value)
106b1eea857SAaron Lu {
107b1eea857SAaron Lu 	return function == ACPI_READ ?
108b1eea857SAaron Lu 		pmic_read_temp(opregion, reg, value) : -EINVAL;
109b1eea857SAaron Lu }
110b1eea857SAaron Lu 
111b1eea857SAaron Lu static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg,
112b1eea857SAaron Lu 			    u32 function, u64 *value)
113b1eea857SAaron Lu {
114b1eea857SAaron Lu 	int raw_temp;
115b1eea857SAaron Lu 
116b1eea857SAaron Lu 	if (function == ACPI_READ)
117b1eea857SAaron Lu 		return pmic_read_temp(opregion, reg, value);
118b1eea857SAaron Lu 
119b1eea857SAaron Lu 	if (!opregion->data->update_aux)
120b1eea857SAaron Lu 		return -ENXIO;
121b1eea857SAaron Lu 
122ac586e2dSSrinivas Pandruvada 	if (opregion->lpat_table) {
123ac586e2dSSrinivas Pandruvada 		raw_temp = acpi_lpat_temp_to_raw(opregion->lpat_table, *value);
124b1eea857SAaron Lu 		if (raw_temp < 0)
125b1eea857SAaron Lu 			return raw_temp;
126b1eea857SAaron Lu 	} else {
127b1eea857SAaron Lu 		raw_temp = *value;
128b1eea857SAaron Lu 	}
129b1eea857SAaron Lu 
130b1eea857SAaron Lu 	return opregion->data->update_aux(opregion->regmap, reg, raw_temp);
131b1eea857SAaron Lu }
132b1eea857SAaron Lu 
133b1eea857SAaron Lu static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg,
134b1eea857SAaron Lu 			    u32 function, u64 *value)
135b1eea857SAaron Lu {
136b1eea857SAaron Lu 	struct intel_pmic_opregion_data *d = opregion->data;
137b1eea857SAaron Lu 	struct regmap *regmap = opregion->regmap;
138b1eea857SAaron Lu 
139b1eea857SAaron Lu 	if (!d->get_policy || !d->update_policy)
140b1eea857SAaron Lu 		return -ENXIO;
141b1eea857SAaron Lu 
142b1eea857SAaron Lu 	if (function == ACPI_READ)
143b1eea857SAaron Lu 		return d->get_policy(regmap, reg, value);
144b1eea857SAaron Lu 
145b1eea857SAaron Lu 	if (*value != 0 && *value != 1)
146b1eea857SAaron Lu 		return -EINVAL;
147b1eea857SAaron Lu 
148b1eea857SAaron Lu 	return d->update_policy(regmap, reg, *value);
149b1eea857SAaron Lu }
150b1eea857SAaron Lu 
151b1eea857SAaron Lu static bool pmic_thermal_is_temp(int address)
152b1eea857SAaron Lu {
153b1eea857SAaron Lu 	return (address <= 0x3c) && !(address % 12);
154b1eea857SAaron Lu }
155b1eea857SAaron Lu 
156b1eea857SAaron Lu static bool pmic_thermal_is_aux(int address)
157b1eea857SAaron Lu {
158b1eea857SAaron Lu 	return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) ||
159b1eea857SAaron Lu 	       (address >= 8 && address <= 0x44 && !((address - 8) % 12));
160b1eea857SAaron Lu }
161b1eea857SAaron Lu 
162b1eea857SAaron Lu static bool pmic_thermal_is_pen(int address)
163b1eea857SAaron Lu {
164b1eea857SAaron Lu 	return address >= 0x48 && address <= 0x5c;
165b1eea857SAaron Lu }
166b1eea857SAaron Lu 
167b1eea857SAaron Lu static acpi_status intel_pmic_thermal_handler(u32 function,
168b1eea857SAaron Lu 		acpi_physical_address address, u32 bits, u64 *value64,
169b1eea857SAaron Lu 		void *handler_context, void *region_context)
170b1eea857SAaron Lu {
171b1eea857SAaron Lu 	struct intel_pmic_opregion *opregion = region_context;
172b1eea857SAaron Lu 	struct intel_pmic_opregion_data *d = opregion->data;
173b1eea857SAaron Lu 	int reg, result;
174b1eea857SAaron Lu 
175b1eea857SAaron Lu 	if (bits != 32 || !value64)
176b1eea857SAaron Lu 		return AE_BAD_PARAMETER;
177b1eea857SAaron Lu 
178b1eea857SAaron Lu 	result = pmic_get_reg_bit(address, d->thermal_table,
179b1eea857SAaron Lu 				  d->thermal_table_count, &reg, NULL);
180b1eea857SAaron Lu 	if (result == -ENOENT)
181b1eea857SAaron Lu 		return AE_BAD_PARAMETER;
182b1eea857SAaron Lu 
183b1eea857SAaron Lu 	mutex_lock(&opregion->lock);
184b1eea857SAaron Lu 
185b1eea857SAaron Lu 	if (pmic_thermal_is_temp(address))
186b1eea857SAaron Lu 		result = pmic_thermal_temp(opregion, reg, function, value64);
187b1eea857SAaron Lu 	else if (pmic_thermal_is_aux(address))
188b1eea857SAaron Lu 		result = pmic_thermal_aux(opregion, reg, function, value64);
189b1eea857SAaron Lu 	else if (pmic_thermal_is_pen(address))
190b1eea857SAaron Lu 		result = pmic_thermal_pen(opregion, reg, function, value64);
191b1eea857SAaron Lu 	else
192b1eea857SAaron Lu 		result = -EINVAL;
193b1eea857SAaron Lu 
194b1eea857SAaron Lu 	mutex_unlock(&opregion->lock);
195b1eea857SAaron Lu 
196b1eea857SAaron Lu 	if (result < 0) {
197b1eea857SAaron Lu 		if (result == -EINVAL)
198b1eea857SAaron Lu 			return AE_BAD_PARAMETER;
199b1eea857SAaron Lu 		else
200b1eea857SAaron Lu 			return AE_ERROR;
201b1eea857SAaron Lu 	}
202b1eea857SAaron Lu 
203b1eea857SAaron Lu 	return AE_OK;
204b1eea857SAaron Lu }
205b1eea857SAaron Lu 
206b1eea857SAaron Lu int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle,
207b1eea857SAaron Lu 					struct regmap *regmap,
208b1eea857SAaron Lu 					struct intel_pmic_opregion_data *d)
209b1eea857SAaron Lu {
210b1eea857SAaron Lu 	acpi_status status;
211b1eea857SAaron Lu 	struct intel_pmic_opregion *opregion;
212ac586e2dSSrinivas Pandruvada 	int ret;
213b1eea857SAaron Lu 
214b1eea857SAaron Lu 	if (!dev || !regmap || !d)
215b1eea857SAaron Lu 		return -EINVAL;
216b1eea857SAaron Lu 
217b1eea857SAaron Lu 	if (!handle)
218b1eea857SAaron Lu 		return -ENODEV;
219b1eea857SAaron Lu 
220b1eea857SAaron Lu 	opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL);
221b1eea857SAaron Lu 	if (!opregion)
222b1eea857SAaron Lu 		return -ENOMEM;
223b1eea857SAaron Lu 
224b1eea857SAaron Lu 	mutex_init(&opregion->lock);
225b1eea857SAaron Lu 	opregion->regmap = regmap;
226ac586e2dSSrinivas Pandruvada 	opregion->lpat_table = acpi_lpat_get_conversion_table(handle);
227b1eea857SAaron Lu 
228b1eea857SAaron Lu 	status = acpi_install_address_space_handler(handle,
229b1eea857SAaron Lu 						    PMIC_POWER_OPREGION_ID,
230b1eea857SAaron Lu 						    intel_pmic_power_handler,
231b1eea857SAaron Lu 						    NULL, opregion);
232ac586e2dSSrinivas Pandruvada 	if (ACPI_FAILURE(status)) {
233ac586e2dSSrinivas Pandruvada 		ret = -ENODEV;
234ac586e2dSSrinivas Pandruvada 		goto out_error;
235ac586e2dSSrinivas Pandruvada 	}
236b1eea857SAaron Lu 
237b1eea857SAaron Lu 	status = acpi_install_address_space_handler(handle,
238b1eea857SAaron Lu 						    PMIC_THERMAL_OPREGION_ID,
239b1eea857SAaron Lu 						    intel_pmic_thermal_handler,
240b1eea857SAaron Lu 						    NULL, opregion);
241b1eea857SAaron Lu 	if (ACPI_FAILURE(status)) {
242b1eea857SAaron Lu 		acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID,
243b1eea857SAaron Lu 						  intel_pmic_power_handler);
244ac586e2dSSrinivas Pandruvada 		ret = -ENODEV;
245ac586e2dSSrinivas Pandruvada 		goto out_error;
246b1eea857SAaron Lu 	}
247b1eea857SAaron Lu 
248b1eea857SAaron Lu 	opregion->data = d;
249b1eea857SAaron Lu 	return 0;
250ac586e2dSSrinivas Pandruvada 
251ac586e2dSSrinivas Pandruvada out_error:
252ac586e2dSSrinivas Pandruvada 	acpi_lpat_free_conversion_table(opregion->lpat_table);
253ac586e2dSSrinivas Pandruvada 	return ret;
254b1eea857SAaron Lu }
255b1eea857SAaron Lu EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler);
256b1eea857SAaron Lu 
257b1eea857SAaron Lu MODULE_LICENSE("GPL");
258