diff options
author | Aaron Lu <aaron.lu@intel.com> | 2016-05-09 03:54:58 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2016-05-10 20:15:31 -0400 |
commit | a3c89334f06bacf7e2f23203ad64cfd6e78dbbf4 (patch) | |
tree | 58f16efc32d0e20fc883946bce2cfb8fd95de1bf | |
parent | 059500940defe285222d3b189b366dfe7f299cae (diff) |
Thermal / ACPI / video: add INT3406 thermal driver
INT3406 ACPI device object resembles an ACPI video output device, but its
_BCM is said to be deprecated and should not be used. So we will make
use of the raw interface to do the actual cooling.
Signed-off-by: Aaron Lu <aaron.lu@intel.com>
Acked-by: Zhang Rui <rui.zhang@intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r-- | drivers/thermal/Kconfig | 28 | ||||
-rw-r--r-- | drivers/thermal/int340x_thermal/Kconfig | 42 | ||||
-rw-r--r-- | drivers/thermal/int340x_thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/int340x_thermal/int3406_thermal.c | 236 |
4 files changed, 282 insertions, 25 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3c3dc4a3d52c..d89d60c8b6cf 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
@@ -338,31 +338,9 @@ config INTEL_QUARK_DTS_THERMAL | |||
338 | hot & critical. The critical trip point default value is set by | 338 | hot & critical. The critical trip point default value is set by |
339 | underlying BIOS/Firmware. | 339 | underlying BIOS/Firmware. |
340 | 340 | ||
341 | config INT340X_THERMAL | 341 | menu "ACPI INT340X thermal drivers" |
342 | tristate "ACPI INT340X thermal drivers" | 342 | source drivers/thermal/int340x_thermal/Kconfig |
343 | depends on X86 && ACPI | 343 | endmenu |
344 | select THERMAL_GOV_USER_SPACE | ||
345 | select ACPI_THERMAL_REL | ||
346 | select ACPI_FAN | ||
347 | select INTEL_SOC_DTS_IOSF_CORE | ||
348 | select THERMAL_WRITABLE_TRIPS | ||
349 | help | ||
350 | Newer laptops and tablets that use ACPI may have thermal sensors and | ||
351 | other devices with thermal control capabilities outside the core | ||
352 | CPU/SOC, for thermal safety reasons. | ||
353 | They are exposed for the OS to use via the INT3400 ACPI device object | ||
354 | as the master, and INT3401~INT340B ACPI device objects as the slaves. | ||
355 | Enable this to expose the temperature information and cooling ability | ||
356 | from these objects to userspace via the normal thermal framework. | ||
357 | This means that a wide range of applications and GUI widgets can show | ||
358 | the information to the user or use this information for making | ||
359 | decisions. For example, the Intel Thermal Daemon can use this | ||
360 | information to allow the user to select his laptop to run without | ||
361 | turning on the fans. | ||
362 | |||
363 | config ACPI_THERMAL_REL | ||
364 | tristate | ||
365 | depends on ACPI | ||
366 | 344 | ||
367 | config INTEL_PCH_THERMAL | 345 | config INTEL_PCH_THERMAL |
368 | tristate "Intel PCH Thermal Reporting Driver" | 346 | tristate "Intel PCH Thermal Reporting Driver" |
diff --git a/drivers/thermal/int340x_thermal/Kconfig b/drivers/thermal/int340x_thermal/Kconfig new file mode 100644 index 000000000000..0582bd12a239 --- /dev/null +++ b/drivers/thermal/int340x_thermal/Kconfig | |||
@@ -0,0 +1,42 @@ | |||
1 | # | ||
2 | # ACPI INT340x thermal drivers configuration | ||
3 | # | ||
4 | |||
5 | config INT340X_THERMAL | ||
6 | tristate "ACPI INT340X thermal drivers" | ||
7 | depends on X86 && ACPI | ||
8 | select THERMAL_GOV_USER_SPACE | ||
9 | select ACPI_THERMAL_REL | ||
10 | select ACPI_FAN | ||
11 | select INTEL_SOC_DTS_IOSF_CORE | ||
12 | help | ||
13 | Newer laptops and tablets that use ACPI may have thermal sensors and | ||
14 | other devices with thermal control capabilities outside the core | ||
15 | CPU/SOC, for thermal safety reasons. | ||
16 | They are exposed for the OS to use via the INT3400 ACPI device object | ||
17 | as the master, and INT3401~INT340B ACPI device objects as the slaves. | ||
18 | Enable this to expose the temperature information and cooling ability | ||
19 | from these objects to userspace via the normal thermal framework. | ||
20 | This means that a wide range of applications and GUI widgets can show | ||
21 | the information to the user or use this information for making | ||
22 | decisions. For example, the Intel Thermal Daemon can use this | ||
23 | information to allow the user to select his laptop to run without | ||
24 | turning on the fans. | ||
25 | |||
26 | config ACPI_THERMAL_REL | ||
27 | tristate | ||
28 | depends on ACPI | ||
29 | |||
30 | if INT340X_THERMAL | ||
31 | |||
32 | config INT3406_THERMAL | ||
33 | tristate "ACPI INT3406 display thermal driver" | ||
34 | depends on ACPI_VIDEO | ||
35 | help | ||
36 | The display thermal device represents the LED/LCD display panel | ||
37 | that may or may not include touch support. The main function of | ||
38 | the display thermal device is to allow control of the display | ||
39 | brightness in order to address a thermal condition or to reduce | ||
40 | power consumed by display device. | ||
41 | |||
42 | endif | ||
diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile index ba77a34f659f..df0df055e7ff 100644 --- a/drivers/thermal/int340x_thermal/Makefile +++ b/drivers/thermal/int340x_thermal/Makefile | |||
@@ -3,4 +3,5 @@ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o | |||
3 | obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o | 3 | obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o |
4 | obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o | 4 | obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o |
5 | obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o | 5 | obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o |
6 | obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o | ||
6 | obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o | 7 | obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o |
diff --git a/drivers/thermal/int340x_thermal/int3406_thermal.c b/drivers/thermal/int340x_thermal/int3406_thermal.c new file mode 100644 index 000000000000..13d431cbd29e --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3406_thermal.c | |||
@@ -0,0 +1,236 @@ | |||
1 | /* | ||
2 | * INT3406 thermal driver for display participant device | ||
3 | * | ||
4 | * Copyright (C) 2016, Intel Corporation | ||
5 | * Authors: Aaron Lu <aaron.lu@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/backlight.h> | ||
17 | #include <linux/thermal.h> | ||
18 | #include <acpi/video.h> | ||
19 | |||
20 | #define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 | ||
21 | |||
22 | struct int3406_thermal_data { | ||
23 | int upper_limit; | ||
24 | int upper_limit_index; | ||
25 | int lower_limit; | ||
26 | int lower_limit_index; | ||
27 | acpi_handle handle; | ||
28 | struct acpi_video_device_brightness *br; | ||
29 | struct backlight_device *raw_bd; | ||
30 | struct thermal_cooling_device *cooling_dev; | ||
31 | }; | ||
32 | |||
33 | static int int3406_thermal_to_raw(int level, struct int3406_thermal_data *d) | ||
34 | { | ||
35 | int max_level = d->br->levels[d->br->count - 1]; | ||
36 | int raw_max = d->raw_bd->props.max_brightness; | ||
37 | |||
38 | return level * raw_max / max_level; | ||
39 | } | ||
40 | |||
41 | static int int3406_thermal_to_acpi(int level, struct int3406_thermal_data *d) | ||
42 | { | ||
43 | int raw_max = d->raw_bd->props.max_brightness; | ||
44 | int max_level = d->br->levels[d->br->count - 1]; | ||
45 | |||
46 | return level * max_level / raw_max; | ||
47 | } | ||
48 | |||
49 | static int | ||
50 | int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, | ||
51 | unsigned long *state) | ||
52 | { | ||
53 | struct int3406_thermal_data *d = cooling_dev->devdata; | ||
54 | int index = d->lower_limit_index ? d->lower_limit_index : 2; | ||
55 | |||
56 | *state = d->br->count - 1 - index; | ||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | static int | ||
61 | int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, | ||
62 | unsigned long state) | ||
63 | { | ||
64 | struct int3406_thermal_data *d = cooling_dev->devdata; | ||
65 | int level, raw_level; | ||
66 | |||
67 | if (state > d->br->count - 3) | ||
68 | return -EINVAL; | ||
69 | |||
70 | state = d->br->count - 1 - state; | ||
71 | level = d->br->levels[state]; | ||
72 | |||
73 | if ((d->upper_limit && level > d->upper_limit) || | ||
74 | (d->lower_limit && level < d->lower_limit)) | ||
75 | return -EINVAL; | ||
76 | |||
77 | raw_level = int3406_thermal_to_raw(level, d); | ||
78 | return backlight_device_set_brightness(d->raw_bd, raw_level); | ||
79 | } | ||
80 | |||
81 | static int | ||
82 | int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, | ||
83 | unsigned long *state) | ||
84 | { | ||
85 | struct int3406_thermal_data *d = cooling_dev->devdata; | ||
86 | int raw_level, level, i; | ||
87 | int *levels = d->br->levels; | ||
88 | |||
89 | raw_level = d->raw_bd->props.brightness; | ||
90 | level = int3406_thermal_to_acpi(raw_level, d); | ||
91 | |||
92 | /* | ||
93 | * There is no 1:1 mapping between the firmware interface level with the | ||
94 | * raw interface level, we will have to find one that is close enough. | ||
95 | */ | ||
96 | for (i = 2; i < d->br->count; i++) { | ||
97 | if (level < levels[i]) { | ||
98 | if (i == 2) | ||
99 | break; | ||
100 | if ((level - levels[i - 1]) < (levels[i] - level)) | ||
101 | i--; | ||
102 | break; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | *state = d->br->count - 1 - i; | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static const struct thermal_cooling_device_ops video_cooling_ops = { | ||
111 | .get_max_state = int3406_thermal_get_max_state, | ||
112 | .get_cur_state = int3406_thermal_get_cur_state, | ||
113 | .set_cur_state = int3406_thermal_set_cur_state, | ||
114 | }; | ||
115 | |||
116 | static int int3406_thermal_get_index(int *array, int nr, int value) | ||
117 | { | ||
118 | int i; | ||
119 | |||
120 | for (i = 0; i < nr; i++) { | ||
121 | if (array[i] == value) | ||
122 | break; | ||
123 | } | ||
124 | return i == nr ? -ENOENT : i; | ||
125 | } | ||
126 | |||
127 | static void int3406_thermal_get_limit(struct int3406_thermal_data *d) | ||
128 | { | ||
129 | acpi_status status; | ||
130 | unsigned long long lower_limit, upper_limit; | ||
131 | int index; | ||
132 | |||
133 | status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); | ||
134 | if (ACPI_SUCCESS(status)) { | ||
135 | index = int3406_thermal_get_index(d->br->levels, d->br->count, | ||
136 | lower_limit); | ||
137 | if (index > 0) { | ||
138 | d->lower_limit = (int)lower_limit; | ||
139 | d->lower_limit_index = index; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); | ||
144 | if (ACPI_SUCCESS(status)) { | ||
145 | index = int3406_thermal_get_index(d->br->levels, d->br->count, | ||
146 | upper_limit); | ||
147 | if (index > 0) { | ||
148 | d->upper_limit = (int)upper_limit; | ||
149 | d->upper_limit_index = index; | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
154 | static void int3406_notify(acpi_handle handle, u32 event, void *data) | ||
155 | { | ||
156 | if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) | ||
157 | int3406_thermal_get_limit(data); | ||
158 | } | ||
159 | |||
160 | static int int3406_thermal_probe(struct platform_device *pdev) | ||
161 | { | ||
162 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); | ||
163 | struct int3406_thermal_data *d; | ||
164 | struct backlight_device *bd; | ||
165 | int ret; | ||
166 | |||
167 | if (!ACPI_HANDLE(&pdev->dev)) | ||
168 | return -ENODEV; | ||
169 | |||
170 | d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); | ||
171 | if (!d) | ||
172 | return -ENOMEM; | ||
173 | d->handle = ACPI_HANDLE(&pdev->dev); | ||
174 | |||
175 | bd = backlight_device_get_by_type(BACKLIGHT_RAW); | ||
176 | if (!bd) | ||
177 | return -ENODEV; | ||
178 | d->raw_bd = bd; | ||
179 | |||
180 | ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br); | ||
181 | if (ret) | ||
182 | return ret; | ||
183 | |||
184 | int3406_thermal_get_limit(d); | ||
185 | |||
186 | d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), | ||
187 | d, &video_cooling_ops); | ||
188 | if (IS_ERR(d->cooling_dev)) | ||
189 | goto err; | ||
190 | |||
191 | ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, | ||
192 | int3406_notify, d); | ||
193 | if (ret) | ||
194 | goto err_cdev; | ||
195 | |||
196 | platform_set_drvdata(pdev, d); | ||
197 | |||
198 | return 0; | ||
199 | |||
200 | err_cdev: | ||
201 | thermal_cooling_device_unregister(d->cooling_dev); | ||
202 | err: | ||
203 | kfree(d->br); | ||
204 | return -ENODEV; | ||
205 | } | ||
206 | |||
207 | static int int3406_thermal_remove(struct platform_device *pdev) | ||
208 | { | ||
209 | struct int3406_thermal_data *d = platform_get_drvdata(pdev); | ||
210 | |||
211 | thermal_cooling_device_unregister(d->cooling_dev); | ||
212 | kfree(d->br); | ||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | static const struct acpi_device_id int3406_thermal_match[] = { | ||
217 | {"INT3406", 0}, | ||
218 | {} | ||
219 | }; | ||
220 | |||
221 | MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); | ||
222 | |||
223 | static struct platform_driver int3406_thermal_driver = { | ||
224 | .probe = int3406_thermal_probe, | ||
225 | .remove = int3406_thermal_remove, | ||
226 | .driver = { | ||
227 | .name = "int3406 thermal", | ||
228 | .owner = THIS_MODULE, | ||
229 | .acpi_match_table = int3406_thermal_match, | ||
230 | }, | ||
231 | }; | ||
232 | |||
233 | module_platform_driver(int3406_thermal_driver); | ||
234 | |||
235 | MODULE_DESCRIPTION("INT3406 Thermal driver"); | ||
236 | MODULE_LICENSE("GPL v2"); | ||