1 /* 2 * Backlight Driver for Intel-based Apples 3 * 4 * Copyright (c) Red Hat <mjg@redhat.com> 5 * Based on code from Pommed: 6 * Copyright (C) 2006 Nicolas Boichat <nicolas @boichat.ch> 7 * Copyright (C) 2006 Felipe Alfaro Solana <felipe_alfaro @linuxmail.org> 8 * Copyright (C) 2007 Julien BLACHE <jb@jblache.org> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 as 12 * published by the Free Software Foundation. 13 * 14 * This driver triggers SMIs which cause the firmware to change the 15 * backlight brightness. This is icky in many ways, but it's impractical to 16 * get at the firmware code in order to figure out what it's actually doing. 17 */ 18 19 #include <linux/module.h> 20 #include <linux/kernel.h> 21 #include <linux/init.h> 22 #include <linux/backlight.h> 23 #include <linux/err.h> 24 #include <linux/io.h> 25 #include <linux/pci.h> 26 #include <linux/acpi.h> 27 #include <linux/atomic.h> 28 29 static struct backlight_device *apple_backlight_device; 30 31 struct hw_data { 32 /* I/O resource to allocate. */ 33 unsigned long iostart; 34 unsigned long iolen; 35 /* Backlight operations structure. */ 36 const struct backlight_ops backlight_ops; 37 void (*set_brightness)(int); 38 }; 39 40 static const struct hw_data *hw_data; 41 42 #define DRIVER "apple_backlight: " 43 44 /* Module parameters. */ 45 static int debug; 46 module_param_named(debug, debug, int, 0644); 47 MODULE_PARM_DESC(debug, "Set to one to enable debugging messages."); 48 49 /* 50 * Implementation for machines with Intel chipset. 51 */ 52 static void intel_chipset_set_brightness(int intensity) 53 { 54 outb(0x04 | (intensity << 4), 0xb3); 55 outb(0xbf, 0xb2); 56 } 57 58 static int intel_chipset_send_intensity(struct backlight_device *bd) 59 { 60 int intensity = bd->props.brightness; 61 62 if (debug) 63 printk(KERN_DEBUG DRIVER "setting brightness to %d\n", 64 intensity); 65 66 intel_chipset_set_brightness(intensity); 67 return 0; 68 } 69 70 static int intel_chipset_get_intensity(struct backlight_device *bd) 71 { 72 int intensity; 73 74 outb(0x03, 0xb3); 75 outb(0xbf, 0xb2); 76 intensity = inb(0xb3) >> 4; 77 78 if (debug) 79 printk(KERN_DEBUG DRIVER "read brightness of %d\n", 80 intensity); 81 82 return intensity; 83 } 84 85 static const struct hw_data intel_chipset_data = { 86 .iostart = 0xb2, 87 .iolen = 2, 88 .backlight_ops = { 89 .options = BL_CORE_SUSPENDRESUME, 90 .get_brightness = intel_chipset_get_intensity, 91 .update_status = intel_chipset_send_intensity, 92 }, 93 .set_brightness = intel_chipset_set_brightness, 94 }; 95 96 /* 97 * Implementation for machines with Nvidia chipset. 98 */ 99 static void nvidia_chipset_set_brightness(int intensity) 100 { 101 outb(0x04 | (intensity << 4), 0x52f); 102 outb(0xbf, 0x52e); 103 } 104 105 static int nvidia_chipset_send_intensity(struct backlight_device *bd) 106 { 107 int intensity = bd->props.brightness; 108 109 if (debug) 110 printk(KERN_DEBUG DRIVER "setting brightness to %d\n", 111 intensity); 112 113 nvidia_chipset_set_brightness(intensity); 114 return 0; 115 } 116 117 static int nvidia_chipset_get_intensity(struct backlight_device *bd) 118 { 119 int intensity; 120 121 outb(0x03, 0x52f); 122 outb(0xbf, 0x52e); 123 intensity = inb(0x52f) >> 4; 124 125 if (debug) 126 printk(KERN_DEBUG DRIVER "read brightness of %d\n", 127 intensity); 128 129 return intensity; 130 } 131 132 static const struct hw_data nvidia_chipset_data = { 133 .iostart = 0x52e, 134 .iolen = 2, 135 .backlight_ops = { 136 .options = BL_CORE_SUSPENDRESUME, 137 .get_brightness = nvidia_chipset_get_intensity, 138 .update_status = nvidia_chipset_send_intensity 139 }, 140 .set_brightness = nvidia_chipset_set_brightness, 141 }; 142 143 static int __devinit apple_bl_add(struct acpi_device *dev) 144 { 145 struct backlight_properties props; 146 struct pci_dev *host; 147 int intensity; 148 149 host = pci_get_bus_and_slot(0, 0); 150 151 if (!host) { 152 printk(KERN_ERR DRIVER "unable to find PCI host\n"); 153 return -ENODEV; 154 } 155 156 if (host->vendor == PCI_VENDOR_ID_INTEL) 157 hw_data = &intel_chipset_data; 158 else if (host->vendor == PCI_VENDOR_ID_NVIDIA) 159 hw_data = &nvidia_chipset_data; 160 161 pci_dev_put(host); 162 163 if (!hw_data) { 164 printk(KERN_ERR DRIVER "unknown hardware\n"); 165 return -ENODEV; 166 } 167 168 /* Check that the hardware responds - this may not work under EFI */ 169 170 intensity = hw_data->backlight_ops.get_brightness(NULL); 171 172 if (!intensity) { 173 hw_data->set_brightness(1); 174 if (!hw_data->backlight_ops.get_brightness(NULL)) 175 return -ENODEV; 176 177 hw_data->set_brightness(0); 178 } 179 180 if (!request_region(hw_data->iostart, hw_data->iolen, 181 "Apple backlight")) 182 return -ENXIO; 183 184 memset(&props, 0, sizeof(struct backlight_properties)); 185 props.type = BACKLIGHT_PLATFORM; 186 props.max_brightness = 15; 187 apple_backlight_device = backlight_device_register("apple_backlight", 188 NULL, NULL, &hw_data->backlight_ops, &props); 189 190 if (IS_ERR(apple_backlight_device)) { 191 release_region(hw_data->iostart, hw_data->iolen); 192 return PTR_ERR(apple_backlight_device); 193 } 194 195 apple_backlight_device->props.brightness = 196 hw_data->backlight_ops.get_brightness(apple_backlight_device); 197 backlight_update_status(apple_backlight_device); 198 199 return 0; 200 } 201 202 static int __devexit apple_bl_remove(struct acpi_device *dev, int type) 203 { 204 backlight_device_unregister(apple_backlight_device); 205 206 release_region(hw_data->iostart, hw_data->iolen); 207 hw_data = NULL; 208 return 0; 209 } 210 211 static const struct acpi_device_id apple_bl_ids[] = { 212 {"APP0002", 0}, 213 {"", 0}, 214 }; 215 216 static struct acpi_driver apple_bl_driver = { 217 .name = "Apple backlight", 218 .ids = apple_bl_ids, 219 .ops = { 220 .add = apple_bl_add, 221 .remove = apple_bl_remove, 222 }, 223 }; 224 225 static atomic_t apple_bl_registered = ATOMIC_INIT(0); 226 227 int apple_bl_register(void) 228 { 229 if (atomic_xchg(&apple_bl_registered, 1) == 0) 230 return acpi_bus_register_driver(&apple_bl_driver); 231 232 return 0; 233 } 234 EXPORT_SYMBOL_GPL(apple_bl_register); 235 236 void apple_bl_unregister(void) 237 { 238 if (atomic_xchg(&apple_bl_registered, 0) == 1) 239 acpi_bus_unregister_driver(&apple_bl_driver); 240 } 241 EXPORT_SYMBOL_GPL(apple_bl_unregister); 242 243 static int __init apple_bl_init(void) 244 { 245 return apple_bl_register(); 246 } 247 248 static void __exit apple_bl_exit(void) 249 { 250 apple_bl_unregister(); 251 } 252 253 module_init(apple_bl_init); 254 module_exit(apple_bl_exit); 255 256 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); 257 MODULE_DESCRIPTION("Apple Backlight Driver"); 258 MODULE_LICENSE("GPL"); 259 MODULE_DEVICE_TABLE(acpi, apple_bl_ids); 260 MODULE_ALIAS("mbp_nvidia_bl"); 261