1 | // SPDX-License-Identifier: MIT |
2 | #include <linux/pci.h> |
3 | #include <linux/acpi.h> |
4 | #include <linux/slab.h> |
5 | #include <linux/mxm-wmi.h> |
6 | #include <linux/vga_switcheroo.h> |
7 | #include <drm/drm_edid.h> |
8 | #include <acpi/video.h> |
9 | |
10 | #include "nouveau_drv.h" |
11 | #include "nouveau_acpi.h" |
12 | |
13 | #define NOUVEAU_DSM_LED 0x02 |
14 | #define NOUVEAU_DSM_LED_STATE 0x00 |
15 | #define NOUVEAU_DSM_LED_OFF 0x10 |
16 | #define NOUVEAU_DSM_LED_STAMINA 0x11 |
17 | #define NOUVEAU_DSM_LED_SPEED 0x12 |
18 | |
19 | #define NOUVEAU_DSM_POWER 0x03 |
20 | #define NOUVEAU_DSM_POWER_STATE 0x00 |
21 | #define NOUVEAU_DSM_POWER_SPEED 0x01 |
22 | #define NOUVEAU_DSM_POWER_STAMINA 0x02 |
23 | |
24 | #define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A |
25 | #define NOUVEAU_DSM_OPTIMUS_FLAGS 0x1B |
26 | |
27 | #define NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 (3 << 24) |
28 | #define NOUVEAU_DSM_OPTIMUS_NO_POWERDOWN_PS3 (2 << 24) |
29 | #define NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED (1) |
30 | |
31 | #define NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN (NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 | NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED) |
32 | |
33 | /* result of the optimus caps function */ |
34 | #define OPTIMUS_ENABLED (1 << 0) |
35 | #define OPTIMUS_STATUS_MASK (3 << 3) |
36 | #define OPTIMUS_STATUS_OFF (0 << 3) |
37 | #define OPTIMUS_STATUS_ON_ENABLED (1 << 3) |
38 | #define OPTIMUS_STATUS_PWR_STABLE (3 << 3) |
39 | #define OPTIMUS_DISPLAY_HOTPLUG (1 << 6) |
40 | #define OPTIMUS_CAPS_MASK (7 << 24) |
41 | #define OPTIMUS_DYNAMIC_PWR_CAP (1 << 24) |
42 | |
43 | #define OPTIMUS_AUDIO_CAPS_MASK (3 << 27) |
44 | #define OPTIMUS_HDA_CODEC_MASK (2 << 27) /* hda bios control */ |
45 | |
46 | static struct nouveau_dsm_priv { |
47 | bool dsm_detected; |
48 | bool optimus_detected; |
49 | bool optimus_flags_detected; |
50 | bool optimus_skip_dsm; |
51 | acpi_handle dhandle; |
52 | } nouveau_dsm_priv; |
53 | |
54 | bool nouveau_is_optimus(void) { |
55 | return nouveau_dsm_priv.optimus_detected; |
56 | } |
57 | |
58 | bool nouveau_is_v1_dsm(void) { |
59 | return nouveau_dsm_priv.dsm_detected; |
60 | } |
61 | |
62 | #ifdef CONFIG_VGA_SWITCHEROO |
63 | static const guid_t nouveau_dsm_muid = |
64 | GUID_INIT(0x9D95A0A0, 0x0060, 0x4D48, |
65 | 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4); |
66 | |
67 | static const guid_t nouveau_op_dsm_muid = |
68 | GUID_INIT(0xA486D8F8, 0x0BDA, 0x471B, |
69 | 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0); |
70 | |
71 | static int nouveau_optimus_dsm(acpi_handle handle, int func, int arg, uint32_t *result) |
72 | { |
73 | int i; |
74 | union acpi_object *obj; |
75 | char args_buff[4]; |
76 | union acpi_object argv4 = { |
77 | .buffer.type = ACPI_TYPE_BUFFER, |
78 | .buffer.length = 4, |
79 | .buffer.pointer = args_buff |
80 | }; |
81 | |
82 | /* ACPI is little endian, AABBCCDD becomes {DD,CC,BB,AA} */ |
83 | for (i = 0; i < 4; i++) |
84 | args_buff[i] = (arg >> i * 8) & 0xFF; |
85 | |
86 | *result = 0; |
87 | obj = acpi_evaluate_dsm_typed(handle, guid: &nouveau_op_dsm_muid, rev: 0x00000100, |
88 | func, argv4: &argv4, ACPI_TYPE_BUFFER); |
89 | if (!obj) { |
90 | acpi_handle_info(handle, "failed to evaluate _DSM\n" ); |
91 | return AE_ERROR; |
92 | } else { |
93 | if (obj->buffer.length == 4) { |
94 | *result |= obj->buffer.pointer[0]; |
95 | *result |= (obj->buffer.pointer[1] << 8); |
96 | *result |= (obj->buffer.pointer[2] << 16); |
97 | *result |= (obj->buffer.pointer[3] << 24); |
98 | } |
99 | ACPI_FREE(obj); |
100 | } |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | /* |
106 | * On some platforms, _DSM(nouveau_op_dsm_muid, func0) has special |
107 | * requirements on the fourth parameter, so a private implementation |
108 | * instead of using acpi_check_dsm(). |
109 | */ |
110 | static int nouveau_dsm_get_optimus_functions(acpi_handle handle) |
111 | { |
112 | int result; |
113 | |
114 | /* |
115 | * Function 0 returns a Buffer containing available functions. |
116 | * The args parameter is ignored for function 0, so just put 0 in it |
117 | */ |
118 | if (nouveau_optimus_dsm(handle, func: 0, arg: 0, result: &result)) |
119 | return 0; |
120 | |
121 | /* |
122 | * ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported. |
123 | * If the n-th bit is enabled, function n is supported |
124 | */ |
125 | if (result & 1 && result & (1 << NOUVEAU_DSM_OPTIMUS_CAPS)) |
126 | return result; |
127 | return 0; |
128 | } |
129 | |
130 | static int nouveau_dsm(acpi_handle handle, int func, int arg) |
131 | { |
132 | int ret = 0; |
133 | union acpi_object *obj; |
134 | union acpi_object argv4 = { |
135 | .integer.type = ACPI_TYPE_INTEGER, |
136 | .integer.value = arg, |
137 | }; |
138 | |
139 | obj = acpi_evaluate_dsm_typed(handle, guid: &nouveau_dsm_muid, rev: 0x00000102, |
140 | func, argv4: &argv4, ACPI_TYPE_INTEGER); |
141 | if (!obj) { |
142 | acpi_handle_info(handle, "failed to evaluate _DSM\n" ); |
143 | return AE_ERROR; |
144 | } else { |
145 | if (obj->integer.value == 0x80000002) |
146 | ret = -ENODEV; |
147 | ACPI_FREE(obj); |
148 | } |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id) |
154 | { |
155 | mxm_wmi_call_mxmx(adapter: mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); |
156 | mxm_wmi_call_mxds(adapter: mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); |
157 | return nouveau_dsm(handle, NOUVEAU_DSM_LED, arg: mux_id); |
158 | } |
159 | |
160 | static int nouveau_dsm_set_discrete_state(acpi_handle handle, enum vga_switcheroo_state state) |
161 | { |
162 | int arg; |
163 | if (state == VGA_SWITCHEROO_ON) |
164 | arg = NOUVEAU_DSM_POWER_SPEED; |
165 | else |
166 | arg = NOUVEAU_DSM_POWER_STAMINA; |
167 | nouveau_dsm(handle, NOUVEAU_DSM_POWER, arg); |
168 | return 0; |
169 | } |
170 | |
171 | static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id) |
172 | { |
173 | if (!nouveau_dsm_priv.dsm_detected) |
174 | return 0; |
175 | if (id == VGA_SWITCHEROO_IGD) |
176 | return nouveau_dsm_switch_mux(handle: nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_STAMINA); |
177 | else |
178 | return nouveau_dsm_switch_mux(handle: nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_SPEED); |
179 | } |
180 | |
181 | static int nouveau_dsm_power_state(enum vga_switcheroo_client_id id, |
182 | enum vga_switcheroo_state state) |
183 | { |
184 | if (id == VGA_SWITCHEROO_IGD) |
185 | return 0; |
186 | |
187 | /* Optimus laptops have the card already disabled in |
188 | * nouveau_switcheroo_set_state */ |
189 | if (!nouveau_dsm_priv.dsm_detected) |
190 | return 0; |
191 | |
192 | return nouveau_dsm_set_discrete_state(handle: nouveau_dsm_priv.dhandle, state); |
193 | } |
194 | |
195 | static enum vga_switcheroo_client_id nouveau_dsm_get_client_id(struct pci_dev *pdev) |
196 | { |
197 | /* easy option one - intel vendor ID means Integrated */ |
198 | if (pdev->vendor == PCI_VENDOR_ID_INTEL) |
199 | return VGA_SWITCHEROO_IGD; |
200 | |
201 | /* is this device on Bus 0? - this may need improving */ |
202 | if (pdev->bus->number == 0) |
203 | return VGA_SWITCHEROO_IGD; |
204 | |
205 | return VGA_SWITCHEROO_DIS; |
206 | } |
207 | |
208 | static const struct vga_switcheroo_handler nouveau_dsm_handler = { |
209 | .switchto = nouveau_dsm_switchto, |
210 | .power_state = nouveau_dsm_power_state, |
211 | .get_client_id = nouveau_dsm_get_client_id, |
212 | }; |
213 | |
214 | static void nouveau_dsm_pci_probe(struct pci_dev *pdev, acpi_handle *dhandle_out, |
215 | bool *has_mux, bool *has_opt, |
216 | bool *has_opt_flags, bool *has_pr3) |
217 | { |
218 | acpi_handle dhandle; |
219 | bool supports_mux; |
220 | int optimus_funcs; |
221 | struct pci_dev *parent_pdev; |
222 | |
223 | if (pdev->vendor != PCI_VENDOR_ID_NVIDIA) |
224 | return; |
225 | |
226 | *has_pr3 = false; |
227 | parent_pdev = pci_upstream_bridge(dev: pdev); |
228 | if (parent_pdev) { |
229 | if (parent_pdev->bridge_d3) |
230 | *has_pr3 = pci_pr3_present(pdev: parent_pdev); |
231 | else |
232 | pci_d3cold_disable(dev: pdev); |
233 | } |
234 | |
235 | dhandle = ACPI_HANDLE(&pdev->dev); |
236 | if (!dhandle) |
237 | return; |
238 | |
239 | if (!acpi_has_method(handle: dhandle, name: "_DSM" )) |
240 | return; |
241 | |
242 | supports_mux = acpi_check_dsm(handle: dhandle, guid: &nouveau_dsm_muid, rev: 0x00000102, |
243 | funcs: 1 << NOUVEAU_DSM_POWER); |
244 | optimus_funcs = nouveau_dsm_get_optimus_functions(handle: dhandle); |
245 | |
246 | /* Does not look like a Nvidia device. */ |
247 | if (!supports_mux && !optimus_funcs) |
248 | return; |
249 | |
250 | *dhandle_out = dhandle; |
251 | *has_mux = supports_mux; |
252 | *has_opt = !!optimus_funcs; |
253 | *has_opt_flags = optimus_funcs & (1 << NOUVEAU_DSM_OPTIMUS_FLAGS); |
254 | |
255 | if (optimus_funcs) { |
256 | uint32_t result; |
257 | nouveau_optimus_dsm(handle: dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, arg: 0, |
258 | result: &result); |
259 | dev_info(&pdev->dev, "optimus capabilities: %s, status %s%s\n" , |
260 | (result & OPTIMUS_ENABLED) ? "enabled" : "disabled" , |
261 | (result & OPTIMUS_DYNAMIC_PWR_CAP) ? "dynamic power, " : "" , |
262 | (result & OPTIMUS_HDA_CODEC_MASK) ? "hda bios codec supported" : "" ); |
263 | } |
264 | } |
265 | |
266 | static bool nouveau_dsm_detect(void) |
267 | { |
268 | char acpi_method_name[255] = { 0 }; |
269 | struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; |
270 | struct pci_dev *pdev = NULL; |
271 | acpi_handle dhandle = NULL; |
272 | bool has_mux = false; |
273 | bool has_optimus = false; |
274 | bool has_optimus_flags = false; |
275 | bool has_power_resources = false; |
276 | int vga_count = 0; |
277 | bool guid_valid; |
278 | bool ret = false; |
279 | |
280 | /* lookup the MXM GUID */ |
281 | guid_valid = mxm_wmi_supported(); |
282 | |
283 | if (guid_valid) |
284 | printk("MXM: GUID detected in BIOS\n" ); |
285 | |
286 | /* now do DSM detection */ |
287 | while ((pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, from: pdev))) { |
288 | if ((pdev->class != PCI_CLASS_DISPLAY_VGA << 8) && |
289 | (pdev->class != PCI_CLASS_DISPLAY_3D << 8)) |
290 | continue; |
291 | |
292 | vga_count++; |
293 | |
294 | nouveau_dsm_pci_probe(pdev, dhandle_out: &dhandle, has_mux: &has_mux, has_opt: &has_optimus, |
295 | has_opt_flags: &has_optimus_flags, has_pr3: &has_power_resources); |
296 | } |
297 | |
298 | /* find the optimus DSM or the old v1 DSM */ |
299 | if (has_optimus) { |
300 | nouveau_dsm_priv.dhandle = dhandle; |
301 | acpi_get_name(object: nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME, |
302 | ret_path_ptr: &buffer); |
303 | pr_info("VGA switcheroo: detected Optimus DSM method %s handle\n" , |
304 | acpi_method_name); |
305 | if (has_power_resources) |
306 | pr_info("nouveau: detected PR support, will not use DSM\n" ); |
307 | nouveau_dsm_priv.optimus_detected = true; |
308 | nouveau_dsm_priv.optimus_flags_detected = has_optimus_flags; |
309 | nouveau_dsm_priv.optimus_skip_dsm = has_power_resources; |
310 | ret = true; |
311 | } else if (vga_count == 2 && has_mux && guid_valid) { |
312 | nouveau_dsm_priv.dhandle = dhandle; |
313 | acpi_get_name(object: nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME, |
314 | ret_path_ptr: &buffer); |
315 | pr_info("VGA switcheroo: detected DSM switching method %s handle\n" , |
316 | acpi_method_name); |
317 | nouveau_dsm_priv.dsm_detected = true; |
318 | ret = true; |
319 | } |
320 | |
321 | |
322 | return ret; |
323 | } |
324 | |
325 | void nouveau_register_dsm_handler(void) |
326 | { |
327 | bool r; |
328 | |
329 | r = nouveau_dsm_detect(); |
330 | if (!r) |
331 | return; |
332 | |
333 | vga_switcheroo_register_handler(handler: &nouveau_dsm_handler, handler_flags: 0); |
334 | } |
335 | |
336 | /* Must be called for Optimus models before the card can be turned off */ |
337 | void nouveau_switcheroo_optimus_dsm(void) |
338 | { |
339 | u32 result = 0; |
340 | if (!nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.optimus_skip_dsm) |
341 | return; |
342 | |
343 | if (nouveau_dsm_priv.optimus_flags_detected) |
344 | nouveau_optimus_dsm(handle: nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS, |
345 | arg: 0x3, result: &result); |
346 | |
347 | nouveau_optimus_dsm(handle: nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, |
348 | NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN, result: &result); |
349 | |
350 | } |
351 | |
352 | void nouveau_unregister_dsm_handler(void) |
353 | { |
354 | if (nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.dsm_detected) |
355 | vga_switcheroo_unregister_handler(); |
356 | } |
357 | #else |
358 | void nouveau_register_dsm_handler(void) {} |
359 | void nouveau_unregister_dsm_handler(void) {} |
360 | void nouveau_switcheroo_optimus_dsm(void) {} |
361 | #endif |
362 | |
363 | void * |
364 | nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector) |
365 | { |
366 | struct acpi_device *acpidev; |
367 | int type, ret; |
368 | void *edid; |
369 | |
370 | switch (connector->connector_type) { |
371 | case DRM_MODE_CONNECTOR_LVDS: |
372 | case DRM_MODE_CONNECTOR_eDP: |
373 | type = ACPI_VIDEO_DISPLAY_LCD; |
374 | break; |
375 | default: |
376 | return NULL; |
377 | } |
378 | |
379 | acpidev = ACPI_COMPANION(dev->dev); |
380 | if (!acpidev) |
381 | return NULL; |
382 | |
383 | ret = acpi_video_get_edid(device: acpidev, type, device_id: -1, edid: &edid); |
384 | if (ret < 0) |
385 | return NULL; |
386 | |
387 | return kmemdup(p: edid, EDID_LENGTH, GFP_KERNEL); |
388 | } |
389 | |
390 | bool nouveau_acpi_video_backlight_use_native(void) |
391 | { |
392 | return acpi_video_backlight_use_native(); |
393 | } |
394 | |
395 | void nouveau_acpi_video_register_backlight(void) |
396 | { |
397 | acpi_video_register_backlight(); |
398 | } |
399 | |