1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
2 | /* |
3 | * apple-gmux.h - microcontroller built into dual GPU MacBook Pro & Mac Pro |
4 | * Copyright (C) 2015 Lukas Wunner <lukas@wunner.de> |
5 | */ |
6 | |
7 | #ifndef LINUX_APPLE_GMUX_H |
8 | #define LINUX_APPLE_GMUX_H |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/io.h> |
12 | #include <linux/pnp.h> |
13 | |
14 | #define GMUX_ACPI_HID "APP000B" |
15 | |
16 | /* |
17 | * gmux port offsets. Many of these are not yet used, but may be in the |
18 | * future, and it's useful to have them documented here anyhow. |
19 | */ |
20 | #define GMUX_PORT_VERSION_MAJOR 0x04 |
21 | #define GMUX_PORT_VERSION_MINOR 0x05 |
22 | #define GMUX_PORT_VERSION_RELEASE 0x06 |
23 | #define GMUX_PORT_SWITCH_DISPLAY 0x10 |
24 | #define GMUX_PORT_SWITCH_GET_DISPLAY 0x11 |
25 | #define GMUX_PORT_INTERRUPT_ENABLE 0x14 |
26 | #define GMUX_PORT_INTERRUPT_STATUS 0x16 |
27 | #define GMUX_PORT_SWITCH_DDC 0x28 |
28 | #define GMUX_PORT_SWITCH_EXTERNAL 0x40 |
29 | #define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41 |
30 | #define GMUX_PORT_DISCRETE_POWER 0x50 |
31 | #define GMUX_PORT_MAX_BRIGHTNESS 0x70 |
32 | #define GMUX_PORT_BRIGHTNESS 0x74 |
33 | #define GMUX_PORT_VALUE 0xc2 |
34 | #define GMUX_PORT_READ 0xd0 |
35 | #define GMUX_PORT_WRITE 0xd4 |
36 | |
37 | #define GMUX_MMIO_PORT_SELECT 0x0e |
38 | #define GMUX_MMIO_COMMAND_SEND 0x0f |
39 | |
40 | #define GMUX_MMIO_READ 0x00 |
41 | #define GMUX_MMIO_WRITE 0x40 |
42 | |
43 | #define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4) |
44 | |
45 | enum apple_gmux_type { |
46 | APPLE_GMUX_TYPE_PIO, |
47 | APPLE_GMUX_TYPE_INDEXED, |
48 | APPLE_GMUX_TYPE_MMIO, |
49 | }; |
50 | |
51 | #if IS_ENABLED(CONFIG_APPLE_GMUX) |
52 | static inline bool apple_gmux_is_indexed(unsigned long iostart) |
53 | { |
54 | u16 val; |
55 | |
56 | outb(value: 0xaa, port: iostart + 0xcc); |
57 | outb(value: 0x55, port: iostart + 0xcd); |
58 | outb(value: 0x00, port: iostart + 0xce); |
59 | |
60 | val = inb(port: iostart + 0xcc) | (inb(port: iostart + 0xcd) << 8); |
61 | if (val == 0x55aa) |
62 | return true; |
63 | |
64 | return false; |
65 | } |
66 | |
67 | static inline bool apple_gmux_is_mmio(unsigned long iostart) |
68 | { |
69 | u8 __iomem *iomem_base = ioremap(offset: iostart, size: 16); |
70 | u8 val; |
71 | |
72 | if (!iomem_base) |
73 | return false; |
74 | |
75 | /* |
76 | * If this is 0xff, then gmux must not be present, as the gmux would |
77 | * reset it to 0x00, or it would be one of 0x1, 0x4, 0x41, 0x44 if a |
78 | * command is currently being processed. |
79 | */ |
80 | val = ioread8(iomem_base + GMUX_MMIO_COMMAND_SEND); |
81 | iounmap(addr: iomem_base); |
82 | return (val != 0xff); |
83 | } |
84 | |
85 | /** |
86 | * apple_gmux_detect() - detect if gmux is built into the machine |
87 | * |
88 | * @pnp_dev: Device to probe or NULL to use the first matching device |
89 | * @type_ret: Returns (by reference) the apple_gmux_type of the device |
90 | * |
91 | * Detect if a supported gmux device is present by actually probing it. |
92 | * This avoids the false positives returned on some models by |
93 | * apple_gmux_present(). |
94 | * |
95 | * Return: %true if a supported gmux ACPI device is detected and the kernel |
96 | * was configured with CONFIG_APPLE_GMUX, %false otherwise. |
97 | */ |
98 | static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, enum apple_gmux_type *type_ret) |
99 | { |
100 | u8 ver_major, ver_minor, ver_release; |
101 | struct device *dev = NULL; |
102 | struct acpi_device *adev; |
103 | struct resource *res; |
104 | enum apple_gmux_type type = APPLE_GMUX_TYPE_PIO; |
105 | bool ret = false; |
106 | |
107 | if (!pnp_dev) { |
108 | adev = acpi_dev_get_first_match_dev(GMUX_ACPI_HID, NULL, hrv: -1); |
109 | if (!adev) |
110 | return false; |
111 | |
112 | dev = get_device(dev: acpi_get_first_physical_node(adev)); |
113 | acpi_dev_put(adev); |
114 | if (!dev) |
115 | return false; |
116 | |
117 | pnp_dev = to_pnp_dev(dev); |
118 | } |
119 | |
120 | res = pnp_get_resource(dev: pnp_dev, IORESOURCE_IO, num: 0); |
121 | if (res && resource_size(res) >= GMUX_MIN_IO_LEN) { |
122 | /* |
123 | * Invalid version information may indicate either that the gmux |
124 | * device isn't present or that it's a new one that uses indexed io. |
125 | */ |
126 | ver_major = inb(port: res->start + GMUX_PORT_VERSION_MAJOR); |
127 | ver_minor = inb(port: res->start + GMUX_PORT_VERSION_MINOR); |
128 | ver_release = inb(port: res->start + GMUX_PORT_VERSION_RELEASE); |
129 | if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { |
130 | if (apple_gmux_is_indexed(iostart: res->start)) |
131 | type = APPLE_GMUX_TYPE_INDEXED; |
132 | else |
133 | goto out; |
134 | } |
135 | } else { |
136 | res = pnp_get_resource(dev: pnp_dev, IORESOURCE_MEM, num: 0); |
137 | if (res && apple_gmux_is_mmio(iostart: res->start)) |
138 | type = APPLE_GMUX_TYPE_MMIO; |
139 | else |
140 | goto out; |
141 | } |
142 | |
143 | if (type_ret) |
144 | *type_ret = type; |
145 | |
146 | ret = true; |
147 | out: |
148 | put_device(dev); |
149 | return ret; |
150 | } |
151 | |
152 | /** |
153 | * apple_gmux_present() - check if gmux ACPI device is present |
154 | * |
155 | * Drivers may use this to activate quirks specific to dual GPU MacBook Pros |
156 | * and Mac Pros, e.g. for deferred probing, runtime pm and backlight. |
157 | * |
158 | * Return: %true if gmux ACPI device is present and the kernel was configured |
159 | * with CONFIG_APPLE_GMUX, %false otherwise. |
160 | */ |
161 | static inline bool apple_gmux_present(void) |
162 | { |
163 | return acpi_dev_found(GMUX_ACPI_HID); |
164 | } |
165 | |
166 | #else /* !CONFIG_APPLE_GMUX */ |
167 | |
168 | static inline bool apple_gmux_present(void) |
169 | { |
170 | return false; |
171 | } |
172 | |
173 | static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, bool *indexed_ret) |
174 | { |
175 | return false; |
176 | } |
177 | |
178 | #endif /* !CONFIG_APPLE_GMUX */ |
179 | |
180 | #endif /* LINUX_APPLE_GMUX_H */ |
181 | |