1 // SPDX-License-Identifier: GPL-2.0+
3 * Surface Book (2 and later) hot-plug driver.
5 * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This
6 * driver is responsible for out-of-band hot-plug event signaling on these
7 * devices. It is specifically required when the hot-plug device is in D3cold
8 * and can thus not generate PCIe hot-plug events itself.
10 * Event signaling is handled via ACPI, which will generate the appropriate
11 * device-check notifications to be picked up by the PCIe hot-plug driver.
13 * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
16 #include <linux/acpi.h>
17 #include <linux/gpio.h>
18 #include <linux/interrupt.h>
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/mutex.h>
22 #include <linux/platform_device.h>
24 static const struct acpi_gpio_params shps_base_presence_int
= { 0, 0, false };
25 static const struct acpi_gpio_params shps_base_presence
= { 1, 0, false };
26 static const struct acpi_gpio_params shps_device_power_int
= { 2, 0, false };
27 static const struct acpi_gpio_params shps_device_power
= { 3, 0, false };
28 static const struct acpi_gpio_params shps_device_presence_int
= { 4, 0, false };
29 static const struct acpi_gpio_params shps_device_presence
= { 5, 0, false };
31 static const struct acpi_gpio_mapping shps_acpi_gpios
[] = {
32 { "base_presence-int-gpio", &shps_base_presence_int
, 1 },
33 { "base_presence-gpio", &shps_base_presence
, 1 },
34 { "device_power-int-gpio", &shps_device_power_int
, 1 },
35 { "device_power-gpio", &shps_device_power
, 1 },
36 { "device_presence-int-gpio", &shps_device_presence_int
, 1 },
37 { "device_presence-gpio", &shps_device_presence
, 1 },
41 /* 5515a847-ed55-4b27-8352-cd320e10360a */
42 static const guid_t shps_dsm_guid
=
43 GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a);
45 #define SHPS_DSM_REVISION 1
48 SHPS_DSM_FN_PCI_NUM_ENTRIES
= 0x01,
49 SHPS_DSM_FN_PCI_GET_ENTRIES
= 0x02,
50 SHPS_DSM_FN_IRQ_BASE_PRESENCE
= 0x03,
51 SHPS_DSM_FN_IRQ_DEVICE_POWER
= 0x04,
52 SHPS_DSM_FN_IRQ_DEVICE_PRESENCE
= 0x05,
56 /* NOTE: Must be in order of enum shps_dsm_fn above. */
57 SHPS_IRQ_TYPE_BASE_PRESENCE
= 0,
58 SHPS_IRQ_TYPE_DEVICE_POWER
= 1,
59 SHPS_IRQ_TYPE_DEVICE_PRESENCE
= 2,
63 static const char *const shps_gpio_names
[] = {
64 [SHPS_IRQ_TYPE_BASE_PRESENCE
] = "base_presence",
65 [SHPS_IRQ_TYPE_DEVICE_POWER
] = "device_power",
66 [SHPS_IRQ_TYPE_DEVICE_PRESENCE
] = "device_presence",
70 struct mutex lock
[SHPS_NUM_IRQS
]; /* Protects update in shps_dsm_notify_irq() */
71 struct gpio_desc
*gpio
[SHPS_NUM_IRQS
];
72 unsigned int irq
[SHPS_NUM_IRQS
];
75 #define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1)
77 static enum shps_dsm_fn
shps_dsm_fn_for_irq(enum shps_irq_type type
)
79 return SHPS_DSM_FN_IRQ_BASE_PRESENCE
+ type
;
82 static void shps_dsm_notify_irq(struct platform_device
*pdev
, enum shps_irq_type type
)
84 struct shps_device
*sdev
= platform_get_drvdata(pdev
);
85 acpi_handle handle
= ACPI_HANDLE(&pdev
->dev
);
86 union acpi_object
*result
;
87 union acpi_object param
;
90 mutex_lock(&sdev
->lock
[type
]);
92 value
= gpiod_get_value_cansleep(sdev
->gpio
[type
]);
94 mutex_unlock(&sdev
->lock
[type
]);
95 dev_err(&pdev
->dev
, "failed to get gpio: %d (irq=%d)\n", type
, value
);
99 dev_dbg(&pdev
->dev
, "IRQ notification via DSM (irq=%d, value=%d)\n", type
, value
);
101 param
.type
= ACPI_TYPE_INTEGER
;
102 param
.integer
.value
= value
;
104 result
= acpi_evaluate_dsm_typed(handle
, &shps_dsm_guid
, SHPS_DSM_REVISION
,
105 shps_dsm_fn_for_irq(type
), ¶m
, ACPI_TYPE_BUFFER
);
107 dev_err(&pdev
->dev
, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n",
110 } else if (result
->buffer
.length
!= 1 || result
->buffer
.pointer
[0] != 0) {
112 "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n",
116 mutex_unlock(&sdev
->lock
[type
]);
121 static irqreturn_t
shps_handle_irq(int irq
, void *data
)
123 struct platform_device
*pdev
= data
;
124 struct shps_device
*sdev
= platform_get_drvdata(pdev
);
127 /* Figure out which IRQ we're handling. */
128 for (type
= 0; type
< SHPS_NUM_IRQS
; type
++)
129 if (irq
== sdev
->irq
[type
])
132 /* We should have found our interrupt, if not: this is a bug. */
133 if (WARN(type
>= SHPS_NUM_IRQS
, "invalid IRQ number: %d\n", irq
))
136 /* Forward interrupt to ACPI via DSM. */
137 shps_dsm_notify_irq(pdev
, type
);
141 static int shps_setup_irq(struct platform_device
*pdev
, enum shps_irq_type type
)
143 unsigned long flags
= IRQF_ONESHOT
| IRQF_TRIGGER_FALLING
| IRQF_TRIGGER_RISING
;
144 struct shps_device
*sdev
= platform_get_drvdata(pdev
);
145 struct gpio_desc
*gpiod
;
146 acpi_handle handle
= ACPI_HANDLE(&pdev
->dev
);
147 const char *irq_name
;
148 const int dsm
= shps_dsm_fn_for_irq(type
);
152 * Only set up interrupts that we actually need: The Surface Book 3
153 * does not have a DSM for base presence, so don't set up an interrupt
156 if (!acpi_check_dsm(handle
, &shps_dsm_guid
, SHPS_DSM_REVISION
, BIT(dsm
))) {
157 dev_dbg(&pdev
->dev
, "IRQ notification via DSM not present (irq=%d)\n", type
);
161 gpiod
= devm_gpiod_get(&pdev
->dev
, shps_gpio_names
[type
], GPIOD_ASIS
);
163 return PTR_ERR(gpiod
);
165 irq
= gpiod_to_irq(gpiod
);
169 irq_name
= devm_kasprintf(&pdev
->dev
, GFP_KERNEL
, "shps-irq-%d", type
);
173 status
= devm_request_threaded_irq(&pdev
->dev
, irq
, NULL
, shps_handle_irq
,
174 flags
, irq_name
, pdev
);
178 dev_dbg(&pdev
->dev
, "set up irq %d as type %d\n", irq
, type
);
180 sdev
->gpio
[type
] = gpiod
;
181 sdev
->irq
[type
] = irq
;
186 static void surface_hotplug_remove(struct platform_device
*pdev
)
188 struct shps_device
*sdev
= platform_get_drvdata(pdev
);
191 /* Ensure that IRQs have been fully handled and won't trigger any more. */
192 for (i
= 0; i
< SHPS_NUM_IRQS
; i
++) {
193 if (sdev
->irq
[i
] != SHPS_IRQ_NOT_PRESENT
)
194 disable_irq(sdev
->irq
[i
]);
196 mutex_destroy(&sdev
->lock
[i
]);
200 static int surface_hotplug_probe(struct platform_device
*pdev
)
202 struct shps_device
*sdev
;
206 * The MSHW0153 device is also present on the Surface Laptop 3,
207 * however that doesn't have a hot-pluggable PCIe device. It also
208 * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter
211 if (gpiod_count(&pdev
->dev
, NULL
) < 0)
214 status
= devm_acpi_dev_add_driver_gpios(&pdev
->dev
, shps_acpi_gpios
);
218 sdev
= devm_kzalloc(&pdev
->dev
, sizeof(*sdev
), GFP_KERNEL
);
222 platform_set_drvdata(pdev
, sdev
);
225 * Initialize IRQs so that we can safely call surface_hotplug_remove()
228 for (i
= 0; i
< SHPS_NUM_IRQS
; i
++)
229 sdev
->irq
[i
] = SHPS_IRQ_NOT_PRESENT
;
232 for (i
= 0; i
< SHPS_NUM_IRQS
; i
++) {
233 mutex_init(&sdev
->lock
[i
]);
235 status
= shps_setup_irq(pdev
, i
);
237 dev_err(&pdev
->dev
, "failed to set up IRQ %d: %d\n", i
, status
);
242 /* Ensure everything is up-to-date. */
243 for (i
= 0; i
< SHPS_NUM_IRQS
; i
++)
244 if (sdev
->irq
[i
] != SHPS_IRQ_NOT_PRESENT
)
245 shps_dsm_notify_irq(pdev
, i
);
250 surface_hotplug_remove(pdev
);
254 static const struct acpi_device_id surface_hotplug_acpi_match
[] = {
258 MODULE_DEVICE_TABLE(acpi
, surface_hotplug_acpi_match
);
260 static struct platform_driver surface_hotplug_driver
= {
261 .probe
= surface_hotplug_probe
,
262 .remove
= surface_hotplug_remove
,
264 .name
= "surface_hotplug",
265 .acpi_match_table
= surface_hotplug_acpi_match
,
266 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
269 module_platform_driver(surface_hotplug_driver
);
271 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
272 MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices");
273 MODULE_LICENSE("GPL");