]>
Commit | Line | Data |
---|---|---|
b1eea857 AL |
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 | ||
6d3ef8d8 | 16 | #include <linux/export.h> |
b1eea857 AL |
17 | #include <linux/acpi.h> |
18 | #include <linux/regmap.h> | |
ac586e2d | 19 | #include <acpi/acpi_lpat.h> |
b1eea857 AL |
20 | #include "intel_pmic.h" |
21 | ||
22 | #define PMIC_POWER_OPREGION_ID 0x8d | |
23 | #define PMIC_THERMAL_OPREGION_ID 0x8c | |
0afa877a FB |
24 | #define PMIC_REGS_OPREGION_ID 0x8f |
25 | ||
26 | struct intel_pmic_regs_handler_ctx { | |
27 | unsigned int val; | |
28 | u16 addr; | |
29 | }; | |
b1eea857 | 30 | |
b1eea857 AL |
31 | struct intel_pmic_opregion { |
32 | struct mutex lock; | |
ac586e2d | 33 | struct acpi_lpat_conversion_table *lpat_table; |
b1eea857 AL |
34 | struct regmap *regmap; |
35 | struct intel_pmic_opregion_data *data; | |
0afa877a | 36 | struct intel_pmic_regs_handler_ctx ctx; |
b1eea857 AL |
37 | }; |
38 | ||
39 | static int pmic_get_reg_bit(int address, struct pmic_table *table, | |
40 | int count, int *reg, int *bit) | |
41 | { | |
42 | int i; | |
43 | ||
44 | for (i = 0; i < count; i++) { | |
45 | if (table[i].address == address) { | |
46 | *reg = table[i].reg; | |
47 | if (bit) | |
48 | *bit = table[i].bit; | |
49 | return 0; | |
50 | } | |
51 | } | |
52 | return -ENOENT; | |
53 | } | |
54 | ||
b1eea857 AL |
55 | static acpi_status intel_pmic_power_handler(u32 function, |
56 | acpi_physical_address address, u32 bits, u64 *value64, | |
57 | void *handler_context, void *region_context) | |
58 | { | |
59 | struct intel_pmic_opregion *opregion = region_context; | |
60 | struct regmap *regmap = opregion->regmap; | |
61 | struct intel_pmic_opregion_data *d = opregion->data; | |
62 | int reg, bit, result; | |
63 | ||
64 | if (bits != 32 || !value64) | |
65 | return AE_BAD_PARAMETER; | |
66 | ||
67 | if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1)) | |
68 | return AE_BAD_PARAMETER; | |
69 | ||
70 | result = pmic_get_reg_bit(address, d->power_table, | |
71 | d->power_table_count, ®, &bit); | |
72 | if (result == -ENOENT) | |
73 | return AE_BAD_PARAMETER; | |
74 | ||
75 | mutex_lock(&opregion->lock); | |
76 | ||
77 | result = function == ACPI_READ ? | |
78 | d->get_power(regmap, reg, bit, value64) : | |
79 | d->update_power(regmap, reg, bit, *value64 == 1); | |
80 | ||
81 | mutex_unlock(&opregion->lock); | |
82 | ||
83 | return result ? AE_ERROR : AE_OK; | |
84 | } | |
85 | ||
86 | static int pmic_read_temp(struct intel_pmic_opregion *opregion, | |
87 | int reg, u64 *value) | |
88 | { | |
89 | int raw_temp, temp; | |
90 | ||
91 | if (!opregion->data->get_raw_temp) | |
92 | return -ENXIO; | |
93 | ||
94 | raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg); | |
95 | if (raw_temp < 0) | |
96 | return raw_temp; | |
97 | ||
ac586e2d | 98 | if (!opregion->lpat_table) { |
b1eea857 AL |
99 | *value = raw_temp; |
100 | return 0; | |
101 | } | |
102 | ||
ac586e2d | 103 | temp = acpi_lpat_raw_to_temp(opregion->lpat_table, raw_temp); |
b1eea857 AL |
104 | if (temp < 0) |
105 | return temp; | |
106 | ||
107 | *value = temp; | |
108 | return 0; | |
109 | } | |
110 | ||
111 | static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg, | |
112 | u32 function, u64 *value) | |
113 | { | |
114 | return function == ACPI_READ ? | |
115 | pmic_read_temp(opregion, reg, value) : -EINVAL; | |
116 | } | |
117 | ||
118 | static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg, | |
119 | u32 function, u64 *value) | |
120 | { | |
121 | int raw_temp; | |
122 | ||
123 | if (function == ACPI_READ) | |
124 | return pmic_read_temp(opregion, reg, value); | |
125 | ||
126 | if (!opregion->data->update_aux) | |
127 | return -ENXIO; | |
128 | ||
ac586e2d SP |
129 | if (opregion->lpat_table) { |
130 | raw_temp = acpi_lpat_temp_to_raw(opregion->lpat_table, *value); | |
b1eea857 AL |
131 | if (raw_temp < 0) |
132 | return raw_temp; | |
133 | } else { | |
134 | raw_temp = *value; | |
135 | } | |
136 | ||
137 | return opregion->data->update_aux(opregion->regmap, reg, raw_temp); | |
138 | } | |
139 | ||
140 | static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg, | |
d8ba8191 | 141 | int bit, u32 function, u64 *value) |
b1eea857 AL |
142 | { |
143 | struct intel_pmic_opregion_data *d = opregion->data; | |
144 | struct regmap *regmap = opregion->regmap; | |
145 | ||
146 | if (!d->get_policy || !d->update_policy) | |
147 | return -ENXIO; | |
148 | ||
149 | if (function == ACPI_READ) | |
d8ba8191 | 150 | return d->get_policy(regmap, reg, bit, value); |
b1eea857 AL |
151 | |
152 | if (*value != 0 && *value != 1) | |
153 | return -EINVAL; | |
154 | ||
d8ba8191 | 155 | return d->update_policy(regmap, reg, bit, *value); |
b1eea857 AL |
156 | } |
157 | ||
158 | static bool pmic_thermal_is_temp(int address) | |
159 | { | |
160 | return (address <= 0x3c) && !(address % 12); | |
161 | } | |
162 | ||
163 | static bool pmic_thermal_is_aux(int address) | |
164 | { | |
165 | return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) || | |
166 | (address >= 8 && address <= 0x44 && !((address - 8) % 12)); | |
167 | } | |
168 | ||
169 | static bool pmic_thermal_is_pen(int address) | |
170 | { | |
171 | return address >= 0x48 && address <= 0x5c; | |
172 | } | |
173 | ||
174 | static acpi_status intel_pmic_thermal_handler(u32 function, | |
175 | acpi_physical_address address, u32 bits, u64 *value64, | |
176 | void *handler_context, void *region_context) | |
177 | { | |
178 | struct intel_pmic_opregion *opregion = region_context; | |
179 | struct intel_pmic_opregion_data *d = opregion->data; | |
d8ba8191 | 180 | int reg, bit, result; |
b1eea857 AL |
181 | |
182 | if (bits != 32 || !value64) | |
183 | return AE_BAD_PARAMETER; | |
184 | ||
185 | result = pmic_get_reg_bit(address, d->thermal_table, | |
d8ba8191 | 186 | d->thermal_table_count, ®, &bit); |
b1eea857 AL |
187 | if (result == -ENOENT) |
188 | return AE_BAD_PARAMETER; | |
189 | ||
190 | mutex_lock(&opregion->lock); | |
191 | ||
192 | if (pmic_thermal_is_temp(address)) | |
193 | result = pmic_thermal_temp(opregion, reg, function, value64); | |
194 | else if (pmic_thermal_is_aux(address)) | |
195 | result = pmic_thermal_aux(opregion, reg, function, value64); | |
196 | else if (pmic_thermal_is_pen(address)) | |
d8ba8191 BG |
197 | result = pmic_thermal_pen(opregion, reg, bit, |
198 | function, value64); | |
b1eea857 AL |
199 | else |
200 | result = -EINVAL; | |
201 | ||
202 | mutex_unlock(&opregion->lock); | |
203 | ||
204 | if (result < 0) { | |
205 | if (result == -EINVAL) | |
206 | return AE_BAD_PARAMETER; | |
207 | else | |
208 | return AE_ERROR; | |
209 | } | |
210 | ||
211 | return AE_OK; | |
212 | } | |
213 | ||
0afa877a FB |
214 | static acpi_status intel_pmic_regs_handler(u32 function, |
215 | acpi_physical_address address, u32 bits, u64 *value64, | |
216 | void *handler_context, void *region_context) | |
217 | { | |
218 | struct intel_pmic_opregion *opregion = region_context; | |
730de199 | 219 | int result = 0; |
0afa877a FB |
220 | |
221 | switch (address) { | |
222 | case 0: | |
223 | return AE_OK; | |
224 | case 1: | |
225 | opregion->ctx.addr |= (*value64 & 0xff) << 8; | |
226 | return AE_OK; | |
227 | case 2: | |
228 | opregion->ctx.addr |= *value64 & 0xff; | |
229 | return AE_OK; | |
230 | case 3: | |
231 | opregion->ctx.val = *value64 & 0xff; | |
232 | return AE_OK; | |
233 | case 4: | |
234 | if (*value64) { | |
235 | result = regmap_write(opregion->regmap, opregion->ctx.addr, | |
236 | opregion->ctx.val); | |
237 | } else { | |
238 | result = regmap_read(opregion->regmap, opregion->ctx.addr, | |
239 | &opregion->ctx.val); | |
240 | if (result == 0) | |
241 | *value64 = opregion->ctx.val; | |
242 | } | |
243 | memset(&opregion->ctx, 0x00, sizeof(opregion->ctx)); | |
244 | } | |
245 | ||
246 | if (result < 0) { | |
247 | if (result == -EINVAL) | |
248 | return AE_BAD_PARAMETER; | |
249 | else | |
250 | return AE_ERROR; | |
251 | } | |
252 | ||
253 | return AE_OK; | |
254 | } | |
255 | ||
b1eea857 AL |
256 | int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle, |
257 | struct regmap *regmap, | |
258 | struct intel_pmic_opregion_data *d) | |
259 | { | |
260 | acpi_status status; | |
261 | struct intel_pmic_opregion *opregion; | |
ac586e2d | 262 | int ret; |
b1eea857 AL |
263 | |
264 | if (!dev || !regmap || !d) | |
265 | return -EINVAL; | |
266 | ||
267 | if (!handle) | |
268 | return -ENODEV; | |
269 | ||
270 | opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL); | |
271 | if (!opregion) | |
272 | return -ENOMEM; | |
273 | ||
274 | mutex_init(&opregion->lock); | |
275 | opregion->regmap = regmap; | |
ac586e2d | 276 | opregion->lpat_table = acpi_lpat_get_conversion_table(handle); |
b1eea857 AL |
277 | |
278 | status = acpi_install_address_space_handler(handle, | |
279 | PMIC_POWER_OPREGION_ID, | |
280 | intel_pmic_power_handler, | |
281 | NULL, opregion); | |
ac586e2d SP |
282 | if (ACPI_FAILURE(status)) { |
283 | ret = -ENODEV; | |
284 | goto out_error; | |
285 | } | |
b1eea857 AL |
286 | |
287 | status = acpi_install_address_space_handler(handle, | |
288 | PMIC_THERMAL_OPREGION_ID, | |
289 | intel_pmic_thermal_handler, | |
290 | NULL, opregion); | |
291 | if (ACPI_FAILURE(status)) { | |
292 | acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID, | |
293 | intel_pmic_power_handler); | |
ac586e2d | 294 | ret = -ENODEV; |
0afa877a FB |
295 | goto out_remove_power_handler; |
296 | } | |
297 | ||
298 | status = acpi_install_address_space_handler(handle, | |
299 | PMIC_REGS_OPREGION_ID, intel_pmic_regs_handler, NULL, | |
300 | opregion); | |
301 | if (ACPI_FAILURE(status)) { | |
302 | ret = -ENODEV; | |
303 | goto out_remove_thermal_handler; | |
b1eea857 AL |
304 | } |
305 | ||
306 | opregion->data = d; | |
307 | return 0; | |
ac586e2d | 308 | |
0afa877a FB |
309 | out_remove_thermal_handler: |
310 | acpi_remove_address_space_handler(handle, PMIC_THERMAL_OPREGION_ID, | |
311 | intel_pmic_thermal_handler); | |
312 | ||
313 | out_remove_power_handler: | |
314 | acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID, | |
315 | intel_pmic_power_handler); | |
316 | ||
ac586e2d SP |
317 | out_error: |
318 | acpi_lpat_free_conversion_table(opregion->lpat_table); | |
319 | return ret; | |
b1eea857 AL |
320 | } |
321 | EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler); |