1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ChromeOS specific ACPI extensions |
4 | * |
5 | * Copyright 2022 Google LLC |
6 | * |
7 | * This driver attaches to the ChromeOS ACPI device and then exports the |
8 | * values reported by the ACPI in a sysfs directory. All values are |
9 | * presented in the string form (numbers as decimal values) and can be |
10 | * accessed as the contents of the appropriate read only files in the |
11 | * sysfs directory tree. |
12 | */ |
13 | #include <linux/acpi.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/list.h> |
17 | #include <linux/module.h> |
18 | |
19 | #define ACPI_ATTR_NAME_LEN 4 |
20 | |
21 | #define DEV_ATTR(_var, _name) \ |
22 | static struct device_attribute dev_attr_##_var = \ |
23 | __ATTR(_name, 0444, chromeos_first_level_attr_show, NULL); |
24 | |
25 | #define GPIO_ATTR_GROUP(_group, _name, _num) \ |
26 | static umode_t attr_is_visible_gpio_##_num(struct kobject *kobj, \ |
27 | struct attribute *attr, int n) \ |
28 | { \ |
29 | if (_num < chromeos_acpi_gpio_groups) \ |
30 | return attr->mode; \ |
31 | return 0; \ |
32 | } \ |
33 | static ssize_t chromeos_attr_show_gpio_##_num(struct device *dev, \ |
34 | struct device_attribute *attr, \ |
35 | char *buf) \ |
36 | { \ |
37 | char name[ACPI_ATTR_NAME_LEN + 1]; \ |
38 | int ret, num; \ |
39 | \ |
40 | ret = parse_attr_name(attr->attr.name, name, &num); \ |
41 | if (ret) \ |
42 | return ret; \ |
43 | return chromeos_acpi_evaluate_method(dev, _num, num, name, buf); \ |
44 | } \ |
45 | static struct device_attribute dev_attr_0_##_group = \ |
46 | __ATTR(GPIO.0, 0444, chromeos_attr_show_gpio_##_num, NULL); \ |
47 | static struct device_attribute dev_attr_1_##_group = \ |
48 | __ATTR(GPIO.1, 0444, chromeos_attr_show_gpio_##_num, NULL); \ |
49 | static struct device_attribute dev_attr_2_##_group = \ |
50 | __ATTR(GPIO.2, 0444, chromeos_attr_show_gpio_##_num, NULL); \ |
51 | static struct device_attribute dev_attr_3_##_group = \ |
52 | __ATTR(GPIO.3, 0444, chromeos_attr_show_gpio_##_num, NULL); \ |
53 | \ |
54 | static struct attribute *attrs_##_group[] = { \ |
55 | &dev_attr_0_##_group.attr, \ |
56 | &dev_attr_1_##_group.attr, \ |
57 | &dev_attr_2_##_group.attr, \ |
58 | &dev_attr_3_##_group.attr, \ |
59 | NULL \ |
60 | }; \ |
61 | static const struct attribute_group attr_group_##_group = { \ |
62 | .name = _name, \ |
63 | .is_visible = attr_is_visible_gpio_##_num, \ |
64 | .attrs = attrs_##_group, \ |
65 | }; |
66 | |
67 | static unsigned int chromeos_acpi_gpio_groups; |
68 | |
69 | /* Parse the ACPI package and return the data related to that attribute */ |
70 | static int chromeos_acpi_handle_package(struct device *dev, union acpi_object *obj, |
71 | int pkg_num, int sub_pkg_num, char *name, char *buf) |
72 | { |
73 | union acpi_object *element = obj->package.elements; |
74 | |
75 | if (pkg_num >= obj->package.count) |
76 | return -EINVAL; |
77 | element += pkg_num; |
78 | |
79 | if (element->type == ACPI_TYPE_PACKAGE) { |
80 | if (sub_pkg_num >= element->package.count) |
81 | return -EINVAL; |
82 | /* select sub element inside this package */ |
83 | element = element->package.elements; |
84 | element += sub_pkg_num; |
85 | } |
86 | |
87 | switch (element->type) { |
88 | case ACPI_TYPE_INTEGER: |
89 | return sysfs_emit(buf, fmt: "%d\n" , (int)element->integer.value); |
90 | case ACPI_TYPE_STRING: |
91 | return sysfs_emit(buf, fmt: "%s\n" , element->string.pointer); |
92 | case ACPI_TYPE_BUFFER: |
93 | { |
94 | int i, r, at, room_left; |
95 | const int byte_per_line = 16; |
96 | |
97 | at = 0; |
98 | room_left = PAGE_SIZE - 1; |
99 | for (i = 0; i < element->buffer.length && room_left; i += byte_per_line) { |
100 | r = hex_dump_to_buffer(buf: element->buffer.pointer + i, |
101 | len: element->buffer.length - i, |
102 | rowsize: byte_per_line, groupsize: 1, linebuf: buf + at, linebuflen: room_left, |
103 | ascii: false); |
104 | if (r > room_left) |
105 | goto truncating; |
106 | at += r; |
107 | room_left -= r; |
108 | |
109 | r = sysfs_emit_at(buf, at, fmt: "\n" ); |
110 | if (!r) |
111 | goto truncating; |
112 | at += r; |
113 | room_left -= r; |
114 | } |
115 | |
116 | buf[at] = 0; |
117 | return at; |
118 | truncating: |
119 | dev_info_once(dev, "truncating sysfs content for %s\n" , name); |
120 | sysfs_emit_at(buf, PAGE_SIZE - 4, fmt: "..\n" ); |
121 | return PAGE_SIZE - 1; |
122 | } |
123 | default: |
124 | dev_err(dev, "element type %d not supported\n" , element->type); |
125 | return -EINVAL; |
126 | } |
127 | } |
128 | |
129 | static int chromeos_acpi_evaluate_method(struct device *dev, int pkg_num, int sub_pkg_num, |
130 | char *name, char *buf) |
131 | { |
132 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
133 | acpi_status status; |
134 | int ret = -EINVAL; |
135 | |
136 | status = acpi_evaluate_object(ACPI_HANDLE(dev), pathname: name, NULL, return_object_buffer: &output); |
137 | if (ACPI_FAILURE(status)) { |
138 | dev_err(dev, "failed to retrieve %s. %s\n" , name, acpi_format_exception(status)); |
139 | return ret; |
140 | } |
141 | |
142 | if (((union acpi_object *)output.pointer)->type == ACPI_TYPE_PACKAGE) |
143 | ret = chromeos_acpi_handle_package(dev, obj: output.pointer, pkg_num, sub_pkg_num, |
144 | name, buf); |
145 | |
146 | kfree(objp: output.pointer); |
147 | return ret; |
148 | } |
149 | |
150 | static int parse_attr_name(const char *name, char *attr_name, int *attr_num) |
151 | { |
152 | int ret; |
153 | |
154 | ret = strscpy(attr_name, name, ACPI_ATTR_NAME_LEN + 1); |
155 | if (ret == -E2BIG) |
156 | return kstrtoint(s: &name[ACPI_ATTR_NAME_LEN + 1], base: 0, res: attr_num); |
157 | return 0; |
158 | } |
159 | |
160 | static ssize_t chromeos_first_level_attr_show(struct device *dev, struct device_attribute *attr, |
161 | char *buf) |
162 | { |
163 | char attr_name[ACPI_ATTR_NAME_LEN + 1]; |
164 | int ret, attr_num = 0; |
165 | |
166 | ret = parse_attr_name(name: attr->attr.name, attr_name, attr_num: &attr_num); |
167 | if (ret) |
168 | return ret; |
169 | return chromeos_acpi_evaluate_method(dev, pkg_num: attr_num, sub_pkg_num: 0, name: attr_name, buf); |
170 | } |
171 | |
172 | static unsigned int get_gpio_pkg_num(struct device *dev) |
173 | { |
174 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
175 | union acpi_object *obj; |
176 | acpi_status status; |
177 | unsigned int count = 0; |
178 | char *name = "GPIO" ; |
179 | |
180 | status = acpi_evaluate_object(ACPI_HANDLE(dev), pathname: name, NULL, return_object_buffer: &output); |
181 | if (ACPI_FAILURE(status)) { |
182 | dev_err(dev, "failed to retrieve %s. %s\n" , name, acpi_format_exception(status)); |
183 | return count; |
184 | } |
185 | |
186 | obj = output.pointer; |
187 | |
188 | if (obj->type == ACPI_TYPE_PACKAGE) |
189 | count = obj->package.count; |
190 | |
191 | kfree(objp: output.pointer); |
192 | return count; |
193 | } |
194 | |
195 | DEV_ATTR(binf2, BINF.2) |
196 | DEV_ATTR(binf3, BINF.3) |
197 | DEV_ATTR(chsw, CHSW) |
198 | DEV_ATTR(fmap, FMAP) |
199 | DEV_ATTR(frid, FRID) |
200 | DEV_ATTR(fwid, FWID) |
201 | DEV_ATTR(hwid, HWID) |
202 | DEV_ATTR(meck, MECK) |
203 | DEV_ATTR(vbnv0, VBNV.0) |
204 | DEV_ATTR(vbnv1, VBNV.1) |
205 | DEV_ATTR(vdat, VDAT) |
206 | |
207 | static struct attribute *first_level_attrs[] = { |
208 | &dev_attr_binf2.attr, |
209 | &dev_attr_binf3.attr, |
210 | &dev_attr_chsw.attr, |
211 | &dev_attr_fmap.attr, |
212 | &dev_attr_frid.attr, |
213 | &dev_attr_fwid.attr, |
214 | &dev_attr_hwid.attr, |
215 | &dev_attr_meck.attr, |
216 | &dev_attr_vbnv0.attr, |
217 | &dev_attr_vbnv1.attr, |
218 | &dev_attr_vdat.attr, |
219 | NULL |
220 | }; |
221 | |
222 | static const struct attribute_group first_level_attr_group = { |
223 | .attrs = first_level_attrs, |
224 | }; |
225 | |
226 | /* |
227 | * Every platform can have a different number of GPIO attribute groups. |
228 | * Define upper limit groups. At run time, the platform decides to show |
229 | * the present number of groups only, others are hidden. |
230 | */ |
231 | GPIO_ATTR_GROUP(gpio0, "GPIO.0" , 0) |
232 | GPIO_ATTR_GROUP(gpio1, "GPIO.1" , 1) |
233 | GPIO_ATTR_GROUP(gpio2, "GPIO.2" , 2) |
234 | GPIO_ATTR_GROUP(gpio3, "GPIO.3" , 3) |
235 | GPIO_ATTR_GROUP(gpio4, "GPIO.4" , 4) |
236 | GPIO_ATTR_GROUP(gpio5, "GPIO.5" , 5) |
237 | GPIO_ATTR_GROUP(gpio6, "GPIO.6" , 6) |
238 | GPIO_ATTR_GROUP(gpio7, "GPIO.7" , 7) |
239 | |
240 | static const struct attribute_group *chromeos_acpi_all_groups[] = { |
241 | &first_level_attr_group, |
242 | &attr_group_gpio0, |
243 | &attr_group_gpio1, |
244 | &attr_group_gpio2, |
245 | &attr_group_gpio3, |
246 | &attr_group_gpio4, |
247 | &attr_group_gpio5, |
248 | &attr_group_gpio6, |
249 | &attr_group_gpio7, |
250 | NULL |
251 | }; |
252 | |
253 | static int chromeos_acpi_device_probe(struct platform_device *pdev) |
254 | { |
255 | chromeos_acpi_gpio_groups = get_gpio_pkg_num(dev: &pdev->dev); |
256 | |
257 | /* |
258 | * If the platform has more GPIO attribute groups than the number of |
259 | * groups this driver supports, give out a warning message. |
260 | */ |
261 | if (chromeos_acpi_gpio_groups > ARRAY_SIZE(chromeos_acpi_all_groups) - 2) |
262 | dev_warn(&pdev->dev, "Only %zu GPIO attr groups supported by the driver out of total %u.\n" , |
263 | ARRAY_SIZE(chromeos_acpi_all_groups) - 2, chromeos_acpi_gpio_groups); |
264 | return 0; |
265 | } |
266 | |
267 | static const struct acpi_device_id chromeos_device_ids[] = { |
268 | { "GGL0001" , 0 }, |
269 | { "GOOG0016" , 0 }, |
270 | {} |
271 | }; |
272 | MODULE_DEVICE_TABLE(acpi, chromeos_device_ids); |
273 | |
274 | static struct platform_driver chromeos_acpi_device_driver = { |
275 | .probe = chromeos_acpi_device_probe, |
276 | .driver = { |
277 | .name = KBUILD_MODNAME, |
278 | .dev_groups = chromeos_acpi_all_groups, |
279 | .acpi_match_table = chromeos_device_ids, |
280 | } |
281 | }; |
282 | module_platform_driver(chromeos_acpi_device_driver); |
283 | |
284 | MODULE_AUTHOR("Muhammad Usama Anjum <usama.anjum@collabora.com>" ); |
285 | MODULE_LICENSE("GPL" ); |
286 | MODULE_DESCRIPTION("ChromeOS specific ACPI extensions" ); |
287 | |