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