| 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 | |