xref: /linux/drivers/acpi/pmic/intel_pmic.c (revision 93d90ad708b8da6efc0e487b66111aa9db7f70c7)
1 /*
2  * intel_pmic.c - Intel PMIC operation region driver
3  *
4  * Copyright (C) 2014 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 #include <linux/module.h>
17 #include <linux/acpi.h>
18 #include <linux/regmap.h>
19 #include "intel_pmic.h"
20 
21 #define PMIC_POWER_OPREGION_ID		0x8d
22 #define PMIC_THERMAL_OPREGION_ID	0x8c
23 
24 struct acpi_lpat {
25 	int temp;
26 	int raw;
27 };
28 
29 struct intel_pmic_opregion {
30 	struct mutex lock;
31 	struct acpi_lpat *lpat;
32 	int lpat_count;
33 	struct regmap *regmap;
34 	struct intel_pmic_opregion_data *data;
35 };
36 
37 static int pmic_get_reg_bit(int address, struct pmic_table *table,
38 			    int count, int *reg, int *bit)
39 {
40 	int i;
41 
42 	for (i = 0; i < count; i++) {
43 		if (table[i].address == address) {
44 			*reg = table[i].reg;
45 			if (bit)
46 				*bit = table[i].bit;
47 			return 0;
48 		}
49 	}
50 	return -ENOENT;
51 }
52 
53 /**
54  * raw_to_temp(): Return temperature from raw value through LPAT table
55  *
56  * @lpat: the temperature_raw mapping table
57  * @count: the count of the above mapping table
58  * @raw: the raw value, used as a key to get the temerature from the
59  *       above mapping table
60  *
61  * A positive value will be returned on success, a negative errno will
62  * be returned in error cases.
63  */
64 static int raw_to_temp(struct acpi_lpat *lpat, int count, int raw)
65 {
66 	int i, delta_temp, delta_raw, temp;
67 
68 	for (i = 0; i < count - 1; i++) {
69 		if ((raw >= lpat[i].raw && raw <= lpat[i+1].raw) ||
70 		    (raw <= lpat[i].raw && raw >= lpat[i+1].raw))
71 			break;
72 	}
73 
74 	if (i == count - 1)
75 		return -ENOENT;
76 
77 	delta_temp = lpat[i+1].temp - lpat[i].temp;
78 	delta_raw = lpat[i+1].raw - lpat[i].raw;
79 	temp = lpat[i].temp + (raw - lpat[i].raw) * delta_temp / delta_raw;
80 
81 	return temp;
82 }
83 
84 /**
85  * temp_to_raw(): Return raw value from temperature through LPAT table
86  *
87  * @lpat: the temperature_raw mapping table
88  * @count: the count of the above mapping table
89  * @temp: the temperature, used as a key to get the raw value from the
90  *        above mapping table
91  *
92  * A positive value will be returned on success, a negative errno will
93  * be returned in error cases.
94  */
95 static int temp_to_raw(struct acpi_lpat *lpat, int count, int temp)
96 {
97 	int i, delta_temp, delta_raw, raw;
98 
99 	for (i = 0; i < count - 1; i++) {
100 		if (temp >= lpat[i].temp && temp <= lpat[i+1].temp)
101 			break;
102 	}
103 
104 	if (i == count - 1)
105 		return -ENOENT;
106 
107 	delta_temp = lpat[i+1].temp - lpat[i].temp;
108 	delta_raw = lpat[i+1].raw - lpat[i].raw;
109 	raw = lpat[i].raw + (temp - lpat[i].temp) * delta_raw / delta_temp;
110 
111 	return raw;
112 }
113 
114 static void pmic_thermal_lpat(struct intel_pmic_opregion *opregion,
115 			      acpi_handle handle, struct device *dev)
116 {
117 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
118 	union acpi_object *obj_p, *obj_e;
119 	int *lpat, i;
120 	acpi_status status;
121 
122 	status = acpi_evaluate_object(handle, "LPAT", NULL, &buffer);
123 	if (ACPI_FAILURE(status))
124 		return;
125 
126 	obj_p = (union acpi_object *)buffer.pointer;
127 	if (!obj_p || (obj_p->type != ACPI_TYPE_PACKAGE) ||
128 	    (obj_p->package.count % 2) || (obj_p->package.count < 4))
129 		goto out;
130 
131 	lpat = devm_kmalloc(dev, sizeof(int) * obj_p->package.count,
132 			    GFP_KERNEL);
133 	if (!lpat)
134 		goto out;
135 
136 	for (i = 0; i < obj_p->package.count; i++) {
137 		obj_e = &obj_p->package.elements[i];
138 		if (obj_e->type != ACPI_TYPE_INTEGER) {
139 			devm_kfree(dev, lpat);
140 			goto out;
141 		}
142 		lpat[i] = (s64)obj_e->integer.value;
143 	}
144 
145 	opregion->lpat = (struct acpi_lpat *)lpat;
146 	opregion->lpat_count = obj_p->package.count / 2;
147 
148 out:
149 	kfree(buffer.pointer);
150 }
151 
152 static acpi_status intel_pmic_power_handler(u32 function,
153 		acpi_physical_address address, u32 bits, u64 *value64,
154 		void *handler_context, void *region_context)
155 {
156 	struct intel_pmic_opregion *opregion = region_context;
157 	struct regmap *regmap = opregion->regmap;
158 	struct intel_pmic_opregion_data *d = opregion->data;
159 	int reg, bit, result;
160 
161 	if (bits != 32 || !value64)
162 		return AE_BAD_PARAMETER;
163 
164 	if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1))
165 		return AE_BAD_PARAMETER;
166 
167 	result = pmic_get_reg_bit(address, d->power_table,
168 				  d->power_table_count, &reg, &bit);
169 	if (result == -ENOENT)
170 		return AE_BAD_PARAMETER;
171 
172 	mutex_lock(&opregion->lock);
173 
174 	result = function == ACPI_READ ?
175 		d->get_power(regmap, reg, bit, value64) :
176 		d->update_power(regmap, reg, bit, *value64 == 1);
177 
178 	mutex_unlock(&opregion->lock);
179 
180 	return result ? AE_ERROR : AE_OK;
181 }
182 
183 static int pmic_read_temp(struct intel_pmic_opregion *opregion,
184 			  int reg, u64 *value)
185 {
186 	int raw_temp, temp;
187 
188 	if (!opregion->data->get_raw_temp)
189 		return -ENXIO;
190 
191 	raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg);
192 	if (raw_temp < 0)
193 		return raw_temp;
194 
195 	if (!opregion->lpat) {
196 		*value = raw_temp;
197 		return 0;
198 	}
199 
200 	temp = raw_to_temp(opregion->lpat, opregion->lpat_count, raw_temp);
201 	if (temp < 0)
202 		return temp;
203 
204 	*value = temp;
205 	return 0;
206 }
207 
208 static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg,
209 			     u32 function, u64 *value)
210 {
211 	return function == ACPI_READ ?
212 		pmic_read_temp(opregion, reg, value) : -EINVAL;
213 }
214 
215 static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg,
216 			    u32 function, u64 *value)
217 {
218 	int raw_temp;
219 
220 	if (function == ACPI_READ)
221 		return pmic_read_temp(opregion, reg, value);
222 
223 	if (!opregion->data->update_aux)
224 		return -ENXIO;
225 
226 	if (opregion->lpat) {
227 		raw_temp = temp_to_raw(opregion->lpat, opregion->lpat_count,
228 				       *value);
229 		if (raw_temp < 0)
230 			return raw_temp;
231 	} else {
232 		raw_temp = *value;
233 	}
234 
235 	return opregion->data->update_aux(opregion->regmap, reg, raw_temp);
236 }
237 
238 static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg,
239 			    u32 function, u64 *value)
240 {
241 	struct intel_pmic_opregion_data *d = opregion->data;
242 	struct regmap *regmap = opregion->regmap;
243 
244 	if (!d->get_policy || !d->update_policy)
245 		return -ENXIO;
246 
247 	if (function == ACPI_READ)
248 		return d->get_policy(regmap, reg, value);
249 
250 	if (*value != 0 && *value != 1)
251 		return -EINVAL;
252 
253 	return d->update_policy(regmap, reg, *value);
254 }
255 
256 static bool pmic_thermal_is_temp(int address)
257 {
258 	return (address <= 0x3c) && !(address % 12);
259 }
260 
261 static bool pmic_thermal_is_aux(int address)
262 {
263 	return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) ||
264 	       (address >= 8 && address <= 0x44 && !((address - 8) % 12));
265 }
266 
267 static bool pmic_thermal_is_pen(int address)
268 {
269 	return address >= 0x48 && address <= 0x5c;
270 }
271 
272 static acpi_status intel_pmic_thermal_handler(u32 function,
273 		acpi_physical_address address, u32 bits, u64 *value64,
274 		void *handler_context, void *region_context)
275 {
276 	struct intel_pmic_opregion *opregion = region_context;
277 	struct intel_pmic_opregion_data *d = opregion->data;
278 	int reg, result;
279 
280 	if (bits != 32 || !value64)
281 		return AE_BAD_PARAMETER;
282 
283 	result = pmic_get_reg_bit(address, d->thermal_table,
284 				  d->thermal_table_count, &reg, NULL);
285 	if (result == -ENOENT)
286 		return AE_BAD_PARAMETER;
287 
288 	mutex_lock(&opregion->lock);
289 
290 	if (pmic_thermal_is_temp(address))
291 		result = pmic_thermal_temp(opregion, reg, function, value64);
292 	else if (pmic_thermal_is_aux(address))
293 		result = pmic_thermal_aux(opregion, reg, function, value64);
294 	else if (pmic_thermal_is_pen(address))
295 		result = pmic_thermal_pen(opregion, reg, function, value64);
296 	else
297 		result = -EINVAL;
298 
299 	mutex_unlock(&opregion->lock);
300 
301 	if (result < 0) {
302 		if (result == -EINVAL)
303 			return AE_BAD_PARAMETER;
304 		else
305 			return AE_ERROR;
306 	}
307 
308 	return AE_OK;
309 }
310 
311 int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle,
312 					struct regmap *regmap,
313 					struct intel_pmic_opregion_data *d)
314 {
315 	acpi_status status;
316 	struct intel_pmic_opregion *opregion;
317 
318 	if (!dev || !regmap || !d)
319 		return -EINVAL;
320 
321 	if (!handle)
322 		return -ENODEV;
323 
324 	opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL);
325 	if (!opregion)
326 		return -ENOMEM;
327 
328 	mutex_init(&opregion->lock);
329 	opregion->regmap = regmap;
330 	pmic_thermal_lpat(opregion, handle, dev);
331 
332 	status = acpi_install_address_space_handler(handle,
333 						    PMIC_POWER_OPREGION_ID,
334 						    intel_pmic_power_handler,
335 						    NULL, opregion);
336 	if (ACPI_FAILURE(status))
337 		return -ENODEV;
338 
339 	status = acpi_install_address_space_handler(handle,
340 						    PMIC_THERMAL_OPREGION_ID,
341 						    intel_pmic_thermal_handler,
342 						    NULL, opregion);
343 	if (ACPI_FAILURE(status)) {
344 		acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID,
345 						  intel_pmic_power_handler);
346 		return -ENODEV;
347 	}
348 
349 	opregion->data = d;
350 	return 0;
351 }
352 EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler);
353 
354 MODULE_LICENSE("GPL");
355