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 <linux/platform_device.h> 28 #include <acpi/video.h> 29 30 static struct backlight_device *apple_backlight_device; 31 32 struct hw_data { 33 /* I/O resource to allocate. */ 34 unsigned long iostart; 35 unsigned long iolen; 36 /* Backlight operations structure. */ 37 const struct backlight_ops backlight_ops; 38 void (*set_brightness)(int); 39 }; 40 41 static const struct hw_data *hw_data; 42 43 /* Module parameters. */ 44 static int debug; 45 module_param_named(debug, debug, int, 0644); 46 MODULE_PARM_DESC(debug, "Set to one to enable debugging messages."); 47 48 /* 49 * Implementation for machines with Intel chipset. 50 */ 51 static void intel_chipset_set_brightness(int intensity) 52 { 53 outb(0x04 | (intensity << 4), 0xb3); 54 outb(0xbf, 0xb2); 55 } 56 57 static int intel_chipset_send_intensity(struct backlight_device *bd) 58 { 59 int intensity = bd->props.brightness; 60 61 if (debug) 62 pr_debug("setting brightness to %d\n", intensity); 63 64 intel_chipset_set_brightness(intensity); 65 return 0; 66 } 67 68 static int intel_chipset_get_intensity(struct backlight_device *bd) 69 { 70 int intensity; 71 72 outb(0x03, 0xb3); 73 outb(0xbf, 0xb2); 74 intensity = inb(0xb3) >> 4; 75 76 if (debug) 77 pr_debug("read brightness of %d\n", intensity); 78 79 return intensity; 80 } 81 82 static const struct hw_data intel_chipset_data = { 83 .iostart = 0xb2, 84 .iolen = 2, 85 .backlight_ops = { 86 .options = BL_CORE_SUSPENDRESUME, 87 .get_brightness = intel_chipset_get_intensity, 88 .update_status = intel_chipset_send_intensity, 89 }, 90 .set_brightness = intel_chipset_set_brightness, 91 }; 92 93 /* 94 * Implementation for machines with Nvidia chipset. 95 */ 96 static void nvidia_chipset_set_brightness(int intensity) 97 { 98 outb(0x04 | (intensity << 4), 0x52f); 99 outb(0xbf, 0x52e); 100 } 101 102 static int nvidia_chipset_send_intensity(struct backlight_device *bd) 103 { 104 int intensity = bd->props.brightness; 105 106 if (debug) 107 pr_debug("setting brightness to %d\n", intensity); 108 109 nvidia_chipset_set_brightness(intensity); 110 return 0; 111 } 112 113 static int nvidia_chipset_get_intensity(struct backlight_device *bd) 114 { 115 int intensity; 116 117 outb(0x03, 0x52f); 118 outb(0xbf, 0x52e); 119 intensity = inb(0x52f) >> 4; 120 121 if (debug) 122 pr_debug("read brightness of %d\n", intensity); 123 124 return intensity; 125 } 126 127 static const struct hw_data nvidia_chipset_data = { 128 .iostart = 0x52e, 129 .iolen = 2, 130 .backlight_ops = { 131 .options = BL_CORE_SUSPENDRESUME, 132 .get_brightness = nvidia_chipset_get_intensity, 133 .update_status = nvidia_chipset_send_intensity 134 }, 135 .set_brightness = nvidia_chipset_set_brightness, 136 }; 137 138 static int apple_bl_probe(struct platform_device *pdev) 139 { 140 struct backlight_properties props; 141 struct pci_dev *host; 142 int intensity; 143 144 host = pci_get_domain_bus_and_slot(0, 0, 0); 145 146 if (!host) { 147 pr_err("unable to find PCI host\n"); 148 return -ENODEV; 149 } 150 151 if (host->vendor == PCI_VENDOR_ID_INTEL) 152 hw_data = &intel_chipset_data; 153 else if (host->vendor == PCI_VENDOR_ID_NVIDIA) 154 hw_data = &nvidia_chipset_data; 155 156 pci_dev_put(host); 157 158 if (!hw_data) { 159 pr_err("unknown hardware\n"); 160 return -ENODEV; 161 } 162 163 /* Check that the hardware responds - this may not work under EFI */ 164 165 intensity = hw_data->backlight_ops.get_brightness(NULL); 166 167 if (!intensity) { 168 hw_data->set_brightness(1); 169 if (!hw_data->backlight_ops.get_brightness(NULL)) 170 return -ENODEV; 171 172 hw_data->set_brightness(0); 173 } 174 175 if (!request_region(hw_data->iostart, hw_data->iolen, 176 "Apple backlight")) 177 return -ENXIO; 178 179 memset(&props, 0, sizeof(struct backlight_properties)); 180 props.type = BACKLIGHT_PLATFORM; 181 props.max_brightness = 15; 182 apple_backlight_device = backlight_device_register("apple_backlight", 183 NULL, NULL, &hw_data->backlight_ops, &props); 184 185 if (IS_ERR(apple_backlight_device)) { 186 release_region(hw_data->iostart, hw_data->iolen); 187 return PTR_ERR(apple_backlight_device); 188 } 189 190 apple_backlight_device->props.brightness = 191 hw_data->backlight_ops.get_brightness(apple_backlight_device); 192 backlight_update_status(apple_backlight_device); 193 194 return 0; 195 } 196 197 static void apple_bl_remove(struct platform_device *pdev) 198 { 199 backlight_device_unregister(apple_backlight_device); 200 201 release_region(hw_data->iostart, hw_data->iolen); 202 hw_data = NULL; 203 } 204 205 static const struct acpi_device_id apple_bl_ids[] = { 206 {"APP0002", 0}, 207 {"", 0}, 208 }; 209 210 static struct platform_driver apple_bl_driver = { 211 .probe = apple_bl_probe, 212 .remove = apple_bl_remove, 213 .driver = { 214 .name = "Apple backlight", 215 .acpi_match_table = apple_bl_ids, 216 }, 217 }; 218 219 static int __init apple_bl_init(void) 220 { 221 /* 222 * Use ACPI video detection code to see if this driver should register 223 * or if another driver, e.g. the apple-gmux driver should be used. 224 */ 225 if (acpi_video_get_backlight_type() != acpi_backlight_vendor) 226 return -ENODEV; 227 228 return platform_driver_register(&apple_bl_driver); 229 } 230 231 static void __exit apple_bl_exit(void) 232 { 233 platform_driver_unregister(&apple_bl_driver); 234 } 235 236 module_init(apple_bl_init); 237 module_exit(apple_bl_exit); 238 239 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); 240 MODULE_DESCRIPTION("Apple Backlight Driver"); 241 MODULE_LICENSE("GPL"); 242 MODULE_DEVICE_TABLE(acpi, apple_bl_ids); 243 MODULE_ALIAS("mbp_nvidia_bl"); 244