diff options
Diffstat (limited to 'drivers/platform/x86/panasonic-laptop.c')
| -rw-r--r-- | drivers/platform/x86/panasonic-laptop.c | 700 |
1 files changed, 595 insertions, 105 deletions
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 8361ad75389a..255317e6fec8 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Panasonic HotKey and LCD brightness control driver * (C) 2004 Hiroshi Miura <miura@da-cha.org> @@ -8,22 +9,25 @@ * * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * publicshed by the Free Software Foundation. - * - * 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 - * *--------------------------------------------------------------------------- * * ChangeLog: + * Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com> + * -v0.98 add platform devices for firmware brightness registers + * add support for battery charging threshold (eco mode) + * resolve hotkey double trigger + * add write support to mute + * fix sticky_key init bug + * fix naming of platform files for consistency with other + * modules + * split MODULE_AUTHOR() by one author per macro call + * replace ACPI prints with pr_*() macros + * -v0.97 add support for cdpower hardware switch + * -v0.96 merge Lucina's enhancement + * Jan.13, 2009 Martin Lucina <mato@kotelna.sk> + * - add support for optical driver power in + * Y and W series + * * Sep.23, 2008 Harald Welte <laforge@gnumonks.org> * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to * drivers/misc/panasonic-laptop.c @@ -113,29 +117,31 @@ * * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org> * - v0.1 start from toshiba_acpi driver written by John Belmonte - * */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/types.h> +#include <linux/acpi.h> #include <linux/backlight.h> +#include <linux/bits.h> #include <linux/ctype.h> -#include <linux/seq_file.h> -#include <linux/uaccess.h> -#include <linux/slab.h> -#include <linux/acpi.h> +#include <linux/i8042.h> +#include <linux/init.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/serio.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <acpi/video.h> -#ifndef ACPI_HOTKEY_COMPONENT -#define ACPI_HOTKEY_COMPONENT 0x10000000 -#endif - -#define _COMPONENT ACPI_HOTKEY_COMPONENT - -MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte"); +MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>"); +MODULE_AUTHOR("David Bronaugh <dbronaugh@linuxboxen.org>"); +MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); +MODULE_AUTHOR("Martin Lucina <mato@kotelna.sk>"); +MODULE_AUTHOR("Kenneth Chan <kenneth.t.chan@gmail.com>"); MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops"); MODULE_LICENSE("GPL"); @@ -147,7 +153,10 @@ MODULE_LICENSE("GPL"); #define METHOD_HKEY_SQTY "SQTY" #define METHOD_HKEY_SINF "SINF" #define METHOD_HKEY_SSET "SSET" -#define HKEY_NOTIFY 0x80 +#define METHOD_ECWR "\\_SB.ECWR" +#define HKEY_NOTIFY 0x80 +#define ECO_MODE_OFF 0x00 +#define ECO_MODE_ON 0x80 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" #define ACPI_PCC_DEVICE_NAME "Hotkey" @@ -156,7 +165,7 @@ MODULE_LICENSE("GPL"); #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent - ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00 + ECO_MODEs: 0x03 = off, 0x83 = on */ enum SINF_BITS { SINF_NUM_BATTERIES = 0, SINF_LCD_TYPE, @@ -168,13 +177,14 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0, SINF_DC_CUR_BRIGHT, SINF_MUTE, SINF_RESERVED, - SINF_ENV_STATE, + SINF_ECO_MODE = 0x0A, + SINF_CUR_BRIGHT = 0x0D, SINF_STICKY_KEY = 0x80, }; /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ static int acpi_pcc_hotkey_add(struct acpi_device *device); -static int acpi_pcc_hotkey_remove(struct acpi_device *device); +static void acpi_pcc_hotkey_remove(struct acpi_device *device); static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event); static const struct acpi_device_id pcc_device_ids[] = { @@ -215,19 +225,72 @@ static const struct key_entry panasonic_keymap[] = { { KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */ { KE_KEY, 9, { KEY_BATTERY } }, { KE_KEY, 10, { KEY_SUSPEND } }, + { KE_KEY, 21, { KEY_MACRO1 } }, + { KE_KEY, 22, { KEY_MACRO2 } }, + { KE_KEY, 24, { KEY_MACRO3 } }, + { KE_KEY, 25, { KEY_MACRO4 } }, + { KE_KEY, 34, { KEY_MACRO5 } }, + { KE_KEY, 35, { KEY_MACRO6 } }, + { KE_KEY, 36, { KEY_MACRO7 } }, + { KE_KEY, 37, { KEY_MACRO8 } }, + { KE_KEY, 41, { KEY_MACRO9 } }, + { KE_KEY, 42, { KEY_MACRO10 } }, + { KE_KEY, 43, { KEY_MACRO11 } }, { KE_END, 0 } }; struct pcc_acpi { acpi_handle handle; unsigned long num_sifr; - int sticky_mode; + int sticky_key; + int eco_mode; + int mute; + int ac_brightness; + int dc_brightness; + int current_brightness; u32 *sinf; struct acpi_device *device; struct input_dev *input_dev; struct backlight_device *backlight; + struct platform_device *platform; }; +/* + * On some Panasonic models the volume up / down / mute keys send duplicate + * keypress events over the PS/2 kbd interface, filter these out. + */ +static bool panasonic_i8042_filter(unsigned char data, unsigned char str, + struct serio *port, void *context) +{ + static bool extended; + + if (str & I8042_STR_AUXDATA) + return false; + + if (data == 0xe0) { + extended = true; + return true; + } else if (extended) { + extended = false; + + switch (data & 0x7f) { + case 0x20: /* e0 20 / e0 a0, Volume Mute press / release */ + case 0x2e: /* e0 2e / e0 ae, Volume Down press / release */ + case 0x30: /* e0 30 / e0 b0, Volume Up press / release */ + return true; + default: + /* + * Report the previously filtered e0 before continuing + * with the next non-filtered byte. + */ + serio_interrupt(port, 0xe0, 0); + return false; + } + } + + return false; +} + /* method access functions */ static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val) { @@ -259,8 +322,7 @@ static inline int acpi_pcc_get_sqty(struct acpi_device *device) if (ACPI_SUCCESS(status)) return s; else { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "evaluation error HKEY.SQTY\n")); + pr_err("evaluation error HKEY.SQTY\n"); return -EINVAL; } } @@ -275,21 +337,20 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL, &buffer); if (ACPI_FAILURE(status)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "evaluation error HKEY.SINF\n")); + pr_err("evaluation error HKEY.SINF\n"); return 0; } hkey = buffer.pointer; if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n")); + pr_err("Invalid HKEY.SINF\n"); status = AE_ERROR; goto end; } if (pcc->num_sifr < hkey->package.count) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "SQTY reports bad SINF length\n")); + pr_err("SQTY reports bad SINF length SQTY: %lu SINF-pkg-count: %u\n", + pcc->num_sifr, hkey->package.count); status = AE_ERROR; goto end; } @@ -299,8 +360,7 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) if (likely(element->type == ACPI_TYPE_INTEGER)) { pcc->sinf[i] = element->integer.value; } else - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Invalid HKEY.SINF data\n")); + pr_err("Invalid HKEY.SINF data\n"); } pcc->sinf[hkey->package.count] = -1; @@ -358,9 +418,101 @@ static const struct backlight_ops pcc_backlight_ops = { }; +/* returns ACPI_SUCCESS if methods to control optical drive are present */ + +static acpi_status check_optd_present(void) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, "\\_SB.STAT", &handle); + if (ACPI_FAILURE(status)) + goto out; + status = acpi_get_handle(NULL, "\\_SB.FBAY", &handle); + if (ACPI_FAILURE(status)) + goto out; + status = acpi_get_handle(NULL, "\\_SB.CDDI", &handle); + if (ACPI_FAILURE(status)) + goto out; + +out: + return status; +} + +/* get optical driver power state */ + +static int get_optd_power_state(void) +{ + acpi_status status; + unsigned long long state; + int result; + + status = acpi_evaluate_integer(NULL, "\\_SB.STAT", NULL, &state); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.STAT\n"); + result = -EIO; + goto out; + } + switch (state) { + case 0: /* power off */ + result = 0; + break; + case 0x0f: /* power on */ + result = 1; + break; + default: + result = -EIO; + break; + } + +out: + return result; +} + +/* set optical drive power state */ + +static int set_optd_power_state(int new_state) +{ + int result; + acpi_status status; + + result = get_optd_power_state(); + if (result < 0) + goto out; + if (new_state == result) + goto out; + + switch (new_state) { + case 0: /* power off */ + /* Call CDDR instead, since they both call the same method + * while CDDI takes 1 arg and we are not quite sure what it is. + */ + status = acpi_evaluate_object(NULL, "\\_SB.CDDR", NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.CDDR\n"); + result = -EIO; + } + break; + case 1: /* power on */ + status = acpi_evaluate_object(NULL, "\\_SB.FBAY", NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.FBAY\n"); + result = -EIO; + } + break; + default: + result = -EINVAL; + break; + } + +out: + return result; +} + + /* sysfs user interface functions */ -static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, +static ssize_t numbatt_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -369,10 +521,10 @@ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; - return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]); + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]); } -static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, +static ssize_t lcdtype_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -381,10 +533,10 @@ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; - return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]); + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_LCD_TYPE]); } -static ssize_t show_mute(struct device *dev, struct device_attribute *attr, +static ssize_t mute_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -393,10 +545,28 @@ static ssize_t show_mute(struct device *dev, struct device_attribute *attr, if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; - return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]); + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_MUTE]); +} + +static ssize_t mute_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val == 0 || val == 1) { + acpi_pcc_write_sset(pcc, SINF_MUTE, val); + pcc->mute = val; + } + + return count; } -static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, +static ssize_t sticky_key_show(struct device *dev, struct device_attribute *attr, char *buf) { struct acpi_device *acpi = to_acpi_device(dev); @@ -405,41 +575,256 @@ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; - return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]); + return sysfs_emit(buf, "%u\n", pcc->sticky_key); } -static ssize_t set_sticky(struct device *dev, struct device_attribute *attr, +static ssize_t sticky_key_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - int val; + int err, val; - if (count && sscanf(buf, "%i", &val) == 1 && - (val == 0 || val == 1)) { + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val == 0 || val == 1) { acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val); - pcc->sticky_mode = val; + pcc->sticky_key = val; } return count; } -static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL); -static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL); -static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL); -static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky); +static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int result; + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + switch (pcc->sinf[SINF_ECO_MODE]) { + case (ECO_MODE_OFF + 3): + result = 0; + break; + case (ECO_MODE_ON + 3): + result = 1; + break; + default: + return -EIO; + } + return sysfs_emit(buf, "%u\n", result); +} + +static ssize_t eco_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, state; + + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x15; + param[1].type = ACPI_TYPE_INTEGER; + input.count = 2; + input.pointer = param; + + err = kstrtoint(buf, 0, &state); + if (err) + return err; + + switch (state) { + case 0: + param[1].integer.value = ECO_MODE_OFF; + pcc->sinf[SINF_ECO_MODE] = 0; + pcc->eco_mode = 0; + break; + case 1: + param[1].integer.value = ECO_MODE_ON; + pcc->sinf[SINF_ECO_MODE] = 1; + pcc->eco_mode = 1; + break; + default: + /* nothing to do */ + return count; + } + + status = acpi_evaluate_object(NULL, METHOD_ECWR, + &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("%s evaluation failed\n", METHOD_ECWR); + return -EINVAL; + } + + return count; +} + +static ssize_t ac_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_AC_CUR_BRIGHT]); +} + +static ssize_t ac_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val >= 0 && val <= 255) { + acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, val); + pcc->ac_brightness = val; + } + + return count; +} + +static ssize_t dc_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_DC_CUR_BRIGHT]); +} + +static ssize_t dc_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + if (val >= 0 && val <= 255) { + acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, val); + pcc->dc_brightness = val; + } + + return count; +} + +static ssize_t current_brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc)) + return -EIO; + + return sysfs_emit(buf, "%u\n", pcc->sinf[SINF_CUR_BRIGHT]); +} + +static ssize_t current_brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err) + return err; + + if (val >= 0 && val <= 255) { + err = acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, val); + pcc->current_brightness = val; + } + + return count; +} + +static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int state = get_optd_power_state(); + + if (state < 0) + return state; + + return sysfs_emit(buf, "%d\n", state); +} + +static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, val; + + err = kstrtoint(buf, 10, &val); + if (err) + return err; + set_optd_power_state(val); + return count; +} + +static DEVICE_ATTR_RO(numbatt); +static DEVICE_ATTR_RO(lcdtype); +static DEVICE_ATTR_RW(mute); +static DEVICE_ATTR_RW(sticky_key); +static DEVICE_ATTR_RW(eco_mode); +static DEVICE_ATTR_RW(ac_brightness); +static DEVICE_ATTR_RW(dc_brightness); +static DEVICE_ATTR_RW(current_brightness); +static DEVICE_ATTR_RW(cdpower); + +static umode_t pcc_sysfs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (attr == &dev_attr_mute.attr) + return (pcc->num_sifr > SINF_MUTE) ? attr->mode : 0; + + if (attr == &dev_attr_eco_mode.attr) + return (pcc->num_sifr > SINF_ECO_MODE) ? attr->mode : 0; + + if (attr == &dev_attr_current_brightness.attr) + return (pcc->num_sifr > SINF_CUR_BRIGHT) ? attr->mode : 0; + + return attr->mode; +} static struct attribute *pcc_sysfs_entries[] = { &dev_attr_numbatt.attr, &dev_attr_lcdtype.attr, &dev_attr_mute.attr, &dev_attr_sticky_key.attr, + &dev_attr_eco_mode.attr, + &dev_attr_ac_brightness.attr, + &dev_attr_dc_brightness.attr, + &dev_attr_current_brightness.attr, + &dev_attr_cdpower.attr, NULL, }; static const struct attribute_group pcc_attr_group = { - .name = NULL, /* put in device directory */ - .attrs = pcc_sysfs_entries, + .name = NULL, /* put in device directory */ + .attrs = pcc_sysfs_entries, + .is_visible = pcc_sysfs_is_visible, }; @@ -451,28 +836,37 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) struct input_dev *hotk_input_dev = pcc->input_dev; int rc; unsigned long long result; + unsigned int key; + unsigned int updown; rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY, NULL, &result); if (ACPI_FAILURE(rc)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "error getting hotkey status\n")); + pr_err("error getting hotkey status\n"); return; } + key = result & GENMASK(6, 0); + updown = result & BIT(7); /* 0x80 == key down; 0x00 = key up */ + /* hack: some firmware sends no key down for sleep / hibernate */ - if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) { - if (result & 0x80) + if (key == 7 || key == 10) { + if (updown) sleep_keydown_seen = 1; if (!sleep_keydown_seen) sparse_keymap_report_event(hotk_input_dev, - result & 0xf, 0x80, false); + key, 0x80, false); } - if (!sparse_keymap_report_event(hotk_input_dev, - result & 0xf, result & 0x80, false)) - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unknown hotkey event: %d\n", result)); + /* + * Don't report brightness key-presses if they are also reported + * by the ACPI video bus. + */ + if ((key == 1 || key == 2) && acpi_video_handles_brightness_key_presses()) + return; + + if (!sparse_keymap_report_event(hotk_input_dev, key, updown, false)) + pr_err("Unknown hotkey event: 0x%04llx\n", result); } static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) @@ -489,6 +883,50 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) } } +static void pcc_optd_notify(acpi_handle handle, u32 event, void *data) +{ + if (event != ACPI_NOTIFY_EJECT_REQUEST) + return; + + set_optd_power_state(0); +} + +static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) +{ + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_install_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + pcc_optd_notify, pcc); + if (ACPI_FAILURE(status)) + pr_err("Failed to register notify on %s\n", node); + } else + return -ENODEV; + + return 0; +} + +static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_remove_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + pcc_optd_notify); + if (ACPI_FAILURE(status)) + pr_err("Error removing optd notify handler %s\n", + node); + } +} + static int acpi_pcc_init_input(struct pcc_acpi *pcc) { struct input_dev *input_dev; @@ -507,15 +945,13 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc) error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL); if (error) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unable to setup input device keymap\n")); + pr_err("Unable to setup input device keymap\n"); goto err_free_dev; } error = input_register_device(input_dev); if (error) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Unable to register input device\n")); + pr_err("Unable to register input device\n"); goto err_free_dev; } @@ -541,10 +977,17 @@ static int acpi_pcc_hotkey_resume(struct device *dev) if (!pcc) return -EINVAL; - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n", - pcc->sticky_mode)); + if (pcc->num_sifr > SINF_MUTE) + acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute); + if (pcc->num_sifr > SINF_ECO_MODE) + acpi_pcc_write_sset(pcc, SINF_ECO_MODE, pcc->eco_mode); + acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_key); + acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, pcc->ac_brightness); + acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, pcc->dc_brightness); + if (pcc->num_sifr > SINF_CUR_BRIGHT) + acpi_pcc_write_sset(pcc, SINF_CUR_BRIGHT, pcc->current_brightness); - return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); + return 0; } #endif @@ -559,15 +1002,24 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) num_sifr = acpi_pcc_get_sqty(device); - if (num_sifr < 0 || num_sifr > 255) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range")); + /* + * pcc->sinf is expected to at least have the AC+DC brightness entries. + * Accesses to higher SINF entries are checked against num_sifr. + */ + if (num_sifr <= SINF_DC_CUR_BRIGHT || num_sifr > 255) { + pr_err("num_sifr %d out of range %d - 255\n", num_sifr, SINF_DC_CUR_BRIGHT + 1); return -ENODEV; } + /* + * Some DSDT-s have an off-by-one bug where the SINF package count is + * one higher than the SQTY reported value, allocate 1 entry extra. + */ + num_sifr++; + pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL); if (!pcc) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't allocate mem for pcc")); + pr_err("Couldn't allocate mem for pcc"); return -ENOMEM; } @@ -581,46 +1033,78 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) pcc->handle = device->handle; pcc->num_sifr = num_sifr; device->driver_data = pcc; - strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_PCC_CLASS); + strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); + strscpy(acpi_device_class(device), ACPI_PCC_CLASS); result = acpi_pcc_init_input(pcc); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Error installing keyinput handler\n")); + pr_err("Error installing keyinput handler\n"); goto out_sinf; } if (!acpi_pcc_retrieve_biosdata(pcc)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't retrieve BIOS data\n")); result = -EIO; + pr_err("Couldn't retrieve BIOS data\n"); goto out_input; } - /* initialize backlight */ - memset(&props, 0, sizeof(struct backlight_properties)); - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT]; - pcc->backlight = backlight_device_register("panasonic", NULL, pcc, - &pcc_backlight_ops, &props); - if (IS_ERR(pcc->backlight)) { - result = PTR_ERR(pcc->backlight); - goto out_input; + + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { + /* initialize backlight */ + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT]; + + pcc->backlight = backlight_device_register("panasonic", NULL, pcc, + &pcc_backlight_ops, &props); + if (IS_ERR(pcc->backlight)) { + result = PTR_ERR(pcc->backlight); + goto out_input; + } + + /* read the initial brightness setting from the hardware */ + pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; } - /* read the initial brightness setting from the hardware */ - pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; + /* Reset initial sticky key mode since the hardware register state is not consistent */ + acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, 0); + pcc->sticky_key = 0; - /* read the initial sticky key mode from the hardware */ - pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY]; + pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; + pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT]; + if (pcc->num_sifr > SINF_MUTE) + pcc->mute = pcc->sinf[SINF_MUTE]; + if (pcc->num_sifr > SINF_ECO_MODE) + pcc->eco_mode = pcc->sinf[SINF_ECO_MODE]; + if (pcc->num_sifr > SINF_CUR_BRIGHT) + pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT]; /* add sysfs attributes */ result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group); if (result) goto out_backlight; + /* optical drive initialization */ + if (ACPI_SUCCESS(check_optd_present())) { + pcc->platform = platform_device_register_simple("panasonic", + PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(pcc->platform)) { + result = PTR_ERR(pcc->platform); + goto out_backlight; + } + result = device_create_file(&pcc->platform->dev, + &dev_attr_cdpower); + pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); + if (result) + goto out_platform; + } else { + pcc->platform = NULL; + } + + i8042_install_filter(panasonic_i8042_filter, NULL); return 0; +out_platform: + platform_device_unregister(pcc->platform); out_backlight: backlight_device_unregister(pcc->backlight); out_input: @@ -633,12 +1117,20 @@ out_hotkey: return result; } -static int acpi_pcc_hotkey_remove(struct acpi_device *device) +static void acpi_pcc_hotkey_remove(struct acpi_device *device) { struct pcc_acpi *pcc = acpi_driver_data(device); if (!device || !pcc) - return -EINVAL; + return; + + i8042_remove_filter(panasonic_i8042_filter); + + if (pcc->platform) { + device_remove_file(&pcc->platform->dev, &dev_attr_cdpower); + platform_device_unregister(pcc->platform); + } + pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); @@ -648,8 +1140,6 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device) kfree(pcc->sinf); kfree(pcc); - - return 0; } module_acpi_driver(acpi_pcc_driver); |
