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 /* 91 * Older gmux versions require writing out lower bytes first then 92 * setting the upper byte to 0 to flush the values. Newer versions 93 * accept a single u32 write, but the old method also works, so we 94 * just use the old method for all gmux versions. 95 */ 96 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS, brightness); 97 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 1, brightness >> 8); 98 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 2, brightness >> 16); 99 gmux_write8(gmux_data, GMUX_PORT_BRIGHTNESS + 3, 0); 100 101 return 0; 102 } 103 104 static const struct backlight_ops gmux_bl_ops = { 105 .get_brightness = gmux_get_brightness, 106 .update_status = gmux_update_status, 107 }; 108 109 static int __devinit gmux_probe(struct pnp_dev *pnp, 110 const struct pnp_device_id *id) 111 { 112 struct apple_gmux_data *gmux_data; 113 struct resource *res; 114 struct backlight_properties props; 115 struct backlight_device *bdev; 116 u8 ver_major, ver_minor, ver_release; 117 int ret = -ENXIO; 118 119 gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); 120 if (!gmux_data) 121 return -ENOMEM; 122 pnp_set_drvdata(pnp, gmux_data); 123 124 res = pnp_get_resource(pnp, IORESOURCE_IO, 0); 125 if (!res) { 126 pr_err("Failed to find gmux I/O resource\n"); 127 goto err_free; 128 } 129 130 gmux_data->iostart = res->start; 131 gmux_data->iolen = res->end - res->start; 132 133 if (gmux_data->iolen < GMUX_MIN_IO_LEN) { 134 pr_err("gmux I/O region too small (%lu < %u)\n", 135 gmux_data->iolen, GMUX_MIN_IO_LEN); 136 goto err_free; 137 } 138 139 if (!request_region(gmux_data->iostart, gmux_data->iolen, 140 "Apple gmux")) { 141 pr_err("gmux I/O already in use\n"); 142 goto err_free; 143 } 144 145 /* 146 * On some machines the gmux is in ACPI even thought the machine 147 * doesn't really have a gmux. Check for invalid version information 148 * to detect this. 149 */ 150 ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR); 151 ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR); 152 ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE); 153 if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { 154 pr_info("gmux device not present\n"); 155 ret = -ENODEV; 156 goto err_release; 157 } 158 159 pr_info("Found gmux version %d.%d.%d\n", ver_major, ver_minor, 160 ver_release); 161 162 memset(&props, 0, sizeof(props)); 163 props.type = BACKLIGHT_PLATFORM; 164 props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); 165 166 /* 167 * Currently it's assumed that the maximum brightness is less than 168 * 2^24 for compatibility with old gmux versions. Cap the max 169 * brightness at this value, but print a warning if the hardware 170 * reports something higher so that it can be fixed. 171 */ 172 if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS)) 173 props.max_brightness = GMUX_MAX_BRIGHTNESS; 174 175 bdev = backlight_device_register("gmux_backlight", &pnp->dev, 176 gmux_data, &gmux_bl_ops, &props); 177 if (IS_ERR(bdev)) { 178 ret = PTR_ERR(bdev); 179 goto err_release; 180 } 181 182 gmux_data->bdev = bdev; 183 bdev->props.brightness = gmux_get_brightness(bdev); 184 backlight_update_status(bdev); 185 186 /* 187 * The backlight situation on Macs is complicated. If the gmux is 188 * present it's the best choice, because it always works for 189 * backlight control and supports more levels than other options. 190 * Disable the other backlight choices. 191 */ 192 acpi_video_unregister(); 193 apple_bl_unregister(); 194 195 return 0; 196 197 err_release: 198 release_region(gmux_data->iostart, gmux_data->iolen); 199 err_free: 200 kfree(gmux_data); 201 return ret; 202 } 203 204 static void __devexit gmux_remove(struct pnp_dev *pnp) 205 { 206 struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); 207 208 backlight_device_unregister(gmux_data->bdev); 209 release_region(gmux_data->iostart, gmux_data->iolen); 210 kfree(gmux_data); 211 212 acpi_video_register(); 213 apple_bl_register(); 214 } 215 216 static const struct pnp_device_id gmux_device_ids[] = { 217 {"APP000B", 0}, 218 {"", 0} 219 }; 220 221 static struct pnp_driver gmux_pnp_driver = { 222 .name = "apple-gmux", 223 .probe = gmux_probe, 224 .remove = __devexit_p(gmux_remove), 225 .id_table = gmux_device_ids, 226 }; 227 228 static int __init apple_gmux_init(void) 229 { 230 return pnp_register_driver(&gmux_pnp_driver); 231 } 232 233 static void __exit apple_gmux_exit(void) 234 { 235 pnp_unregister_driver(&gmux_pnp_driver); 236 } 237 238 module_init(apple_gmux_init); 239 module_exit(apple_gmux_exit); 240 241 MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); 242 MODULE_DESCRIPTION("Apple Gmux Driver"); 243 MODULE_LICENSE("GPL"); 244 MODULE_DEVICE_TABLE(pnp, gmux_device_ids); 245