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