1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Driver to instantiate Chromebook i2c/smbus devices. |
3 | // |
4 | // Copyright (C) 2012 Google, Inc. |
5 | // Author: Benson Leung <bleung@chromium.org> |
6 | |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
8 | |
9 | #include <linux/acpi.h> |
10 | #include <linux/dmi.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/input.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/ioport.h> |
15 | #include <linux/module.h> |
16 | #include <linux/pci.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/property.h> |
19 | |
20 | #define ATMEL_TP_I2C_ADDR 0x4b |
21 | #define ATMEL_TP_I2C_BL_ADDR 0x25 |
22 | #define ATMEL_TS_I2C_ADDR 0x4a |
23 | #define ATMEL_TS_I2C_BL_ADDR 0x26 |
24 | #define CYAPA_TP_I2C_ADDR 0x67 |
25 | #define ELAN_TP_I2C_ADDR 0x15 |
26 | #define ISL_ALS_I2C_ADDR 0x44 |
27 | #define TAOS_ALS_I2C_ADDR 0x29 |
28 | |
29 | static const char *i2c_adapter_names[] = { |
30 | "SMBus I801 adapter" , |
31 | "i915 gmbus vga" , |
32 | "i915 gmbus panel" , |
33 | "Synopsys DesignWare I2C adapter" , |
34 | }; |
35 | |
36 | /* Keep this enum consistent with i2c_adapter_names */ |
37 | enum i2c_adapter_type { |
38 | I2C_ADAPTER_SMBUS = 0, |
39 | I2C_ADAPTER_VGADDC, |
40 | I2C_ADAPTER_PANEL, |
41 | I2C_ADAPTER_DESIGNWARE, |
42 | }; |
43 | |
44 | struct i2c_peripheral { |
45 | struct i2c_board_info board_info; |
46 | unsigned short alt_addr; |
47 | |
48 | const char *dmi_name; |
49 | unsigned long irqflags; |
50 | struct resource irq_resource; |
51 | |
52 | enum i2c_adapter_type type; |
53 | u32 pci_devid; |
54 | |
55 | const struct property_entry *properties; |
56 | |
57 | struct i2c_client *client; |
58 | }; |
59 | |
60 | struct acpi_peripheral { |
61 | char hid[ACPI_ID_LEN]; |
62 | struct software_node swnode; |
63 | struct i2c_client *client; |
64 | }; |
65 | |
66 | struct chromeos_laptop { |
67 | /* |
68 | * Note that we can't mark this pointer as const because |
69 | * i2c_new_scanned_device() changes passed in I2C board info, so. |
70 | */ |
71 | struct i2c_peripheral *i2c_peripherals; |
72 | unsigned int num_i2c_peripherals; |
73 | |
74 | struct acpi_peripheral *acpi_peripherals; |
75 | unsigned int num_acpi_peripherals; |
76 | }; |
77 | |
78 | static const struct chromeos_laptop *cros_laptop; |
79 | |
80 | static struct i2c_client * |
81 | chromes_laptop_instantiate_i2c_device(struct i2c_adapter *adapter, |
82 | struct i2c_board_info *info, |
83 | unsigned short alt_addr) |
84 | { |
85 | const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; |
86 | struct i2c_client *client; |
87 | |
88 | /* |
89 | * Add the i2c device. If we can't detect it at the primary |
90 | * address we scan secondary addresses. In any case the client |
91 | * structure gets assigned primary address. |
92 | */ |
93 | client = i2c_new_scanned_device(adap: adapter, info, addr_list, NULL); |
94 | if (IS_ERR(ptr: client) && alt_addr) { |
95 | struct i2c_board_info dummy_info = { |
96 | I2C_BOARD_INFO("dummy" , info->addr), |
97 | }; |
98 | const unsigned short alt_addr_list[] = { |
99 | alt_addr, I2C_CLIENT_END |
100 | }; |
101 | struct i2c_client *dummy; |
102 | |
103 | dummy = i2c_new_scanned_device(adap: adapter, info: &dummy_info, |
104 | addr_list: alt_addr_list, NULL); |
105 | if (!IS_ERR(ptr: dummy)) { |
106 | pr_debug("%d-%02x is probed at %02x\n" , |
107 | adapter->nr, info->addr, dummy->addr); |
108 | i2c_unregister_device(client: dummy); |
109 | client = i2c_new_client_device(adap: adapter, info); |
110 | } |
111 | } |
112 | |
113 | if (IS_ERR(ptr: client)) { |
114 | client = NULL; |
115 | pr_debug("failed to register device %d-%02x\n" , |
116 | adapter->nr, info->addr); |
117 | } else { |
118 | pr_debug("added i2c device %d-%02x\n" , |
119 | adapter->nr, info->addr); |
120 | } |
121 | |
122 | return client; |
123 | } |
124 | |
125 | static bool chromeos_laptop_match_adapter_devid(struct device *dev, u32 devid) |
126 | { |
127 | struct pci_dev *pdev; |
128 | |
129 | if (!dev_is_pci(dev)) |
130 | return false; |
131 | |
132 | pdev = to_pci_dev(dev); |
133 | return devid == pci_dev_id(dev: pdev); |
134 | } |
135 | |
136 | static void chromeos_laptop_check_adapter(struct i2c_adapter *adapter) |
137 | { |
138 | struct i2c_peripheral *i2c_dev; |
139 | int i; |
140 | |
141 | for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { |
142 | i2c_dev = &cros_laptop->i2c_peripherals[i]; |
143 | |
144 | /* Skip devices already created */ |
145 | if (i2c_dev->client) |
146 | continue; |
147 | |
148 | if (strncmp(adapter->name, i2c_adapter_names[i2c_dev->type], |
149 | strlen(i2c_adapter_names[i2c_dev->type]))) |
150 | continue; |
151 | |
152 | if (i2c_dev->pci_devid && |
153 | !chromeos_laptop_match_adapter_devid(dev: adapter->dev.parent, |
154 | devid: i2c_dev->pci_devid)) { |
155 | continue; |
156 | } |
157 | |
158 | i2c_dev->client = |
159 | chromes_laptop_instantiate_i2c_device(adapter, |
160 | info: &i2c_dev->board_info, |
161 | alt_addr: i2c_dev->alt_addr); |
162 | } |
163 | } |
164 | |
165 | static bool chromeos_laptop_adjust_client(struct i2c_client *client) |
166 | { |
167 | struct acpi_peripheral *acpi_dev; |
168 | struct acpi_device_id acpi_ids[2] = { }; |
169 | int i; |
170 | int error; |
171 | |
172 | if (!has_acpi_companion(dev: &client->dev)) |
173 | return false; |
174 | |
175 | for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) { |
176 | acpi_dev = &cros_laptop->acpi_peripherals[i]; |
177 | |
178 | memcpy(acpi_ids[0].id, acpi_dev->hid, ACPI_ID_LEN); |
179 | |
180 | if (acpi_match_device(ids: acpi_ids, dev: &client->dev)) { |
181 | error = device_add_software_node(dev: &client->dev, node: &acpi_dev->swnode); |
182 | if (error) { |
183 | dev_err(&client->dev, |
184 | "failed to add properties: %d\n" , |
185 | error); |
186 | break; |
187 | } |
188 | |
189 | acpi_dev->client = client; |
190 | |
191 | return true; |
192 | } |
193 | } |
194 | |
195 | return false; |
196 | } |
197 | |
198 | static void chromeos_laptop_detach_i2c_client(struct i2c_client *client) |
199 | { |
200 | struct acpi_peripheral *acpi_dev; |
201 | struct i2c_peripheral *i2c_dev; |
202 | int i; |
203 | |
204 | if (has_acpi_companion(dev: &client->dev)) |
205 | for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) { |
206 | acpi_dev = &cros_laptop->acpi_peripherals[i]; |
207 | |
208 | if (acpi_dev->client == client) { |
209 | acpi_dev->client = NULL; |
210 | return; |
211 | } |
212 | } |
213 | else |
214 | for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { |
215 | i2c_dev = &cros_laptop->i2c_peripherals[i]; |
216 | |
217 | if (i2c_dev->client == client) { |
218 | i2c_dev->client = NULL; |
219 | return; |
220 | } |
221 | } |
222 | } |
223 | |
224 | static int chromeos_laptop_i2c_notifier_call(struct notifier_block *nb, |
225 | unsigned long action, void *data) |
226 | { |
227 | struct device *dev = data; |
228 | |
229 | switch (action) { |
230 | case BUS_NOTIFY_ADD_DEVICE: |
231 | if (dev->type == &i2c_adapter_type) |
232 | chromeos_laptop_check_adapter(to_i2c_adapter(dev)); |
233 | else if (dev->type == &i2c_client_type) |
234 | chromeos_laptop_adjust_client(to_i2c_client(dev)); |
235 | break; |
236 | |
237 | case BUS_NOTIFY_REMOVED_DEVICE: |
238 | if (dev->type == &i2c_client_type) |
239 | chromeos_laptop_detach_i2c_client(to_i2c_client(dev)); |
240 | break; |
241 | } |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static struct notifier_block chromeos_laptop_i2c_notifier = { |
247 | .notifier_call = chromeos_laptop_i2c_notifier_call, |
248 | }; |
249 | |
250 | #define DECLARE_CROS_LAPTOP(_name) \ |
251 | static const struct chromeos_laptop _name __initconst = { \ |
252 | .i2c_peripherals = _name##_peripherals, \ |
253 | .num_i2c_peripherals = ARRAY_SIZE(_name##_peripherals), \ |
254 | } |
255 | |
256 | #define DECLARE_ACPI_CROS_LAPTOP(_name) \ |
257 | static const struct chromeos_laptop _name __initconst = { \ |
258 | .acpi_peripherals = _name##_peripherals, \ |
259 | .num_acpi_peripherals = ARRAY_SIZE(_name##_peripherals), \ |
260 | } |
261 | |
262 | static struct i2c_peripheral samsung_series_5_550_peripherals[] __initdata = { |
263 | /* Touchpad. */ |
264 | { |
265 | .board_info = { |
266 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
267 | .flags = I2C_CLIENT_WAKE, |
268 | }, |
269 | .dmi_name = "trackpad" , |
270 | .type = I2C_ADAPTER_SMBUS, |
271 | }, |
272 | /* Light Sensor. */ |
273 | { |
274 | .board_info = { |
275 | I2C_BOARD_INFO("isl29018" , ISL_ALS_I2C_ADDR), |
276 | }, |
277 | .dmi_name = "lightsensor" , |
278 | .type = I2C_ADAPTER_SMBUS, |
279 | }, |
280 | }; |
281 | DECLARE_CROS_LAPTOP(samsung_series_5_550); |
282 | |
283 | static struct i2c_peripheral samsung_series_5_peripherals[] __initdata = { |
284 | /* Light Sensor. */ |
285 | { |
286 | .board_info = { |
287 | I2C_BOARD_INFO("tsl2583" , TAOS_ALS_I2C_ADDR), |
288 | }, |
289 | .type = I2C_ADAPTER_SMBUS, |
290 | }, |
291 | }; |
292 | DECLARE_CROS_LAPTOP(samsung_series_5); |
293 | |
294 | static const int chromebook_pixel_tp_keys[] __initconst = { |
295 | KEY_RESERVED, |
296 | KEY_RESERVED, |
297 | KEY_RESERVED, |
298 | KEY_RESERVED, |
299 | KEY_RESERVED, |
300 | BTN_LEFT |
301 | }; |
302 | |
303 | static const struct property_entry |
304 | chromebook_pixel_trackpad_props[] __initconst = { |
305 | PROPERTY_ENTRY_STRING("compatible" , "atmel,maxtouch" ), |
306 | PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap" , chromebook_pixel_tp_keys), |
307 | { } |
308 | }; |
309 | |
310 | static const struct property_entry |
311 | chromebook_atmel_touchscreen_props[] __initconst = { |
312 | PROPERTY_ENTRY_STRING("compatible" , "atmel,maxtouch" ), |
313 | { } |
314 | }; |
315 | |
316 | static struct i2c_peripheral chromebook_pixel_peripherals[] __initdata = { |
317 | /* Touch Screen. */ |
318 | { |
319 | .board_info = { |
320 | I2C_BOARD_INFO("atmel_mxt_ts" , |
321 | ATMEL_TS_I2C_ADDR), |
322 | .flags = I2C_CLIENT_WAKE, |
323 | }, |
324 | .dmi_name = "touchscreen" , |
325 | .irqflags = IRQF_TRIGGER_FALLING, |
326 | .type = I2C_ADAPTER_PANEL, |
327 | .alt_addr = ATMEL_TS_I2C_BL_ADDR, |
328 | .properties = chromebook_atmel_touchscreen_props, |
329 | }, |
330 | /* Touchpad. */ |
331 | { |
332 | .board_info = { |
333 | I2C_BOARD_INFO("atmel_mxt_tp" , |
334 | ATMEL_TP_I2C_ADDR), |
335 | .flags = I2C_CLIENT_WAKE, |
336 | }, |
337 | .dmi_name = "trackpad" , |
338 | .irqflags = IRQF_TRIGGER_FALLING, |
339 | .type = I2C_ADAPTER_VGADDC, |
340 | .alt_addr = ATMEL_TP_I2C_BL_ADDR, |
341 | .properties = chromebook_pixel_trackpad_props, |
342 | }, |
343 | /* Light Sensor. */ |
344 | { |
345 | .board_info = { |
346 | I2C_BOARD_INFO("isl29018" , ISL_ALS_I2C_ADDR), |
347 | }, |
348 | .dmi_name = "lightsensor" , |
349 | .type = I2C_ADAPTER_PANEL, |
350 | }, |
351 | }; |
352 | DECLARE_CROS_LAPTOP(chromebook_pixel); |
353 | |
354 | static struct i2c_peripheral hp_chromebook_14_peripherals[] __initdata = { |
355 | /* Touchpad. */ |
356 | { |
357 | .board_info = { |
358 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
359 | .flags = I2C_CLIENT_WAKE, |
360 | }, |
361 | .dmi_name = "trackpad" , |
362 | .type = I2C_ADAPTER_DESIGNWARE, |
363 | }, |
364 | }; |
365 | DECLARE_CROS_LAPTOP(hp_chromebook_14); |
366 | |
367 | static struct i2c_peripheral dell_chromebook_11_peripherals[] __initdata = { |
368 | /* Touchpad. */ |
369 | { |
370 | .board_info = { |
371 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
372 | .flags = I2C_CLIENT_WAKE, |
373 | }, |
374 | .dmi_name = "trackpad" , |
375 | .type = I2C_ADAPTER_DESIGNWARE, |
376 | }, |
377 | /* Elan Touchpad option. */ |
378 | { |
379 | .board_info = { |
380 | I2C_BOARD_INFO("elan_i2c" , ELAN_TP_I2C_ADDR), |
381 | .flags = I2C_CLIENT_WAKE, |
382 | }, |
383 | .dmi_name = "trackpad" , |
384 | .type = I2C_ADAPTER_DESIGNWARE, |
385 | }, |
386 | }; |
387 | DECLARE_CROS_LAPTOP(dell_chromebook_11); |
388 | |
389 | static struct i2c_peripheral toshiba_cb35_peripherals[] __initdata = { |
390 | /* Touchpad. */ |
391 | { |
392 | .board_info = { |
393 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
394 | .flags = I2C_CLIENT_WAKE, |
395 | }, |
396 | .dmi_name = "trackpad" , |
397 | .type = I2C_ADAPTER_DESIGNWARE, |
398 | }, |
399 | }; |
400 | DECLARE_CROS_LAPTOP(toshiba_cb35); |
401 | |
402 | static struct i2c_peripheral acer_c7_chromebook_peripherals[] __initdata = { |
403 | /* Touchpad. */ |
404 | { |
405 | .board_info = { |
406 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
407 | .flags = I2C_CLIENT_WAKE, |
408 | }, |
409 | .dmi_name = "trackpad" , |
410 | .type = I2C_ADAPTER_SMBUS, |
411 | }, |
412 | }; |
413 | DECLARE_CROS_LAPTOP(acer_c7_chromebook); |
414 | |
415 | static struct i2c_peripheral acer_ac700_peripherals[] __initdata = { |
416 | /* Light Sensor. */ |
417 | { |
418 | .board_info = { |
419 | I2C_BOARD_INFO("tsl2583" , TAOS_ALS_I2C_ADDR), |
420 | }, |
421 | .type = I2C_ADAPTER_SMBUS, |
422 | }, |
423 | }; |
424 | DECLARE_CROS_LAPTOP(acer_ac700); |
425 | |
426 | static struct i2c_peripheral acer_c720_peripherals[] __initdata = { |
427 | /* Touchscreen. */ |
428 | { |
429 | .board_info = { |
430 | I2C_BOARD_INFO("atmel_mxt_ts" , |
431 | ATMEL_TS_I2C_ADDR), |
432 | .flags = I2C_CLIENT_WAKE, |
433 | }, |
434 | .dmi_name = "touchscreen" , |
435 | .irqflags = IRQF_TRIGGER_FALLING, |
436 | .type = I2C_ADAPTER_DESIGNWARE, |
437 | .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x2)), |
438 | .alt_addr = ATMEL_TS_I2C_BL_ADDR, |
439 | .properties = chromebook_atmel_touchscreen_props, |
440 | }, |
441 | /* Touchpad. */ |
442 | { |
443 | .board_info = { |
444 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
445 | .flags = I2C_CLIENT_WAKE, |
446 | }, |
447 | .dmi_name = "trackpad" , |
448 | .type = I2C_ADAPTER_DESIGNWARE, |
449 | .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x1)), |
450 | }, |
451 | /* Elan Touchpad option. */ |
452 | { |
453 | .board_info = { |
454 | I2C_BOARD_INFO("elan_i2c" , ELAN_TP_I2C_ADDR), |
455 | .flags = I2C_CLIENT_WAKE, |
456 | }, |
457 | .dmi_name = "trackpad" , |
458 | .type = I2C_ADAPTER_DESIGNWARE, |
459 | .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x1)), |
460 | }, |
461 | /* Light Sensor. */ |
462 | { |
463 | .board_info = { |
464 | I2C_BOARD_INFO("isl29018" , ISL_ALS_I2C_ADDR), |
465 | }, |
466 | .dmi_name = "lightsensor" , |
467 | .type = I2C_ADAPTER_DESIGNWARE, |
468 | .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x2)), |
469 | }, |
470 | }; |
471 | DECLARE_CROS_LAPTOP(acer_c720); |
472 | |
473 | static struct i2c_peripheral |
474 | hp_pavilion_14_chromebook_peripherals[] __initdata = { |
475 | /* Touchpad. */ |
476 | { |
477 | .board_info = { |
478 | I2C_BOARD_INFO("cyapa" , CYAPA_TP_I2C_ADDR), |
479 | .flags = I2C_CLIENT_WAKE, |
480 | }, |
481 | .dmi_name = "trackpad" , |
482 | .type = I2C_ADAPTER_SMBUS, |
483 | }, |
484 | }; |
485 | DECLARE_CROS_LAPTOP(hp_pavilion_14_chromebook); |
486 | |
487 | static struct i2c_peripheral cr48_peripherals[] __initdata = { |
488 | /* Light Sensor. */ |
489 | { |
490 | .board_info = { |
491 | I2C_BOARD_INFO("tsl2563" , TAOS_ALS_I2C_ADDR), |
492 | }, |
493 | .type = I2C_ADAPTER_SMBUS, |
494 | }, |
495 | }; |
496 | DECLARE_CROS_LAPTOP(cr48); |
497 | |
498 | static const u32 samus_touchpad_buttons[] __initconst = { |
499 | KEY_RESERVED, |
500 | KEY_RESERVED, |
501 | KEY_RESERVED, |
502 | BTN_LEFT |
503 | }; |
504 | |
505 | static const struct property_entry samus_trackpad_props[] __initconst = { |
506 | PROPERTY_ENTRY_STRING("compatible" , "atmel,maxtouch" ), |
507 | PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap" , samus_touchpad_buttons), |
508 | { } |
509 | }; |
510 | |
511 | static struct acpi_peripheral samus_peripherals[] __initdata = { |
512 | /* Touchpad */ |
513 | { |
514 | .hid = "ATML0000" , |
515 | .swnode = { |
516 | .properties = samus_trackpad_props, |
517 | }, |
518 | }, |
519 | /* Touchsceen */ |
520 | { |
521 | .hid = "ATML0001" , |
522 | .swnode = { |
523 | .properties = chromebook_atmel_touchscreen_props, |
524 | }, |
525 | }, |
526 | }; |
527 | DECLARE_ACPI_CROS_LAPTOP(samus); |
528 | |
529 | static struct acpi_peripheral generic_atmel_peripherals[] __initdata = { |
530 | /* Touchpad */ |
531 | { |
532 | .hid = "ATML0000" , |
533 | .swnode = { |
534 | .properties = chromebook_pixel_trackpad_props, |
535 | }, |
536 | }, |
537 | /* Touchsceen */ |
538 | { |
539 | .hid = "ATML0001" , |
540 | .swnode = { |
541 | .properties = chromebook_atmel_touchscreen_props, |
542 | }, |
543 | }, |
544 | }; |
545 | DECLARE_ACPI_CROS_LAPTOP(generic_atmel); |
546 | |
547 | static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { |
548 | { |
549 | .ident = "Samsung Series 5 550" , |
550 | .matches = { |
551 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG" ), |
552 | DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy" ), |
553 | }, |
554 | .driver_data = (void *)&samsung_series_5_550, |
555 | }, |
556 | { |
557 | .ident = "Samsung Series 5" , |
558 | .matches = { |
559 | DMI_MATCH(DMI_PRODUCT_NAME, "Alex" ), |
560 | }, |
561 | .driver_data = (void *)&samsung_series_5, |
562 | }, |
563 | { |
564 | .ident = "Chromebook Pixel" , |
565 | .matches = { |
566 | DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE" ), |
567 | DMI_MATCH(DMI_PRODUCT_NAME, "Link" ), |
568 | }, |
569 | .driver_data = (void *)&chromebook_pixel, |
570 | }, |
571 | { |
572 | .ident = "Wolf" , |
573 | .matches = { |
574 | DMI_MATCH(DMI_BIOS_VENDOR, "coreboot" ), |
575 | DMI_MATCH(DMI_PRODUCT_NAME, "Wolf" ), |
576 | }, |
577 | .driver_data = (void *)&dell_chromebook_11, |
578 | }, |
579 | { |
580 | .ident = "HP Chromebook 14" , |
581 | .matches = { |
582 | DMI_MATCH(DMI_BIOS_VENDOR, "coreboot" ), |
583 | DMI_MATCH(DMI_PRODUCT_NAME, "Falco" ), |
584 | }, |
585 | .driver_data = (void *)&hp_chromebook_14, |
586 | }, |
587 | { |
588 | .ident = "Toshiba CB35" , |
589 | .matches = { |
590 | DMI_MATCH(DMI_BIOS_VENDOR, "coreboot" ), |
591 | DMI_MATCH(DMI_PRODUCT_NAME, "Leon" ), |
592 | }, |
593 | .driver_data = (void *)&toshiba_cb35, |
594 | }, |
595 | { |
596 | .ident = "Acer C7 Chromebook" , |
597 | .matches = { |
598 | DMI_MATCH(DMI_PRODUCT_NAME, "Parrot" ), |
599 | }, |
600 | .driver_data = (void *)&acer_c7_chromebook, |
601 | }, |
602 | { |
603 | .ident = "Acer AC700" , |
604 | .matches = { |
605 | DMI_MATCH(DMI_PRODUCT_NAME, "ZGB" ), |
606 | }, |
607 | .driver_data = (void *)&acer_ac700, |
608 | }, |
609 | { |
610 | .ident = "Acer C720" , |
611 | .matches = { |
612 | DMI_MATCH(DMI_PRODUCT_NAME, "Peppy" ), |
613 | }, |
614 | .driver_data = (void *)&acer_c720, |
615 | }, |
616 | { |
617 | .ident = "HP Pavilion 14 Chromebook" , |
618 | .matches = { |
619 | DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly" ), |
620 | }, |
621 | .driver_data = (void *)&hp_pavilion_14_chromebook, |
622 | }, |
623 | { |
624 | .ident = "Cr-48" , |
625 | .matches = { |
626 | DMI_MATCH(DMI_PRODUCT_NAME, "Mario" ), |
627 | }, |
628 | .driver_data = (void *)&cr48, |
629 | }, |
630 | /* Devices with peripherals incompletely described in ACPI */ |
631 | { |
632 | .ident = "Chromebook Pro" , |
633 | .matches = { |
634 | DMI_MATCH(DMI_SYS_VENDOR, "Google" ), |
635 | DMI_MATCH(DMI_PRODUCT_NAME, "Caroline" ), |
636 | }, |
637 | .driver_data = (void *)&samus, |
638 | }, |
639 | { |
640 | .ident = "Google Pixel 2 (2015)" , |
641 | .matches = { |
642 | DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE" ), |
643 | DMI_MATCH(DMI_PRODUCT_NAME, "Samus" ), |
644 | }, |
645 | .driver_data = (void *)&samus, |
646 | }, |
647 | { |
648 | .ident = "Samsung Chromebook 3" , |
649 | .matches = { |
650 | DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE" ), |
651 | DMI_MATCH(DMI_PRODUCT_NAME, "Celes" ), |
652 | }, |
653 | .driver_data = (void *)&samus, |
654 | }, |
655 | { |
656 | /* |
657 | * Other Chromebooks with Atmel touch controllers: |
658 | * - Winky (touchpad) |
659 | * - Clapper, Expresso, Rambi, Glimmer (touchscreen) |
660 | */ |
661 | .ident = "Other Chromebook" , |
662 | .matches = { |
663 | /* |
664 | * This will match all Google devices, not only devices |
665 | * with Atmel, but we will validate that the device |
666 | * actually has matching peripherals. |
667 | */ |
668 | DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE" ), |
669 | }, |
670 | .driver_data = (void *)&generic_atmel, |
671 | }, |
672 | { } |
673 | }; |
674 | MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table); |
675 | |
676 | static int __init chromeos_laptop_scan_peripherals(struct device *dev, void *data) |
677 | { |
678 | int error; |
679 | |
680 | if (dev->type == &i2c_adapter_type) { |
681 | chromeos_laptop_check_adapter(to_i2c_adapter(dev)); |
682 | } else if (dev->type == &i2c_client_type) { |
683 | if (chromeos_laptop_adjust_client(to_i2c_client(dev))) { |
684 | /* |
685 | * Now that we have needed properties re-trigger |
686 | * driver probe in case driver was initialized |
687 | * earlier and probe failed. |
688 | */ |
689 | error = device_attach(dev); |
690 | if (error < 0) |
691 | dev_warn(dev, |
692 | "%s: device_attach() failed: %d\n" , |
693 | __func__, error); |
694 | } |
695 | } |
696 | |
697 | return 0; |
698 | } |
699 | |
700 | static int __init chromeos_laptop_get_irq_from_dmi(const char *dmi_name) |
701 | { |
702 | const struct dmi_device *dmi_dev; |
703 | const struct dmi_dev_onboard *dev_data; |
704 | |
705 | dmi_dev = dmi_find_device(type: DMI_DEV_TYPE_DEV_ONBOARD, name: dmi_name, NULL); |
706 | if (!dmi_dev) { |
707 | pr_err("failed to find DMI device '%s'\n" , dmi_name); |
708 | return -ENOENT; |
709 | } |
710 | |
711 | dev_data = dmi_dev->device_data; |
712 | if (!dev_data) { |
713 | pr_err("failed to get data from DMI for '%s'\n" , dmi_name); |
714 | return -EINVAL; |
715 | } |
716 | |
717 | return dev_data->instance; |
718 | } |
719 | |
720 | static int __init chromeos_laptop_setup_irq(struct i2c_peripheral *i2c_dev) |
721 | { |
722 | int irq; |
723 | |
724 | if (i2c_dev->dmi_name) { |
725 | irq = chromeos_laptop_get_irq_from_dmi(dmi_name: i2c_dev->dmi_name); |
726 | if (irq < 0) |
727 | return irq; |
728 | |
729 | i2c_dev->irq_resource = (struct resource) |
730 | DEFINE_RES_NAMED(irq, 1, NULL, |
731 | IORESOURCE_IRQ | i2c_dev->irqflags); |
732 | i2c_dev->board_info.resources = &i2c_dev->irq_resource; |
733 | i2c_dev->board_info.num_resources = 1; |
734 | } |
735 | |
736 | return 0; |
737 | } |
738 | |
739 | static int __init |
740 | chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, |
741 | const struct chromeos_laptop *src) |
742 | { |
743 | struct i2c_peripheral *i2c_peripherals; |
744 | struct i2c_peripheral *i2c_dev; |
745 | struct i2c_board_info *info; |
746 | int i; |
747 | int error; |
748 | |
749 | if (!src->num_i2c_peripherals) |
750 | return 0; |
751 | |
752 | i2c_peripherals = kmemdup(p: src->i2c_peripherals, |
753 | size: src->num_i2c_peripherals * |
754 | sizeof(*src->i2c_peripherals), |
755 | GFP_KERNEL); |
756 | if (!i2c_peripherals) |
757 | return -ENOMEM; |
758 | |
759 | for (i = 0; i < src->num_i2c_peripherals; i++) { |
760 | i2c_dev = &i2c_peripherals[i]; |
761 | info = &i2c_dev->board_info; |
762 | |
763 | error = chromeos_laptop_setup_irq(i2c_dev); |
764 | if (error) |
765 | goto err_out; |
766 | |
767 | /* Create primary fwnode for the device - copies everything */ |
768 | if (i2c_dev->properties) { |
769 | info->fwnode = fwnode_create_software_node(properties: i2c_dev->properties, NULL); |
770 | if (IS_ERR(ptr: info->fwnode)) { |
771 | error = PTR_ERR(ptr: info->fwnode); |
772 | goto err_out; |
773 | } |
774 | } |
775 | } |
776 | |
777 | cros_laptop->i2c_peripherals = i2c_peripherals; |
778 | cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals; |
779 | |
780 | return 0; |
781 | |
782 | err_out: |
783 | while (--i >= 0) { |
784 | i2c_dev = &i2c_peripherals[i]; |
785 | info = &i2c_dev->board_info; |
786 | if (!IS_ERR_OR_NULL(ptr: info->fwnode)) |
787 | fwnode_remove_software_node(fwnode: info->fwnode); |
788 | } |
789 | kfree(objp: i2c_peripherals); |
790 | return error; |
791 | } |
792 | |
793 | static int __init |
794 | chromeos_laptop_prepare_acpi_peripherals(struct chromeos_laptop *cros_laptop, |
795 | const struct chromeos_laptop *src) |
796 | { |
797 | struct acpi_peripheral *acpi_peripherals; |
798 | struct acpi_peripheral *acpi_dev; |
799 | const struct acpi_peripheral *src_dev; |
800 | int n_peripherals = 0; |
801 | int i; |
802 | int error; |
803 | |
804 | for (i = 0; i < src->num_acpi_peripherals; i++) { |
805 | if (acpi_dev_present(hid: src->acpi_peripherals[i].hid, NULL, hrv: -1)) |
806 | n_peripherals++; |
807 | } |
808 | |
809 | if (!n_peripherals) |
810 | return 0; |
811 | |
812 | acpi_peripherals = kcalloc(n: n_peripherals, |
813 | size: sizeof(*src->acpi_peripherals), |
814 | GFP_KERNEL); |
815 | if (!acpi_peripherals) |
816 | return -ENOMEM; |
817 | |
818 | acpi_dev = acpi_peripherals; |
819 | for (i = 0; i < src->num_acpi_peripherals; i++) { |
820 | src_dev = &src->acpi_peripherals[i]; |
821 | if (!acpi_dev_present(hid: src_dev->hid, NULL, hrv: -1)) |
822 | continue; |
823 | |
824 | *acpi_dev = *src_dev; |
825 | |
826 | /* We need to deep-copy properties */ |
827 | if (src_dev->swnode.properties) { |
828 | acpi_dev->swnode.properties = |
829 | property_entries_dup(properties: src_dev->swnode.properties); |
830 | if (IS_ERR(ptr: acpi_dev->swnode.properties)) { |
831 | error = PTR_ERR(ptr: acpi_dev->swnode.properties); |
832 | goto err_out; |
833 | } |
834 | } |
835 | |
836 | acpi_dev++; |
837 | } |
838 | |
839 | cros_laptop->acpi_peripherals = acpi_peripherals; |
840 | cros_laptop->num_acpi_peripherals = n_peripherals; |
841 | |
842 | return 0; |
843 | |
844 | err_out: |
845 | while (--i >= 0) { |
846 | acpi_dev = &acpi_peripherals[i]; |
847 | if (!IS_ERR_OR_NULL(ptr: acpi_dev->swnode.properties)) |
848 | property_entries_free(properties: acpi_dev->swnode.properties); |
849 | } |
850 | |
851 | kfree(objp: acpi_peripherals); |
852 | return error; |
853 | } |
854 | |
855 | static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop) |
856 | { |
857 | const struct acpi_peripheral *acpi_dev; |
858 | struct i2c_peripheral *i2c_dev; |
859 | int i; |
860 | |
861 | for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { |
862 | i2c_dev = &cros_laptop->i2c_peripherals[i]; |
863 | i2c_unregister_device(client: i2c_dev->client); |
864 | } |
865 | |
866 | for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) { |
867 | acpi_dev = &cros_laptop->acpi_peripherals[i]; |
868 | |
869 | if (acpi_dev->client) |
870 | device_remove_software_node(dev: &acpi_dev->client->dev); |
871 | |
872 | property_entries_free(properties: acpi_dev->swnode.properties); |
873 | } |
874 | |
875 | kfree(objp: cros_laptop->i2c_peripherals); |
876 | kfree(objp: cros_laptop->acpi_peripherals); |
877 | kfree(objp: cros_laptop); |
878 | } |
879 | |
880 | static struct chromeos_laptop * __init |
881 | chromeos_laptop_prepare(const struct chromeos_laptop *src) |
882 | { |
883 | struct chromeos_laptop *cros_laptop; |
884 | int error; |
885 | |
886 | cros_laptop = kzalloc(size: sizeof(*cros_laptop), GFP_KERNEL); |
887 | if (!cros_laptop) |
888 | return ERR_PTR(error: -ENOMEM); |
889 | |
890 | error = chromeos_laptop_prepare_i2c_peripherals(cros_laptop, src); |
891 | if (!error) |
892 | error = chromeos_laptop_prepare_acpi_peripherals(cros_laptop, |
893 | src); |
894 | |
895 | if (error) { |
896 | chromeos_laptop_destroy(cros_laptop); |
897 | return ERR_PTR(error); |
898 | } |
899 | |
900 | return cros_laptop; |
901 | } |
902 | |
903 | static int __init chromeos_laptop_init(void) |
904 | { |
905 | const struct dmi_system_id *dmi_id; |
906 | int error; |
907 | |
908 | dmi_id = dmi_first_match(list: chromeos_laptop_dmi_table); |
909 | if (!dmi_id) { |
910 | pr_debug("unsupported system\n" ); |
911 | return -ENODEV; |
912 | } |
913 | |
914 | pr_debug("DMI Matched %s\n" , dmi_id->ident); |
915 | |
916 | cros_laptop = chromeos_laptop_prepare(src: (void *)dmi_id->driver_data); |
917 | if (IS_ERR(ptr: cros_laptop)) |
918 | return PTR_ERR(ptr: cros_laptop); |
919 | |
920 | if (!cros_laptop->num_i2c_peripherals && |
921 | !cros_laptop->num_acpi_peripherals) { |
922 | pr_debug("no relevant devices detected\n" ); |
923 | error = -ENODEV; |
924 | goto err_destroy_cros_laptop; |
925 | } |
926 | |
927 | error = bus_register_notifier(bus: &i2c_bus_type, |
928 | nb: &chromeos_laptop_i2c_notifier); |
929 | if (error) { |
930 | pr_err("failed to register i2c bus notifier: %d\n" , |
931 | error); |
932 | goto err_destroy_cros_laptop; |
933 | } |
934 | |
935 | /* |
936 | * Scan adapters that have been registered and clients that have |
937 | * been created before we installed the notifier to make sure |
938 | * we do not miss any devices. |
939 | */ |
940 | i2c_for_each_dev(NULL, fn: chromeos_laptop_scan_peripherals); |
941 | |
942 | return 0; |
943 | |
944 | err_destroy_cros_laptop: |
945 | chromeos_laptop_destroy(cros_laptop); |
946 | return error; |
947 | } |
948 | |
949 | static void __exit chromeos_laptop_exit(void) |
950 | { |
951 | bus_unregister_notifier(bus: &i2c_bus_type, nb: &chromeos_laptop_i2c_notifier); |
952 | chromeos_laptop_destroy(cros_laptop); |
953 | } |
954 | |
955 | module_init(chromeos_laptop_init); |
956 | module_exit(chromeos_laptop_exit); |
957 | |
958 | MODULE_DESCRIPTION("Chrome OS Laptop driver" ); |
959 | MODULE_AUTHOR("Benson Leung <bleung@chromium.org>" ); |
960 | MODULE_LICENSE("GPL" ); |
961 | |