1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * INT3406 thermal driver for display participant device |
4 | * |
5 | * Copyright (C) 2016, Intel Corporation |
6 | * Authors: Aaron Lu <aaron.lu@intel.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/acpi.h> |
12 | #include <linux/backlight.h> |
13 | #include <linux/thermal.h> |
14 | #include <acpi/video.h> |
15 | |
16 | #define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 |
17 | |
18 | struct int3406_thermal_data { |
19 | int upper_limit; |
20 | int lower_limit; |
21 | acpi_handle handle; |
22 | struct acpi_video_device_brightness *br; |
23 | struct backlight_device *raw_bd; |
24 | struct thermal_cooling_device *cooling_dev; |
25 | }; |
26 | |
27 | /* |
28 | * According to the ACPI spec, |
29 | * "Each brightness level is represented by a number between 0 and 100, |
30 | * and can be thought of as a percentage. For example, 50 can be 50% |
31 | * power consumption or 50% brightness, as defined by the OEM." |
32 | * |
33 | * As int3406 device uses this value to communicate with the native |
34 | * graphics driver, we make the assumption that it represents |
35 | * the percentage of brightness only |
36 | */ |
37 | #define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) |
38 | #define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) |
39 | |
40 | static int |
41 | int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, |
42 | unsigned long *state) |
43 | { |
44 | struct int3406_thermal_data *d = cooling_dev->devdata; |
45 | |
46 | *state = d->upper_limit - d->lower_limit; |
47 | return 0; |
48 | } |
49 | |
50 | static int |
51 | int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, |
52 | unsigned long state) |
53 | { |
54 | struct int3406_thermal_data *d = cooling_dev->devdata; |
55 | int acpi_level, raw_level; |
56 | |
57 | if (state > d->upper_limit - d->lower_limit) |
58 | return -EINVAL; |
59 | |
60 | acpi_level = d->br->levels[d->upper_limit - state]; |
61 | |
62 | raw_level = ACPI_TO_RAW(acpi_level, d); |
63 | |
64 | return backlight_device_set_brightness(bd: d->raw_bd, brightness: raw_level); |
65 | } |
66 | |
67 | static int |
68 | int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, |
69 | unsigned long *state) |
70 | { |
71 | struct int3406_thermal_data *d = cooling_dev->devdata; |
72 | int acpi_level; |
73 | int index; |
74 | |
75 | acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); |
76 | |
77 | /* |
78 | * There is no 1:1 mapping between the firmware interface level |
79 | * with the raw interface level, we will have to find one that is |
80 | * right above it. |
81 | */ |
82 | for (index = d->lower_limit; index < d->upper_limit; index++) { |
83 | if (acpi_level <= d->br->levels[index]) |
84 | break; |
85 | } |
86 | |
87 | *state = d->upper_limit - index; |
88 | return 0; |
89 | } |
90 | |
91 | static const struct thermal_cooling_device_ops video_cooling_ops = { |
92 | .get_max_state = int3406_thermal_get_max_state, |
93 | .get_cur_state = int3406_thermal_get_cur_state, |
94 | .set_cur_state = int3406_thermal_set_cur_state, |
95 | }; |
96 | |
97 | static int int3406_thermal_get_index(int *array, int nr, int value) |
98 | { |
99 | int i; |
100 | |
101 | for (i = 2; i < nr; i++) { |
102 | if (array[i] == value) |
103 | break; |
104 | } |
105 | return i == nr ? -ENOENT : i; |
106 | } |
107 | |
108 | static void int3406_thermal_get_limit(struct int3406_thermal_data *d) |
109 | { |
110 | acpi_status status; |
111 | unsigned long long lower_limit, upper_limit; |
112 | |
113 | status = acpi_evaluate_integer(handle: d->handle, pathname: "DDDL" , NULL, data: &lower_limit); |
114 | if (ACPI_SUCCESS(status)) |
115 | d->lower_limit = int3406_thermal_get_index(array: d->br->levels, |
116 | nr: d->br->count, value: lower_limit); |
117 | |
118 | status = acpi_evaluate_integer(handle: d->handle, pathname: "DDPC" , NULL, data: &upper_limit); |
119 | if (ACPI_SUCCESS(status)) |
120 | d->upper_limit = int3406_thermal_get_index(array: d->br->levels, |
121 | nr: d->br->count, value: upper_limit); |
122 | |
123 | /* lower_limit and upper_limit should be always set */ |
124 | d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; |
125 | d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; |
126 | } |
127 | |
128 | static void int3406_notify(acpi_handle handle, u32 event, void *data) |
129 | { |
130 | if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) |
131 | int3406_thermal_get_limit(d: data); |
132 | } |
133 | |
134 | static int int3406_thermal_probe(struct platform_device *pdev) |
135 | { |
136 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
137 | struct int3406_thermal_data *d; |
138 | struct backlight_device *bd; |
139 | int ret; |
140 | |
141 | if (!ACPI_HANDLE(&pdev->dev)) |
142 | return -ENODEV; |
143 | |
144 | d = devm_kzalloc(dev: &pdev->dev, size: sizeof(*d), GFP_KERNEL); |
145 | if (!d) |
146 | return -ENOMEM; |
147 | d->handle = ACPI_HANDLE(&pdev->dev); |
148 | |
149 | bd = backlight_device_get_by_type(type: BACKLIGHT_RAW); |
150 | if (!bd) |
151 | return -ENODEV; |
152 | d->raw_bd = bd; |
153 | |
154 | ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), dev_br: &d->br, NULL); |
155 | if (ret) |
156 | return ret; |
157 | |
158 | int3406_thermal_get_limit(d); |
159 | |
160 | d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), |
161 | d, &video_cooling_ops); |
162 | if (IS_ERR(ptr: d->cooling_dev)) |
163 | goto err; |
164 | |
165 | ret = acpi_install_notify_handler(device: adev->handle, ACPI_DEVICE_NOTIFY, |
166 | handler: int3406_notify, context: d); |
167 | if (ret) |
168 | goto err_cdev; |
169 | |
170 | platform_set_drvdata(pdev, data: d); |
171 | |
172 | return 0; |
173 | |
174 | err_cdev: |
175 | thermal_cooling_device_unregister(d->cooling_dev); |
176 | err: |
177 | kfree(objp: d->br); |
178 | return -ENODEV; |
179 | } |
180 | |
181 | static void int3406_thermal_remove(struct platform_device *pdev) |
182 | { |
183 | struct int3406_thermal_data *d = platform_get_drvdata(pdev); |
184 | |
185 | thermal_cooling_device_unregister(d->cooling_dev); |
186 | kfree(objp: d->br); |
187 | } |
188 | |
189 | static const struct acpi_device_id int3406_thermal_match[] = { |
190 | {"INT3406" , 0}, |
191 | {} |
192 | }; |
193 | |
194 | MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); |
195 | |
196 | static struct platform_driver int3406_thermal_driver = { |
197 | .probe = int3406_thermal_probe, |
198 | .remove_new = int3406_thermal_remove, |
199 | .driver = { |
200 | .name = "int3406 thermal" , |
201 | .acpi_match_table = int3406_thermal_match, |
202 | }, |
203 | }; |
204 | |
205 | module_platform_driver(int3406_thermal_driver); |
206 | |
207 | MODULE_DESCRIPTION("INT3406 Thermal driver" ); |
208 | MODULE_LICENSE("GPL v2" ); |
209 | |