diff options
Diffstat (limited to 'drivers/thermal/int340x_thermal/int3400_thermal.c')
-rw-r--r-- | drivers/thermal/int340x_thermal/int3400_thermal.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c new file mode 100644 index 000000000000..edc1cce117ba --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c | |||
@@ -0,0 +1,271 @@ | |||
1 | /* | ||
2 | * INT3400 thermal driver | ||
3 | * | ||
4 | * Copyright (C) 2014, Intel Corporation | ||
5 | * Authors: Zhang Rui <rui.zhang@intel.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | * | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/acpi.h> | ||
16 | #include <linux/thermal.h> | ||
17 | #include "acpi_thermal_rel.h" | ||
18 | |||
19 | enum int3400_thermal_uuid { | ||
20 | INT3400_THERMAL_PASSIVE_1, | ||
21 | INT3400_THERMAL_PASSIVE_2, | ||
22 | INT3400_THERMAL_ACTIVE, | ||
23 | INT3400_THERMAL_CRITICAL, | ||
24 | INT3400_THERMAL_COOLING_MODE, | ||
25 | INT3400_THERMAL_MAXIMUM_UUID, | ||
26 | }; | ||
27 | |||
28 | static u8 *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { | ||
29 | "42A441D6-AE6A-462b-A84B-4A8CE79027D3", | ||
30 | "9E04115A-AE87-4D1C-9500-0F3E340BFE75", | ||
31 | "3A95C389-E4B8-4629-A526-C52C88626BAE", | ||
32 | "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", | ||
33 | "16CAF1B7-DD38-40ed-B1C1-1B8A1913D531", | ||
34 | }; | ||
35 | |||
36 | struct int3400_thermal_priv { | ||
37 | struct acpi_device *adev; | ||
38 | struct thermal_zone_device *thermal; | ||
39 | int mode; | ||
40 | int art_count; | ||
41 | struct art *arts; | ||
42 | int trt_count; | ||
43 | struct trt *trts; | ||
44 | u8 uuid_bitmap; | ||
45 | int rel_misc_dev_res; | ||
46 | }; | ||
47 | |||
48 | static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) | ||
49 | { | ||
50 | struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; | ||
51 | union acpi_object *obja, *objb; | ||
52 | int i, j; | ||
53 | int result = 0; | ||
54 | acpi_status status; | ||
55 | |||
56 | status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); | ||
57 | if (ACPI_FAILURE(status)) | ||
58 | return -ENODEV; | ||
59 | |||
60 | obja = (union acpi_object *)buf.pointer; | ||
61 | if (obja->type != ACPI_TYPE_PACKAGE) { | ||
62 | result = -EINVAL; | ||
63 | goto end; | ||
64 | } | ||
65 | |||
66 | for (i = 0; i < obja->package.count; i++) { | ||
67 | objb = &obja->package.elements[i]; | ||
68 | if (objb->type != ACPI_TYPE_BUFFER) { | ||
69 | result = -EINVAL; | ||
70 | goto end; | ||
71 | } | ||
72 | |||
73 | /* UUID must be 16 bytes */ | ||
74 | if (objb->buffer.length != 16) { | ||
75 | result = -EINVAL; | ||
76 | goto end; | ||
77 | } | ||
78 | |||
79 | for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { | ||
80 | u8 uuid[16]; | ||
81 | |||
82 | acpi_str_to_uuid(int3400_thermal_uuids[j], uuid); | ||
83 | if (!strncmp(uuid, objb->buffer.pointer, 16)) { | ||
84 | priv->uuid_bitmap |= (1 << j); | ||
85 | break; | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | end: | ||
91 | kfree(buf.pointer); | ||
92 | return result; | ||
93 | } | ||
94 | |||
95 | static int int3400_thermal_run_osc(acpi_handle handle, | ||
96 | enum int3400_thermal_uuid uuid, bool enable) | ||
97 | { | ||
98 | u32 ret, buf[2]; | ||
99 | acpi_status status; | ||
100 | int result = 0; | ||
101 | struct acpi_osc_context context = { | ||
102 | .uuid_str = int3400_thermal_uuids[uuid], | ||
103 | .rev = 1, | ||
104 | .cap.length = 8, | ||
105 | }; | ||
106 | |||
107 | buf[OSC_QUERY_DWORD] = 0; | ||
108 | buf[OSC_SUPPORT_DWORD] = enable; | ||
109 | |||
110 | context.cap.pointer = buf; | ||
111 | |||
112 | status = acpi_run_osc(handle, &context); | ||
113 | if (ACPI_SUCCESS(status)) { | ||
114 | ret = *((u32 *)(context.ret.pointer + 4)); | ||
115 | if (ret != enable) | ||
116 | result = -EPERM; | ||
117 | } else | ||
118 | result = -EPERM; | ||
119 | |||
120 | kfree(context.ret.pointer); | ||
121 | return result; | ||
122 | } | ||
123 | |||
124 | static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, | ||
125 | unsigned long *temp) | ||
126 | { | ||
127 | *temp = 20 * 1000; /* faked temp sensor with 20C */ | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int int3400_thermal_get_mode(struct thermal_zone_device *thermal, | ||
132 | enum thermal_device_mode *mode) | ||
133 | { | ||
134 | struct int3400_thermal_priv *priv = thermal->devdata; | ||
135 | |||
136 | if (!priv) | ||
137 | return -EINVAL; | ||
138 | |||
139 | *mode = priv->mode; | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, | ||
145 | enum thermal_device_mode mode) | ||
146 | { | ||
147 | struct int3400_thermal_priv *priv = thermal->devdata; | ||
148 | bool enable; | ||
149 | int result = 0; | ||
150 | |||
151 | if (!priv) | ||
152 | return -EINVAL; | ||
153 | |||
154 | if (mode == THERMAL_DEVICE_ENABLED) | ||
155 | enable = true; | ||
156 | else if (mode == THERMAL_DEVICE_DISABLED) | ||
157 | enable = false; | ||
158 | else | ||
159 | return -EINVAL; | ||
160 | |||
161 | if (enable != priv->mode) { | ||
162 | priv->mode = enable; | ||
163 | /* currently, only PASSIVE COOLING is supported */ | ||
164 | result = int3400_thermal_run_osc(priv->adev->handle, | ||
165 | INT3400_THERMAL_PASSIVE_1, enable); | ||
166 | } | ||
167 | return result; | ||
168 | } | ||
169 | |||
170 | static struct thermal_zone_device_ops int3400_thermal_ops = { | ||
171 | .get_temp = int3400_thermal_get_temp, | ||
172 | }; | ||
173 | |||
174 | static struct thermal_zone_params int3400_thermal_params = { | ||
175 | .governor_name = "user_space", | ||
176 | .no_hwmon = true, | ||
177 | }; | ||
178 | |||
179 | static int int3400_thermal_probe(struct platform_device *pdev) | ||
180 | { | ||
181 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); | ||
182 | struct int3400_thermal_priv *priv; | ||
183 | int result; | ||
184 | |||
185 | if (!adev) | ||
186 | return -ENODEV; | ||
187 | |||
188 | priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); | ||
189 | if (!priv) | ||
190 | return -ENOMEM; | ||
191 | |||
192 | priv->adev = adev; | ||
193 | |||
194 | result = int3400_thermal_get_uuids(priv); | ||
195 | if (result) | ||
196 | goto free_priv; | ||
197 | |||
198 | result = acpi_parse_art(priv->adev->handle, &priv->art_count, | ||
199 | &priv->arts, true); | ||
200 | if (result) | ||
201 | goto free_priv; | ||
202 | |||
203 | |||
204 | result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, | ||
205 | &priv->trts, true); | ||
206 | if (result) | ||
207 | goto free_art; | ||
208 | |||
209 | platform_set_drvdata(pdev, priv); | ||
210 | |||
211 | if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) { | ||
212 | int3400_thermal_ops.get_mode = int3400_thermal_get_mode; | ||
213 | int3400_thermal_ops.set_mode = int3400_thermal_set_mode; | ||
214 | } | ||
215 | priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, | ||
216 | priv, &int3400_thermal_ops, | ||
217 | &int3400_thermal_params, 0, 0); | ||
218 | if (IS_ERR(priv->thermal)) { | ||
219 | result = PTR_ERR(priv->thermal); | ||
220 | goto free_trt; | ||
221 | } | ||
222 | |||
223 | priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( | ||
224 | priv->adev->handle); | ||
225 | |||
226 | return 0; | ||
227 | free_trt: | ||
228 | kfree(priv->trts); | ||
229 | free_art: | ||
230 | kfree(priv->arts); | ||
231 | free_priv: | ||
232 | kfree(priv); | ||
233 | return result; | ||
234 | } | ||
235 | |||
236 | static int int3400_thermal_remove(struct platform_device *pdev) | ||
237 | { | ||
238 | struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); | ||
239 | |||
240 | if (!priv->rel_misc_dev_res) | ||
241 | acpi_thermal_rel_misc_device_remove(priv->adev->handle); | ||
242 | |||
243 | thermal_zone_device_unregister(priv->thermal); | ||
244 | kfree(priv->trts); | ||
245 | kfree(priv->arts); | ||
246 | kfree(priv); | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | static const struct acpi_device_id int3400_thermal_match[] = { | ||
251 | {"INT3400", 0}, | ||
252 | {} | ||
253 | }; | ||
254 | |||
255 | MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); | ||
256 | |||
257 | static struct platform_driver int3400_thermal_driver = { | ||
258 | .probe = int3400_thermal_probe, | ||
259 | .remove = int3400_thermal_remove, | ||
260 | .driver = { | ||
261 | .name = "int3400 thermal", | ||
262 | .owner = THIS_MODULE, | ||
263 | .acpi_match_table = ACPI_PTR(int3400_thermal_match), | ||
264 | }, | ||
265 | }; | ||
266 | |||
267 | module_platform_driver(int3400_thermal_driver); | ||
268 | |||
269 | MODULE_DESCRIPTION("INT3400 Thermal driver"); | ||
270 | MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); | ||
271 | MODULE_LICENSE("GPL"); | ||