1 /* 2 * Gmux driver for Apple laptops 3 * 4 * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 12 13 #include <linux/module.h> 14 #include <linux/kernel.h> 15 #include <linux/init.h> 16 #include <linux/backlight.h> 17 #include <linux/acpi.h> 18 #include <linux/pnp.h> 19 #include <linux/apple_bl.h> 20 #include <linux/slab.h> 21 #include <acpi/video.h> 22 #include <asm/io.h> 23 24 struct apple_gmux_data { 25 unsigned long iostart; 26 unsigned long iolen; 27 28 struct backlight_device *bdev; 29 }; 30 31 /* 32 * gmux port offsets. Many of these are not yet used, but may be in the 33 * future, and it's useful to have them documented here anyhow. 34 */ 35 #define GMUX_PORT_VERSION_MAJOR 0x04 36 #define GMUX_PORT_VERSION_MINOR 0x05 37 #define GMUX_PORT_VERSION_RELEASE 0x06 38 #define GMUX_PORT_SWITCH_DISPLAY 0x10 39 #define GMUX_PORT_SWITCH_GET_DISPLAY 0x11 40 #define GMUX_PORT_INTERRUPT_ENABLE 0x14 41 #define GMUX_PORT_INTERRUPT_STATUS 0x16 42 #define GMUX_PORT_SWITCH_DDC 0x28 43 #define GMUX_PORT_SWITCH_EXTERNAL 0x40 44 #define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41 45 #define GMUX_PORT_DISCRETE_POWER 0x50 46 #define GMUX_PORT_MAX_BRIGHTNESS 0x70 47 #define GMUX_PORT_BRIGHTNESS 0x74 48 49 #define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4) 50 51 #define GMUX_INTERRUPT_ENABLE 0xff 52 #define GMUX_INTERRUPT_DISABLE 0x00 53 54 #define GMUX_INTERRUPT_STATUS_ACTIVE 0 55 #define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0) 56 #define GMUX_INTERRUPT_STATUS_POWER (1 << 2) 57 #define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3) 58 59 #define GMUX_BRIGHTNESS_MASK 0x00ffffff 60 #define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK 61 62 static inline u8 gmux_read8(struct apple_gmux_data *gmux_data, int port) 63 { 64 return inb(gmux_data->iostart + port); 65 } 66 67 static inline void gmux_write8(struct apple_gmux_data *gmux_data, int port, 68 u8 val) 69 { 70 outb(val, gmux_data->iostart + port); 71 } 72 73 static inline u32 gmux_read32(struct apple_gmux_data *gmux_data, int port) 74 { 75 return inl(gmux_data->iostart + port); 76 } 77 78 static int gmux_get_brightness(struct backlight_device *bd) 79 { 80 struct apple_gmux_data *gmux_data = bl_get_data(bd); 81 return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) & 82 GMUX_BRIGHTNESS_MASK; 83 } 84 85 static int gmux_update_status(struct backlight_device *bd) 86 { 87 struct apple_gmux_data *gmux_data = bl_get_data(bd); 88 u32 brightness = bd->props.brightness; 89 90 if (bd->props.state & BL_CORE_SUSPENDED) 91 return 0; 92 93 /* 94 * Older gmux versions require writing out lower bytes first then 95 * setting the upper byte to 0 to flush the values. Newer versions 96 * accept a single u32 write, but the old method also works, so we 97 * just use the old method for all gmux versions. 98 */ 99 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS, brightness); 100 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 1, brightness >> 8); 101 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 2, brightness >> 16); 102 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 3, 0); 103 104 return 0; 105 } 106 107 static const struct backlight_ops gmux_bl_ops = { 108 .options = BL_CORE_SUSPENDRESUME, 109 .get_brightness = gmux_get_brightness, 110 .update_status = gmux_update_status, 111 }; 112 113 static int __devinit gmux_probe(struct pnp_dev *pnp, 114 const struct pnp_device_id *id) 115 { 116 struct apple_gmux_data *gmux_data; 117 struct resource *res; 118 struct backlight_properties props; 119 struct backlight_device *bdev; 120 u8 ver_major, ver_minor, ver_release; 121 int ret = -ENXIO; 122 123 gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); 124 if (!gmux_data) 125 return -ENOMEM; 126 pnp_set_drvdata(pnp, gmux_data); 127 128 res = pnp_get_resource(pnp, IORESOURCE_IO, 0); 129 if (!res) { 130 pr_err("Failed to find gmux I/O resource\n"); 131 goto err_free; 132 } 133 134 gmux_data->iostart = res->start; 135 gmux_data->iolen = res->end - res->start; 136 137 if (gmux_data->iolen < GMUX_MIN_IO_LEN) { 138 pr_err("gmux I/O region too small (%lu < %u)\n", 139 gmux_data->iolen, GMUX_MIN_IO_LEN); 140 goto err_free; 141 } 142 143 if (!request_region(gmux_data->iostart, gmux_data->iolen, 144 "Apple gmux")) { 145 pr_err("gmux I/O already in use\n"); 146 goto err_free; 147 } 148 149 /* 150 * On some machines the gmux is in ACPI even thought the machine 151 * doesn't really have a gmux. Check for invalid version information 152 * to detect this. 153 */ 154 ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR); 155 ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR); 156 ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE); 157 if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { 158 pr_info("gmux device not present\n"); 159 ret = -ENODEV; 160 goto err_release; 161 } 162 163 pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor, 164 ver_release); 165 166 memset(&props, 0, sizeof(props)); 167 props.type = BACKLIGHT_PLATFORM; 168 props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); 169 170 /* 171 * Currently it's assumed that the maximum brightness is less than 172 * 2^24 for compatibility with old gmux versions. Cap the max 173 * brightness at this value, but print a warning if the hardware 174 * reports something higher so that it can be fixed. 175 */ 176 if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS)) 177 props.max_brightness = GMUX_MAX_BRIGHTNESS; 178 179 bdev = backlight_device_register("gmux_backlight", &pnp->dev, 180 gmux_data, &gmux_bl_ops, &props); 181 if (IS_ERR(bdev)) { 182 ret = PTR_ERR(bdev); 183 goto err_release; 184 } 185 186 gmux_data->bdev = bdev; 187 bdev->props.brightness = gmux_get_brightness(bdev); 188 backlight_update_status(bdev); 189 190 /* 191 * The backlight situation on Macs is complicated. If the gmux is 192 * present it's the best choice, because it always works for 193 * backlight control and supports more levels than other options. 194 * Disable the other backlight choices. 195 */ 196 acpi_video_unregister(); 197 apple_bl_unregister(); 198 199 return 0; 200 201 err_release: 202 release_region(gmux_data->iostart, gmux_data->iolen); 203 err_free: 204 kfree(gmux_data); 205 return ret; 206 } 207 208 static void __devexit gmux_remove(struct pnp_dev *pnp) 209 { 210 struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); 211 212 backlight_device_unregister(gmux_data->bdev); 213 release_region(gmux_data->iostart, gmux_data->iolen); 214 kfree(gmux_data); 215 216 acpi_video_register(); 217 apple_bl_register(); 218 } 219 220 static const struct pnp_device_id gmux_device_ids[] = { 221 {"APP000B", 0}, 222 {"", 0} 223 }; 224 225 static struct pnp_driver gmux_pnp_driver = { 226 .name = "apple-gmux", 227 .probe = gmux_probe, 228 .remove = __devexit_p(gmux_remove), 229 .id_table = gmux_device_ids, 230 }; 231 232 static int __init apple_gmux_init(void) 233 { 234 return pnp_register_driver(&gmux_pnp_driver); 235 } 236 237 static void __exit apple_gmux_exit(void) 238 { 239 pnp_unregister_driver(&gmux_pnp_driver); 240 } 241 242 module_init(apple_gmux_init); 243 module_exit(apple_gmux_exit); 244 245 MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); 246 MODULE_DESCRIPTION("Apple Gmux Driver"); 247 MODULE_LICENSE("GPL"); 248 MODULE_DEVICE_TABLE(pnp, gmux_device_ids); 249