.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Supports for the button array on SoC tablets originally running |
---|
3 | 4 | * Windows 8. |
---|
4 | 5 | * |
---|
5 | 6 | * (C) Copyright 2014 Intel Corporation |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or |
---|
8 | | - * modify it under the terms of the GNU General Public License |
---|
9 | | - * as published by the Free Software Foundation; version 2 |
---|
10 | | - * of the License. |
---|
11 | 7 | */ |
---|
12 | 8 | |
---|
13 | 9 | #include <linux/module.h> |
---|
14 | 10 | #include <linux/input.h> |
---|
15 | 11 | #include <linux/init.h> |
---|
| 12 | +#include <linux/irq.h> |
---|
16 | 13 | #include <linux/kernel.h> |
---|
17 | 14 | #include <linux/acpi.h> |
---|
| 15 | +#include <linux/dmi.h> |
---|
18 | 16 | #include <linux/gpio/consumer.h> |
---|
19 | 17 | #include <linux/gpio_keys.h> |
---|
20 | 18 | #include <linux/gpio.h> |
---|
21 | 19 | #include <linux/platform_device.h> |
---|
| 20 | + |
---|
| 21 | +static bool use_low_level_irq; |
---|
| 22 | +module_param(use_low_level_irq, bool, 0444); |
---|
| 23 | +MODULE_PARM_DESC(use_low_level_irq, "Use low-level triggered IRQ instead of edge triggered"); |
---|
22 | 24 | |
---|
23 | 25 | struct soc_button_info { |
---|
24 | 26 | const char *name; |
---|
.. | .. |
---|
27 | 29 | unsigned int event_code; |
---|
28 | 30 | bool autorepeat; |
---|
29 | 31 | bool wakeup; |
---|
| 32 | + bool active_low; |
---|
| 33 | +}; |
---|
| 34 | + |
---|
| 35 | +struct soc_device_data { |
---|
| 36 | + const struct soc_button_info *button_info; |
---|
| 37 | + int (*check)(struct device *dev); |
---|
30 | 38 | }; |
---|
31 | 39 | |
---|
32 | 40 | /* |
---|
.. | .. |
---|
41 | 49 | }; |
---|
42 | 50 | |
---|
43 | 51 | /* |
---|
| 52 | + * Some 2-in-1s which use the soc_button_array driver have this ugly issue in |
---|
| 53 | + * their DSDT where the _LID method modifies the irq-type settings of the GPIOs |
---|
| 54 | + * used for the power and home buttons. The intend of this AML code is to |
---|
| 55 | + * disable these buttons when the lid is closed. |
---|
| 56 | + * The AML does this by directly poking the GPIO controllers registers. This is |
---|
| 57 | + * problematic because when re-enabling the irq, which happens whenever _LID |
---|
| 58 | + * gets called with the lid open (e.g. on boot and on resume), it sets the |
---|
| 59 | + * irq-type to IRQ_TYPE_LEVEL_LOW. Where as the gpio-keys driver programs the |
---|
| 60 | + * type to, and expects it to be, IRQ_TYPE_EDGE_BOTH. |
---|
| 61 | + * To work around this we don't set gpio_keys_button.gpio on these 2-in-1s, |
---|
| 62 | + * instead we get the irq for the GPIO ourselves, configure it as |
---|
| 63 | + * IRQ_TYPE_LEVEL_LOW (to match how the _LID AML code configures it) and pass |
---|
| 64 | + * the irq in gpio_keys_button.irq. Below is a list of affected devices. |
---|
| 65 | + */ |
---|
| 66 | +static const struct dmi_system_id dmi_use_low_level_irq[] = { |
---|
| 67 | + { |
---|
| 68 | + /* |
---|
| 69 | + * Acer Switch 10 SW5-012. _LID method messes with home- and |
---|
| 70 | + * power-button GPIO IRQ settings. When (re-)enabling the irq |
---|
| 71 | + * it ors in its own flags without clearing the previous set |
---|
| 72 | + * ones, leading to an irq-type of IRQ_TYPE_LEVEL_LOW | |
---|
| 73 | + * IRQ_TYPE_LEVEL_HIGH causing a continuous interrupt storm. |
---|
| 74 | + */ |
---|
| 75 | + .matches = { |
---|
| 76 | + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), |
---|
| 77 | + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"), |
---|
| 78 | + }, |
---|
| 79 | + }, |
---|
| 80 | + { |
---|
| 81 | + /* Acer Switch V 10 SW5-017, same issue as Acer Switch 10 SW5-012. */ |
---|
| 82 | + .matches = { |
---|
| 83 | + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), |
---|
| 84 | + DMI_MATCH(DMI_PRODUCT_NAME, "SW5-017"), |
---|
| 85 | + }, |
---|
| 86 | + }, |
---|
| 87 | + { |
---|
| 88 | + /* |
---|
| 89 | + * Acer One S1003. _LID method messes with power-button GPIO |
---|
| 90 | + * IRQ settings, leading to a non working power-button. |
---|
| 91 | + */ |
---|
| 92 | + .matches = { |
---|
| 93 | + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), |
---|
| 94 | + DMI_MATCH(DMI_PRODUCT_NAME, "One S1003"), |
---|
| 95 | + }, |
---|
| 96 | + }, |
---|
| 97 | + { |
---|
| 98 | + /* |
---|
| 99 | + * Lenovo Yoga Tab2 1051F/1051L, something messes with the home-button |
---|
| 100 | + * IRQ settings, leading to a non working home-button. |
---|
| 101 | + */ |
---|
| 102 | + .matches = { |
---|
| 103 | + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
---|
| 104 | + DMI_MATCH(DMI_PRODUCT_NAME, "60073"), |
---|
| 105 | + DMI_MATCH(DMI_PRODUCT_VERSION, "1051"), |
---|
| 106 | + }, |
---|
| 107 | + }, |
---|
| 108 | + {} /* Terminating entry */ |
---|
| 109 | +}; |
---|
| 110 | + |
---|
| 111 | +/* |
---|
| 112 | + * Some devices have a wrong entry which points to a GPIO which is |
---|
| 113 | + * required in another driver, so this driver must not claim it. |
---|
| 114 | + */ |
---|
| 115 | +static const struct dmi_system_id dmi_invalid_acpi_index[] = { |
---|
| 116 | + { |
---|
| 117 | + /* |
---|
| 118 | + * Lenovo Yoga Book X90F / X90L, the PNP0C40 home button entry |
---|
| 119 | + * points to a GPIO which is not a home button and which is |
---|
| 120 | + * required by the lenovo-yogabook driver. |
---|
| 121 | + */ |
---|
| 122 | + .matches = { |
---|
| 123 | + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), |
---|
| 124 | + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"), |
---|
| 125 | + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"), |
---|
| 126 | + }, |
---|
| 127 | + .driver_data = (void *)1l, |
---|
| 128 | + }, |
---|
| 129 | + {} /* Terminating entry */ |
---|
| 130 | +}; |
---|
| 131 | + |
---|
| 132 | +/* |
---|
44 | 133 | * Get the Nth GPIO number from the ACPI object. |
---|
45 | 134 | */ |
---|
46 | | -static int soc_button_lookup_gpio(struct device *dev, int acpi_index) |
---|
| 135 | +static int soc_button_lookup_gpio(struct device *dev, int acpi_index, |
---|
| 136 | + int *gpio_ret, int *irq_ret) |
---|
47 | 137 | { |
---|
48 | 138 | struct gpio_desc *desc; |
---|
49 | | - int gpio; |
---|
50 | 139 | |
---|
51 | 140 | desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); |
---|
52 | 141 | if (IS_ERR(desc)) |
---|
53 | 142 | return PTR_ERR(desc); |
---|
54 | 143 | |
---|
55 | | - gpio = desc_to_gpio(desc); |
---|
| 144 | + *gpio_ret = desc_to_gpio(desc); |
---|
| 145 | + *irq_ret = gpiod_to_irq(desc); |
---|
56 | 146 | |
---|
57 | 147 | gpiod_put(desc); |
---|
58 | 148 | |
---|
59 | | - return gpio; |
---|
| 149 | + return 0; |
---|
60 | 150 | } |
---|
61 | 151 | |
---|
62 | 152 | static struct platform_device * |
---|
.. | .. |
---|
68 | 158 | struct platform_device *pd; |
---|
69 | 159 | struct gpio_keys_button *gpio_keys; |
---|
70 | 160 | struct gpio_keys_platform_data *gpio_keys_pdata; |
---|
| 161 | + const struct dmi_system_id *dmi_id; |
---|
| 162 | + int invalid_acpi_index = -1; |
---|
| 163 | + int error, gpio, irq; |
---|
71 | 164 | int n_buttons = 0; |
---|
72 | | - int gpio; |
---|
73 | | - int error; |
---|
74 | 165 | |
---|
75 | 166 | for (info = button_info; info->name; info++) |
---|
76 | 167 | if (info->autorepeat == autorepeat) |
---|
.. | .. |
---|
86 | 177 | gpio_keys = (void *)(gpio_keys_pdata + 1); |
---|
87 | 178 | n_buttons = 0; |
---|
88 | 179 | |
---|
| 180 | + dmi_id = dmi_first_match(dmi_invalid_acpi_index); |
---|
| 181 | + if (dmi_id) |
---|
| 182 | + invalid_acpi_index = (long)dmi_id->driver_data; |
---|
| 183 | + |
---|
89 | 184 | for (info = button_info; info->name; info++) { |
---|
90 | 185 | if (info->autorepeat != autorepeat) |
---|
91 | 186 | continue; |
---|
92 | 187 | |
---|
93 | | - gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index); |
---|
94 | | - if (!gpio_is_valid(gpio)) |
---|
| 188 | + if (info->acpi_index == invalid_acpi_index) |
---|
95 | 189 | continue; |
---|
| 190 | + |
---|
| 191 | + error = soc_button_lookup_gpio(&pdev->dev, info->acpi_index, &gpio, &irq); |
---|
| 192 | + if (error || irq < 0) { |
---|
| 193 | + /* |
---|
| 194 | + * Skip GPIO if not present. Note we deliberately |
---|
| 195 | + * ignore -EPROBE_DEFER errors here. On some devices |
---|
| 196 | + * Intel is using so called virtual GPIOs which are not |
---|
| 197 | + * GPIOs at all but some way for AML code to check some |
---|
| 198 | + * random status bits without need a custom opregion. |
---|
| 199 | + * In some cases the resources table we parse points to |
---|
| 200 | + * such a virtual GPIO, since these are not real GPIOs |
---|
| 201 | + * we do not have a driver for these so they will never |
---|
| 202 | + * show up, therefore we ignore -EPROBE_DEFER. |
---|
| 203 | + */ |
---|
| 204 | + continue; |
---|
| 205 | + } |
---|
| 206 | + |
---|
| 207 | + /* See dmi_use_low_level_irq[] comment */ |
---|
| 208 | + if (!autorepeat && (use_low_level_irq || |
---|
| 209 | + dmi_check_system(dmi_use_low_level_irq))) { |
---|
| 210 | + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); |
---|
| 211 | + gpio_keys[n_buttons].irq = irq; |
---|
| 212 | + gpio_keys[n_buttons].gpio = -ENOENT; |
---|
| 213 | + } else { |
---|
| 214 | + gpio_keys[n_buttons].gpio = gpio; |
---|
| 215 | + } |
---|
96 | 216 | |
---|
97 | 217 | gpio_keys[n_buttons].type = info->event_type; |
---|
98 | 218 | gpio_keys[n_buttons].code = info->event_code; |
---|
99 | | - gpio_keys[n_buttons].gpio = gpio; |
---|
100 | | - gpio_keys[n_buttons].active_low = 1; |
---|
| 219 | + gpio_keys[n_buttons].active_low = info->active_low; |
---|
101 | 220 | gpio_keys[n_buttons].desc = info->name; |
---|
102 | 221 | gpio_keys[n_buttons].wakeup = info->wakeup; |
---|
103 | 222 | /* These devices often use cheap buttons, use 50 ms debounce */ |
---|
.. | .. |
---|
114 | 233 | gpio_keys_pdata->nbuttons = n_buttons; |
---|
115 | 234 | gpio_keys_pdata->rep = autorepeat; |
---|
116 | 235 | |
---|
117 | | - pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); |
---|
118 | | - if (!pd) { |
---|
119 | | - error = -ENOMEM; |
---|
| 236 | + pd = platform_device_register_resndata(&pdev->dev, "gpio-keys", |
---|
| 237 | + PLATFORM_DEVID_AUTO, NULL, 0, |
---|
| 238 | + gpio_keys_pdata, |
---|
| 239 | + sizeof(*gpio_keys_pdata)); |
---|
| 240 | + error = PTR_ERR_OR_ZERO(pd); |
---|
| 241 | + if (error) { |
---|
| 242 | + dev_err(&pdev->dev, |
---|
| 243 | + "failed registering gpio-keys: %d\n", error); |
---|
120 | 244 | goto err_free_mem; |
---|
121 | 245 | } |
---|
122 | 246 | |
---|
123 | | - error = platform_device_add_data(pd, gpio_keys_pdata, |
---|
124 | | - sizeof(*gpio_keys_pdata)); |
---|
125 | | - if (error) |
---|
126 | | - goto err_free_pdev; |
---|
127 | | - |
---|
128 | | - error = platform_device_add(pd); |
---|
129 | | - if (error) |
---|
130 | | - goto err_free_pdev; |
---|
131 | | - |
---|
132 | 247 | return pd; |
---|
133 | 248 | |
---|
134 | | -err_free_pdev: |
---|
135 | | - platform_device_put(pd); |
---|
136 | 249 | err_free_mem: |
---|
137 | 250 | devm_kfree(&pdev->dev, gpio_keys_pdata); |
---|
138 | 251 | return ERR_PTR(error); |
---|
.. | .. |
---|
166 | 279 | } |
---|
167 | 280 | |
---|
168 | 281 | info->event_type = EV_KEY; |
---|
| 282 | + info->active_low = true; |
---|
169 | 283 | info->acpi_index = |
---|
170 | 284 | soc_button_get_acpi_object_int(&desc->package.elements[1]); |
---|
171 | 285 | upage = soc_button_get_acpi_object_int(&desc->package.elements[3]); |
---|
.. | .. |
---|
185 | 299 | info->name = "power"; |
---|
186 | 300 | info->event_code = KEY_POWER; |
---|
187 | 301 | info->wakeup = true; |
---|
| 302 | + } else if (upage == 0x01 && usage == 0xca) { |
---|
| 303 | + info->name = "rotation lock switch"; |
---|
| 304 | + info->event_type = EV_SW; |
---|
| 305 | + info->event_code = SW_ROTATE_LOCK; |
---|
188 | 306 | } else if (upage == 0x07 && usage == 0xe3) { |
---|
189 | 307 | info->name = "home"; |
---|
190 | 308 | info->event_code = KEY_LEFTMETA; |
---|
.. | .. |
---|
309 | 427 | static int soc_button_probe(struct platform_device *pdev) |
---|
310 | 428 | { |
---|
311 | 429 | struct device *dev = &pdev->dev; |
---|
312 | | - const struct acpi_device_id *id; |
---|
313 | | - struct soc_button_info *button_info; |
---|
| 430 | + const struct soc_device_data *device_data; |
---|
| 431 | + const struct soc_button_info *button_info; |
---|
314 | 432 | struct soc_button_data *priv; |
---|
315 | 433 | struct platform_device *pd; |
---|
316 | 434 | int i; |
---|
317 | 435 | int error; |
---|
318 | 436 | |
---|
319 | | - id = acpi_match_device(dev->driver->acpi_match_table, dev); |
---|
320 | | - if (!id) |
---|
321 | | - return -ENODEV; |
---|
| 437 | + device_data = acpi_device_get_match_data(dev); |
---|
| 438 | + if (device_data && device_data->check) { |
---|
| 439 | + error = device_data->check(dev); |
---|
| 440 | + if (error) |
---|
| 441 | + return error; |
---|
| 442 | + } |
---|
322 | 443 | |
---|
323 | | - if (!id->driver_data) { |
---|
| 444 | + if (device_data && device_data->button_info) { |
---|
| 445 | + button_info = device_data->button_info; |
---|
| 446 | + } else { |
---|
324 | 447 | button_info = soc_button_get_button_info(dev); |
---|
325 | 448 | if (IS_ERR(button_info)) |
---|
326 | 449 | return PTR_ERR(button_info); |
---|
327 | | - } else { |
---|
328 | | - button_info = (struct soc_button_info *)id->driver_data; |
---|
329 | 450 | } |
---|
330 | 451 | |
---|
331 | 452 | error = gpiod_count(dev, NULL); |
---|
.. | .. |
---|
357 | 478 | if (!priv->children[0] && !priv->children[1]) |
---|
358 | 479 | return -ENODEV; |
---|
359 | 480 | |
---|
360 | | - if (!id->driver_data) |
---|
| 481 | + if (!device_data || !device_data->button_info) |
---|
361 | 482 | devm_kfree(dev, button_info); |
---|
362 | 483 | |
---|
363 | 484 | return 0; |
---|
.. | .. |
---|
368 | 489 | * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC |
---|
369 | 490 | * Platforms" |
---|
370 | 491 | */ |
---|
371 | | -static struct soc_button_info soc_button_PNP0C40[] = { |
---|
372 | | - { "power", 0, EV_KEY, KEY_POWER, false, true }, |
---|
373 | | - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true }, |
---|
374 | | - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, |
---|
375 | | - { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false }, |
---|
376 | | - { "rotation_lock", 4, EV_KEY, KEY_ROTATE_LOCK_TOGGLE, false, false }, |
---|
| 492 | +static const struct soc_button_info soc_button_PNP0C40[] = { |
---|
| 493 | + { "power", 0, EV_KEY, KEY_POWER, false, true, true }, |
---|
| 494 | + { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, true }, |
---|
| 495 | + { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, |
---|
| 496 | + { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, |
---|
| 497 | + { "rotation_lock", 4, EV_KEY, KEY_ROTATE_LOCK_TOGGLE, false, false, true }, |
---|
377 | 498 | { } |
---|
378 | 499 | }; |
---|
379 | 500 | |
---|
| 501 | +static const struct soc_device_data soc_device_PNP0C40 = { |
---|
| 502 | + .button_info = soc_button_PNP0C40, |
---|
| 503 | +}; |
---|
| 504 | + |
---|
| 505 | +static const struct soc_button_info soc_button_INT33D3[] = { |
---|
| 506 | + { "tablet_mode", 0, EV_SW, SW_TABLET_MODE, false, false, false }, |
---|
| 507 | + { } |
---|
| 508 | +}; |
---|
| 509 | + |
---|
| 510 | +static const struct soc_device_data soc_device_INT33D3 = { |
---|
| 511 | + .button_info = soc_button_INT33D3, |
---|
| 512 | +}; |
---|
| 513 | + |
---|
| 514 | +/* |
---|
| 515 | + * Special device check for Surface Book 2 and Surface Pro (2017). |
---|
| 516 | + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned |
---|
| 517 | + * devices use MSHW0040 for power and volume buttons, however the way they |
---|
| 518 | + * have to be addressed differs. Make sure that we only load this drivers |
---|
| 519 | + * for the correct devices by checking the OEM Platform Revision provided by |
---|
| 520 | + * the _DSM method. |
---|
| 521 | + */ |
---|
| 522 | +#define MSHW0040_DSM_REVISION 0x01 |
---|
| 523 | +#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision |
---|
| 524 | +static const guid_t MSHW0040_DSM_UUID = |
---|
| 525 | + GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, |
---|
| 526 | + 0x49, 0x80, 0x35); |
---|
| 527 | + |
---|
| 528 | +static int soc_device_check_MSHW0040(struct device *dev) |
---|
| 529 | +{ |
---|
| 530 | + acpi_handle handle = ACPI_HANDLE(dev); |
---|
| 531 | + union acpi_object *result; |
---|
| 532 | + u64 oem_platform_rev = 0; // valid revisions are nonzero |
---|
| 533 | + |
---|
| 534 | + // get OEM platform revision |
---|
| 535 | + result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, |
---|
| 536 | + MSHW0040_DSM_REVISION, |
---|
| 537 | + MSHW0040_DSM_GET_OMPR, NULL, |
---|
| 538 | + ACPI_TYPE_INTEGER); |
---|
| 539 | + |
---|
| 540 | + if (result) { |
---|
| 541 | + oem_platform_rev = result->integer.value; |
---|
| 542 | + ACPI_FREE(result); |
---|
| 543 | + } |
---|
| 544 | + |
---|
| 545 | + /* |
---|
| 546 | + * If the revision is zero here, the _DSM evaluation has failed. This |
---|
| 547 | + * indicates that we have a Pro 4 or Book 1 and this driver should not |
---|
| 548 | + * be used. |
---|
| 549 | + */ |
---|
| 550 | + if (oem_platform_rev == 0) |
---|
| 551 | + return -ENODEV; |
---|
| 552 | + |
---|
| 553 | + dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); |
---|
| 554 | + |
---|
| 555 | + return 0; |
---|
| 556 | +} |
---|
| 557 | + |
---|
| 558 | +/* |
---|
| 559 | + * Button infos for Microsoft Surface Book 2 and Surface Pro (2017). |
---|
| 560 | + * Obtained from DSDT/testing. |
---|
| 561 | + */ |
---|
| 562 | +static const struct soc_button_info soc_button_MSHW0040[] = { |
---|
| 563 | + { "power", 0, EV_KEY, KEY_POWER, false, true, true }, |
---|
| 564 | + { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, |
---|
| 565 | + { "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, |
---|
| 566 | + { } |
---|
| 567 | +}; |
---|
| 568 | + |
---|
| 569 | +static const struct soc_device_data soc_device_MSHW0040 = { |
---|
| 570 | + .button_info = soc_button_MSHW0040, |
---|
| 571 | + .check = soc_device_check_MSHW0040, |
---|
| 572 | +}; |
---|
| 573 | + |
---|
380 | 574 | static const struct acpi_device_id soc_button_acpi_match[] = { |
---|
381 | | - { "PNP0C40", (unsigned long)soc_button_PNP0C40 }, |
---|
| 575 | + { "PNP0C40", (unsigned long)&soc_device_PNP0C40 }, |
---|
| 576 | + { "INT33D3", (unsigned long)&soc_device_INT33D3 }, |
---|
| 577 | + { "ID9001", (unsigned long)&soc_device_INT33D3 }, |
---|
382 | 578 | { "ACPI0011", 0 }, |
---|
| 579 | + |
---|
| 580 | + /* Microsoft Surface Devices (5th and 6th generation) */ |
---|
| 581 | + { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, |
---|
| 582 | + |
---|
383 | 583 | { } |
---|
384 | 584 | }; |
---|
385 | 585 | |
---|