diff options
Diffstat (limited to 'drivers/acpi/acpi_video.c')
-rw-r--r-- | drivers/acpi/acpi_video.c | 2275 |
1 files changed, 2275 insertions, 0 deletions
diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c new file mode 100644 index 000000000000..db4f353e1e5c --- /dev/null +++ b/drivers/acpi/acpi_video.c @@ -0,0 +1,2275 @@ +/* + * video.c - ACPI Video Driver + * + * Copyright (C) 2004 Luming Yu <luming.yu@intel.com> + * Copyright (C) 2004 Bruno Ducrot <ducrot@poupinou.org> + * Copyright (C) 2006 Thomas Tuttle <linux-kernel@ttuttle.net> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/input.h> +#include <linux/backlight.h> +#include <linux/thermal.h> +#include <linux/sort.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/slab.h> +#include <linux/dmi.h> +#include <linux/suspend.h> +#include <linux/acpi.h> +#include <acpi/video.h> +#include <asm/uaccess.h> + +#define PREFIX "ACPI: " + +#define ACPI_VIDEO_BUS_NAME "Video Bus" +#define ACPI_VIDEO_DEVICE_NAME "Video Device" +#define ACPI_VIDEO_NOTIFY_SWITCH 0x80 +#define ACPI_VIDEO_NOTIFY_PROBE 0x81 +#define ACPI_VIDEO_NOTIFY_CYCLE 0x82 +#define ACPI_VIDEO_NOTIFY_NEXT_OUTPUT 0x83 +#define ACPI_VIDEO_NOTIFY_PREV_OUTPUT 0x84 + +#define ACPI_VIDEO_NOTIFY_CYCLE_BRIGHTNESS 0x85 +#define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS 0x86 +#define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS 0x87 +#define ACPI_VIDEO_NOTIFY_ZERO_BRIGHTNESS 0x88 +#define ACPI_VIDEO_NOTIFY_DISPLAY_OFF 0x89 + +#define MAX_NAME_LEN 20 + +#define _COMPONENT ACPI_VIDEO_COMPONENT +ACPI_MODULE_NAME("video"); + +MODULE_AUTHOR("Bruno Ducrot"); +MODULE_DESCRIPTION("ACPI Video Driver"); +MODULE_LICENSE("GPL"); + +static bool brightness_switch_enabled = 1; +module_param(brightness_switch_enabled, bool, 0644); + +/* + * By default, we don't allow duplicate ACPI video bus devices + * under the same VGA controller + */ +static bool allow_duplicates; +module_param(allow_duplicates, bool, 0644); + +/* + * For Windows 8 systems: used to decide if video module + * should skip registering backlight interface of its own. + */ +enum { + NATIVE_BACKLIGHT_NOT_SET = -1, + NATIVE_BACKLIGHT_OFF, + NATIVE_BACKLIGHT_ON, +}; + +static int use_native_backlight_param = NATIVE_BACKLIGHT_NOT_SET; +module_param_named(use_native_backlight, use_native_backlight_param, int, 0444); +static int use_native_backlight_dmi = NATIVE_BACKLIGHT_NOT_SET; + +static int disable_backlight_sysfs_if = -1; +module_param(disable_backlight_sysfs_if, int, 0444); + +static int register_count; +static struct mutex video_list_lock; +static struct list_head video_bus_head; +static int acpi_video_bus_add(struct acpi_device *device); +static int acpi_video_bus_remove(struct acpi_device *device); +static void acpi_video_bus_notify(struct acpi_device *device, u32 event); + +static const struct acpi_device_id video_device_ids[] = { + {ACPI_VIDEO_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, video_device_ids); + +static struct acpi_driver acpi_video_bus = { + .name = "video", + .class = ACPI_VIDEO_CLASS, + .ids = video_device_ids, + .ops = { + .add = acpi_video_bus_add, + .remove = acpi_video_bus_remove, + .notify = acpi_video_bus_notify, + }, +}; + +struct acpi_video_bus_flags { + u8 multihead:1; /* can switch video heads */ + u8 rom:1; /* can retrieve a video rom */ + u8 post:1; /* can configure the head to */ + u8 reserved:5; +}; + +struct acpi_video_bus_cap { + u8 _DOS:1; /* Enable/Disable output switching */ + u8 _DOD:1; /* Enumerate all devices attached to display adapter */ + u8 _ROM:1; /* Get ROM Data */ + u8 _GPD:1; /* Get POST Device */ + u8 _SPD:1; /* Set POST Device */ + u8 _VPO:1; /* Video POST Options */ + u8 reserved:2; +}; + +struct acpi_video_device_attrib { + u32 display_index:4; /* A zero-based instance of the Display */ + u32 display_port_attachment:4; /* This field differentiates the display type */ + u32 display_type:4; /* Describe the specific type in use */ + u32 vendor_specific:4; /* Chipset Vendor Specific */ + u32 bios_can_detect:1; /* BIOS can detect the device */ + u32 depend_on_vga:1; /* Non-VGA output device whose power is related to + the VGA device. */ + u32 pipe_id:3; /* For VGA multiple-head devices. */ + u32 reserved:10; /* Must be 0 */ + u32 device_id_scheme:1; /* Device ID Scheme */ +}; + +struct acpi_video_enumerated_device { + union { + u32 int_val; + struct acpi_video_device_attrib attrib; + } value; + struct acpi_video_device *bind_info; +}; + +struct acpi_video_bus { + struct acpi_device *device; + bool backlight_registered; + bool backlight_notifier_registered; + u8 dos_setting; + struct acpi_video_enumerated_device *attached_array; + u8 attached_count; + u8 child_count; + struct acpi_video_bus_cap cap; + struct acpi_video_bus_flags flags; + struct list_head video_device_list; + struct mutex device_list_lock; /* protects video_device_list */ + struct list_head entry; + struct input_dev *input; + char phys[32]; /* for input device */ + struct notifier_block pm_nb; + struct notifier_block backlight_nb; +}; + +struct acpi_video_device_flags { + u8 crt:1; + u8 lcd:1; + u8 tvout:1; + u8 dvi:1; + u8 bios:1; + u8 unknown:1; + u8 notify:1; + u8 reserved:1; +}; + +struct acpi_video_device_cap { + u8 _ADR:1; /* Return the unique ID */ + u8 _BCL:1; /* Query list of brightness control levels supported */ + u8 _BCM:1; /* Set the brightness level */ + u8 _BQC:1; /* Get current brightness level */ + u8 _BCQ:1; /* Some buggy BIOS uses _BCQ instead of _BQC */ + u8 _DDC:1; /* Return the EDID for this device */ +}; + +struct acpi_video_brightness_flags { + u8 _BCL_no_ac_battery_levels:1; /* no AC/Battery levels in _BCL */ + u8 _BCL_reversed:1; /* _BCL package is in a reversed order */ + u8 _BQC_use_index:1; /* _BQC returns an index value */ +}; + +struct acpi_video_device_brightness { + int curr; + int count; + int *levels; + struct acpi_video_brightness_flags flags; +}; + +struct acpi_video_device { + unsigned long device_id; + struct acpi_video_device_flags flags; + struct acpi_video_device_cap cap; + struct list_head entry; + struct delayed_work switch_brightness_work; + int switch_brightness_event; + struct acpi_video_bus *video; + struct acpi_device *dev; + struct acpi_video_device_brightness *brightness; + struct backlight_device *backlight; + struct thermal_cooling_device *cooling_dev; +}; + +static const char device_decode[][30] = { + "motherboard VGA device", + "PCI VGA device", + "AGP VGA device", + "UNKNOWN", +}; + +static void acpi_video_device_notify(acpi_handle handle, u32 event, void *data); +static void acpi_video_device_rebind(struct acpi_video_bus *video); +static void acpi_video_device_bind(struct acpi_video_bus *video, + struct acpi_video_device *device); +static int acpi_video_device_enumerate(struct acpi_video_bus *video); +static int acpi_video_device_lcd_set_level(struct acpi_video_device *device, + int level); +static int acpi_video_device_lcd_get_level_current( + struct acpi_video_device *device, + unsigned long long *level, bool raw); +static int acpi_video_get_next_level(struct acpi_video_device *device, + u32 level_current, u32 event); +static void acpi_video_switch_brightness(struct work_struct *work); + +static bool acpi_video_use_native_backlight(void) +{ + if (use_native_backlight_param != NATIVE_BACKLIGHT_NOT_SET) + return use_native_backlight_param; + else if (use_native_backlight_dmi != NATIVE_BACKLIGHT_NOT_SET) + return use_native_backlight_dmi; + return acpi_osi_is_win8(); +} + +bool acpi_video_verify_backlight_support(void) +{ + if (acpi_video_use_native_backlight() && + backlight_device_registered(BACKLIGHT_RAW)) + return false; + return acpi_video_backlight_support(); +} +EXPORT_SYMBOL_GPL(acpi_video_verify_backlight_support); + +/* backlight device sysfs support */ +static int acpi_video_get_brightness(struct backlight_device *bd) +{ + unsigned long long cur_level; + int i; + struct acpi_video_device *vd = bl_get_data(bd); + + if (acpi_video_device_lcd_get_level_current(vd, &cur_level, false)) + return -EINVAL; + for (i = 2; i < vd->brightness->count; i++) { + if (vd->brightness->levels[i] == cur_level) + /* + * The first two entries are special - see page 575 + * of the ACPI spec 3.0 + */ + return i - 2; + } + return 0; +} + +static int acpi_video_set_brightness(struct backlight_device *bd) +{ + int request_level = bd->props.brightness + 2; + struct acpi_video_device *vd = bl_get_data(bd); + + cancel_delayed_work(&vd->switch_brightness_work); + return acpi_video_device_lcd_set_level(vd, + vd->brightness->levels[request_level]); +} + +static const struct backlight_ops acpi_backlight_ops = { + .get_brightness = acpi_video_get_brightness, + .update_status = acpi_video_set_brightness, +}; + +/* thermal cooling device callbacks */ +static int video_get_max_state(struct thermal_cooling_device *cooling_dev, unsigned + long *state) +{ + struct acpi_device *device = cooling_dev->devdata; + struct acpi_video_device *video = acpi_driver_data(device); + + *state = video->brightness->count - 3; + return 0; +} + +static int video_get_cur_state(struct thermal_cooling_device *cooling_dev, unsigned + long *state) +{ + struct acpi_device *device = cooling_dev->devdata; + struct acpi_video_device *video = acpi_driver_data(device); + unsigned long long level; + int offset; + + if (acpi_video_device_lcd_get_level_current(video, &level, false)) + return -EINVAL; + for (offset = 2; offset < video->brightness->count; offset++) + if (level == video->brightness->levels[offset]) { + *state = video->brightness->count - offset - 1; + return 0; + } + + return -EINVAL; +} + +static int +video_set_cur_state(struct thermal_cooling_device *cooling_dev, unsigned long state) +{ + struct acpi_device *device = cooling_dev->devdata; + struct acpi_video_device *video = acpi_driver_data(device); + int level; + + if (state >= video->brightness->count - 2) + return -EINVAL; + + state = video->brightness->count - state; + level = video->brightness->levels[state - 1]; + return acpi_video_device_lcd_set_level(video, level); +} + +static const struct thermal_cooling_device_ops video_cooling_ops = { + .get_max_state = video_get_max_state, + .get_cur_state = video_get_cur_state, + .set_cur_state = video_set_cur_state, +}; + +/* + * -------------------------------------------------------------------------- + * Video Management + * -------------------------------------------------------------------------- + */ + +static int +acpi_video_device_lcd_query_levels(struct acpi_video_device *device, + union acpi_object **levels) +{ + int status; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + + + *levels = NULL; + + status = acpi_evaluate_object(device->dev->handle, "_BCL", NULL, &buffer); + if (!ACPI_SUCCESS(status)) + return status; + obj = (union acpi_object *)buffer.pointer; + if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { + printk(KERN_ERR PREFIX "Invalid _BCL data\n"); + status = -EFAULT; + goto err; + } + + *levels = obj; + + return 0; + +err: + kfree(buffer.pointer); + + return status; +} + +static int +acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level) +{ + int status; + int state; + + status = acpi_execute_simple_method(device->dev->handle, + "_BCM", level); + if (ACPI_FAILURE(status)) { + ACPI_ERROR((AE_INFO, "Evaluating _BCM failed")); + return -EIO; + } + + device->brightness->curr = level; + for (state = 2; state < device->brightness->count; state++) + if (level == device->brightness->levels[state]) { + if (device->backlight) + device->backlight->props.brightness = state - 2; + return 0; + } + + ACPI_ERROR((AE_INFO, "Current brightness invalid")); + return -EINVAL; +} + +/* + * For some buggy _BQC methods, we need to add a constant value to + * the _BQC return value to get the actual current brightness level + */ + +static int bqc_offset_aml_bug_workaround; +static int __init video_set_bqc_offset(const struct dmi_system_id *d) +{ + bqc_offset_aml_bug_workaround = 9; + return 0; +} + +static int __init video_disable_native_backlight(const struct dmi_system_id *d) +{ + use_native_backlight_dmi = NATIVE_BACKLIGHT_OFF; + return 0; +} + +static int __init video_enable_native_backlight(const struct dmi_system_id *d) +{ + use_native_backlight_dmi = NATIVE_BACKLIGHT_ON; + return 0; +} + +static int __init video_disable_backlight_sysfs_if( + const struct dmi_system_id *d) +{ + if (disable_backlight_sysfs_if == -1) + disable_backlight_sysfs_if = 1; + return 0; +} + +static struct dmi_system_id video_dmi_table[] __initdata = { + /* + * Broken _BQC workaround http://bugzilla.kernel.org/show_bug.cgi?id=13121 + */ + { + .callback = video_set_bqc_offset, + .ident = "Acer Aspire 5720", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5720"), + }, + }, + { + .callback = video_set_bqc_offset, + .ident = "Acer Aspire 5710Z", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5710Z"), + }, + }, + { + .callback = video_set_bqc_offset, + .ident = "eMachines E510", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "EMACHINES"), + DMI_MATCH(DMI_PRODUCT_NAME, "eMachines E510"), + }, + }, + { + .callback = video_set_bqc_offset, + .ident = "Acer Aspire 5315", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5315"), + }, + }, + { + .callback = video_set_bqc_offset, + .ident = "Acer Aspire 7720", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 7720"), + }, + }, + + /* + * These models have a working acpi_video backlight control, and using + * native backlight causes a regression where backlight does not work + * when userspace is not handling brightness key events. Disable + * native_backlight on these to fix this: + * https://bugzilla.kernel.org/show_bug.cgi?id=81691 + */ + { + .callback = video_disable_native_backlight, + .ident = "ThinkPad T420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T420"), + }, + }, + { + .callback = video_disable_native_backlight, + .ident = "ThinkPad T520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T520"), + }, + }, + { + .callback = video_disable_native_backlight, + .ident = "ThinkPad X201s", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X201s"), + }, + }, + + /* The native backlight controls do not work on some older machines */ + { + /* https://bugs.freedesktop.org/show_bug.cgi?id=81515 */ + .callback = video_disable_native_backlight, + .ident = "HP ENVY 15 Notebook", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY 15 Notebook PC"), + }, + }, + + { + .callback = video_disable_native_backlight, + .ident = "SAMSUNG 870Z5E/880Z5E/680Z5E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "870Z5E/880Z5E/680Z5E"), + }, + }, + { + .callback = video_disable_native_backlight, + .ident = "SAMSUNG 370R4E/370R4V/370R5E/3570RE/370R5V", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "370R4E/370R4V/370R5E/3570RE/370R5V"), + }, + }, + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1186097 */ + .callback = video_disable_native_backlight, + .ident = "SAMSUNG 3570R/370R/470R/450R/510R/4450RV", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "3570R/370R/470R/450R/510R/4450RV"), + }, + }, + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1094948 */ + .callback = video_disable_native_backlight, + .ident = "SAMSUNG 730U3E/740U3E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"), + }, + }, + { + /* https://bugs.freedesktop.org/show_bug.cgi?id=87286 */ + .callback = video_disable_native_backlight, + .ident = "SAMSUNG 900X3C/900X3D/900X3E/900X4C/900X4D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "900X3C/900X3D/900X3E/900X4C/900X4D"), + }, + }, + + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1163574 */ + .callback = video_disable_native_backlight, + .ident = "Dell XPS15 L521X", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS L521X"), + }, + }, + + /* Non win8 machines which need native backlight nevertheless */ + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1187004 */ + .callback = video_enable_native_backlight, + .ident = "Lenovo Ideapad Z570", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "102434U"), + }, + }, + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1217249 */ + .callback = video_enable_native_backlight, + .ident = "Apple MacBook Pro 12,1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro12,1"), + }, + }, + + /* + * Some machines have a broken acpi-video interface for brightness + * control, but still need an acpi_video_device_lcd_set_level() call + * on resume to turn the backlight power on. We Enable backlight + * control on these systems, but do not register a backlight sysfs + * as brightness control does not work. + */ + { + /* https://bugs.freedesktop.org/show_bug.cgi?id=82634 */ + .callback = video_disable_backlight_sysfs_if, + .ident = "Toshiba Portege R830", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE R830"), + }, + }, + {} +}; + +static unsigned long long +acpi_video_bqc_value_to_level(struct acpi_video_device *device, + unsigned long long bqc_value) +{ + unsigned long long level; + + if (device->brightness->flags._BQC_use_index) { + /* + * _BQC returns an index that doesn't account for + * the first 2 items with special meaning, so we need + * to compensate for that by offsetting ourselves + */ + if (device->brightness->flags._BCL_reversed) + bqc_value = device->brightness->count - 3 - bqc_value; + + level = device->brightness->levels[bqc_value + 2]; + } else { + level = bqc_value; + } + + level += bqc_offset_aml_bug_workaround; + + return level; +} + +static int +acpi_video_device_lcd_get_level_current(struct acpi_video_device *device, + unsigned long long *level, bool raw) +{ + acpi_status status = AE_OK; + int i; + + if (device->cap._BQC || device->cap._BCQ) { + char *buf = device->cap._BQC ? "_BQC" : "_BCQ"; + + status = acpi_evaluate_integer(device->dev->handle, buf, + NULL, level); + if (ACPI_SUCCESS(status)) { + if (raw) { + /* + * Caller has indicated he wants the raw + * value returned by _BQC, so don't furtherly + * mess with the value. + */ + return 0; + } + + *level = acpi_video_bqc_value_to_level(device, *level); + + for (i = 2; i < device->brightness->count; i++) + if (device->brightness->levels[i] == *level) { + device->brightness->curr = *level; + return 0; + } + /* + * BQC returned an invalid level. + * Stop using it. + */ + ACPI_WARNING((AE_INFO, + "%s returned an invalid level", + buf)); + device->cap._BQC = device->cap._BCQ = 0; + } else { + /* + * Fixme: + * should we return an error or ignore this failure? + * dev->brightness->curr is a cached value which stores + * the correct current backlight level in most cases. + * ACPI video backlight still works w/ buggy _BQC. + * http://bugzilla.kernel.org/show_bug.cgi?id=12233 + */ + ACPI_WARNING((AE_INFO, "Evaluating %s failed", buf)); + device->cap._BQC = device->cap._BCQ = 0; + } + } + + *level = device->brightness->curr; + return 0; +} + +static int +acpi_video_device_EDID(struct acpi_video_device *device, + union acpi_object **edid, ssize_t length) +{ + int status; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + + + *edid = NULL; + + if (!device) + return -ENODEV; + if (length == 128) + arg0.integer.value = 1; + else if (length == 256) + arg0.integer.value = 2; + else + return -EINVAL; + + status = acpi_evaluate_object(device->dev->handle, "_DDC", &args, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obj = buffer.pointer; + + if (obj && obj->type == ACPI_TYPE_BUFFER) + *edid = obj; + else { + printk(KERN_ERR PREFIX "Invalid _DDC data\n"); + status = -EFAULT; + kfree(obj); + } + + return status; +} + +/* bus */ + +/* + * Arg: + * video : video bus device pointer + * bios_flag : + * 0. The system BIOS should NOT automatically switch(toggle) + * the active display output. + * 1. The system BIOS should automatically switch (toggle) the + * active display output. No switch event. + * 2. The _DGS value should be locked. + * 3. The system BIOS should not automatically switch (toggle) the + * active display output, but instead generate the display switch + * event notify code. + * lcd_flag : + * 0. The system BIOS should automatically control the brightness level + * of the LCD when the power changes from AC to DC + * 1. The system BIOS should NOT automatically control the brightness + * level of the LCD when the power changes from AC to DC. + * Return Value: + * -EINVAL wrong arg. + */ + +static int +acpi_video_bus_DOS(struct acpi_video_bus *video, int bios_flag, int lcd_flag) +{ + acpi_status status; + + if (!video->cap._DOS) + return 0; + + if (bios_flag < 0 || bios_flag > 3 || lcd_flag < 0 || lcd_flag > 1) + return -EINVAL; + video->dos_setting = (lcd_flag << 2) | bios_flag; + status = acpi_execute_simple_method(video->device->handle, "_DOS", + (lcd_flag << 2) | bios_flag); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * Simple comparison function used to sort backlight levels. + */ + +static int +acpi_video_cmp_level(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +/* + * Decides if _BQC/_BCQ for this system is usable + * + * We do this by changing the level first and then read out the current + * brightness level, if the value does not match, find out if it is using + * index. If not, clear the _BQC/_BCQ capability. + */ +static int acpi_video_bqc_quirk(struct acpi_video_device *device, + int max_level, int current_level) +{ + struct acpi_video_device_brightness *br = device->brightness; + int result; + unsigned long long level; + int test_level; + + /* don't mess with existing known broken systems */ + if (bqc_offset_aml_bug_workaround) + return 0; + + /* + * Some systems always report current brightness level as maximum + * through _BQC, we need to test another value for them. + */ + test_level = current_level == max_level ? br->levels[3] : max_level; + + result = acpi_video_device_lcd_set_level(device, test_level); + if (result) + return result; + + result = acpi_video_device_lcd_get_level_current(device, &level, true); + if (result) + return result; + + if (level != test_level) { + /* buggy _BQC found, need to find out if it uses index */ + if (level < br->count) { + if (br->flags._BCL_reversed) + level = br->count - 3 - level; + if (br->levels[level + 2] == test_level) + br->flags._BQC_use_index = 1; + } + + if (!br->flags._BQC_use_index) + device->cap._BQC = device->cap._BCQ = 0; + } + + return 0; +} + + +/* + * Arg: + * device : video output device (LCD, CRT, ..) + * + * Return Value: + * Maximum brightness level + * + * Allocate and initialize device->brightness. + */ + +static int +acpi_video_init_brightness(struct acpi_video_device *device) +{ + union acpi_object *obj = NULL; + int i, max_level = 0, count = 0, level_ac_battery = 0; + unsigned long long level, level_old; + union acpi_object *o; + struct acpi_video_device_brightness *br = NULL; + int result = -EINVAL; + u32 value; + + if (!ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device, &obj))) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Could not query available " + "LCD brightness level\n")); + goto out; + } + + if (obj->package.count < 2) + goto out; + + br = kzalloc(sizeof(*br), GFP_KERNEL); + if (!br) { + printk(KERN_ERR "can't allocate memory\n"); + result = -ENOMEM; + goto out; + } + + br->levels = kmalloc((obj->package.count + 2) * sizeof *(br->levels), + GFP_KERNEL); + if (!br->levels) { + result = -ENOMEM; + goto out_free; + } + + for (i = 0; i < obj->package.count; i++) { + o = (union acpi_object *)&obj->package.elements[i]; + if (o->type != ACPI_TYPE_INTEGER) { + printk(KERN_ERR PREFIX "Invalid data\n"); + continue; + } + value = (u32) o->integer.value; + /* Skip duplicate entries */ + if (count > 2 && br->levels[count - 1] == value) + continue; + + br->levels[count] = value; + + if (br->levels[count] > max_level) + max_level = br->levels[count]; + count++; + } + + /* + * some buggy BIOS don't export the levels + * when machine is on AC/Battery in _BCL package. + * In this case, the first two elements in _BCL packages + * are also supported brightness levels that OS should take care of. + */ + for (i = 2; i < count; i++) { + if (br->levels[i] == br->levels[0]) + level_ac_battery++; + if (br->levels[i] == br->levels[1]) + level_ac_battery++; + } + + if (level_ac_battery < 2) { + level_ac_battery = 2 - level_ac_battery; + br->flags._BCL_no_ac_battery_levels = 1; + for (i = (count - 1 + level_ac_battery); i >= 2; i--) + br->levels[i] = br->levels[i - level_ac_battery]; + count += level_ac_battery; + } else if (level_ac_battery > 2) + ACPI_ERROR((AE_INFO, "Too many duplicates in _BCL package")); + + /* Check if the _BCL package is in a reversed order */ + if (max_level == br->levels[2]) { + br->flags._BCL_reversed = 1; + sort(&br->levels[2], count - 2, sizeof(br->levels[2]), + acpi_video_cmp_level, NULL); + } else if (max_level != br->levels[count - 1]) + ACPI_ERROR((AE_INFO, + "Found unordered _BCL package")); + + br->count = count; + device->brightness = br; + + /* _BQC uses INDEX while _BCL uses VALUE in some laptops */ + br->curr = level = max_level; + + if (!device->cap._BQC) + goto set_level; + + result = acpi_video_device_lcd_get_level_current(device, + &level_old, true); + if (result) + goto out_free_levels; + + result = acpi_video_bqc_quirk(device, max_level, level_old); + if (result) + goto out_free_levels; + /* + * cap._BQC may get cleared due to _BQC is found to be broken + * in acpi_video_bqc_quirk, so check again here. + */ + if (!device->cap._BQC) + goto set_level; + + level = acpi_video_bqc_value_to_level(device, level_old); + /* + * On some buggy laptops, _BQC returns an uninitialized + * value when invoked for the first time, i.e. + * level_old is invalid (no matter whether it's a level + * or an index). Set the backlight to max_level in this case. + */ + for (i = 2; i < br->count; i++) + if (level == br->levels[i]) + break; + if (i == br->count || !level) + level = max_level; + +set_level: + result = acpi_video_device_lcd_set_level(device, level); + if (result) + goto out_free_levels; + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "found %d brightness levels\n", count - 2)); + kfree(obj); + return result; + +out_free_levels: + kfree(br->levels); +out_free: + kfree(br); +out: + device->brightness = NULL; + kfree(obj); + return result; +} + +/* + * Arg: + * device : video output device (LCD, CRT, ..) + * + * Return Value: + * None + * + * Find out all required AML methods defined under the output + * device. + */ + +static void acpi_video_device_find_cap(struct acpi_video_device *device) +{ + if (acpi_has_method(device->dev->handle, "_ADR")) + device->cap._ADR = 1; + if (acpi_has_method(device->dev->handle, "_BCL")) + device->cap._BCL = 1; + if (acpi_has_method(device->dev->handle, "_BCM")) + device->cap._BCM = 1; + if (acpi_has_method(device->dev->handle, "_BQC")) { + device->cap._BQC = 1; + } else if (acpi_has_method(device->dev->handle, "_BCQ")) { + printk(KERN_WARNING FW_BUG "_BCQ is used instead of _BQC\n"); + device->cap._BCQ = 1; + } + + if (acpi_has_method(device->dev->handle, "_DDC")) + device->cap._DDC = 1; +} + +/* + * Arg: + * device : video output device (VGA) + * + * Return Value: + * None + * + * Find out all required AML methods defined under the video bus device. + */ + +static void acpi_video_bus_find_cap(struct acpi_video_bus *video) +{ + if (acpi_has_method(video->device->handle, "_DOS")) + video->cap._DOS = 1; + if (acpi_has_method(video->device->handle, "_DOD")) + video->cap._DOD = 1; + if (acpi_has_method(video->device->handle, "_ROM")) + video->cap._ROM = 1; + if (acpi_has_method(video->device->handle, "_GPD")) + video->cap._GPD = 1; + if (acpi_has_method(video->device->handle, "_SPD")) + video->cap._SPD = 1; + if (acpi_has_method(video->device->handle, "_VPO")) + video->cap._VPO = 1; +} + +/* + * Check whether the video bus device has required AML method to + * support the desired features + */ + +static int acpi_video_bus_check(struct acpi_video_bus *video) +{ + acpi_status status = -ENOENT; + struct pci_dev *dev; + + if (!video) + return -EINVAL; + + dev = acpi_get_pci_dev(video->device->handle); + if (!dev) + return -ENODEV; + pci_dev_put(dev); + + /* + * Since there is no HID, CID and so on for VGA driver, we have + * to check well known required nodes. + */ + + /* Does this device support video switching? */ + if (video->cap._DOS || video->cap._DOD) { + if (!video->cap._DOS) { + printk(KERN_WARNING FW_BUG + "ACPI(%s) defines _DOD but not _DOS\n", + acpi_device_bid(video->device)); + } + video->flags.multihead = 1; + status = 0; + } + + /* Does this device support retrieving a video ROM? */ + if (video->cap._ROM) { + video->flags.rom = 1; + status = 0; + } + + /* Does this device support configuring which video device to POST? */ + if (video->cap._GPD && video->cap._SPD && video->cap._VPO) { + video->flags.post = 1; + status = 0; + } + + return status; +} + +/* + * -------------------------------------------------------------------------- + * Driver Interface + * -------------------------------------------------------------------------- + */ + +/* device interface */ +static struct acpi_video_device_attrib * +acpi_video_get_device_attr(struct acpi_video_bus *video, unsigned long device_id) +{ + struct acpi_video_enumerated_device *ids; + int i; + + for (i = 0; i < video->attached_count; i++) { + ids = &video->attached_array[i]; + if ((ids->value.int_val & 0xffff) == device_id) + return &ids->value.attrib; + } + + return NULL; +} + +static int +acpi_video_get_device_type(struct acpi_video_bus *video, + unsigned long device_id) +{ + struct acpi_video_enumerated_device *ids; + int i; + + for (i = 0; i < video->attached_count; i++) { + ids = &video->attached_array[i]; + if ((ids->value.int_val & 0xffff) == device_id) + return ids->value.int_val; + } + + return 0; +} + +static int +acpi_video_bus_get_one_device(struct acpi_device *device, + struct acpi_video_bus *video) +{ + unsigned long long device_id; + int status, device_type; + struct acpi_video_device *data; + struct acpi_video_device_attrib *attribute; + + status = + acpi_evaluate_integer(device->handle, "_ADR", NULL, &device_id); + /* Some device omits _ADR, we skip them instead of fail */ + if (ACPI_FAILURE(status)) + return 0; + + data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL); + if (!data) + return -ENOMEM; |