| 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(0xaa, iostart + 0xcc); | 
|---|
| 57 | outb(0x55, iostart + 0xcd); | 
|---|
| 58 | outb(0x00, iostart + 0xce); | 
|---|
| 59 |  | 
|---|
| 60 | val = inb(iostart + 0xcc) | (inb(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(iostart, 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(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, -1); | 
|---|
| 109 | if (!adev) | 
|---|
| 110 | return false; | 
|---|
| 111 |  | 
|---|
| 112 | dev = get_device(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(pnp_dev, IORESOURCE_IO, 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(res->start + GMUX_PORT_VERSION_MAJOR); | 
|---|
| 127 | ver_minor = inb(res->start + GMUX_PORT_VERSION_MINOR); | 
|---|
| 128 | ver_release = inb(res->start + GMUX_PORT_VERSION_RELEASE); | 
|---|
| 129 | if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { | 
|---|
| 130 | if (apple_gmux_is_indexed(res->start)) | 
|---|
| 131 | type = APPLE_GMUX_TYPE_INDEXED; | 
|---|
| 132 | else | 
|---|
| 133 | goto out; | 
|---|
| 134 | } | 
|---|
| 135 | } else { | 
|---|
| 136 | res = pnp_get_resource(pnp_dev, IORESOURCE_MEM, 0); | 
|---|
| 137 | if (res && apple_gmux_is_mmio(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 |  | 
|---|