• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
int3406_thermal_to_raw(int level,struct int3406_thermal_data * d)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 
int3406_thermal_to_acpi(int level,struct int3406_thermal_data * d)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
int3406_thermal_get_max_state(struct thermal_cooling_device * cooling_dev,unsigned long * state)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
int3406_thermal_set_cur_state(struct thermal_cooling_device * cooling_dev,unsigned long state)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
int3406_thermal_get_cur_state(struct thermal_cooling_device * cooling_dev,unsigned long * state)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 
int3406_thermal_get_index(int * array,int nr,int value)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 
int3406_thermal_get_limit(struct int3406_thermal_data * d)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 
int3406_notify(acpi_handle handle,u32 event,void * data)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 
int3406_thermal_probe(struct platform_device * pdev)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, NULL);
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 
int3406_thermal_remove(struct platform_device * pdev)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 		   .acpi_match_table = int3406_thermal_match,
229 		   },
230 };
231 
232 module_platform_driver(int3406_thermal_driver);
233 
234 MODULE_DESCRIPTION("INT3406 Thermal driver");
235 MODULE_LICENSE("GPL v2");
236