diff options
Diffstat (limited to 'drivers/platform')
53 files changed, 5215 insertions, 835 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 09fde58b12e0..0adccbf5c83f 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -1,6 +1,9 @@ if X86 source "drivers/platform/x86/Kconfig" endif +if MIPS +source "drivers/platform/mips/Kconfig" +endif if GOLDFISH source "drivers/platform/goldfish/Kconfig" endif diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 3656b7b17b99..ca2692510733 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_X86) += x86/ +obj-$(CONFIG_MIPS) += mips/ obj-$(CONFIG_OLPC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 440ed776efd4..2a6531a5fde8 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -4,7 +4,7 @@ menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" - depends on X86 + depends on X86 || ARM ---help--- Say Y here to get to see options for platform support for various Chromebooks and Chromeboxes. This option alone does @@ -16,8 +16,7 @@ if CHROME_PLATFORMS config CHROMEOS_LAPTOP tristate "Chrome OS Laptop" - depends on I2C - depends on DMI + depends on I2C && DMI && X86 ---help--- This driver instantiates i2c and smbus devices such as light sensors and touchpads. @@ -27,6 +26,7 @@ config CHROMEOS_LAPTOP config CHROMEOS_PSTORE tristate "Chrome OS pstore support" + depends on X86 ---help--- This module instantiates the persistent storage on x86 ChromeOS devices. It can be used to store away console logs and crash @@ -38,5 +38,25 @@ config CHROMEOS_PSTORE If you have a supported Chromebook, choose Y or M here. The module will be called chromeos_pstore. +config CROS_EC_CHARDEV + tristate "Chrome OS Embedded Controller userspace device interface" + depends on MFD_CROS_EC + ---help--- + This driver adds support to talk with the ChromeOS EC from userspace. + + If you have a supported Chromebook, choose Y or M here. + The module will be called cros_ec_dev. + +config CROS_EC_LPC + tristate "ChromeOS Embedded Controller (LPC)" + depends on MFD_CROS_EC && (X86 || COMPILE_TEST) + help + If you say Y here, you get support for talking to the ChromeOS EC + over an LPC bus. This uses a simple byte-level protocol with a + checksum. This is used for userspace access only. The kernel + typically has its own communication methods. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_lpc. endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 2b860ca7450f..bd8d8601e875 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,3 +1,6 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o +obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index d866db80b4fd..a04019ab9feb 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -133,12 +133,13 @@ static struct i2c_client *__add_probed_i2c_device( const char *name, int bus, struct i2c_board_info *info, - const unsigned short *addrs) + const unsigned short *alt_addr_list) { const struct dmi_device *dmi_dev; const struct dmi_dev_onboard *dev_data; struct i2c_adapter *adapter; - struct i2c_client *client; + struct i2c_client *client = NULL; + const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; if (bus < 0) return NULL; @@ -169,8 +170,28 @@ static struct i2c_client *__add_probed_i2c_device( return NULL; } - /* add the i2c device */ - client = i2c_new_probed_device(adapter, info, addrs, NULL); + /* + * Add the i2c device. If we can't detect it at the primary + * address we scan secondary addresses. In any case the client + * structure gets assigned primary address. + */ + client = i2c_new_probed_device(adapter, info, addr_list, NULL); + if (!client && alt_addr_list) { + struct i2c_board_info dummy_info = { + I2C_BOARD_INFO("dummy", info->addr), + }; + struct i2c_client *dummy; + + dummy = i2c_new_probed_device(adapter, &dummy_info, + alt_addr_list, NULL); + if (dummy) { + pr_debug("%s %d-%02x is probed at %02x\n", + __func__, bus, info->addr, dummy->addr); + i2c_unregister_device(dummy); + client = i2c_new_device(adapter, info); + } + } + if (!client) pr_notice("%s failed to register device %d-%02x\n", __func__, bus, info->addr); @@ -254,12 +275,10 @@ static struct i2c_client *add_i2c_device(const char *name, enum i2c_adapter_type type, struct i2c_board_info *info) { - const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; - return __add_probed_i2c_device(name, find_i2c_adapter_num(type), info, - addr_list); + NULL); } static int setup_cyapa_tp(enum i2c_adapter_type type) @@ -275,7 +294,6 @@ static int setup_cyapa_tp(enum i2c_adapter_type type) static int setup_atmel_224s_tp(enum i2c_adapter_type type) { const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR, - ATMEL_TP_I2C_ADDR, I2C_CLIENT_END }; if (tp) return 0; @@ -289,7 +307,6 @@ static int setup_atmel_224s_tp(enum i2c_adapter_type type) static int setup_atmel_1664s_ts(enum i2c_adapter_type type) { const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, - ATMEL_TS_I2C_ADDR, I2C_CLIENT_END }; if (ts) return 0; @@ -571,7 +588,6 @@ static struct platform_device *cros_platform_device; static struct platform_driver cros_platform_driver = { .driver = { .name = "chromeos_laptop", - .owner = THIS_MODULE, }, .probe = chromeos_laptop_probe, }; diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c new file mode 100644 index 000000000000..6090d0b2826f --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -0,0 +1,274 @@ +/* + * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space + * + * Copyright (C) 2014 Google, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> + +#include "cros_ec_dev.h" + +/* Device variables */ +#define CROS_MAX_DEV 128 +static struct class *cros_class; +static int ec_major; + +/* Basic communication */ +static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) +{ + struct ec_response_get_version *resp; + static const char * const current_image_name[] = { + "unknown", "read-only", "read-write", "invalid", + }; + struct cros_ec_command msg = { + .version = 0, + .command = EC_CMD_GET_VERSION, + .outdata = { 0 }, + .outsize = 0, + .indata = { 0 }, + .insize = sizeof(*resp), + }; + int ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) { + snprintf(str, maxlen, + "%s\nUnknown EC version: EC returned %d\n", + CROS_EC_DEV_VERSION, msg.result); + return 0; + } + + resp = (struct ec_response_get_version *)msg.indata; + if (resp->current_image >= ARRAY_SIZE(current_image_name)) + resp->current_image = 3; /* invalid */ + + snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, + resp->version_string_ro, resp->version_string_rw, + current_image_name[resp->current_image]); + + return 0; +} + +/* Device file ops */ +static int ec_device_open(struct inode *inode, struct file *filp) +{ + filp->private_data = container_of(inode->i_cdev, + struct cros_ec_device, cdev); + return 0; +} + +static int ec_device_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static ssize_t ec_device_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + struct cros_ec_device *ec = filp->private_data; + char msg[sizeof(struct ec_response_get_version) + + sizeof(CROS_EC_DEV_VERSION)]; + size_t count; + int ret; + + if (*offset != 0) + return 0; + + ret = ec_get_version(ec, msg, sizeof(msg)); + if (ret) + return ret; + + count = min(length, strlen(msg)); + + if (copy_to_user(buffer, msg, count)) + return -EFAULT; + + *offset = count; + return count; +} + +/* Ioctls */ +static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) +{ + long ret; + struct cros_ec_command s_cmd = { }; + + if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) + return -EFAULT; + + ret = cros_ec_cmd_xfer(ec, &s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + return ret; + + if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) + return -EFAULT; + + return 0; +} + +static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) +{ + struct cros_ec_readmem s_mem = { }; + long num; + + /* Not every platform supports direct reads */ + if (!ec->cmd_readmem) + return -ENOTTY; + + if (copy_from_user(&s_mem, arg, sizeof(s_mem))) + return -EFAULT; + + num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); + if (num <= 0) + return num; + + if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) + return -EFAULT; + + return 0; +} + +static long ec_device_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct cros_ec_device *ec = filp->private_data; + + if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) + return -ENOTTY; + + switch (cmd) { + case CROS_EC_DEV_IOCXCMD: + return ec_device_ioctl_xcmd(ec, (void __user *)arg); + case CROS_EC_DEV_IOCRDMEM: + return ec_device_ioctl_readmem(ec, (void __user *)arg); + } + + return -ENOTTY; +} + +/* Module initialization */ +static const struct file_operations fops = { + .open = ec_device_open, + .release = ec_device_release, + .read = ec_device_read, + .unlocked_ioctl = ec_device_ioctl, +}; + +static int ec_device_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + int retval = -ENOTTY; + dev_t devno = MKDEV(ec_major, 0); + + /* Instantiate it (and remember the EC) */ + cdev_init(&ec->cdev, &fops); + + retval = cdev_add(&ec->cdev, devno, 1); + if (retval) { + dev_err(&pdev->dev, ": failed to add character device\n"); + return retval; + } + + ec->vdev = device_create(cros_class, NULL, devno, ec, + CROS_EC_DEV_NAME); + if (IS_ERR(ec->vdev)) { + retval = PTR_ERR(ec->vdev); + dev_err(&pdev->dev, ": failed to create device\n"); + cdev_del(&ec->cdev); + return retval; + } + + /* Initialize extra interfaces */ + ec_dev_sysfs_init(ec); + ec_dev_lightbar_init(ec); + + return 0; +} + +static int ec_device_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + + ec_dev_lightbar_remove(ec); + ec_dev_sysfs_remove(ec); + device_destroy(cros_class, MKDEV(ec_major, 0)); + cdev_del(&ec->cdev); + return 0; +} + +static struct platform_driver cros_ec_dev_driver = { + .driver = { + .name = "cros-ec-ctl", + }, + .probe = ec_device_probe, + .remove = ec_device_remove, +}; + +static int __init cros_ec_dev_init(void) +{ + int ret; + dev_t dev = 0; + + cros_class = class_create(THIS_MODULE, "chromeos"); + if (IS_ERR(cros_class)) { + pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); + return PTR_ERR(cros_class); + } + + /* Get a range of minor numbers (starting with 0) to work with */ + ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); + if (ret < 0) { + pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); + goto failed_chrdevreg; + } + ec_major = MAJOR(dev); + + /* Register the driver */ + ret = platform_driver_register(&cros_ec_dev_driver); + if (ret < 0) { + pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); + goto failed_devreg; + } + return 0; + +failed_devreg: + unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); +failed_chrdevreg: + class_destroy(cros_class); + return ret; +} + +static void __exit cros_ec_dev_exit(void) +{ + platform_driver_unregister(&cros_ec_dev_driver); + unregister_chrdev(ec_major, CROS_EC_DEV_NAME); + class_destroy(cros_class); +} + +module_init(cros_ec_dev_init); +module_exit(cros_ec_dev_exit); + +MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); +MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h new file mode 100644 index 000000000000..45d67f7e518c --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -0,0 +1,53 @@ +/* + * cros_ec_dev - expose the Chrome OS Embedded Controller to userspace + * + * Copyright (C) 2014 Google, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CROS_EC_DEV_H_ +#define _CROS_EC_DEV_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> +#include <linux/mfd/cros_ec.h> + +#define CROS_EC_DEV_NAME "cros_ec" +#define CROS_EC_DEV_VERSION "1.0.0" + +/* + * @offset: within EC_LPC_ADDR_MEMMAP region + * @bytes: number of bytes to read. zero means "read a string" (including '\0') + * (at most only EC_MEMMAP_SIZE bytes can be read) + * @buffer: where to store the result + * ioctl returns the number of bytes read, negative on error + */ +struct cros_ec_readmem { + uint32_t offset; + uint32_t bytes; + uint8_t buffer[EC_MEMMAP_SIZE]; +}; + +#define CROS_EC_DEV_IOC 0xEC +#define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) +#define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) + +void ec_dev_sysfs_init(struct cros_ec_device *); +void ec_dev_sysfs_remove(struct cros_ec_device *); + +void ec_dev_lightbar_init(struct cros_ec_device *); +void ec_dev_lightbar_remove(struct cros_ec_device *); + +#endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c new file mode 100644 index 000000000000..b4ff47a9069a --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -0,0 +1,367 @@ +/* + * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace + * + * Copyright (C) 2014 Google, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define pr_fmt(fmt) "cros_ec_lightbar: " fmt + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kobject.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#include "cros_ec_dev.h" + +/* Rate-limit the lightbar interface to prevent DoS. */ +static unsigned long lb_interval_jiffies = 50 * HZ / 1000; + +static ssize_t interval_msec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long msec = lb_interval_jiffies * 1000 / HZ; + + return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); +} + +static ssize_t interval_msec_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long msec; + + if (kstrtoul(buf, 0, &msec)) + return -EINVAL; + + lb_interval_jiffies = msec * HZ / 1000; + + return count; +} + +static DEFINE_MUTEX(lb_mutex); +/* Return 0 if able to throttle correctly, error otherwise */ +static int lb_throttle(void) +{ + static unsigned long last_access; + unsigned long now, next_timeslot; + long delay; + int ret = 0; + + mutex_lock(&lb_mutex); + + now = jiffies; + next_timeslot = last_access + lb_interval_jiffies; + + if (time_before(now, next_timeslot)) { + delay = (long)(next_timeslot) - (long)now; + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(delay) > 0) { + /* interrupted - just abort */ + ret = -EINTR; + goto out; + } + now = jiffies; + } + + last_access = now; +out: + mutex_unlock(&lb_mutex); + + return ret; +} + +#define INIT_MSG(P, R) { \ + .command = EC_CMD_LIGHTBAR_CMD, \ + .outsize = sizeof(*P), \ + .insize = sizeof(*R), \ + } + +static int get_lightbar_version(struct cros_ec_device *ec, + uint32_t *ver_ptr, uint32_t *flg_ptr) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_VERSION; + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return 0; + + switch (msg.result) { + case EC_RES_INVALID_PARAM: + /* Pixel had no version command. */ + if (ver_ptr) + *ver_ptr = 0; + if (flg_ptr) + *flg_ptr = 0; + return 1; + + case EC_RES_SUCCESS: + resp = (struct ec_response_lightbar *)msg.indata; + + /* Future devices w/lightbars should implement this command */ + if (ver_ptr) + *ver_ptr = resp->version.num; + if (flg_ptr) + *flg_ptr = resp->version.flags; + return 1; + } + + /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ + return 0; +} + +static ssize_t version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint32_t version, flags; + struct cros_ec_device *ec = dev_get_drvdata(dev); + int ret; + + ret = lb_throttle(); + if (ret) + return ret; + + /* This should always succeed, because we check during init. */ + if (!get_lightbar_version(ec, &version, &flags)) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); +} + +static ssize_t brightness_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + unsigned int val; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_BRIGHTNESS; + param->brightness.num = val; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + + +/* + * We expect numbers, and we'll keep reading until we find them, skipping over + * any whitespace (sysfs guarantees that the input is null-terminated). Every + * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first + * parsing error, if we don't parse any numbers, or if we have numbers left + * over. + */ +static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_device *ec = dev_get_drvdata(dev); + unsigned int val[4]; + int ret, i = 0, j = 0, ok = 0; + + do { + /* Skip any whitespace */ + while (*buf && isspace(*buf)) + buf++; + + if (!*buf) + break; + + ret = sscanf(buf, "%i", &val[i++]); + if (ret == 0) + return -EINVAL; + + if (i == 4) { + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_RGB; + param->rgb.led = val[0]; + param->rgb.red = val[1]; + param->rgb.green = val[2]; + param->rgb.blue = val[3]; + /* + * Throttle only the first of every four transactions, + * so that the user can update all four LEDs at once. + */ + if ((j++ % 4) == 0) { + ret = lb_throttle(); + if (ret) + return ret; + } + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + i = 0; + ok = 1; + } + + /* Skip over the number we just read */ + while (*buf && !isspace(*buf)) + buf++; + + } while (*buf); + + return (ok && i == 0) ? count : -EINVAL; +} + +static char const *seqname[] = { + "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", + "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", +}; + +static ssize_t sequence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_GET_SEQ; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_lightbar *)msg.indata; + if (resp->get_seq.num >= ARRAY_SIZE(seqname)) + return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + else + return scnprintf(buf, PAGE_SIZE, "%s\n", + seqname[resp->get_seq.num]); +} + +static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + unsigned int num; + int ret, len; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + for (len = 0; len < count; len++) + if (!isalnum(buf[len])) + break; + + for (num = 0; num < ARRAY_SIZE(seqname); num++) + if (!strncasecmp(seqname[num], buf, len)) + break; + + if (num >= ARRAY_SIZE(seqname)) { + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + } + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_SEQ; + param->seq.num = num; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + +/* Module initialization */ + +static DEVICE_ATTR_RW(interval_msec); +static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_WO(brightness); +static DEVICE_ATTR_WO(led_rgb); +static DEVICE_ATTR_RW(sequence); +static struct attribute *__lb_cmds_attrs[] = { + &dev_attr_interval_msec.attr, + &dev_attr_version.attr, + &dev_attr_brightness.attr, + &dev_attr_led_rgb.attr, + &dev_attr_sequence.attr, + NULL, +}; +static struct attribute_group lb_cmds_attr_group = { + .name = "lightbar", + .attrs = __lb_cmds_attrs, +}; + +void ec_dev_lightbar_init(struct cros_ec_device *ec) +{ + int ret = 0; + + /* Only instantiate this stuff if the EC has a lightbar */ + if (!get_lightbar_version(ec, NULL, NULL)) + return; + + ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); + if (ret) + pr_warn("sysfs_create_group() failed: %d\n", ret); +} + +void ec_dev_lightbar_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); +} diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c new file mode 100644 index 000000000000..8f9ac4d7bbd0 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -0,0 +1,319 @@ +/* + * cros_ec_lpc - LPC access to the Chrome OS Embedded Controller + * + * Copyright (C) 2012-2015 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * This driver uses the Chrome OS EC byte-level message-based protocol for + * communicating the keyboard state (which keys are pressed) from a keyboard EC + * to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing, + * but everything else (including deghosting) is done here. The main + * motivation for this is to keep the EC firmware as simple as possible, since + * it cannot be easily upgraded and EC flash/IRAM space is relatively + * expensive. + */ + +#include <linux/dmi.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/printk.h> + +#define DRV_NAME "cros_ec_lpc" + +static int ec_response_timed_out(void) +{ + unsigned long one_second = jiffies + HZ; + + usleep_range(200, 300); + do { + if (!(inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK)) + return 0; + usleep_range(100, 200); + } while (time_before(jiffies, one_second)); + + return 1; +} + +static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, + struct cros_ec_command *msg) +{ + struct ec_lpc_host_args args; + int csum; + int i; + int ret = 0; + + if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE || + msg->insize > EC_PROTO2_MAX_PARAM_SIZE) { + dev_err(ec->dev, + "invalid buffer sizes (out %d, in %d)\n", + msg->outsize, msg->insize); + return -EINVAL; + } + + /* Now actually send the command to the EC and get the result */ + args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; + args.command_version = msg->version; + args.data_size = msg->outsize; + + /* Initialize checksum */ + csum = msg->command + args.flags + + args.command_version + args.data_size; + + /* Copy data and update checksum */ + for (i = 0; i < msg->outsize; i++) { + outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->outdata[i]; + } + + /* Finalize checksum and write args */ + args.checksum = csum & 0xFF; + outb(args.flags, EC_LPC_ADDR_HOST_ARGS); + outb(args.command_version, EC_LPC_ADDR_HOST_ARGS + 1); + outb(args.data_size, EC_LPC_ADDR_HOST_ARGS + 2); + outb(args.checksum, EC_LPC_ADDR_HOST_ARGS + 3); + + /* Here we go */ + outb(msg->command, EC_LPC_ADDR_HOST_CMD); + + if (ec_response_timed_out()) { + dev_warn(ec->dev, "EC responsed timed out\n"); + ret = -EIO; + goto done; + } + + /* Check result */ + msg->result = inb(EC_LPC_ADDR_HOST_DATA); + + switch (msg->result) { + case EC_RES_SUCCESS: + break; + case EC_RES_IN_PROGRESS: + ret = -EAGAIN; + dev_dbg(ec->dev, "command 0x%02x in progress\n", + msg->command); + goto done; + default: + dev_dbg(ec->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + } + + /* Read back args */ + args.flags = inb(EC_LPC_ADDR_HOST_ARGS); + args.command_version = inb(EC_LPC_ADDR_HOST_ARGS + 1); + args.data_size = inb(EC_LPC_ADDR_HOST_ARGS + 2); + args.checksum = inb(EC_LPC_ADDR_HOST_ARGS + 3); + + if (args.data_size > msg->insize) { + dev_err(ec->dev, + "packet too long (%d bytes, expected %d)", + args.data_size, msg->insize); + ret = -ENOSPC; + goto done; + } + + /* Start calculating response checksum */ + csum = msg->command + args.flags + + args.command_version + args.data_size; + + /* Read response and update checksum */ + for (i = 0; i < args.data_size; i++) { + msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->indata[i]; + } + + /* Verify checksum */ + if (args.checksum != (csum & 0xFF)) { + dev_err(ec->dev, + "bad packet checksum, expected %02x, got %02x\n", + args.checksum, csum & 0xFF); + ret = -EBADMSG; + goto done; + } + + /* Return actual amount of data received */ + ret = args.data_size; +done: + return ret; +} + +/* Returns num bytes read, or negative on error. Doesn't need locking. */ +static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, + unsigned int bytes, void *dest) +{ + int i = offset; + char *s = dest; + int cnt = 0; + + if (offset >= EC_MEMMAP_SIZE - bytes) + return -EINVAL; + + /* fixed length */ + if (bytes) { + for (; cnt < bytes; i++, s++, cnt++) + *s = inb(EC_LPC_ADDR_MEMMAP + i); + return cnt; + } + + /* string */ + for (; i < EC_MEMMAP_SIZE; i++, s++) { + *s = inb(EC_LPC_ADDR_MEMMAP + i); + cnt++; + if (!*s) + break; + } + + return cnt; +} + +static int cros_ec_lpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec_dev; + int ret; + + if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE, + dev_name(dev))) { + dev_err(dev, "couldn't reserve memmap region\n"); + return -EBUSY; + } + + if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') || + (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) { + dev_err(dev, "EC ID not detected\n"); + return -ENODEV; + } + + if (!devm_request_region(dev, EC_HOST_CMD_REGION0, + EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { + dev_err(dev, "couldn't reserve region0\n"); + return -EBUSY; + } + if (!devm_request_region(dev, EC_HOST_CMD_REGION1, + EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { + dev_err(dev, "couldn't reserve region1\n"); + return -EBUSY; + } + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, ec_dev); + ec_dev->dev = dev; + ec_dev->ec_name = pdev->name; + ec_dev->phys_name = dev_name(dev); + ec_dev->parent = dev; + ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; + ec_dev->cmd_readmem = cros_ec_lpc_readmem; + + ret = cros_ec_register(ec_dev); + if (ret) { + dev_err(dev, "couldn't register ec_dev (%d)\n", ret); + return ret; + } + + return 0; +} + +static int cros_ec_lpc_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec_dev; + + ec_dev = platform_get_drvdata(pdev); + cros_ec_remove(ec_dev); + + return 0; +} + +static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { + { + /* + * Today all Chromebooks/boxes ship with Google_* as version and + * coreboot as bios vendor. No other systems with this + * combination are known to date. + */ + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), + DMI_MATCH(DMI_BIOS_VERSION, "Google_"), + }, + }, + { + /* x86-link, the Chromebook Pixel. */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + }, + { + /* x86-peppy, the Acer C720 Chromebook. */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), + }, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); + +static struct platform_driver cros_ec_lpc_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_lpc_probe, + .remove = cros_ec_lpc_remove, +}; + +static struct platform_device cros_ec_lpc_device = { + .name = DRV_NAME +}; + +static int __init cros_ec_lpc_init(void) +{ + int ret; + + if (!dmi_check_system(cros_ec_lpc_dmi_table)) { + pr_err(DRV_NAME ": unsupported system.\n"); + return -ENODEV; + } + + /* Register the driver */ + ret = platform_driver_register(&cros_ec_lpc_driver); + if (ret) { + pr_err(DRV_NAME ": can't register driver: %d\n", ret); + return ret; + } + + /* Register the device, and it'll get hooked up automatically */ + ret = platform_device_register(&cros_ec_lpc_device); + if (ret) { + pr_err(DRV_NAME ": can't register device: %d\n", ret); + platform_driver_unregister(&cros_ec_lpc_driver); + return ret; + } + + return 0; +} + +static void __exit cros_ec_lpc_exit(void) +{ + platform_device_unregister(&cros_ec_lpc_device); + platform_driver_unregister(&cros_ec_lpc_driver); +} + +module_init(cros_ec_lpc_init); +module_exit(cros_ec_lpc_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC LPC driver"); diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c new file mode 100644 index 000000000000..fb62ab6cc659 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -0,0 +1,271 @@ +/* + * cros_ec_sysfs - expose the Chrome OS EC through sysfs + * + * Copyright (C) 2014 Google, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define pr_fmt(fmt) "cros_ec_sysfs: " fmt + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kobject.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/stat.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#include "cros_ec_dev.h" + +/* Accessor functions */ + +static ssize_t show_ec_reboot(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "ro|rw|cancel|cold|disable-jump|hibernate"); + count += scnprintf(buf + count, PAGE_SIZE - count, + " [at-shutdown]\n"); + return count; +} + +static ssize_t store_ec_reboot(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + static const struct { + const char * const str; + uint8_t cmd; + uint8_t flags; + } words[] = { + {"cancel", EC_REBOOT_CANCEL, 0}, + {"ro", EC_REBOOT_JUMP_RO, 0}, + {"rw", EC_REBOOT_JUMP_RW, 0}, + {"cold", EC_REBOOT_COLD, 0}, + {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, + {"hibernate", EC_REBOOT_HIBERNATE, 0}, + {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, + }; + struct cros_ec_command msg = { 0 }; + struct ec_params_reboot_ec *param = + (struct ec_params_reboot_ec *)msg.outdata; + int got_cmd = 0, offset = 0; + int i; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param->flags = 0; + while (1) { + /* Find word to start scanning */ + while (buf[offset] && isspace(buf[offset])) + offset++; + if (!buf[offset]) + break; + + for (i = 0; i < ARRAY_SIZE(words); i++) { + if (!strncasecmp(words[i].str, buf+offset, + strlen(words[i].str))) { + if (words[i].flags) { + param->flags |= words[i].flags; + } else { + param->cmd = words[i].cmd; + got_cmd = 1; + } + break; + } + } + + /* On to the next word, if any */ + while (buf[offset] && !isspace(buf[offset])) + offset++; + } + + if (!got_cmd) + return -EINVAL; + + msg.command = EC_CMD_REBOOT_EC; + msg.outsize = sizeof(param); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) { + dev_dbg(ec->dev, "EC result %d\n", msg.result); + return -EINVAL; + } + + return count; +} + +static ssize_t show_ec_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static const char * const image_names[] = {"unknown", "RO", "RW"}; + struct ec_response_get_version *r_ver; + struct ec_response_get_chip_info *r_chip; + struct ec_response_board_version *r_board; + struct cros_ec_command msg = { 0 }; + int ret; + int count = 0; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* Get versions. RW may change. */ + msg.command = EC_CMD_GET_VERSION; + msg.insize = sizeof(*r_ver); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + r_ver = (struct ec_response_get_version *)msg.indata; + /* Strings should be null-terminated, but let's be sure. */ + r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; + r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "RO version: %s\n", r_ver->version_string_ro); + count += scnprintf(buf + count, PAGE_SIZE - count, + "RW version: %s\n", r_ver->version_string_rw); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Firmware copy: %s\n", + (r_ver->current_image < ARRAY_SIZE(image_names) ? + image_names[r_ver->current_image] : "?")); + + /* Get build info. */ + msg.command = EC_CMD_GET_BUILD_INFO; + msg.insize = sizeof(msg.indata); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: EC error %d\n", msg.result); + else { + msg.indata[sizeof(msg.indata) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: %s\n", msg.indata); + } + + /* Get chip info. */ + msg.command = EC_CMD_GET_CHIP_INFO; + msg.insize = sizeof(*r_chip); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: EC error %d\n", msg.result); + else { + r_chip = (struct ec_response_get_chip_info *)msg.indata; + + r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; + r_chip->name[sizeof(r_chip->name) - 1] = '\0'; + r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip vendor: %s\n", r_chip->vendor); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip name: %s\n", r_chip->name); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip revision: %s\n", r_chip->revision); + } + + /* Get board version */ + msg.command = EC_CMD_GET_BOARD_VERSION; + msg.insize = sizeof(*r_board); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: EC error %d\n", msg.result); + else { + r_board = (struct ec_response_board_version *)msg.indata; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: %d\n", + r_board->board_version); + } + + return count; +} + +static ssize_t show_ec_flashinfo(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_response_flash_info *resp; + struct cros_ec_command msg = { 0 }; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* The flash info shouldn't ever change, but ask each time anyway. */ + msg.command = EC_CMD_FLASH_INFO; + msg.insize = sizeof(*resp); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_flash_info *)msg.indata; + + return scnprintf(buf, PAGE_SIZE, + "FlashSize %d\nWriteSize %d\n" + "EraseSize %d\nProtectSize %d\n", + resp->flash_size, resp->write_block_size, + resp->erase_block_size, resp->protect_block_size); +} + +/* Module initialization */ + +static DEVICE_ATTR(reboot, S_IWUSR | S_IRUGO, show_ec_reboot, store_ec_reboot); +static DEVICE_ATTR(version, S_IRUGO, show_ec_version, NULL); +static DEVICE_ATTR(flashinfo, S_IRUGO, show_ec_flashinfo, NULL); + +static struct attribute *__ec_attrs[] = { + &dev_attr_reboot.attr, + &dev_attr_version.attr, + &dev_attr_flashinfo.attr, + NULL, +}; + +static struct attribute_group ec_attr_group = { + .attrs = __ec_attrs, +}; + +void ec_dev_sysfs_init(struct cros_ec_device *ec) +{ + int error; + + error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); + if (error) + pr_warn("failed to create group: %d\n", error); +} + +void ec_dev_sysfs_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); +} diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig new file mode 100644 index 000000000000..125e569017be --- /dev/null +++ b/drivers/platform/mips/Kconfig @@ -0,0 +1,30 @@ +# +# MIPS Platform Specific Drivers +# + +menuconfig MIPS_PLATFORM_DEVICES + bool "MIPS Platform Specific Device Drivers" + default y + help + Say Y here to get to see options for device drivers of various + MIPS platforms, including vendor-specific netbook/laptop/desktop + extension and hardware monitor drivers. This option itself does + not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if MIPS_PLATFORM_DEVICES + +config MIPS_ACPI + bool + default y if LOONGSON_MACH3X + +config CPU_HWMON + tristate "Loongson CPU HWMon Driver" + depends on LOONGSON_MACH3X + select HWMON + default y + help + Loongson-3A/3B CPU Hwmon (temperature sensor) driver. + +endif # MIPS_PLATFORM_DEVICES diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile new file mode 100644 index 000000000000..43412849b195 --- /dev/null +++ b/drivers/platform/mips/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MIPS_ACPI) += acpi_init.o +obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o diff --git a/drivers/platform/mips/acpi_init.c b/drivers/platform/mips/acpi_init.c new file mode 100644 index 000000000000..dbdad79ead8f --- /dev/null +++ b/drivers/platform/mips/acpi_init.c @@ -0,0 +1,150 @@ +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/export.h> + +#define SBX00_ACPI_IO_BASE 0x800 +#define SBX00_ACPI_IO_SIZE 0x100 + +#define ACPI_PM_EVT_BLK (SBX00_ACPI_IO_BASE + 0x00) /* 4 bytes */ +#define ACPI_PM_CNT_BLK (SBX00_ACPI_IO_BASE + 0x04) /* 2 bytes */ +#define ACPI_PMA_CNT_BLK (SBX00_ACPI_IO_BASE + 0x0F) /* 1 byte */ +#define ACPI_PM_TMR_BLK (SBX00_ACPI_IO_BASE + 0x18) /* 4 bytes */ +#define ACPI_GPE0_BLK (SBX00_ACPI_IO_BASE + 0x10) /* 8 bytes */ +#define ACPI_END (SBX00_ACPI_IO_BASE + 0x80) + +#define PM_INDEX 0xCD6 +#define PM_DATA 0xCD7 +#define PM2_INDEX 0xCD0 +#define PM2_DATA 0xCD1 + +/* + * SCI interrupt need acpi space, allocate here + */ + +static int __init register_acpi_resource(void) +{ + request_region(SBX00_ACPI_IO_BASE, SBX00_ACPI_IO_SIZE, "acpi"); + return 0; +} + +static void pmio_write_index(u16 index, u8 reg, u8 value) +{ + outb(reg, index); + outb(value, index + 1); +} + +static u8 pmio_read_index(u16 index, u8 reg) +{ + outb(reg, index); + return inb(index + 1); +} + +void pm_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM_INDEX, reg, value); +} +EXPORT_SYMBOL(pm_iowrite); + +u8 pm_ioread(u8 reg) +{ + return pmio_read_index(PM_INDEX, reg); +} +EXPORT_SYMBOL(pm_ioread); + +void pm2_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM2_INDEX, reg, value); +} +EXPORT_SYMBOL(pm2_iowrite); + +u8 pm2_ioread(u8 reg) +{ + return pmio_read_index(PM2_INDEX, reg); +} +EXPORT_SYMBOL(pm2_ioread); + +static void acpi_hw_clear_status(void) +{ + u16 value; + + /* PMStatus: Clear WakeStatus/PwrBtnStatus */ + value = inw(ACPI_PM_EVT_BLK); + value |= (1 << 8 | 1 << 15); + outw(value, ACPI_PM_EVT_BLK); + + /* GPEStatus: Clear all generated events */ + outl(inl(ACPI_GPE0_BLK), ACPI_GPE0_BLK); +} + +void acpi_registers_setup(void) +{ + u32 value; + + /* PM Status Base */ + pm_iowrite(0x20, ACPI_PM_EVT_BLK & 0xff); + pm_iowrite(0x21, ACPI_PM_EVT_BLK >> 8); + + /* PM Control Base */ + pm_iowrite(0x22, ACPI_PM_CNT_BLK & 0xff); + pm_iowrite(0x23, ACPI_PM_CNT_BLK >> 8); + + /* GPM Base */ + pm_iowrite(0x28, ACPI_GPE0_BLK & 0xff); + pm_iowrite(0x29, ACPI_GPE0_BLK >> 8); + + /* ACPI End */ + pm_iowrite(0x2e, ACPI_END & 0xff); + pm_iowrite(0x2f, ACPI_END >> 8); + + /* IO Decode: When AcpiDecodeEnable set, South-Bridge uses the contents + * of the PM registers at index 0x20~0x2B to decode ACPI I/O address. */ + pm_iowrite(0x0e, 1 << 3); + + /* SCI_EN set */ + outw(1, ACPI_PM_CNT_BLK); + + /* Enable to generate SCI */ + pm_iowrite(0x10, pm_ioread(0x10) | 1); + + /* GPM3/GPM9 enable */ + value = inl(ACPI_GPE0_BLK + 4); + outl(value | (1 << 14) | (1 << 22), ACPI_GPE0_BLK + 4); + + /* Set GPM9 as input */ + pm_iowrite(0x8d, pm_ioread(0x8d) & (~(1 << 1))); + + /* Set GPM9 as non-output */ + pm_iowrite(0x94, pm_ioread(0x94) | (1 << 3)); + + /* GPM3 config ACPI trigger SCIOUT */ + pm_iowrite(0x33, pm_ioread(0x33) & (~(3 << 4))); + + /* GPM9 config ACPI trigger SCIOUT */ + pm_iowrite(0x3d, pm_ioread(0x3d) & (~(3 << 2))); + + /* GPM3 config falling edge trigger */ + pm_iowrite(0x37, pm_ioread(0x37) & (~(1 << 6))); + + /* No wait for STPGNT# in ACPI Sx state */ + pm_iowrite(0x7c, pm_ioread(0x7c) | (1 << 6)); + + /* Set GPM3 pull-down enable */ + value = pm2_ioread(0xf6); + value |= ((1 << 7) | (1 << 3)); + pm2_iowrite(0xf6, value); + + /* Set GPM9 pull-down enable */ + value = pm2_ioread(0xf8); + value |= ((1 << 5) | (1 << 1)); + pm2_iowrite(0xf8, value); +} + +int __init sbx00_acpi_init(void) +{ + register_acpi_resource(); + acpi_registers_setup(); + acpi_hw_clear_status(); + + return 0; +} diff --git a/drivers/platform/mips/cpu_hwmon.c b/drivers/platform/mips/cpu_hwmon.c new file mode 100644 index 000000000000..0f6c63e17049 --- /dev/null +++ b/drivers/platform/mips/cpu_hwmon.c @@ -0,0 +1,207 @@ +#include <linux/err.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/jiffies.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#include <loongson.h> +#include <boot_param.h> +#include <loongson_hwmon.h> + +/* + * Loongson-3 series cpu has two sensors inside, + * each of them from 0 to 255, + * if more than 127, that is dangerous. + * here only provide sensor1 data, because it always hot than sensor0 + */ +int loongson3_cpu_temp(int cpu) +{ + u32 reg; + + reg = LOONGSON_CHIPTEMP(cpu); + if (loongson_sysconf.cputype == Loongson_3A) + reg = (reg >> 8) & 0xff; + else if (loongson_sysconf.cputype == Loongson_3B) + reg = ((reg >> 8) & 0xff) - 100; + + return (int)reg * 1000; +} + +static struct device *cpu_hwmon_dev; + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *cpu_hwmon_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group cpu_hwmon_attribute_group = { + .attrs = cpu_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "cpu-hwmon\n"); +} + +static ssize_t get_cpu0_temp(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t get_cpu1_temp(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t cpu0_temp_label(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t cpu1_temp_label(struct device *dev, + struct device_attribute *attr, char *buf); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_cpu0_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, cpu0_temp_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, get_cpu1_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, cpu1_temp_label, NULL, 2); + +static const struct attribute *hwmon_cputemp1[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + NULL +}; + +static const struct attribute *hwmon_cputemp2[] = { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + NULL +}; + +static ssize_t cpu0_temp_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "CPU 0 Temprature\n"); +} + +static ssize_t cpu1_temp_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "CPU 1 Temprature\n"); +} + +static ssize_t get_cpu0_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int value = loongson3_cpu_temp(0); + return sprintf(buf, "%d\n", value); +} + +static ssize_t get_cpu1_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int value = loongson3_cpu_temp(1); + return sprintf(buf, "%d\n", value); +} + +static int create_sysfs_cputemp_files(struct kobject *kobj) +{ + int ret; + + ret = sysfs_create_files(kobj, hwmon_cputemp1); + if (ret) + goto sysfs_create_temp1_fail; + + if (loongson_sysconf.nr_cpus <= loongson_sysconf.cores_per_package) + return 0; + + ret = sysfs_create_files(kobj, hwmon_cputemp2); + if (ret) + goto sysfs_create_temp2_fail; + + return 0; + +sysfs_create_temp2_fail: + sysfs_remove_files(kobj, hwmon_cputemp1); + +sysfs_create_temp1_fail: + return -1; +} + +static void remove_sysfs_cputemp_files(struct kobject *kobj) +{ + sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp1); + + if (loongson_sysconf.nr_cpus > loongson_sysconf.cores_per_package) + sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp2); +} + +#define CPU_THERMAL_THRESHOLD 90000 +static struct delayed_work thermal_work; + +static void do_thermal_timer(struct work_struct *work) +{ + int value = loongson3_cpu_temp(0); + if (value <= CPU_THERMAL_THRESHOLD) + schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000)); + else + orderly_poweroff(true); +} + +static int __init loongson_hwmon_init(void) +{ + int ret; + + pr_info("Loongson Hwmon Enter...\n"); + + cpu_hwmon_dev = hwmon_device_register(NULL); + if (IS_ERR(cpu_hwmon_dev)) { + ret = -ENOMEM; + pr_err("hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&cpu_hwmon_dev->kobj, + &cpu_hwmon_attribute_group); + if (ret) { + pr_err("fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + ret = create_sysfs_cputemp_files(&cpu_hwmon_dev->kobj); + if (ret) { + pr_err("fail to create cpu temprature interface!\n"); + goto fail_create_sysfs_cputemp_files; + } + + INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer); + schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000)); + + return ret; + +fail_create_sysfs_cputemp_files: + sysfs_remove_group(&cpu_hwmon_dev->kobj, + &cpu_hwmon_attribute_group); + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(cpu_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void __exit loongson_hwmon_exit(void) +{ + cancel_delayed_work_sync(&thermal_work); + remove_sysfs_cputemp_files(&cpu_hwmon_dev->kobj); + sysfs_remove_group(&cpu_hwmon_dev->kobj, + &cpu_hwmon_attribute_group); + hwmon_device_unregister(cpu_hwmon_dev); +} + +module_init(loongson_hwmon_init); +module_exit(loongson_hwmon_exit); + +MODULE_AUTHOR("Yu Xiang <xiangy@lemote.com>"); +MODULE_AUTHOR("Huacai Chen <chenhc@lemote.com>"); +MODULE_DESCRIPTION("Loongson CPU Hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ede5461fbe96..68f3d2b55632 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -38,7 +38,8 @@ config ACER_WMI config ACERHDF tristate "Acer Aspire One temperature and fan driver" - depends on THERMAL && ACPI + depends on ACPI && THERMAL + select THERMAL_GOV_BANG_BANG ---help--- This is a driver for Acer Aspire One netbooks. It allows to access the temperature sensor and to control the fan. @@ -128,10 +129,10 @@ config DELL_WMI_AIO be called dell-wmi-aio. config DELL_SMO8800 - tristate "Dell Latitude freefall driver (ACPI SMO8800/SMO8810)" + tristate "Dell Latitude freefall driver (ACPI SMO88XX)" depends on ACPI ---help--- - Say Y here if you want to support SMO8800/SMO8810 freefall device + Say Y here if you want to support SMO88XX freefall devices on Dell Latitude laptops. To compile this driver as a module, choose M here: the module will @@ -613,6 +614,7 @@ config ACPI_TOSHIBA depends on INPUT depends on RFKILL || RFKILL = n depends on SERIO_I8042 || SERIO_I8042 = n + depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_POLLDEV select INPUT_SPARSEKMAP ---help--- @@ -734,6 +736,31 @@ config INTEL_IPS functionality. If in doubt, say Y here; it will only load on supported platforms. +config INTEL_IMR + bool "Intel Isolated Memory Region support" + default n + depends on X86_INTEL_QUARK && IOSF_MBI + ---help--- + This option provides a means to manipulate Isolated Memory Regions. + IMRs are a set of registers that define read and write access masks + to prohibit certain system agents from accessing memory with 1 KiB + granularity. + + IMRs make it possible to control read/write access to an address + by hardware agents inside the SoC. Read and write masks can be + defined for: + - eSRAM flush + - Dirty CPU snoop (write only) + - RMU access + - PCI Virtual Channel 0/Virtual Channel 1 + - SMM mode + - Non SMM mode + + Quark contains a set of eight IMR registers and makes use of those + registers during its bootup process. + + If you are running on a Galileo/Quark say Y here. + config IBM_RTL tristate "Device driver to enable PRTL support" depends on X86 && PCI diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 26c4fd1394da..3ac29a1e8f92 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -2117,7 +2117,6 @@ static void acer_platform_shutdown(struct platform_device *device) static struct platform_driver acer_platform_driver = { .driver = { .name = "acer-wmi", - .owner = THIS_MODULE, .pm = &acer_pm, }, .probe = acer_platform_probe, diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index f94467c05225..594c918b553d 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -50,7 +50,7 @@ */ #undef START_IN_KERNEL_MODE -#define DRV_VER "0.5.26" +#define DRV_VER "0.7.0" /* * According to the Atom N270 datasheet, @@ -119,116 +119,152 @@ struct fancmd { u8 cmd_auto; }; +struct manualcmd { + u8 mreg; + u8 moff; +}; + +/* default register and command to disable fan in manual mode */ +static const struct manualcmd mcmd = { + .mreg = 0x94, + .moff = 0xff, +}; + /* BIOS settings */ -struct bios_settings_t { +struct bios_settings { const char *vendor; const char *product; const char *version; - unsigned char fanreg; - unsigned char tempreg; + u8 fanreg; + u8 tempreg; struct fancmd cmd; + int mcmd_enable; }; /* Register addresses and values for different BIOS versions */ -static const struct bios_settings_t bios_tbl[] = { +static const struct bios_settings bios_tbl[] = { /* AOA110 */ - {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, - {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, - {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00}, 0}, + {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00}, 0}, + {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00}, 0}, + {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00}, 0}, + {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00}, 0}, + {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00}, 0}, + {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00}, 0}, /* AOA150 */ - {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, - {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00}, 0}, + {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00}, 0}, /* LT1005u */ - {"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00}, 0}, /* Acer 1410 */ - {"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0}, /* Acer 1810xx */ - {"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1810T", "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0}, + /* Acer 5755G */ + {"Acer", "Aspire 5755G", "V1.20", 0xab, 0xb4, {0x00, 0x08}, 0}, + {"Acer", "Aspire 5755G", "V1.21", 0xab, 0xb3, {0x00, 0x08}, 0}, + /* Acer 521 */ + {"Acer", "AO521", "V1.11", 0x55, 0x58, {0x1f, 0x00}, 0}, /* Acer 531 */ - {"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00}, 0}, /* Acer 751 */ - {"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AO751h", "V0.3206", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00}, 0}, + /* Acer 753 */ + {"Acer", "Aspire One 753", "V1.24", 0x93, 0xac, {0x14, 0x04}, 1}, /* Acer 1825 */ - {"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00} }, - {"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00}, 0}, + /* Acer Extensa 5420 */ + {"Acer", "Extensa 5420", "V1.17", 0x93, 0xac, {0x14, 0x04}, 1}, + /* Acer Aspire 5315 */ + {"Acer", "Aspire 5315", "V1.19", 0x93, 0xac, {0x14, 0x04}, 1}, + /* Acer Aspire 5739 */ + {"Acer", "Aspire 5739G", "V1.3311", 0x55, 0x58, {0x20, 0x00}, 0}, /* Acer TravelMate 7730 */ - {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00}, 0}, + /* Acer TravelMate TM8573T */ + {"Acer", "TM8573T", "V1.13", 0x93, 0xa8, {0x14, 0x04}, 1}, /* Gateway */ - {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, - {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00} }, - {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x9e, 0x00} }, - {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, - {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, - {"Gateway", "LT31", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Gateway", "LT31", "v1.3303t", 0x55, 0x58, {0x9e, 0x00}, 0}, /* Packard Bell */ - {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00} }, - {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, - {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, - {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, - {"Packard Bell", "ENBFT", "V1.3118", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "ENBFT", "V1.3127", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMA", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMA", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTMA", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} }, - {"Packard Bell", "DOTVR46", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00}, 0}, + {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00}, 0}, + {"Packard Bell", "ENBFT", "V1.3118", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "ENBFT", "V1.3127", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMU", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMA", "v1.3201", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMA", "v1.3302", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTMA", "v1.3303t", 0x55, 0x58, {0x9e, 0x00}, 0}, + {"Packard Bell", "DOTVR46", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0}, /* pewpew-terminator */ - {"", "", "", 0, 0, {0, 0} } + {"", "", "", 0, 0, {0, 0}, 0} }; -static const struct bios_settings_t *bios_cfg __read_mostly; +static const struct bios_settings *bios_cfg __read_mostly; + +/* + * this struct is used to instruct thermal layer to use bang_bang instead of + * default governor for acerhdf + */ +static struct thermal_zone_params acerhdf_zone_params = { + .governor_name = "bang_bang", +}; static int acerhdf_get_temp(int *temp) { @@ -275,6 +311,12 @@ static void acerhdf_change_fanstate(int state) fanstate = state; ec_write(bios_cfg->fanreg, cmd); + + if (bios_cfg->mcmd_enable && state == ACERHDF_FAN_OFF) { + if (verbose) + pr_notice("turning off fan manually\n"); + ec_write(mcmd.mreg, mcmd.moff); + } } static void acerhdf_check_param(struct thermal_zone_device *thermal) @@ -401,6 +443,21 @@ static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip, { if (trip == 0) *type = THERMAL_TRIP_ACTIVE; + else if (trip == 1) + *type = THERMAL_TRIP_CRITICAL; + else + return -EINVAL; + + return 0; +} + +static int acerhdf_get_trip_hyst(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + if (trip != 0) + return -EINVAL; + + *temp = fanon - fanoff; return 0; } @@ -410,6 +467,10 @@ static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip, { if (trip == 0) *temp = fanon; + else if (trip == 1) + *temp = ACERHDF_TEMP_CRIT; + else + return -EINVAL; return 0; } @@ -429,6 +490,7 @@ static struct thermal_zone_device_ops acerhdf_dev_ops = { .get_mode = acerhdf_get_mode, .set_mode = acerhdf_set_mode, .get_trip_type = acerhdf_get_trip_type, + .get_trip_hyst = acerhdf_get_trip_hyst, .get_trip_temp = acerhdf_get_trip_temp, .get_crit_temp = acerhdf_get_crit_temp, }; @@ -481,9 +543,7 @@ static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev, } if (state == 0) { - /* turn fan off only if below fanoff temperature */ - if ((cur_state == ACERHDF_FAN_AUTO) && - (cur_temp < fanoff)) + if (cur_state == ACERHDF_FAN_AUTO) acerhdf_change_fanstate(ACERHDF_FAN_OFF); } else { if (cur_state == ACERHDF_FAN_OFF) @@ -533,7 +593,6 @@ static const struct dev_pm_ops acerhdf_pm_ops = { static struct platform_driver acerhdf_driver = { .driver = { .name = "acerhdf", - .owner = THIS_MODULE, .pm = &acerhdf_pm_ops, }, .probe = acerhdf_probe, @@ -559,7 +618,7 @@ static int str_starts_with(const char *str, const char *start) static int acerhdf_check_hardware(void) { char const *vendor, *version, *product; - const struct bios_settings_t *bt = NULL; + const struct bios_settings *bt = NULL; /* get BIOS data */ vendor = dmi_get_system_info(DMI_SYS_VENDOR); @@ -661,12 +720,20 @@ static int acerhdf_register_thermal(void) if (IS_ERR(cl_dev)) return -EINVAL; - thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL, - &acerhdf_dev_ops, NULL, 0, + thz_dev = thermal_zone_device_register("acerhdf", 2, 0, NULL, + &acerhdf_dev_ops, + &acerhdf_zone_params, 0, (kernelmode) ? interval*1000 : 0); if (IS_ERR(thz_dev)) return -EINVAL; + if (strcmp(thz_dev->governor->name, + acerhdf_zone_params.governor_name)) { + pr_err("Didn't get thermal governor %s, perhaps not compiled into thermal subsystem.\n", + acerhdf_zone_params.governor_name); + return -EINVAL; + } + return 0; } @@ -723,9 +790,15 @@ MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5755G:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAO521*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5739G:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:"); MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:"); +MODULE_ALIAS("dmi:*:*Acer*:TM8573T:"); MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:"); MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:"); @@ -734,6 +807,7 @@ MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:"); MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:"); MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:"); MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnExtensa 5420*:"); module_init(acerhdf_init); module_exit(acerhdf_exit); diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c index c5af23b64438..1e1e59423889 100644 --- a/drivers/platform/x86/alienware-wmi.c +++ b/drivers/platform/x86/alienware-wmi.c @@ -157,7 +157,6 @@ static struct platform_zone *zone_data; static struct platform_driver platform_driver = { .driver = { .name = "alienware-wmi", - .owner = THIS_MODULE, } }; diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c index da36b5e824d4..0157625cb918 100644 --- a/drivers/platform/x86/amilo-rfkill.c +++ b/drivers/platform/x86/amilo-rfkill.c @@ -138,7 +138,6 @@ static int amilo_rfkill_remove(struct platform_device *device) static struct platform_driver amilo_rfkill_driver = { .driver = { .name = KBUILD_MODNAME, - .owner = THIS_MODULE, }, .probe = amilo_rfkill_probe, .remove = amilo_rfkill_remove, diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index b9429fbf1cd8..6808715003f6 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -22,6 +22,7 @@ #include <linux/delay.h> #include <linux/pci.h> #include <linux/vga_switcheroo.h> +#include <linux/vgaarb.h> #include <acpi/video.h> #include <asm/io.h> @@ -31,6 +32,7 @@ struct apple_gmux_data { bool indexed; struct mutex index_lock; + struct pci_dev *pdev; struct backlight_device *bdev; /* switcheroo data */ @@ -415,6 +417,23 @@ static int gmux_resume(struct device *dev) return 0; } +static struct pci_dev *gmux_get_io_pdev(void) +{ + struct pci_dev *pdev = NULL; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_IO)) + continue; + + return pdev; + } + + return NULL; +} + static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) { struct apple_gmux_data *gmux_data; @@ -425,6 +444,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) int ret = -ENXIO; acpi_status status; unsigned long long gpe; + struct pci_dev *pdev = NULL; if (apple_gmux_data) return -EBUSY; @@ -475,7 +495,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) ver_minor = (version >> 16) & 0xff; ver_release = (version >> 8) & 0xff; } else { - pr_info("gmux device not present\n"); + pr_info("gmux device not present or IO disabled\n"); ret = -ENODEV; goto err_release; } @@ -483,6 +503,23 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor, ver_release, (gmux_data->indexed ? "indexed" : "classic")); + /* + * Apple systems with gmux are EFI based and normally don't use + * VGA. In addition changing IO+MEM ownership between IGP and dGPU + * disables IO/MEM used for backlight control on some systems. + * Lock IO+MEM to GPU with active IO to prevent switch. + */ + pdev = gmux_get_io_pdev(); + if (pdev && vga_tryget(pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM)) { + pr_err("IO+MEM vgaarb-locking for PCI:%s failed\n", + pci_name(pdev)); + ret = -EBUSY; + goto err_release; + } else if (pdev) + pr_info("locked IO for PCI:%s\n", pci_name(pdev)); + gmux_data->pdev = pdev; + memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); @@ -574,6 +611,10 @@ err_enable_gpe: err_notify: backlight_device_unregister(bdev); err_release: + if (gmux_data->pdev) + vga_put(gmux_data->pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); + pci_dev_put(pdev); release_region(gmux_data->iostart, gmux_data->iolen); err_free: kfree(gmux_data); @@ -593,6 +634,11 @@ static void gmux_remove(struct pnp_dev *pnp) &gmux_notify_handler); } + if (gmux_data->pdev) { + vga_put(gmux_data->pdev, + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); + pci_dev_put(gmux_data->pdev); + } backlight_device_unregister(gmux_data->bdev); release_region(gmux_data->iostart, gmux_data->iolen); @@ -624,19 +670,7 @@ static struct pnp_driver gmux_pnp_driver = { }, }; -static int __init apple_gmux_init(void) -{ - return pnp_register_driver(&gmux_pnp_driver); -} - -static void __exit apple_gmux_exit(void) -{ - pnp_unregister_driver(&gmux_pnp_driver); -} - -module_init(apple_gmux_init); -module_exit(apple_gmux_exit); - +module_pnp_driver(gmux_pnp_driver); MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); MODULE_DESCRIPTION("Apple Gmux Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 7f4dc6f51f8a..46b274693872 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -843,8 +843,7 @@ static int asus_backlight_init(struct asus_laptop *asus) static void asus_backlight_exit(struct asus_laptop *asus) { - if (asus->backlight_device) - backlight_device_unregister(asus->backlight_device); + backlight_device_unregister(asus->backlight_device); asus->backlight_device = NULL; } @@ -857,8 +856,8 @@ static void asus_backlight_exit(struct asus_laptop *asus) * than count bytes. We set eof to 1 if we handle those 2 values. We return the * number of bytes written in page */ -static ssize_t show_infos(struct device *dev, - struct device_attribute *attr, char *page) +static ssize_t infos_show(struct device *dev, struct device_attribute *attr, + char *page) { struct asus_laptop *asus = dev_get_drvdata(dev); int len = 0; @@ -927,6 +926,7 @@ static ssize_t show_infos(struct device *dev, return len; } +static DEVICE_ATTR_RO(infos); static int parse_arg(const char *buf, unsigned long count, int *val) { @@ -958,15 +958,15 @@ static ssize_t sysfs_acpi_set(struct asus_laptop *asus, /* * LEDD display */ -static ssize_t show_ledd(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t ledd_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "0x%08x\n", asus->ledd_status); } -static ssize_t store_ledd(struct device *dev, struct device_attribute *attr, +static ssize_t ledd_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); @@ -982,6 +982,7 @@ static ssize_t store_ledd(struct device *dev, struct device_attribute *attr, } return rv; } +static DEVICE_ATTR_RW(ledd); /* * Wireless @@ -1015,21 +1016,22 @@ static int asus_wlan_set(struct asus_laptop *asus, int status) return 0; } -static ssize_t show_wlan(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t wlan_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS)); } -static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, +static ssize_t wlan_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); return sysfs_acpi_set(asus, buf, count, METHOD_WLAN); } +static DEVICE_ATTR_RW(wlan); /*e * Bluetooth @@ -1043,15 +1045,15 @@ static int asus_bluetooth_set(struct asus_laptop *asus, int status) return 0; } -static ssize_t show_bluetooth(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t bluetooth_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS)); } -static ssize_t store_bluetooth(struct device *dev, +static ssize_t bluetooth_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -1059,6 +1061,7 @@ static ssize_t store_bluetooth(struct device *dev, return sysfs_acpi_set(asus, buf, count, METHOD_BLUETOOTH); } +static DEVICE_ATTR_RW(bluetooth); /* * Wimax @@ -1072,22 +1075,22 @@ static int asus_wimax_set(struct asus_laptop *asus, int status) return 0; } -static ssize_t show_wimax(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t wimax_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus_wireless_status(asus, WM_RSTS)); } -static ssize_t store_wimax(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) +static ssize_t wimax_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); return sysfs_acpi_set(asus, buf, count, METHOD_WIMAX); } +static DEVICE_ATTR_RW(wimax); /* * Wwan @@ -1101,22 +1104,22 @@ static int asus_wwan_set(struct asus_laptop *asus, int status) return 0; } -static ssize_t show_wwan(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t wwan_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus_wireless_status(asus, WW_RSTS)); } -static ssize_t store_wwan(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) +static ssize_t wwan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); return sysfs_acpi_set(asus, buf, count, METHOD_WWAN); } +static DEVICE_ATTR_RW(wwan); /* * Display @@ -1136,8 +1139,8 @@ static void asus_set_display(struct asus_laptop *asus, int value) * displays hooked up simultaneously, so be warned. See the acpi4asus README * for more info. */ -static ssize_t store_disp(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t display_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; @@ -1147,6 +1150,7 @@ static ssize_t store_disp(struct device *dev, struct device_attribute *attr, asus_set_display(asus, value); return rv; } +static DEVICE_ATTR_WO(display); /* * Light Sens @@ -1168,16 +1172,17 @@ static void asus_als_switch(struct asus_laptop *asus, int value) asus->light_switch = value; } -static ssize_t show_lssw(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t ls_switch_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus->light_switch); } -static ssize_t store_lssw(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t ls_switch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; @@ -1188,6 +1193,7 @@ static ssize_t store_lssw(struct device *dev, struct device_attribute *attr, return rv; } +static DEVICE_ATTR_RW(ls_switch); static void asus_als_level(struct asus_laptop *asus, int value) { @@ -1196,16 +1202,16 @@ static void asus_als_level(struct asus_laptop *asus, int value) asus->light_level = value; } -static ssize_t show_lslvl(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t ls_level_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus->light_level); } -static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t ls_level_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; @@ -1219,6 +1225,7 @@ static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr, return rv; } +static DEVICE_ATTR_RW(ls_level); static int pega_int_read(struct asus_laptop *asus, int arg, int *result) { @@ -1235,8 +1242,8 @@ static int pega_int_read(struct asus_laptop *asus, int arg, int *result) return err; } -static ssize_t show_lsvalue(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t ls_value_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); int err, hi, lo; @@ -1248,6 +1255,7 @@ static ssize_t show_lsvalue(struct device *dev, return sprintf(buf, "%d\n", 10 * hi + lo); return err; } +static DEVICE_ATTR_RO(ls_value); /* * GPS @@ -1275,15 +1283,15 @@ static int asus_gps_switch(struct asus_laptop *asus, int status) return 0; } -static ssize_t show_gps(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t gps_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct asus_laptop *asus = dev_get_drvdata(dev); return sprintf(buf, "%d\n", asus_gps_status(asus)); } -static ssize_t store_gps(struct device *dev, struct device_attribute *attr, +static ssize_t gps_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct asus_laptop *asus = dev_get_drvdata(dev); @@ -1299,6 +1307,7 @@ static ssize_t store_gps(struct device *dev, struct device_attribute *attr, rfkill_set_sw_state(asus->gps.rfkill, !value); return rv; } +static DEVICE_ATTR_RW(gps); /* * rfkill @@ -1570,19 +1579,6 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event) asus_input_notify(asus, event); } -static DEVICE_ATTR(infos, S_IRUGO, show_infos, NULL); -static DEVICE_ATTR(wlan, S_IRUGO | S_IWUSR, show_wlan, store_wlan); -static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR, - show_bluetooth, store_bluetooth); -static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax); -static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan); -static DEVICE_ATTR(display, S_IWUSR, NULL, store_disp); -static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd); -static DEVICE_ATTR(ls_value, S_IRUGO, show_lsvalue, NULL); -static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl); -static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw); -static DEVICE_ATTR(gps, S_IRUGO | S_IWUSR, show_gps, store_gps); - static struct attribute *asus_attributes[] = { &dev_attr_infos.attr, &dev_attr_wlan.attr, @@ -1617,7 +1613,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, else goto normal; - return supported; + return supported ? attr->mode : 0; } normal: @@ -1699,7 +1695,6 @@ static void asus_platform_exit(struct asus_laptop *asus) static struct platform_driver platform_driver = { .driver = { .name = ASUS_LAPTOP_FILE, - .owner = THIS_MODULE, }, }; diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index c1a6cd66af42..abdaed34c728 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -191,6 +191,15 @@ static const struct dmi_system_id asus_quirks[] = { }, { .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X551CA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X551CA"), + }, + .driver_data = &quirk_asus_wapf4, + }, + { + .callback = dmi_matched, .ident = "ASUSTeK COMPUTER INC. X55A", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 21fc932da3a1..7543a56e0f45 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1308,8 +1308,7 @@ static int asus_wmi_backlight_init(struct asus_wmi *asus) static void asus_wmi_backlight_exit(struct asus_wmi *asus) { - if (asus->backlight_device) - backlight_device_unregister(asus->backlight_device); + backlight_device_unregister(asus->backlight_device); asus->backlight_device = NULL; } diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 70d355a9ae2c..55cf10bc7817 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -520,7 +520,7 @@ static acpi_status cmpc_get_accel(acpi_handle handle, { union acpi_object param[2]; struct acpi_object_list input; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; unsigned char *locs; acpi_status status; diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index 26bfd7bb5c13..b4e94471f3d5 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -177,7 +177,7 @@ struct compal_data{ unsigned char curr_pwm; /* Power supply */ - struct power_supply psy; + struct power_supply *psy; struct power_supply_info psy_info; char bat_model_name[BAT_MODEL_NAME_LEN + 1]; char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1]; @@ -565,8 +565,7 @@ static int bat_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct compal_data *data; - data = container_of(psy, struct compal_data, psy); + struct compal_data *data = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -710,7 +709,6 @@ static int compal_remove(struct platform_device *); static struct platform_driver compal_driver = { .driver = { .name = DRIVER_NAME, - .owner = THIS_MODULE, }, .probe = compal_probe, .remove = compal_remove, @@ -876,13 +874,16 @@ static struct dmi_system_id __initdata compal_dmi_table[] = { }; MODULE_DEVICE_TABLE(dmi, compal_dmi_table); +static const struct power_supply_desc psy_bat_desc = { + .name = DRIVER_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = compal_bat_properties, + .num_properties = ARRAY_SIZE(compal_bat_properties), + .get_property = bat_get_property, +}; + static void initialize_power_supply_data(struct compal_data *data) { - data->psy.name = DRIVER_NAME; - data->psy.type = POWER_SUPPLY_TYPE_BATTERY; - data->psy.properties = compal_bat_properties; - data->psy.num_properties = ARRAY_SIZE(compal_bat_properties); - data->psy.get_property = bat_get_property; ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR, data->bat_manufacturer_name, @@ -1012,6 +1013,7 @@ static int compal_probe(struct platform_device *pdev) int err; struct compal_data *data; struct device *hwmon_dev; + struct power_supply_config psy_cfg = {}; if (!extra_features) return 0; @@ -1027,9 +1029,9 @@ static int compal_probe(struct platform_device *pdev) if (err) return err; - hwmon_dev = hwmon_device_register_with_groups(&pdev->dev, - "compal", data, - compal_hwmon_groups); + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + "compal", data, + compal_hwmon_groups); if (IS_ERR(hwmon_dev)) { err = PTR_ERR(hwmon_dev); goto remove; @@ -1037,7 +1039,13 @@ static int compal_probe(struct platform_device *pdev) /* Power supply */ initialize_power_supply_data(data); - power_supply_register(&compal_device->dev, &data->psy); + psy_cfg.drv_data = data; + data->psy = power_supply_register(&compal_device->dev, &psy_bat_desc, + &psy_cfg); + if (IS_ERR(data->psy)) { + err = PTR_ERR(data->psy); + goto remove; + } platform_set_drvdata(pdev, data); @@ -1072,7 +1080,7 @@ static int compal_remove(struct platform_device *pdev) pwm_disable_control(); data = platform_get_drvdata(pdev); - power_supply_unregister(&data->psy); + power_supply_unregister(data->psy); sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 233d2ee598a6..d688d806a8a5 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -2,9 +2,11 @@ * Driver for Dell laptop extras * * Copyright (c) Red Hat <mjg@redhat.com> + * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com> + * Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com> * - * Based on documentation in the libsmbios package, Copyright (C) 2005 Dell - * Inc. + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. * * 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 @@ -32,6 +34,13 @@ #include "../../firmware/dcdbas.h" #define BRIGHTNESS_TOKEN 0x7d +#define KBD_LED_OFF_TOKEN 0x01E1 +#define KBD_LED_ON_TOKEN 0x01E2 +#define KBD_LED_AUTO_TOKEN 0x01E3 +#define KBD_LED_AUTO_25_TOKEN 0x02EA +#define KBD_LED_AUTO_50_TOKEN 0x02EB +#define KBD_LED_AUTO_75_TOKEN 0x02EC +#define KBD_LED_AUTO_100_TOKEN 0x02F6 /* This structure will be modified by the firmware when we enter * system management mode, hence the volatiles */ @@ -62,6 +71,13 @@ struct calling_interface_structure { struct quirk_entry { u8 touchpad_led; + + int needs_kbd_timeouts; + /* + * Ordered list of timeouts expressed in seconds. + * The list must end with -1 + */ + int kbd_timeouts[]; }; static struct quirk_entry *quirks; @@ -76,6 +92,15 @@ static int __init dmi_matched(const struct dmi_system_id *dmi) return 1; } +/* + * These values come from Windows utility provided by Dell. If any other value + * is used then BIOS silently set timeout to 0 without any error message. + */ +static struct quirk_entry quirk_dell_xps13_9333 = { + .needs_kbd_timeouts = 1, + .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, +}; + static int da_command_address; static int da_command_code; static int da_num_tokens; @@ -84,7 +109,6 @@ static struct calling_interface_token *da_tokens; static struct platform_driver platform_driver = { .driver = { .name = "dell-laptop", - .owner = THIS_MODULE, } }; @@ -268,6 +292,15 @@ static const struct dmi_system_id dell_quirks[] __initconst = { }, .driver_data = &quirk_dell_vostro_v130, }, + { + .callback = dmi_matched, + .ident = "Dell XPS13 9333", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), + }, + .driver_data = &quirk_dell_xps13_9333, + }, { } }; @@ -332,17 +365,29 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) } } -static int find_token_location(int tokenid) +static int find_token_id(int tokenid) { int i; + for (i = 0; i < da_num_tokens; i++) { if (da_tokens[i].tokenID == tokenid) - return da_tokens[i].location; + return i; } return -1; } +static int find_token_location(int tokenid) +{ + int id; + + id = find_token_id(tokenid); + if (id == -1) + return -1; + + return da_tokens[id].location; +} + static struct calling_interface_buffer * dell_send_request(struct calling_interface_buffer *buffer, int class, int select) @@ -363,6 +408,20 @@ dell_send_request(struct calling_interface_buffer *buffer, int class, return buffer; } +static inline int dell_smi_error(int value) +{ + switch (value) { + case 0: /* Completed successfully */ + return 0; + case -1: /* Completed with error */ + return -EIO; + case -2: /* Function not supported */ + return -ENXIO; + default: /* Unknown error */ + return -EINVAL; + } +} + /* Derived from information in DellWirelessCtl.cpp: Class 17, select 11 is radio control. It returns an array of 32-bit values. @@ -564,7 +623,7 @@ static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, { static bool extended; - if (str & 0x20) + if (str & I8042_STR_AUXDATA) return false; if (unlikely(data == 0xe0)) { @@ -717,7 +776,7 @@ static int dell_send_intensity(struct backlight_device *bd) else dell_send_request(buffer, 1, 1); -out: + out: release_buffer(); return ret; } @@ -741,7 +800,7 @@ static int dell_get_intensity(struct backlight_device *bd) ret = buffer->output[1]; -out: + out: release_buffer(); return ret; } @@ -790,6 +849,1018 @@ static void touchpad_led_exit(void) led_classdev_unregister(&touchpad_led); } +/* + * Derived from information in smbios-keyboard-ctl: + * + * cbClass 4 + * cbSelect 11 + * Keyboard illumination + * cbArg1 determines the function to be performed + * + * cbArg1 0x0 = Get Feature Information + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of user-selectable modes + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * cbRES2, byte2 Reserved for future use + * cbRES2, byte3 Keyboard illumination type + * 0 Reserved + * 1 Tasklight + * 2 Backlight + * 3-255 Reserved for future use + * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES3, byte1 Supported timeout unit bitmap + * bit 0 Seconds + * bit 1 Minutes + * bit 2 Hours + * bit 3 Days + * bits 4-7 Reserved for future use + * cbRES3, byte2 Number of keyboard light brightness levels + * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). + * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). + * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). + * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) + * + * cbArg1 0x1 = Get Current State + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbRES2, byte2 Currently active auto keyboard illumination triggers. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES2, byte3 Current Timeout + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte + * are set upon return from the [Get feature information] call. + * cbRES3, byte0 Current setting of ALS value that turns the light on or off. + * cbRES3, byte1 Current ALS reading + * cbRES3, byte2 Current keyboard light level. + * + * cbArg1 0x2 = Set New State + * cbRES1 Standard return codes (0, -1, -2) + * cbArg2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow + * keyboard to turn off automatically. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbArg2, byte3 Desired Timeout + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. + * cbArg3, byte2 Desired keyboard light level. + */ + + +enum kbd_timeout_unit { + KBD_TIMEOUT_SECONDS = 0, + KBD_TIMEOUT_MINUTES, + KBD_TIMEOUT_HOURS, + KBD_TIMEOUT_DAYS, +}; + +enum kbd_mode_bit { + KBD_MODE_BIT_OFF = 0, + KBD_MODE_BIT_ON, + KBD_MODE_BIT_ALS, + KBD_MODE_BIT_TRIGGER_ALS, + KBD_MODE_BIT_TRIGGER, + KBD_MODE_BIT_TRIGGER_25, + KBD_MODE_BIT_TRIGGER_50, + KBD_MODE_BIT_TRIGGER_75, + KBD_MODE_BIT_TRIGGER_100, +}; + +#define kbd_is_als_mode_bit(bit) \ + ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) +#define kbd_is_trigger_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) +#define kbd_is_level_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) + +struct kbd_info { + u16 modes; + u8 type; + u8 triggers; + u8 levels; + u8 seconds; + u8 minutes; + u8 hours; + u8 days; +}; + +struct kbd_state { + u8 mode_bit; + u8 triggers; + u8 timeout_value; + u8 timeout_unit; + u8 als_setting; + u8 als_value; + u8 level; +}; + +static const int kbd_tokens[] = { + KBD_LED_OFF_TOKEN, + KBD_LED_AUTO_25_TOKEN, + KBD_LED_AUTO_50_TOKEN, + KBD_LED_AUTO_75_TOKEN, + KBD_LED_AUTO_100_TOKEN, + KBD_LED_ON_TOKEN, +}; + +static u16 kbd_token_bits; + +static struct kbd_info kbd_info; +static bool kbd_als_supported; +static bool kbd_triggers_supported; + +static u8 kbd_mode_levels[16]; +static int kbd_mode_levels_count; + +static u8 kbd_previous_level; +static u8 kbd_previous_mode_bit; + +static bool kbd_led_present; + +/* + * NOTE: there are three ways to set the keyboard backlight level. + * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). + * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). + * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) + * + * There are laptops which support only one of these methods. If we want to + * support as many machines as possible we need to implement all three methods. + * The first two methods use the kbd_state structure. The third uses SMBIOS + * tokens. If kbd_info.levels == 0, the machine does not support setting the + * keyboard backlight level via kbd_state.level. + */ + +static int kbd_get_info(struct kbd_info *info) +{ + u8 units; + int ret; + + get_buffer(); + + buffer->input[0] = 0x0; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + + if (ret) { + ret = dell_smi_error(ret); + goto out; + } + + info->modes = buffer->output[1] & 0xFFFF; + info->type = (buffer->output[1] >> 24) & 0xFF; + info->triggers = buffer->output[2] & 0xFF; + units = (buffer->output[2] >> 8) & 0xFF; + info->levels = (buffer->output[2] >> 16) & 0xFF; + + if (units & BIT(0)) + info->seconds = (buffer->output[3] >> 0) & 0xFF; + if (units & BIT(1)) + info->minutes = (buffer->output[3] >> 8) & 0xFF; + if (units & BIT(2)) + info->hours = (buffer->output[3] >> 16) & 0xFF; + if (units & BIT(3)) + info->days = (buffer->output[3] >> 24) & 0xFF; + + out: + release_buffer(); + return ret; +} + +static unsigned int kbd_get_max_level(void) +{ + if (kbd_info.levels != 0) + return kbd_info.levels; + if (kbd_mode_levels_count > 0) + return kbd_mode_levels_count - 1; + return 0; +} + +static int kbd_get_level(struct kbd_state *state) +{ + int i; + + if (kbd_info.levels != 0) + return state->level; + + if (kbd_mode_levels_count > 0) { + for (i = 0; i < kbd_mode_levels_count; ++i) + if (kbd_mode_levels[i] == state->mode_bit) + return i; + return 0; + } + + return -EINVAL; +} + +static int kbd_set_level(struct kbd_state *state, u8 level) +{ + if (kbd_info.levels != 0) { + if (level != 0) + kbd_previous_level = level; + if (state->level == level) + return 0; + state->level = level; + if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) + state->mode_bit = kbd_previous_mode_bit; + else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = state->mode_bit; + state->mode_bit = KBD_MODE_BIT_OFF; + } + return 0; + } + + if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { + if (level != 0) + kbd_previous_level = level; + state->mode_bit = kbd_mode_levels[level]; + return 0; + } + + return -EINVAL; +} + +static int kbd_get_state(struct kbd_state *state) +{ + int ret; + + get_buffer(); + + buffer->input[0] = 0x1; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + + if (ret) { + ret = dell_smi_error(ret); + goto out; + } + + state->mode_bit = ffs(buffer->output[1] & 0xFFFF); + if (state->mode_bit != 0) + state->mode_bit--; + + state->triggers = (buffer->output[1] >> 16) & 0xFF; + state->timeout_value = (buffer->output[1] >> 24) & 0x3F; + state->timeout_unit = (buffer->output[1] >> 30) & 0x3; + state->als_setting = buffer->output[2] & 0xFF; + state->als_value = (buffer->output[2] >> 8) & 0xFF; + state->level = (buffer->output[2] >> 16) & 0xFF; + + out: + release_buffer(); + return ret; +} + +static int kbd_set_state(struct kbd_state *state) +{ + int ret; + + get_buffer(); + buffer->input[0] = 0x2; + buffer->input[1] = BIT(state->mode_bit) & 0xFFFF; + buffer->input[1] |= (state->triggers & 0xFF) << 16; + buffer->input[1] |= (state->timeout_value & 0x3F) << 24; + buffer->input[1] |= (state->timeout_unit & 0x3) << 30; + buffer->input[2] = state->als_setting & 0xFF; + buffer->input[2] |= (state->level & 0xFF) << 16; + dell_send_request(buffer, 4, 11); + ret = buffer->output[0]; + release_buffer(); + + return dell_smi_error(ret); +} + +static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) +{ + int ret; + + ret = kbd_set_state(state); + if (ret == 0) + return 0; + + /* + * When setting the new state fails,try to restore the previous one. + * This is needed on some machines where BIOS sets a default state when + * setting a new state fails. This default state could be all off. + */ + + if (kbd_set_state(old)) + pr_err("Setting old previous keyboard state failed\n"); + + return ret; +} + +static int kbd_set_token_bit(u8 bit) +{ + int id; + int ret; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + id = find_token_id(kbd_tokens[bit]); + if (id == -1) + return -EINVAL; + + get_buffer(); + buffer->input[0] = da_tokens[id].location; + buffer->input[1] = da_tokens[id].value; + dell_send_request(buffer, 1, 0); + ret = buffer->output[0]; + release_buffer(); + + return dell_smi_error(ret); +} + +static int kbd_get_token_bit(u8 bit) +{ + int id; + int ret; + int val; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + id = find_token_id(kbd_tokens[bit]); + if (id == -1) + return -EINVAL; + + get_buffer(); + buffer->input[0] = da_tokens[id].location; + dell_send_request(buffer, 0, 0); + ret = buffer->output[0]; + val = buffer->output[1]; + release_buffer(); + + if (ret) + return dell_smi_error(ret); + + return (val == da_tokens[id].value); +} + +static int kbd_get_first_active_token_bit(void) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { + ret = kbd_get_token_bit(i); + if (ret == 1) + return i; + } + + return ret; +} + +static int kbd_get_valid_token_counts(void) +{ + return hweight16(kbd_token_bits); +} + +static inline int kbd_init_info(void) +{ + struct kbd_state state; + int ret; + int i; + + ret = kbd_get_info(&kbd_info); + if (ret) + return ret; + + kbd_get_state(&state); + + /* NOTE: timeout value is stored in 6 bits so max value is 63 */ + if (kbd_info.seconds > 63) + kbd_info.seconds = 63; + if (kbd_info.minutes > 63) + kbd_info.minutes = 63; + if (kbd_info.hours > 63) + kbd_info.hours = 63; + if (kbd_info.days > 63) + kbd_info.days = 63; + + /* NOTE: On tested machines ON mode did not work and caused + * problems (turned backlight off) so do not use it + */ + kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); + + kbd_previous_level = kbd_get_level(&state); + kbd_previous_mode_bit = state.mode_bit; + + if (kbd_previous_level == 0 && kbd_get_max_level() != 0) + kbd_previous_level = 1; + + if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = + ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); + if (kbd_previous_mode_bit != 0) + kbd_previous_mode_bit--; + } + + if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | + BIT(KBD_MODE_BIT_TRIGGER_ALS))) + kbd_als_supported = true; + + if (kbd_info.modes & ( + BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | + BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | + BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) + )) + kbd_triggers_supported = true; + + /* kbd_mode_levels[0] is reserved, see below */ + for (i = 0; i < 16; ++i) + if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) + kbd_mode_levels[1 + kbd_mode_levels_count++] = i; + + /* + * Find the first supported mode and assign to kbd_mode_levels[0]. + * This should be 0 (off), but we cannot depend on the BIOS to + * support 0. + */ + if (kbd_mode_levels_count > 0) { + for (i = 0; i < 16; ++i) { + if (BIT(i) & kbd_info.modes) { + kbd_mode_levels[0] = i; + break; + } + } + kbd_mode_levels_count++; + } + + return 0; + +} + +static inline void kbd_init_tokens(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) + if (find_token_id(kbd_tokens[i]) != -1) + kbd_token_bits |= BIT(i); +} + +static void kbd_init(void) +{ + int ret; + + ret = kbd_init_info(); + kbd_init_tokens(); + + if (kbd_token_bits != 0 || ret == 0) + kbd_led_present = true; +} + +static ssize_t kbd_led_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool convert; + int value; + int ret; + char ch; + u8 unit; + int i; + + ret = sscanf(buf, "%d %c", &value, &ch); + if (ret < 1) + return -EINVAL; + else if (ret == 1) + ch = 's'; + + if (value < 0) + return -EINVAL; + + convert = false; + + switch (ch) { + case 's': + if (value > kbd_info.seconds) + convert = true; + unit = KBD_TIMEOUT_SECONDS; + break; + case 'm': + if (value > kbd_info.minutes) + convert = true; + unit = KBD_TIMEOUT_MINUTES; + break; + case 'h': + if (value > kbd_info.hours) + convert = true; + unit = KBD_TIMEOUT_HOURS; + break; + case 'd': + if (value > kbd_info.days) + convert = true; + unit = KBD_TIMEOUT_DAYS; + break; + default: + return -EINVAL; + } + + if (quirks && quirks->needs_kbd_timeouts) + convert = true; + + if (convert) { + /* Convert value from current units to seconds */ + switch (unit) { + case KBD_TIMEOUT_DAYS: + value *= 24; + case KBD_TIMEOUT_HOURS: + value *= 60; + case KBD_TIMEOUT_MINUTES: + value *= 60; + unit = KBD_TIMEOUT_SECONDS; + } + + if (quirks && quirks->needs_kbd_timeouts) { + for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { + if (value <= quirks->kbd_timeouts[i]) { + value = quirks->kbd_timeouts[i]; + break; + } + } + } + + if (value <= kbd_info.seconds && kbd_info.seconds) { + unit = KBD_TIMEOUT_SECONDS; + } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { + value /= 60; + unit = KBD_TIMEOUT_MINUTES; + } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { + value /= (60 * 60); + unit = KBD_TIMEOUT_HOURS; + } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { + value /= (60 * 60 * 24); + unit = KBD_TIMEOUT_DAYS; + } else { + return -EINVAL; + } + } + + ret = kbd_get_state(&state); + if (ret) + return ret; + + new_state = state; + new_state.timeout_value = value; + new_state.timeout_unit = unit; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + + return count; +} + +static ssize_t kbd_led_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + int ret; + int len; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = sprintf(buf, "%d", state.timeout_value); + + switch (state.timeout_unit) { + case KBD_TIMEOUT_SECONDS: + return len + sprintf(buf+len, "s\n"); + case KBD_TIMEOUT_MINUTES: + return len + sprintf(buf+len, "m\n"); + case KBD_TIMEOUT_HOURS: + return len + sprintf(buf+len, "h\n"); + case KBD_TIMEOUT_DAYS: + return len + sprintf(buf+len, "d\n"); + default: + return -EINVAL; + } + + return len; +} + +static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, + kbd_led_timeout_show, kbd_led_timeout_store); + +static const char * const kbd_led_triggers[] = { + "keyboard", + "touchpad", + /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ + "mouse", +}; + +static ssize_t kbd_led_triggers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int trigger_bit = -1; + char trigger[21]; + int i, ret; + + ret = sscanf(buf, "%20s", trigger); + if (ret != 1) + return -EINVAL; + + if (trigger[0] != '+' && trigger[0] != '-') + return -EINVAL; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (kbd_triggers_supported) { + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) + continue; + if (trigger[0] == '+' && + triggers_enabled && (state.triggers & BIT(i))) + return count; + if (trigger[0] == '-' && + (!triggers_enabled || !(state.triggers & BIT(i)))) + return count; + trigger_bit = i; + break; + } + } + + if (trigger_bit != -1) { + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) + return -EINVAL; + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) + return -EINVAL; + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + return count; + } + + return -EINVAL; +} + +static ssize_t kbd_led_triggers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + bool triggers_enabled; + int level, i, ret; + int len = 0; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = 0; + + if (kbd_triggers_supported) { + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + level = kbd_get_level(&state); + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if ((triggers_enabled || level <= 0) && + (state.triggers & BIT(i))) + buf[len++] = '+'; + else + buf[len++] = '-'; + len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); + } + } + + if (len) + buf[len - 1] = '\n'; + + return len; +} + +static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, + kbd_led_triggers_show, kbd_led_triggers_store); + +static ssize_t kbd_led_als_enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int enable; + int ret; + + ret = kstrtoint(buf, 0, &enable); + if (ret) + return ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (enable == kbd_is_als_mode_bit(state.mode_bit)) + return count; + + new_state = state; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (enable) { + if (triggers_enabled) + new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; + else + new_state.mode_bit = KBD_MODE_BIT_ALS; + } else { + if (triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else { + new_state.mode_bit = KBD_MODE_BIT_ON; + } + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) + return -EINVAL; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + kbd_previous_mode_bit = new_state.mode_bit; + + return count; +} + +static ssize_t kbd_led_als_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + bool enabled = false; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + enabled = kbd_is_als_mode_bit(state.mode_bit); + + return sprintf(buf, "%d\n", enabled ? 1 : 0); +} + +static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, + kbd_led_als_enabled_show, kbd_led_als_enabled_store); + +static ssize_t kbd_led_als_setting_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state state; + struct kbd_state new_state; + u8 setting; + int ret; + + ret = kstrtou8(buf, 10, &setting); + if (ret) + return ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + new_state = state; + new_state.als_setting = setting; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + return ret; + + return count; +} + +static ssize_t kbd_led_als_setting_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + return sprintf(buf, "%d\n", state.als_setting); +} + +static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, + kbd_led_als_setting_show, kbd_led_als_setting_store); + +static struct attribute *kbd_led_attrs[] = { + &dev_attr_stop_timeout.attr, + &dev_attr_start_triggers.attr, + NULL, +}; + +static const struct attribute_group kbd_led_group = { + .attrs = kbd_led_attrs, +}; + +static struct attribute *kbd_led_als_attrs[] = { + &dev_attr_als_enabled.attr, + &dev_attr_als_setting.attr, + NULL, +}; + +static const struct attribute_group kbd_led_als_group = { + .attrs = kbd_led_als_attrs, +}; + +static const struct attribute_group *kbd_led_groups[] = { + &kbd_led_group, + &kbd_led_als_group, + NULL, +}; + +static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) +{ + int ret; + u16 num; + struct kbd_state state; + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + return 0; + ret = kbd_get_level(&state); + if (ret < 0) + return 0; + return ret; + } + + if (kbd_get_valid_token_counts()) { + ret = kbd_get_first_active_token_bit(); + if (ret < 0) + return 0; + for (num = kbd_token_bits; num != 0 && ret > 0; --ret) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return 0; + return ffs(num) - 1; + } + + pr_warn("Keyboard brightness level control not supported\n"); + return 0; +} + +static void kbd_led_level_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct kbd_state state; + struct kbd_state new_state; + u16 num; + + if (kbd_get_max_level()) { + if (kbd_get_state(&state)) + return; + new_state = state; + if (kbd_set_level(&new_state, value)) + return; + kbd_set_state_safe(&new_state, &state); + return; + } + + if (kbd_get_valid_token_counts()) { + for (num = kbd_token_bits; num != 0 && value > 0; --value) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return; + kbd_set_token_bit(ffs(num) - 1); + return; + } + + pr_warn("Keyboard brightness level control not supported\n"); +} + +static struct led_classdev kbd_led = { + .name = "dell::kbd_backlight", + .brightness_set = kbd_led_level_set, + .brightness_get = kbd_led_level_get, + .groups = kbd_led_groups, +}; + +static int __init kbd_led_init(struct device *dev) +{ + kbd_init(); + if (!kbd_led_present) + return -ENODEV; + if (!kbd_als_supported) + kbd_led_groups[1] = NULL; + kbd_led.max_brightness = kbd_get_max_level(); + if (!kbd_led.max_brightness) { + kbd_led.max_brightness = kbd_get_valid_token_counts(); + if (kbd_led.max_brightness) + kbd_led.max_brightness--; + } + return led_classdev_register(dev, &kbd_led); +} + +static void brightness_set_exit(struct led_classdev *led_cdev, + enum led_brightness value) +{ + /* Don't change backlight level on exit */ +}; + +static void kbd_led_exit(void) +{ + if (!kbd_led_present) + return; + kbd_led.brightness_set = brightness_set_exit; + led_classdev_unregister(&kbd_led); +} + static int __init dell_init(void) { int max_intensity = 0; @@ -842,6 +1913,8 @@ static int __init dell_init(void) if (quirks && quirks->touchpad_led) touchpad_led_init(&platform_device->dev); + kbd_led_init(&platform_device->dev); + dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); if (dell_laptop_dir != NULL) debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, @@ -909,6 +1982,7 @@ static void __exit dell_exit(void) debugfs_remove_recursive(dell_laptop_dir); if (quirks && quirks->touchpad_led) touchpad_led_exit(); + kbd_led_exit(); i8042_remove_filter(dell_laptop_i8042_filter); cancel_delayed_work_sync(&dell_rfkill_work); backlight_device_unregister(dell_backlight_device); @@ -925,5 +1999,7 @@ module_init(dell_init); module_exit(dell_exit); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); +MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); MODULE_DESCRIPTION("Dell laptop driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c index a653716055d1..0aec4fd4c48e 100644 --- a/drivers/platform/x86/dell-smo8800.c +++ b/drivers/platform/x86/dell-smo8800.c @@ -1,5 +1,5 @@ /* - * dell-smo8800.c - Dell Latitude ACPI SMO8800/SMO8810 freefall sensor driver + * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver * * Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com> * Copyright (C) 2014 Pali Rohár <pali.rohar@gmail.com> @@ -209,7 +209,13 @@ static int smo8800_remove(struct acpi_device *device) static const struct acpi_device_id smo8800_ids[] = { { "SMO8800", 0 }, + { "SMO8801", 0 }, { "SMO8810", 0 }, + { "SMO8811", 0 }, + { "SMO8820", 0 }, + { "SMO8821", 0 }, + { "SMO8830", 0 }, + { "SMO8831", 0 }, { "", 0 }, }; @@ -228,6 +234,6 @@ static struct acpi_driver smo8800_driver = { module_acpi_driver(smo8800_driver); -MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO8800/SMO8810)"); +MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 25721bf20092..6512a06bc053 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -65,10 +65,8 @@ static const struct key_entry dell_wmi_legacy_keymap[] __initconst = { /* Battery health status button */ { KE_KEY, 0xe007, { KEY_BATTERY } }, - /* This is actually for all radios. Although physically a - * switch, the notification does not provide an indication of - * state and so it should be reported as a key */ - { KE_KEY, 0xe008, { KEY_WLAN } }, + /* Radio devices state change */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, /* The next device is at offset 6, the active devices are at offset 8 and the attached devices at offset 10 */ @@ -145,57 +143,154 @@ static const u16 bios_to_linux_keycode[256] __initconst = { static struct input_dev *dell_wmi_input_dev; +static void dell_wmi_process_key(int reported_key) +{ + const struct key_entry *key; + + key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, + reported_key); + if (!key) { + pr_info("Unknown key %x pressed\n", reported_key); + return; + } + + pr_debug("Key %x pressed\n", reported_key); + + /* Don't report brightness notifications that will also come via ACPI */ + if ((key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) + return; + + sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true); +} + static void dell_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; + acpi_size buffer_size; + u16 *buffer_entry, *buffer_end; + int len, i; status = wmi_get_event_data(value, &response); if (status != AE_OK) { - pr_info("bad event status 0x%x\n", status); + pr_warn("bad event status 0x%x\n", status); return; } obj = (union acpi_object *)response.pointer; + if (!obj) { + pr_warn("no response\n"); + return; + } - if (obj && obj->type == ACPI_TYPE_BUFFER) { - const struct key_entry *key; - int reported_key; - u16 *buffer_entry = (u16 *)obj->buffer.pointer; - int buffer_size = obj->buffer.length/2; - - if (buffer_size >= 2 && dell_new_hk_type && buffer_entry[1] != 0x10) { - pr_info("Received unknown WMI event (0x%x)\n", - buffer_entry[1]); - kfree(obj); - return; - } + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("bad response type %x\n", obj->type); + kfree(obj); + return; + } - if (buffer_size >= 3 && (dell_new_hk_type || buffer_entry[1] == 0x0)) - reported_key = (int)buffer_entry[2]; + pr_debug("Received WMI event (%*ph)\n", + obj->buffer.length, obj->buffer.pointer); + + buffer_entry = (u16 *)obj->buffer.pointer; + buffer_size = obj->buffer.length/2; + + if (!dell_new_hk_type) { + if (buffer_size >= 3 && buffer_entry[1] == 0x0) + dell_wmi_process_key(buffer_entry[2]); else if (buffer_size >= 2) - reported_key = (int)buffer_entry[1] & 0xffff; - else { + dell_wmi_process_key(buffer_entry[1]); + else pr_info("Received unknown WMI event\n"); - kfree(obj); - return; + kfree(obj); + return; + } + + buffer_end = buffer_entry + buffer_size; + + while (buffer_entry < buffer_end) { + + len = buffer_entry[0]; + if (len == 0) + break; + + len++; + + if (buffer_entry + len > buffer_end) { + pr_warn("Invalid length of WMI event\n"); + break; } - key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, - reported_key); - if (!key) { - pr_info("Unknown key %x pressed\n", reported_key); - } else if ((key->keycode == KEY_BRIGHTNESSUP || - key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { - /* Don't report brightness notifications that will also - * come via ACPI */ - ; - } else { - sparse_keymap_report_entry(dell_wmi_input_dev, key, - 1, true); + pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); + + switch (buffer_entry[1]) { + case 0x00: + for (i = 2; i < len; ++i) { + switch (buffer_entry[i]) { + case 0xe043: + /* NIC Link is Up */ + pr_debug("NIC Link is Up\n"); + break; + case 0xe044: + /* NIC Link is Down */ + pr_debug("NIC Link is Down\n"); + break; + case 0xe045: + /* Unknown event but defined in DSDT */ + default: + /* Unknown event */ + pr_info("Unknown WMI event type 0x00: " + "0x%x\n", (int)buffer_entry[i]); + break; + } + } + break; + case 0x10: + /* Keys pressed */ + for (i = 2; i < len; ++i) + dell_wmi_process_key(buffer_entry[i]); + break; + case 0x11: + for (i = 2; i < len; ++i) { + switch (buffer_entry[i]) { + case 0xfff0: + /* Battery unplugged */ + pr_debug("Battery unplugged\n"); + break; + case 0xfff1: + /* Battery inserted */ + pr_debug("Battery inserted\n"); + break; + case 0x01e1: + case 0x02ea: + case 0x02eb: + case 0x02ec: + case 0x02f6: + /* Keyboard backlight level changed */ + pr_debug("Keyboard backlight level " + "changed\n"); + break; + default: + /* Unknown event */ + pr_info("Unknown WMI event type 0x11: " + "0x%x\n", (int)buffer_entry[i]); + break; + } + } + break; + default: + /* Unknown event */ + pr_info("Unknown WMI event type 0x%x\n", + (int)buffer_entry[1]); + break; } + + buffer_entry += len; + } + kfree(obj); } @@ -213,11 +308,16 @@ static const struct key_entry * __init dell_wmi_prepare_new_keymap(void) for (i = 0; i < hotkey_num; i++) { const struct dell_bios_keymap_entry *bios_entry = &dell_bios_hotkey_table->keymap[i]; - keymap[i].type = KE_KEY; - keymap[i].code = bios_entry->scancode; - keymap[i].keycode = bios_entry->keycode < 256 ? + u16 keycode = bios_entry->keycode < 256 ? bios_to_linux_keycode[bios_entry->keycode] : KEY_RESERVED; + + if (keycode == KEY_KBDILLUMTOGGLE) + keymap[i].type = KE_IGNORE; + else + keymap[i].type = KE_KEY; + keymap[i].code = bios_entry->scancode; + keymap[i].keycode = keycode; } keymap[hotkey_num].type = KE_END; diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index db79902c4a8e..844c2096bde9 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -417,8 +417,7 @@ static ssize_t cpufv_disabled_store(struct device *dev, switch (value) { case 0: if (eeepc->cpufv_disabled) - pr_warn("cpufv enabled (not officially supported " - "on this model)\n"); + pr_warn("cpufv enabled (not officially supported on this model)\n"); eeepc->cpufv_disabled = false; return count; case 1: @@ -580,59 +579,58 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) mutex_lock(&eeepc->hotplug_lock); pci_lock_rescan_remove(); - if (eeepc->hotplug_slot) { - port = acpi_get_pci_dev(handle); - if (!port) { - pr_warning("Unable to find port\n"); - goto out_unlock; - } + if (!eeepc->hotplug_slot) + goto out_unlock; - bus = port->subordinate; + port = acpi_get_pci_dev(handle); + if (!port) { + pr_warning("Unable to find port\n"); + goto out_unlock; + } - if (!bus) { - pr_warn("Unable to find PCI bus 1?\n"); - goto out_put_dev; - } + bus = port->subordinate; - if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { - pr_err("Unable to read PCI config space?\n"); - goto out_put_dev; - } + if (!bus) { + pr_warn("Unable to find PCI bus 1?\n"); + goto out_put_dev; + } + + if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { + pr_err("Unable to read PCI config space?\n"); + goto out_put_dev; + } - absent = (l == 0xffffffff); + absent = (l == 0xffffffff); - if (blocked != absent) { - pr_warn("BIOS says wireless lan is %s, " - "but the pci device is %s\n", - blocked ? "blocked" : "unblocked", - absent ? "absent" : "present"); - pr_warn("skipped wireless hotplug as probably " - "inappropriate for this model\n"); + if (blocked != absent) { + pr_warn("BIOS says wireless lan is %s, but the pci device is %s\n", + blocked ? "blocked" : "unblocked", + absent ? "absent" : "present"); + pr_warn("skipped wireless hotplug as probably inappropriate for this model\n"); + goto out_put_dev; + } + + if (!blocked) { + dev = pci_get_slot(bus, 0); + if (dev) { + /* Device already present */ + pci_dev_put(dev); goto out_put_dev; } - - if (!blocked) { - dev = pci_get_slot(bus, 0); - if (dev) { - /* Device already present */ - pci_dev_put(dev); - goto out_put_dev; - } - dev = pci_scan_single_device(bus, 0); - if (dev) { - pci_bus_assign_resources(bus); - pci_bus_add_device(dev); - } - } else { - dev = pci_get_slot(bus, 0); - if (dev) { - pci_stop_and_remove_bus_device(dev); - pci_dev_put(dev); - } + dev = pci_scan_single_device(bus, 0); + if (dev) { + pci_bus_assign_resources(bus); + pci_bus_add_device(dev); + } + } else { + dev = pci_get_slot(bus, 0); + if (dev) { + pci_stop_and_remove_bus_device(dev); + pci_dev_put(dev); } -out_put_dev: - pci_dev_put(port); } +out_put_dev: + pci_dev_put(port); out_unlock: pci_unlock_rescan_remove(); @@ -821,11 +819,15 @@ static int eeepc_new_rfkill(struct eeepc_laptop *eeepc, return 0; } +static char EEEPC_RFKILL_NODE_1[] = "\\_SB.PCI0.P0P5"; +static char EEEPC_RFKILL_NODE_2[] = "\\_SB.PCI0.P0P6"; +static char EEEPC_RFKILL_NODE_3[] = "\\_SB.PCI0.P0P7"; + static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc) { - eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); - eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); - eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); + eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1); + eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2); + eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3); if (eeepc->wlan_rfkill) { rfkill_unregister(eeepc->wlan_rfkill); rfkill_destroy(eeepc->wlan_rfkill); @@ -897,9 +899,9 @@ static int eeepc_rfkill_init(struct eeepc_laptop *eeepc) if (result == -EBUSY) result = 0; - eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); - eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); - eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); + eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1); + eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2); + eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3); exit: if (result && result != -ENODEV) @@ -915,7 +917,7 @@ static int eeepc_hotk_thaw(struct device *device) struct eeepc_laptop *eeepc = dev_get_drvdata(device); if (eeepc->wlan_rfkill) { - bool wlan; + int wlan; /* * Work around bios bug - acpi _PTS turns off the wireless led @@ -923,7 +925,8 @@ static int eeepc_hotk_thaw(struct device *device) * we should kick it ourselves in case hibernation is aborted. */ wlan = get_acpi(eeepc, CM_ASL_WLAN); - set_acpi(eeepc, CM_ASL_WLAN, wlan); + if (wlan >= 0) + set_acpi(eeepc, CM_ASL_WLAN, wlan); } return 0; @@ -935,9 +938,9 @@ static int eeepc_hotk_restore(struct device *device) /* Refresh both wlan rfkill state and pci hotplug */ if (eeepc->wlan_rfkill) { - eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P5"); - eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P6"); - eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P7"); + eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_1); + eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_2); + eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_3); } if (eeepc->bluetooth_rfkill) @@ -961,7 +964,6 @@ static const struct dev_pm_ops eeepc_pm_ops = { static struct platform_driver platform_driver = { .driver = { .name = EEEPC_LAPTOP_FILE, - .owner = THIS_MODULE, .pm = &eeepc_pm_ops, } }; @@ -978,18 +980,28 @@ static struct platform_driver platform_driver = { #define EEEPC_EC_SFB0 0xD0 #define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */ +static inline int eeepc_pwm_to_lmsensors(int value) +{ + return value * 255 / 100; +} + +static inline int eeepc_lmsensors_to_pwm(int value) +{ + value = clamp_val(value, 0, 255); + return value * 100 / 255; +} + static int eeepc_get_fan_pwm(void) { u8 value = 0; ec_read(EEEPC_EC_FAN_PWM, &value); - return value * 255 / 100; + return eeepc_pwm_to_lmsensors(value); } static void eeepc_set_fan_pwm(int value) { - value = clamp_val(value, 0, 255); - value = value * 100 / 255; + value = eeepc_lmsensors_to_pwm(value); ec_write(EEEPC_EC_FAN_PWM, value); } @@ -1003,15 +1015,19 @@ static int eeepc_get_fan_rpm(void) return high << 8 | low; } +#define EEEPC_EC_FAN_CTRL_BIT 0x02 +#define EEEPC_FAN_CTRL_MANUAL 1 +#define EEEPC_FAN_CTRL_AUTO 2 + static int eeepc_get_fan_ctrl(void) { u8 value = 0; ec_read(EEEPC_EC_FAN_CTRL, &value); - if (value & 0x02) - return 1; /* manual */ + if (value & EEEPC_EC_FAN_CTRL_BIT) + return EEEPC_FAN_CTRL_MANUAL; else - return 2; /* automatic */ + return EEEPC_FAN_CTRL_AUTO; } static void eeepc_set_fan_ctrl(int manual) @@ -1019,10 +1035,10 @@ static void eeepc_set_fan_ctrl(int manual) u8 value = 0; ec_read(EEEPC_EC_FAN_CTRL, &value); - if (manual == 1) - value |= 0x02; + if (manual == EEEPC_FAN_CTRL_MANUAL) + value |= EEEPC_EC_FAN_CTRL_BIT; else - value &= ~0x02; + value &= ~EEEPC_EC_FAN_CTRL_BIT; ec_write(EEEPC_EC_FAN_CTRL, value); } @@ -1157,8 +1173,7 @@ static int eeepc_backlight_init(struct eeepc_laptop *eeepc) static void eeepc_backlight_exit(struct eeepc_laptop *eeepc) { - if (eeepc->backlight_device) - backlight_device_unregister(eeepc->backlight_device); + backlight_device_unregister(eeepc->backlight_device); eeepc->backlight_device = NULL; } @@ -1217,7 +1232,7 @@ static void eeepc_input_exit(struct eeepc_laptop *eeepc) static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) { if (!eeepc->inputdev) - return ; + return; if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true)) pr_info("Unknown key %x pressed\n", event); } @@ -1225,6 +1240,7 @@ static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) static void eeepc_acpi_notify(struct acpi_device *device, u32 event) { struct eeepc_laptop *eeepc = acpi_driver_data(device); + int old_brightness, new_brightness; u16 count; if (event > ACPI_MAX_SYS_NOTIFY) @@ -1235,34 +1251,32 @@ static void eeepc_acpi_notify(struct acpi_device *device, u32 event) count); /* Brightness events are special */ - if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) { - - /* Ignore them completely if the acpi video driver is used */ - if (eeepc->backlight_device != NULL) { - int old_brightness, new_brightness; - - /* Update the backlight device. */ - old_brightness = eeepc_backlight_notify(eeepc); - - /* Convert event to keypress (obsolescent hack) */ - new_brightness = event - NOTIFY_BRN_MIN; - - if (new_brightness < old_brightness) { - event = NOTIFY_BRN_MIN; /* brightness down */ - } else if (new_brightness > old_brightness) { - event = NOTIFY_BRN_MAX; /* brightness up */ - } else { - /* - * no change in brightness - already at min/max, - * event will be desired value (or else ignored) - */ - } - eeepc_input_notify(eeepc, event); - } - } else { - /* Everything else is a bona-fide keypress event */ + if (event < NOTIFY_BRN_MIN || event > NOTIFY_BRN_MAX) { eeepc_input_notify(eeepc, event); + return; + } + + /* Ignore them completely if the acpi video driver is used */ + if (!eeepc->backlight_device) + return; + + /* Update the backlight device. */ + old_brightness = eeepc_backlight_notify(eeepc); + + /* Convert event to keypress (obsolescent hack) */ + new_brightness = event - NOTIFY_BRN_MIN; + + if (new_brightness < old_brightness) { + event = NOTIFY_BRN_MIN; /* brightness down */ + } else if (new_brightness > old_brightness) { + event = NOTIFY_BRN_MAX; /* brightness up */ + } else { + /* + * no change in brightness - already at min/max, + * event will be desired value (or else ignored) + */ } + eeepc_input_notify(eeepc, event); } static void eeepc_dmi_check(struct eeepc_laptop *eeepc) @@ -1294,8 +1308,8 @@ static void eeepc_dmi_check(struct eeepc_laptop *eeepc) */ if (strcmp(model, "701") == 0 || strcmp(model, "702") == 0) { eeepc->cpufv_disabled = true; - pr_info("model %s does not officially support setting cpu " - "speed\n", model); + pr_info("model %s does not officially support setting cpu speed\n", + model); pr_info("cpufv disabled to avoid instability\n"); } @@ -1321,8 +1335,8 @@ static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name) Check if cm_getv[cm] works and, if yes, assume cm should be set. */ if (!(eeepc->cm_supported & (1 << cm)) && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) { - pr_info("%s (%x) not reported by BIOS," - " enabling anyway\n", name, 1 << cm); + pr_info("%s (%x) not reported by BIOS, enabling anyway\n", + name, 1 << cm); eeepc->cm_supported |= 1 << cm; } } diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 2655d4a988f3..2a9afa261c61 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -64,6 +64,7 @@ #include <linux/acpi.h> #include <linux/dmi.h> #include <linux/backlight.h> +#include <linux/fb.h> #include <linux/input.h> #include <linux/kfifo.h> #include <linux/platform_device.h> @@ -398,7 +399,7 @@ static int bl_get_brightness(struct backlight_device *b) static int bl_update_status(struct backlight_device *b) { int ret; - if (b->props.power == 4) + if (b->props.power == FB_BLANK_POWERDOWN) ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3); else ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0); @@ -559,7 +560,6 @@ static struct attribute_group fujitsupf_attribute_group = { static struct platform_driver fujitsupf_driver = { .driver = { .name = "fujitsu-laptop", - .owner = THIS_MODULE, } }; @@ -1140,9 +1140,9 @@ static int __init fujitsu_init(void) if (!acpi_video_backlight_support()) { if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) - fujitsu->bl_device->props.power = 4; + fujitsu->bl_device->props.power = FB_BLANK_POWERDOWN; else - fujitsu->bl_device->props.power = 0; + fujitsu->bl_device->props.power = FB_BLANK_UNBLANK; } pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n"); @@ -1154,8 +1154,7 @@ fail_hotkey1: fail_hotkey: platform_driver_unregister(&fujitsupf_driver); fail_backlight: - if (fujitsu->bl_device) - backlight_device_unregister(fujitsu->bl_device); + backlight_device_unregister(fujitsu->bl_device); fail_sysfs_group: sysfs_remove_group(&fujitsu->pf_device->dev.kobj, &fujitsupf_attribute_group); @@ -1179,8 +1178,7 @@ static void __exit fujitsu_cleanup(void) platform_driver_unregister(&fujitsupf_driver); - if (fujitsu->bl_device) - backlight_device_unregister(fujitsu->bl_device); + backlight_device_unregister(fujitsu->bl_device); sysfs_remove_group(&fujitsu->pf_device->dev.kobj, &fujitsupf_attribute_group); diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index 53bdbb01bd3f..baea077a02cc 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -59,7 +59,7 @@ static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = { KEY_RESERVED, KEY_SCROLLDOWN, KEY_SCROLLUP, - KEY_DIRECTION, + KEY_ROTATE_DISPLAY, KEY_LEFTCTRL, KEY_BRIGHTNESSUP, KEY_BRIGHTNESSDOWN, @@ -116,7 +116,7 @@ static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = { KEY_RESERVED, KEY_PROG1, KEY_PROG2, - KEY_DIRECTION, + KEY_ROTATE_DISPLAY, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, @@ -153,7 +153,7 @@ static unsigned short keymap_Stylistic_ST5xxx[KEYMAP_LEN] __initdata = { KEY_RESERVED, KEY_RESERVED, KEY_MAIL, - KEY_DIRECTION, + KEY_ROTATE_DISPLAY, KEY_ESC, KEY_ENTER, KEY_BRIGHTNESSUP, diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c index 777c7e3dda51..458e6c948c11 100644 --- a/drivers/platform/x86/hdaps.c +++ b/drivers/platform/x86/hdaps.c @@ -318,7 +318,6 @@ static struct platform_driver hdaps_driver = { .probe = hdaps_probe, .driver = { .name = "hdaps", - .owner = THIS_MODULE, .pm = &hdaps_pm, }, }; diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c index 415348fc1210..4e4cc8bd7557 100644 --- a/drivers/platform/x86/hp-wireless.c +++ b/drivers/platform/x86/hp-wireless.c @@ -85,6 +85,9 @@ static int hpwl_add(struct acpi_device *device) int err; err = hp_wireless_input_setup(); + if (err) + pr_err("Failed to setup hp wireless hotkeys\n"); + return err; } diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 4c559640dcba..06697315a088 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -144,7 +144,7 @@ static const struct key_entry hp_wmi_keymap[] = { { KE_KEY, 0x20e8, { KEY_MEDIA } }, { KE_KEY, 0x2142, { KEY_MEDIA } }, { KE_KEY, 0x213b, { KEY_INFO } }, - { KE_KEY, 0x2169, { KEY_DIRECTION } }, + { KE_KEY, 0x2169, { KEY_ROTATE_DISPLAY } }, { KE_KEY, 0x216a, { KEY_SETUP } }, { KE_KEY, 0x231b, { KEY_HELP } }, { KE_END, 0 } @@ -1006,7 +1006,6 @@ static const struct dev_pm_ops hp_wmi_pm_ops = { static struct platform_driver hp_wmi_driver = { .driver = { .name = "hp-wmi", - .owner = THIS_MODULE, .pm = &hp_wmi_pm_ops, }, .remove = __exit_p(hp_wmi_bios_remove), diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 6bec745b6b92..10ce6cba4455 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -246,6 +246,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("HPB64xx", "HP ProBook 64", xy_swap), AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap), AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted), + AXIS_DMI_MATCH("HPZBook15", "HP ZBook 15", x_inverted), { NULL, } /* Laptop models without axis info (yet): * "NC6910" "HP Compaq 6910" diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index ed494f37c40f..b496db87bc05 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -729,8 +729,7 @@ static int ideapad_backlight_init(struct ideapad_private *priv) static void ideapad_backlight_exit(struct ideapad_private *priv) { - if (priv->blightdev) - backlight_device_unregister(priv->blightdev); + backlight_device_unregister(priv->blightdev); priv->blightdev = NULL; } @@ -831,6 +830,13 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) */ static const struct dmi_system_id no_hw_rfkill_list[] = { { + .ident = "Lenovo G40-30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"), + }, + }, + { .ident = "Lenovo Yoga 2 11 / 13 / Pro", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), @@ -966,7 +972,6 @@ static struct platform_driver ideapad_acpi_driver = { .remove = ideapad_acpi_remove, .driver = { .name = "ideapad_acpi", - .owner = THIS_MODULE, .pm = &ideapad_pm, .acpi_match_table = ACPI_PTR(ideapad_device_ids), }, diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index ecd36e332c3c..e2065e06a3f3 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -33,7 +33,7 @@ * performance by allocating more power or thermal budget to the CPU or GPU * based on available headroom and activity. * - * The basic algorithm is driven by a 5s moving average of tempurature. If + * The basic algorithm is driven by a 5s moving average of temperature. If * thermal headroom is available, the CPU and/or GPU power clamps may be * adjusted upwards. If we hit the thermal ceiling or a thermal trigger, * we scale back the clamp. Aside from trigger events (when we're critically diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c index 8d6775266d66..22606d6b2af3 100644 --- a/drivers/platform/x86/intel_mid_powerbtn.c +++ b/drivers/platform/x86/intel_mid_powerbtn.c @@ -133,7 +133,6 @@ static int mfld_pb_remove(struct platform_device *pdev) static struct platform_driver mfld_pb_driver = { .driver = { .name = DRIVER_NAME, - .owner = THIS_MODULE, }, .probe = mfld_pb_probe, .remove = mfld_pb_remove, diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c index ab7860a21a22..0944e834af8d 100644 --- a/drivers/platform/x86/intel_mid_thermal.c +++ b/drivers/platform/x86/intel_mid_thermal.c @@ -555,7 +555,6 @@ static const struct platform_device_id therm_id_table[] = { static struct platform_driver mid_thermal_driver = { .driver = { .name = DRIVER_NAME, - .owner = THIS_MODULE, .pm = &mid_thermal_pm, }, .probe = mid_thermal_probe, diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c index 4bc960416785..8037c8b46241 100644 --- a/drivers/platform/x86/intel_oaktrail.c +++ b/drivers/platform/x86/intel_oaktrail.c @@ -62,7 +62,7 @@ * (1 << 1): Bluetooth enable/disable, RW. * (1 << 2): GPS enable/disable, RW. * (1 << 3): WiFi enable/disable, RW. - * (1 << 4): WWAN (3G) enable/disalbe, RW. + * (1 << 4): WWAN (3G) enable/disable, RW. * (1 << 5): Touchscreen enable/disable, Read Only. */ #define OT_EC_DEVICE_STATE_ADDRESS 0xD6 @@ -271,8 +271,7 @@ static int oaktrail_backlight_init(void) static void oaktrail_backlight_exit(void) { - if (oaktrail_bl_device) - backlight_device_unregister(oaktrail_bl_device); + backlight_device_unregister(oaktrail_bl_device); } static int oaktrail_probe(struct platform_device *pdev) @@ -288,7 +287,6 @@ static int oaktrail_remove(struct platform_device *pdev) static struct platform_driver oaktrail_driver = { .driver = { .name = DRIVER_NAME, - .owner = THIS_MODULE, }, .probe = oaktrail_probe, .remove = oaktrail_remove, diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c index 04fed00b88e9..709f0afdafa8 100644 --- a/drivers/platform/x86/intel_pmic_gpio.c +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -314,7 +314,6 @@ err2: static struct platform_driver platform_pmic_gpio_driver = { .driver = { .name = DRIVER_NAME, - .owner = THIS_MODULE, }, .probe = platform_pmic_gpio_probe, }; diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index 66a4d3284aab..001b199a8c33 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -1,7 +1,7 @@ /* * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism * - * (C) Copyright 2008-2010 Intel Corporation + * (C) Copyright 2008-2010,2015 Intel Corporation * Author: Sreedhara DS (sreedhara.ds@intel.com) * * This program is free software; you can redistribute it and/or @@ -43,10 +43,9 @@ /* * IPC register summary * - * IPC register blocks are memory mapped at fixed address of 0xFF11C000 + * IPC register blocks are memory mapped at fixed address of PCI BAR 0. * To read or write information to the SCU, driver writes to IPC-1 memory - * mapped registers (base address 0xFF11C000). The following is the IPC - * mechanism + * mapped registers. The following is the IPC mechanism * * 1. IA core cDMI interface claims this transaction and converts it to a * Transaction Layer Packet (TLP) message which is sent across the cDMI. @@ -67,36 +66,28 @@ #define PCI_DEVICE_ID_CLOVERVIEW 0x08ea #define PCI_DEVICE_ID_TANGIER 0x11a0 -/* intel scu ipc driver data*/ +/* intel scu ipc driver data */ struct intel_scu_ipc_pdata_t { - u32 ipc_base; u32 i2c_base; - u32 ipc_len; u32 i2c_len; u8 irq_mode; }; static struct intel_scu_ipc_pdata_t intel_scu_ipc_lincroft_pdata = { - .ipc_base = 0xff11c000, .i2c_base = 0xff12b000, - .ipc_len = 0x100, .i2c_len = 0x10, .irq_mode = 0, }; /* Penwell and Cloverview */ static struct intel_scu_ipc_pdata_t intel_scu_ipc_penwell_pdata = { - .ipc_base = 0xff11c000, .i2c_base = 0xff12b000, - .ipc_len = 0x100, .i2c_len = 0x10, .irq_mode = 1, }; static struct intel_scu_ipc_pdata_t intel_scu_ipc_tangier_pdata = { - .ipc_base = 0xff009000, .i2c_base = 0xff00d000, - .ipc_len = 0x100, .i2c_len = 0x10, .irq_mode = 0, }; @@ -114,8 +105,6 @@ struct intel_scu_ipc_dev { static struct intel_scu_ipc_dev ipcdev; /* Only one for now */ -static int platform; /* Platform type */ - /* * IPC Read Buffer (Read Only): * 16 byte buffer for receiving data from SCU, if IPC command @@ -160,7 +149,6 @@ static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */ * Format: * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| */ - static inline u8 ipc_read_status(void) { return __raw_readl(ipcdev.ipc_base + 0x04); @@ -176,23 +164,24 @@ static inline u32 ipc_data_readl(u32 offset) /* Read ipc u32 data */ return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset); } -static inline int busy_loop(void) /* Wait till scu status is busy */ +/* Wait till scu status is busy */ +static inline int busy_loop(void) { - u32 status = 0; - u32 loop_count = 0; + u32 status = ipc_read_status(); + u32 loop_count = 100000; - status = ipc_read_status(); - while (status & 1) { + /* break if scu doesn't reset busy bit after huge retry */ + while ((status & BIT(0)) && --loop_count) { udelay(1); /* scu processing time is in few u secods */ status = ipc_read_status(); - loop_count++; - /* break if scu doesn't reset busy bit after huge retry */ - if (loop_count > 100000) { - dev_err(&ipcdev.pdev->dev, "IPC timed out"); - return -ETIMEDOUT; - } } - if ((status >> 1) & 1) + + if (status & BIT(0)) { + dev_err(&ipcdev.pdev->dev, "IPC timed out"); + return -ETIMEDOUT; + } + + if (status & BIT(1)) return -EIO; return 0; @@ -210,14 +199,13 @@ static inline int ipc_wait_for_interrupt(void) } status = ipc_read_status(); - - if ((status >> 1) & 1) + if (status & BIT(1)) return -EIO; return 0; } -int intel_scu_ipc_check_status(void) +static int intel_scu_ipc_check_status(void) { return ipcdev.irq_mode ? ipc_wait_for_interrupt() : busy_loop(); } @@ -248,18 +236,18 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) if (id == IPC_CMD_PCNTRL_R) { for (nc = 0, offset = 0; nc < count; nc++, offset += 4) ipc_data_writel(wbuf[nc], offset); - ipc_command((count*2) << 16 | id << 12 | 0 << 8 | op); + ipc_command((count * 2) << 16 | id << 12 | 0 << 8 | op); } else if (id == IPC_CMD_PCNTRL_W) { for (nc = 0; nc < count; nc++, offset += 1) cbuf[offset] = data[nc]; for (nc = 0, offset = 0; nc < count; nc++, offset += 4) ipc_data_writel(wbuf[nc], offset); - ipc_command((count*3) << 16 | id << 12 | 0 << 8 | op); + ipc_command((count * 3) << 16 | id << 12 | 0 << 8 | op); } else if (id == IPC_CMD_PCNTRL_M) { cbuf[offset] = data[0]; cbuf[offset + 1] = data[1]; ipc_data_writel(wbuf[0], 0); /* Write wbuff */ - ipc_command(4 << 16 | id << 12 | 0 << 8 | op); + ipc_command(4 << 16 | id << 12 | 0 << 8 | op); } err = intel_scu_ipc_check_status(); @@ -301,7 +289,7 @@ EXPORT_SYMBOL(intel_scu_ipc_ioread8); */ int intel_scu_ipc_ioread16(u16 addr, u16 *data) { - u16 x[2] = {addr, addr + 1 }; + u16 x[2] = {addr, addr + 1}; return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R); } EXPORT_SYMBOL(intel_scu_ipc_ioread16); @@ -351,7 +339,7 @@ EXPORT_SYMBOL(intel_scu_ipc_iowrite8); */ int intel_scu_ipc_iowrite16(u16 addr, u16 data) { - u16 x[2] = {addr, addr + 1 }; + u16 x[2] = {addr, addr + 1}; return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W); } EXPORT_SYMBOL(intel_scu_ipc_iowrite16); @@ -412,7 +400,6 @@ int intel_scu_ipc_writev(u16 *addr, u8 *data, int len) } EXPORT_SYMBOL(intel_scu_ipc_writev); - /** * intel_scu_ipc_update_register - r/m/w a register * @addr: register address @@ -475,9 +462,8 @@ EXPORT_SYMBOL(intel_scu_ipc_simple_command); * Issue a command to the SCU which involves data transfers. Do the * data copies under the lock but leave it for the caller to interpret */ - int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, - u32 *out, int outlen) + u32 *out, int outlen) { int i, err; @@ -503,7 +489,7 @@ int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, } EXPORT_SYMBOL(intel_scu_ipc_command); -/*I2C commands */ +/* I2C commands */ #define IPC_I2C_WRITE 1 /* I2C Write command */ #define IPC_I2C_READ 2 /* I2C Read command */ @@ -577,7 +563,7 @@ static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) { int err; struct intel_scu_ipc_pdata_t *pdata; - resource_size_t pci_resource; + resource_size_t base; if (ipcdev.pdev) /* We support only one SCU */ return -EBUSY; @@ -595,8 +581,8 @@ static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) if (err) return err; - pci_resource = pci_resource_start(dev, 0); - if (!pci_resource) + base = pci_resource_start(dev, 0); + if (!base) return -ENOMEM; init_completion(&ipcdev.cmd_complete); @@ -604,7 +590,7 @@ static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev)) return -EBUSY; - ipcdev.ipc_base = ioremap_nocache(pdata->ipc_base, pdata->ipc_len); + ipcdev.ipc_base = ioremap_nocache(base, pci_resource_len(dev, 0)); if (!ipcdev.ipc_base) return -ENOMEM; @@ -666,9 +652,10 @@ static struct pci_driver ipc_driver = { .remove = ipc_remove, }; - static int __init intel_scu_ipc_init(void) { + int platform; /* Platform type */ + platform = intel_mid_identify_cpu(); if (platform == 0) return -ENODEV; diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 62f8030b9e77..085987730aab 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -573,7 +573,6 @@ static struct attribute_group msipf_old_attribute_group = { static struct platform_driver msipf_driver = { .driver = { .name = "msi-laptop-pf", - .owner = THIS_MODULE, .pm = &msi_laptop_pm, }, }; @@ -821,7 +820,7 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, { static bool extended; - if (str & 0x20) + if (str & I8042_STR_AUXDATA) return false; /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/ diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 70222f265f68..6d2bac0c463c 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -354,8 +354,7 @@ static void __exit msi_wmi_exit(void) sparse_keymap_free(msi_wmi_input_dev); input_unregister_device(msi_wmi_input_dev); } - if (backlight) - backlight_device_unregister(backlight); + backlight_device_unregister(backlight); } module_init(msi_wmi_init); diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index ff765d8e1a09..9e701b2256f9 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -124,6 +124,10 @@ struct sabi_commands { u16 get_wireless_status; u16 set_wireless_status; + /* 0x80 is off, 0x81 is on */ + u16 get_lid_handling; + u16 set_lid_handling; + /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */ u16 kbd_backlight; @@ -194,6 +198,9 @@ static const struct sabi_config sabi_configs[] = { .get_wireless_status = 0xFFFF, .set_wireless_status = 0xFFFF, + .get_lid_handling = 0xFFFF, + .set_lid_handling = 0xFFFF, + .kbd_backlight = 0xFFFF, .set_linux = 0x0a, @@ -254,6 +261,9 @@ static const struct sabi_config sabi_configs[] = { .get_wireless_status = 0x69, .set_wireless_status = 0x6a, + .get_lid_handling = 0x6d, + .set_lid_handling = 0x6e, + .kbd_backlight = 0x78, .set_linux = 0xff, @@ -353,6 +363,8 @@ struct samsung_quirks { bool broken_acpi_video; bool four_kbd_backlight_levels; bool enable_kbd_backlight; + bool use_native_backlight; + bool lid_handling; }; static struct samsung_quirks samsung_unknown = {}; @@ -361,11 +373,19 @@ static struct samsung_quirks samsung_broken_acpi_video = { .broken_acpi_video = true, }; +static struct samsung_quirks samsung_use_native_backlight = { + .use_native_backlight = true, +}; + static struct samsung_quirks samsung_np740u3e = { .four_kbd_backlight_levels = true, .enable_kbd_backlight = true, }; +static struct samsung_quirks samsung_lid_handling = { + .lid_handling = true, +}; + static bool force; module_param(force, bool, 0); MODULE_PARM_DESC(force, @@ -748,7 +768,7 @@ static ssize_t set_battery_life_extender(struct device *dev, struct samsung_laptop *samsung = dev_get_drvdata(dev); int ret, value; - if (!count || sscanf(buf, "%i", &value) != 1) + if (!count || kstrtoint(buf, 0, &value) != 0) return -EINVAL; ret = write_battery_life_extender(samsung, !!value); @@ -817,7 +837,7 @@ static ssize_t set_usb_charge(struct device *dev, struct samsung_laptop *samsung = dev_get_drvdata(dev); int ret, value; - if (!count || sscanf(buf, "%i", &value) != 1) + if (!count || kstrtoint(buf, 0, &value) != 0) return -EINVAL; ret = write_usb_charge(samsung, !!value); @@ -830,10 +850,76 @@ static ssize_t set_usb_charge(struct device *dev, static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, get_usb_charge, set_usb_charge); +static int read_lid_handling(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->get_lid_handling == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + retval = sabi_command(samsung, commands->get_lid_handling, + &data, &data); + + if (retval) + return retval; + + return data.data[0] & 0x1; +} + +static int write_lid_handling(struct samsung_laptop *samsung, + int enabled) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80 | enabled; + return sabi_command(samsung, commands->set_lid_handling, + &data, NULL); +} + +static ssize_t get_lid_handling(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret; + + ret = read_lid_handling(samsung); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_lid_handling(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret, value; + + if (!count || kstrtoint(buf, 0, &value) != 0) + return -EINVAL; + + ret = write_lid_handling(samsung, !!value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO, + get_lid_handling, set_lid_handling); + static struct attribute *platform_attributes[] = { &dev_attr_performance_level.attr, &dev_attr_battery_life_extender.attr, &dev_attr_usb_charge.attr, + &dev_attr_lid_handling.attr, NULL }; @@ -956,6 +1042,22 @@ static int __init samsung_rfkill_init(struct samsung_laptop *samsung) return 0; } +static void samsung_lid_handling_exit(struct samsung_laptop *samsung) +{ + if (samsung->quirks->lid_handling) + write_lid_handling(samsung, 0); +} + +static int __init samsung_lid_handling_init(struct samsung_laptop *samsung) +{ + int retval = 0; + + if (samsung->quirks->lid_handling) + retval = write_lid_handling(samsung, 1); + + return retval; +} + static int kbd_backlight_enable(struct samsung_laptop *samsung) { const struct sabi_commands *commands = &samsung->config->commands; @@ -1111,7 +1213,7 @@ static int __init samsung_backlight_init(struct samsung_laptop *samsung) } static umode_t samsung_sysfs_is_visible(struct kobject *kobj, - struct attribute *attr, int idx) + struct attribute *attr, int idx) { struct device *dev = container_of(kobj, struct device, kobj); struct platform_device *pdev = to_platform_device(dev); @@ -1124,6 +1226,8 @@ static umode_t samsung_sysfs_is_visible(struct kobject *kobj, ok = !!(read_battery_life_extender(samsung) >= 0); if (attr == &dev_attr_usb_charge.attr) ok = !!(read_usb_charge(samsung) >= 0); + if (attr == &dev_attr_lid_handling.attr) + ok = !!(read_lid_handling(samsung) >= 0); return ok ? attr->mode : 0; } @@ -1357,7 +1461,7 @@ static int __init samsung_sabi_init(struct samsung_laptop *samsung) samsung_sabi_diag(samsung); /* Try to find one of the signatures in memory to find the header */ - for (i = 0; sabi_configs[i].test_string != 0; ++i) { + for (i = 0; sabi_configs[i].test_string != NULL; ++i) { samsung->config = &sabi_configs[i]; loca = find_signature(samsung->f0000_segment, samsung->config->test_string); @@ -1436,6 +1540,9 @@ static int samsung_pm_notification(struct notifier_block *nb, samsung->quirks->enable_kbd_backlight) kbd_backlight_enable(samsung); + if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling) + write_lid_handling(samsung, 1); + return 0; } @@ -1507,7 +1614,7 @@ static struct dmi_system_id __initdata samsung_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "N150P"), DMI_MATCH(DMI_BOARD_NAME, "N150P"), }, - .driver_data = &samsung_broken_acpi_video, + .driver_data = &samsung_use_native_backlight, }, { .callback = samsung_dmi_matched, @@ -1517,7 +1624,7 @@ static struct dmi_system_id __initdata samsung_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), }, - .driver_data = &samsung_broken_acpi_video, + .driver_data = &samsung_use_native_backlight, }, { .callback = samsung_dmi_matched, @@ -1557,7 +1664,7 @@ static struct dmi_system_id __initdata samsung_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "N250P"), DMI_MATCH(DMI_BOARD_NAME, "N250P"), }, - .driver_data = &samsung_broken_acpi_video, + .driver_data = &samsung_use_native_backlight, }, { .callback = samsung_dmi_matched, @@ -1578,6 +1685,15 @@ static struct dmi_system_id __initdata samsung_dmi_table[] = { }, .driver_data = &samsung_np740u3e, }, + { + .callback = samsung_dmi_matched, + .ident = "300V3Z/300V4Z/300V5Z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"), + }, + .driver_data = &samsung_lid_handling, + }, { }, }; MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); @@ -1616,6 +1732,15 @@ static int __init samsung_init(void) pr_info("Disabling ACPI video driver\n"); acpi_video_unregister(); } + + if (samsung->quirks->use_native_backlight) { + pr_info("Using native backlight driver\n"); + /* Tell acpi-video to not handle the backlight */ + acpi_video_dmi_promote_vendor(); + acpi_video_unregister(); + /* And also do not handle it ourselves */ + samsung->handle_backlight = false; + } #endif ret = samsung_platform_init(samsung); @@ -1648,6 +1773,10 @@ static int __init samsung_init(void) if (ret) goto error_leds; + ret = samsung_lid_handling_init(samsung); + if (ret) + goto error_lid_handling; + ret = samsung_debugfs_init(samsung); if (ret) goto error_debugfs; @@ -1659,6 +1788,8 @@ static int __init samsung_init(void) return ret; error_debugfs: + samsung_lid_handling_exit(samsung); +error_lid_handling: samsung_leds_exit(samsung); error_leds: samsung_rfkill_exit(samsung); @@ -1683,6 +1814,7 @@ static void __exit samsung_exit(void) unregister_pm_notifier(&samsung->pm_nb); samsung_debugfs_exit(samsung); + samsung_lid_handling_exit(samsung); samsung_leds_exit(samsung); samsung_rfkill_exit(samsung); samsung_backlight_exit(samsung); diff --git a/drivers/platform/x86/samsung-q10.c b/drivers/platform/x86/samsung-q10.c index 28d12bda3ac1..e6aac725a0af 100644 --- a/drivers/platform/x86/samsung-q10.c +++ b/drivers/platform/x86/samsung-q10.c @@ -82,7 +82,6 @@ static int samsungq10_remove(struct platform_device *pdev) static struct platform_driver samsungq10_driver = { .driver = { .name = KBUILD_MODNAME, - .owner = THIS_MODULE, }, .probe = samsungq10_probe, .remove = samsungq10_remove, diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 26ad9ff12ac5..e51c1e753607 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -581,7 +581,6 @@ static atomic_t sony_pf_users = ATOMIC_INIT(0); static struct platform_driver sony_pf_driver = { .driver = { .name = "sony-laptop", - .owner = THIS_MODULE, } }; static struct platform_device *sony_pf_device; @@ -1033,7 +1032,7 @@ struct sony_backlight_props { u8 offset; u8 maxlvl; }; -struct sony_backlight_props sony_bl_props; +static struct sony_backlight_props sony_bl_props; static int sony_backlight_update_status(struct backlight_device *bd) { @@ -3141,8 +3140,7 @@ static void sony_nc_backlight_setup(void) static void sony_nc_backlight_cleanup(void) { - if (sony_bl_props.dev) - backlight_device_unregister(sony_bl_props.dev); + backlight_device_unregister(sony_bl_props.dev); } static int sony_nc_add(struct acpi_device *device) @@ -3717,8 +3715,7 @@ static void sony_pic_detect_device_type(struct sony_pic_dev *dev) dev->event_types = type2_events; out: - if (pcidev) - pci_dev_put(pcidev); + pci_dev_put(pcidev); pr_info("detected Type%d model\n", dev->model == SONYPI_DEVICE_TYPE1 ? 1 : diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index 6a6ea28a7e51..e36542564131 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -234,7 +234,6 @@ static const struct dev_pm_ops tc1100_pm_ops = { static struct platform_driver tc1100_driver = { .driver = { .name = "tc1100-wmi", - .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &tc1100_pm_ops, #endif diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index cf0f89364d44..28f328136f0d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -196,6 +196,7 @@ enum tpacpi_hkey_event_t { /* Key-related user-interface events */ TP_HKEY_EV_KEY_NUMLOCK = 0x6000, /* NumLock key pressed */ TP_HKEY_EV_KEY_FN = 0x6005, /* Fn key pressed? E420 */ + TP_HKEY_EV_KEY_FN_ESC = 0x6060, /* Fn+Esc key pressed X240 */ /* Thermal events */ TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */ @@ -318,6 +319,7 @@ static struct { u32 sensors_pdrv_attrs_registered:1; u32 sensors_pdev_attrs_registered:1; u32 hotkey_poll_active:1; + u32 has_adaptive_kbd:1; } tp_features; static struct { @@ -972,7 +974,6 @@ static void tpacpi_shutdown_handler(struct platform_device *pdev) static struct platform_driver tpacpi_pdriver = { .driver = { .name = TPACPI_DRVR_NAME, - .owner = THIS_MODULE, .pm = &tpacpi_pm, }, .shutdown = tpacpi_shutdown_handler, @@ -981,7 +982,6 @@ static struct platform_driver tpacpi_pdriver = { static struct platform_driver tpacpi_hwmon_pdriver = { .driver = { .name = TPACPI_HWMON_DRVR_NAME, - .owner = THIS_MODULE, }, }; @@ -1912,6 +1912,27 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_UNK7, TP_ACPI_HOTKEYSCAN_UNK8, + TP_ACPI_HOTKEYSCAN_MUTE2, + TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, + TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, + TP_ACPI_HOTKEYSCAN_CLOUD, + TP_ACPI_HOTKEYSCAN_UNK9, + TP_ACPI_HOTKEYSCAN_VOICE, + TP_ACPI_HOTKEYSCAN_UNK10, + TP_ACPI_HOTKEYSCAN_GESTURES, + TP_ACPI_HOTKEYSCAN_UNK11, + TP_ACPI_HOTKEYSCAN_UNK12, + TP_ACPI_HOTKEYSCAN_UNK13, + TP_ACPI_HOTKEYSCAN_CONFIG, + TP_ACPI_HOTKEYSCAN_NEW_TAB, + TP_ACPI_HOTKEYSCAN_RELOAD, + TP_ACPI_HOTKEYSCAN_BACK, + TP_ACPI_HOTKEYSCAN_MIC_DOWN, + TP_ACPI_HOTKEYSCAN_MIC_UP, + TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION, + TP_ACPI_HOTKEYSCAN_CAMERA_MODE, + TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, + /* Hotkey keymap size */ TPACPI_HOTKEY_MAP_LEN }; @@ -2094,7 +2115,7 @@ static int hotkey_mask_get(void) return 0; } -void static hotkey_mask_warn_incomplete_mask(void) +static void hotkey_mask_warn_incomplete_mask(void) { /* log only what the user can fix... */ const u32 wantedmask = hotkey_driver_mask & @@ -2648,9 +2669,7 @@ static ssize_t hotkey_enable_store(struct device *dev, return count; } -static struct device_attribute dev_attr_hotkey_enable = - __ATTR(hotkey_enable, S_IWUSR | S_IRUGO, - hotkey_enable_show, hotkey_enable_store); +static DEVICE_ATTR_RW(hotkey_enable); /* sysfs hotkey mask --------------------------------------------------- */ static ssize_t hotkey_mask_show(struct device *dev, @@ -2686,9 +2705,7 @@ static ssize_t hotkey_mask_store(struct device *dev, return (res) ? res : count; } -static struct device_attribute dev_attr_hotkey_mask = - __ATTR(hotkey_mask, S_IWUSR | S_IRUGO, - hotkey_mask_show, hotkey_mask_store); +static DEVICE_ATTR_RW(hotkey_mask); /* sysfs hotkey bios_enabled ------------------------------------------- */ static ssize_t hotkey_bios_enabled_show(struct device *dev, @@ -2698,8 +2715,7 @@ static ssize_t hotkey_bios_enabled_show(struct device *dev, return sprintf(buf, "0\n"); } -static struct device_attribute dev_attr_hotkey_bios_enabled = - __ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_enabled); /* sysfs hotkey bios_mask ---------------------------------------------- */ static ssize_t hotkey_bios_mask_show(struct device *dev, @@ -2711,8 +2727,7 @@ static ssize_t hotkey_bios_mask_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask); } -static struct device_attribute dev_attr_hotkey_bios_mask = - __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_bios_mask); /* sysfs hotkey all_mask ----------------------------------------------- */ static ssize_t hotkey_all_mask_show(struct device *dev, @@ -2723,8 +2738,7 @@ static ssize_t hotkey_all_mask_show(struct device *dev, hotkey_all_mask | hotkey_source_mask); } -static struct device_attribute dev_attr_hotkey_all_mask = - __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_all_mask); /* sysfs hotkey recommended_mask --------------------------------------- */ static ssize_t hotkey_recommended_mask_show(struct device *dev, @@ -2736,9 +2750,7 @@ static ssize_t hotkey_recommended_mask_show(struct device *dev, & ~hotkey_reserved_mask); } -static struct device_attribute dev_attr_hotkey_recommended_mask = - __ATTR(hotkey_recommended_mask, S_IRUGO, - hotkey_recommended_mask_show, NULL); +static DEVICE_ATTR_RO(hotkey_recommended_mask); #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2793,9 +2805,7 @@ static ssize_t hotkey_source_mask_store(struct device *dev, return (rc < 0) ? rc : count; } -static struct device_attribute dev_attr_hotkey_source_mask = - __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO, - hotkey_source_mask_show, hotkey_source_mask_store); +static DEVICE_ATTR_RW(hotkey_source_mask); /* sysfs hotkey hotkey_poll_freq --------------------------------------- */ static ssize_t hotkey_poll_freq_show(struct device *dev, @@ -2827,9 +2837,7 @@ static ssize_t hotkey_poll_freq_store(struct device *dev, return count; } -static struct device_attribute dev_attr_hotkey_poll_freq = - __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO, - hotkey_poll_freq_show, hotkey_poll_freq_store); +static DEVICE_ATTR_RW(hotkey_poll_freq); #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ @@ -2850,8 +2858,7 @@ static ssize_t hotkey_radio_sw_show(struct device *dev, (res == TPACPI_RFK_RADIO_OFF) ? 0 : 1); } -static struct device_attribute dev_attr_hotkey_radio_sw = - __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL); +static DEVICE_ATTR_RO(hotkey_radio_sw); static void hotkey_radio_sw_notify_change(void) { @@ -2873,8 +2880,7 @@ static ssize_t hotkey_tablet_mode_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", !!s); } -static struct device_attribute dev_attr_hotkey_tablet_mode = - __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL); +static DEVICE_ATTR_RO(hotkey_tablet_mode); static void hotkey_tablet_mode_notify_change(void) { @@ -2891,8 +2897,7 @@ static ssize_t hotkey_wakeup_reason_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason); } -static struct device_attribute dev_attr_hotkey_wakeup_reason = - __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); +static DEVICE_ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); static void hotkey_wakeup_reason_notify_change(void) { @@ -2908,9 +2913,8 @@ static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack); } -static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete = - __ATTR(wakeup_hotunplug_complete, S_IRUGO, - hotkey_wakeup_hotunplug_complete_show, NULL); +static DEVICE_ATTR(wakeup_hotunplug_complete, S_IRUGO, + hotkey_wakeup_hotunplug_complete_show, NULL); static void hotkey_wakeup_hotunplug_complete_notify_change(void) { @@ -2918,14 +2922,65 @@ static void hotkey_wakeup_hotunplug_complete_notify_change(void) "wakeup_hotunplug_complete"); } +/* sysfs adaptive kbd mode --------------------------------------------- */ + +static int adaptive_keyboard_get_mode(void); +static int adaptive_keyboard_set_mode(int new_mode); + +enum ADAPTIVE_KEY_MODE { + HOME_MODE, + WEB_BROWSER_MODE, + WEB_CONFERENCE_MODE, + FUNCTION_MODE, + LAYFLAT_MODE +}; + +static ssize_t adaptive_kbd_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int current_mode; + + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) + return current_mode; + + return snprintf(buf, PAGE_SIZE, "%d\n", current_mode); +} + +static ssize_t adaptive_kbd_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, LAYFLAT_MODE, &t)) + return -EINVAL; + + res = adaptive_keyboard_set_mode(t); + return (res < 0) ? res : count; +} + +static DEVICE_ATTR_RW(adaptive_kbd_mode); + +static struct attribute *adaptive_kbd_attributes[] = { + &dev_attr_adaptive_kbd_mode.attr, + NULL +}; + +static const struct attribute_group adaptive_kbd_attr_group = { + .attrs = adaptive_kbd_attributes, +}; + /* --------------------------------------------------------------------- */ static struct attribute *hotkey_attributes[] __initdata = { &dev_attr_hotkey_enable.attr, &dev_attr_hotkey_bios_enabled.attr, &dev_attr_hotkey_bios_mask.attr, - &dev_attr_hotkey_wakeup_reason.attr, - &dev_attr_hotkey_wakeup_hotunplug_complete.attr, + &dev_attr_wakeup_reason.attr, + &dev_attr_wakeup_hotunplug_complete.attr, &dev_attr_hotkey_mask.attr, &dev_attr_hotkey_all_mask.attr, &dev_attr_hotkey_recommended_mask.attr, @@ -3119,6 +3174,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + + /* No assignments, only used for Adaptive keyboards. */ + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, }, /* Generic keymap for Lenovo ThinkPads */ @@ -3175,6 +3237,35 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Extra keys in use since the X240 / T440 / T540 */ KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_FILE, + + /* + * These are the adaptive keyboard keycodes for Carbon X1 2014. + * The first item in this list is the Mute button which is + * emitted with 0x103 through + * adaptive_keyboard_hotkey_notify_hotkey() when the sound + * symbol is held. + * We'll need to offset those by 0x20. + */ + KEY_RESERVED, /* Mute held, 0x103 */ + KEY_BRIGHTNESS_MIN, /* Backlight off */ + KEY_RESERVED, /* Clipping tool */ + KEY_RESERVED, /* Cloud */ + KEY_RESERVED, + KEY_VOICECOMMAND, /* Voice */ + KEY_RESERVED, + KEY_RESERVED, /* Gestures */ + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_CONFIG, /* Settings */ + KEY_RESERVED, /* New tab */ + KEY_REFRESH, /* Reload */ + KEY_BACK, /* Back */ + KEY_RESERVED, /* Microphone down */ + KEY_RESERVED, /* Microphone up */ + KEY_RESERVED, /* Microphone cancellation */ + KEY_RESERVED, /* Camera mode */ + KEY_RESERVED, /* Rotate display, 0x116 */ }, }; @@ -3228,6 +3319,20 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (!tp_features.hotkey) return 1; + /* + * Check if we have an adaptive keyboard, like on the + * Lenovo Carbon X1 2014 (2nd Gen). + */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + tp_features.has_adaptive_kbd = true; + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + if (res) + goto err_exit; + } + } + quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable, ARRAY_SIZE(tpacpi_hotkey_qtable)); @@ -3438,6 +3543,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) err_exit: delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &adaptive_kbd_attr_group); + hotkey_dev_attributes = NULL; return (res < 0) ? res : 1; @@ -3450,15 +3558,7 @@ err_exit: * Will consider support rest of modes in future. * */ -enum ADAPTIVE_KEY_MODE { - HOME_MODE, - WEB_BROWSER_MODE, - WEB_CONFERENCE_MODE, - FUNCTION_MODE, - LAYFLAT_MODE -}; - -const int adaptive_keyboard_modes[] = { +static const int adaptive_keyboard_modes[] = { HOME_MODE, /* WEB_BROWSER_MODE = 2, WEB_CONFERENCE_MODE = 3, */ @@ -3467,6 +3567,8 @@ const int adaptive_keyboard_modes[] = { #define DFR_CHANGE_ROW 0x101 #define DFR_SHOW_QUICKVIEW_ROW 0x102 +#define FIRST_ADAPTIVE_KEY 0x103 +#define ADAPTIVE_KEY_OFFSET 0x020 /* press Fn key a while second, it will switch to Function Mode. Then * release Fn key, previous mode be restored. @@ -3474,6 +3576,32 @@ const int adaptive_keyboard_modes[] = { static bool adaptive_keyboard_mode_is_saved; static int adaptive_keyboard_prev_mode; +static int adaptive_keyboard_get_mode(void) +{ + int mode = 0; + + if (!acpi_evalf(hkey_handle, &mode, "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return -EIO; + } + + return mode; +} + +static int adaptive_keyboard_set_mode(int new_mode) +{ + if (new_mode < 0 || + new_mode > LAYFLAT_MODE) + return -EINVAL; + + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return -EIO; + } + + return 0; +} + static int adaptive_keyboard_get_next_mode(int mode) { size_t i; @@ -3494,8 +3622,9 @@ static int adaptive_keyboard_get_next_mode(int mode) static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) { - u32 current_mode = 0; + int current_mode = 0; int new_mode = 0; + int keycode; switch (scancode) { case DFR_CHANGE_ROW: @@ -3503,43 +3632,51 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) new_mode = adaptive_keyboard_prev_mode; adaptive_keyboard_mode_is_saved = false; } else { - if (!acpi_evalf( - hkey_handle, ¤t_mode, - "GTRW", "dd", 0)) { - pr_err("Cannot read adaptive keyboard mode\n"); + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) return false; - } else { - new_mode = adaptive_keyboard_get_next_mode( - current_mode); - } + new_mode = adaptive_keyboard_get_next_mode( + current_mode); } - if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { - pr_err("Cannot set adaptive keyboard mode\n"); + if (adaptive_keyboard_set_mode(new_mode) < 0) return false; - } return true; case DFR_SHOW_QUICKVIEW_ROW: - if (!acpi_evalf(hkey_handle, - &adaptive_keyboard_prev_mode, - "GTRW", "dd", 0)) { - pr_err("Cannot read adaptive keyboard mode\n"); + current_mode = adaptive_keyboard_get_mode(); + if (current_mode < 0) return false; - } else { - adaptive_keyboard_mode_is_saved = true; - if (!acpi_evalf(hkey_handle, - NULL, "STRW", "vd", FUNCTION_MODE)) { - pr_err("Cannot set adaptive keyboard mode\n"); - return false; - } - } + adaptive_keyboard_prev_mode = current_mode; + adaptive_keyboard_mode_is_saved = true; + + if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0) + return false; return true; default: - return false; + if (scancode < FIRST_ADAPTIVE_KEY || + scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN - + ADAPTIVE_KEY_OFFSET) { + pr_info("Unhandled adaptive keyboard key: 0x%x\n", + scancode); + return false; + } + keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET]; + if (keycode != KEY_RESERVED) { + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_key(tpacpi_inputdev, keycode, 1); + input_sync(tpacpi_inputdev); + + input_report_key(tpacpi_inputdev, keycode, 0); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + } + return true; } } @@ -3714,6 +3851,7 @@ static bool hotkey_notify_6xxx(const u32 hkey, case TP_HKEY_EV_KEY_NUMLOCK: case TP_HKEY_EV_KEY_FN: + case TP_HKEY_EV_KEY_FN_ESC: /* key press events, we just ignore them as long as the EC * is still reporting them in the normal keyboard stream */ *send_acpi_ev = false; @@ -3836,28 +3974,21 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) static void hotkey_suspend(void) { - int hkeyv; - /* Do these on suspend, we get the events on early resume! */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE; hotkey_autosleep_ack = 0; /* save previous mode of adaptive keyboard of X1 Carbon */ - if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { - if ((hkeyv >> 8) == 2) { - if (!acpi_evalf(hkey_handle, - &adaptive_keyboard_prev_mode, - "GTRW", "dd", 0)) { - pr_err("Cannot read adaptive keyboard mode.\n"); - } + if (tp_features.has_adaptive_kbd) { + if (!acpi_evalf(hkey_handle, &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode.\n"); } } } static void hotkey_resume(void) { - int hkeyv; - tpacpi_disable_brightness_delay(); if (hotkey_status_set(true) < 0 || @@ -3872,14 +4003,10 @@ static void hotkey_resume(void) hotkey_poll_setup_safe(false); /* restore previous mode of adapive keyboard of X1 Carbon */ - if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { - if ((hkeyv >> 8) == 2) { - if (!acpi_evalf(hkey_handle, - NULL, - "STRW", "vd", - adaptive_keyboard_prev_mode)) { - pr_err("Cannot set adaptive keyboard mode.\n"); - } + if (tp_features.has_adaptive_kbd) { + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", + adaptive_keyboard_prev_mode)) { + pr_err("Cannot set adaptive keyboard mode.\n"); } } } @@ -4079,9 +4206,7 @@ static ssize_t bluetooth_enable_store(struct device *dev, attr, buf, count); } -static struct device_attribute dev_attr_bluetooth_enable = - __ATTR(bluetooth_enable, S_IWUSR | S_IRUGO, - bluetooth_enable_show, bluetooth_enable_store); +static DEVICE_ATTR_RW(bluetooth_enable); /* --------------------------------------------------------------------- */ @@ -4269,14 +4394,13 @@ static ssize_t wan_enable_store(struct device *dev, attr, buf, count); } -static struct device_attribute dev_attr_wan_enable = - __ATTR(wwan_enable, S_IWUSR | S_IRUGO, - wan_enable_show, wan_enable_store); +static DEVICE_ATTR(wwan_enable, S_IWUSR | S_IRUGO, + wan_enable_show, wan_enable_store); /* --------------------------------------------------------------------- */ static struct attribute *wan_attributes[] = { - &dev_attr_wan_enable.attr, + &dev_attr_wwan_enable.attr, NULL }; @@ -5048,8 +5172,7 @@ static ssize_t cmos_command_store(struct device *dev, return (res) ? res : count; } -static struct device_attribute dev_attr_cmos_command = - __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store); +static DEVICE_ATTR_WO(cmos_command); /* --------------------------------------------------------------------- */ @@ -6559,6 +6682,17 @@ static struct ibm_struct brightness_driver_data = { * bits 3-0 (volume). Other bits in NVRAM may have other functions, * such as bit 7 which is used to detect repeated presses of MUTE, * and we leave them unchanged. + * + * On newer Lenovo ThinkPads, the EC can automatically change the volume + * in response to user input. Unfortunately, this rarely works well. + * The laptop changes the state of its internal MUTE gate and, on some + * models, sends KEY_MUTE, causing any user code that responds to the + * mute button to get confused. The hardware MUTE gate is also + * unnecessary, since user code can handle the mute button without + * kernel or EC help. + * + * To avoid confusing userspace, we simply disable all EC-based mute + * and volume controls when possible. */ #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT @@ -6613,11 +6747,21 @@ enum tpacpi_volume_capabilities { TPACPI_VOL_CAP_MAX }; +enum tpacpi_mute_btn_mode { + TP_EC_MUTE_BTN_LATCH = 0, /* Mute mutes; up/down unmutes */ + /* We don't know what mode 1 is. */ + TP_EC_MUTE_BTN_NONE = 2, /* Mute and up/down are just keys */ + TP_EC_MUTE_BTN_TOGGLE = 3, /* Mute toggles; up/down unmutes */ +}; + static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; static bool volume_control_allowed; +static bool software_mute_requested = true; +static bool software_mute_active; +static int software_mute_orig_mode; /* * Used to syncronize writers to TP_EC_AUDIO and @@ -6635,6 +6779,8 @@ static void tpacpi_volume_checkpoint_nvram(void) return; if (!volume_control_allowed) return; + if (software_mute_active) + return; vdbg_printk(TPACPI_DBG_MIXER, "trying to checkpoint mixer state to NVRAM...\n"); @@ -6696,6 +6842,12 @@ static int volume_set_status_ec(const u8 status) dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status); + /* + * On X200s, and possibly on others, it can take a while for + * reads to become correct. + */ + msleep(1); + return 0; } @@ -6778,6 +6930,57 @@ unlock: return rc; } +static int volume_set_software_mute(bool startup) +{ + int result; + + if (!tpacpi_is_lenovo()) + return -ENODEV; + + if (startup) { + if (!acpi_evalf(ec_handle, &software_mute_orig_mode, + "HAUM", "qd")) + return -EIO; + + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "Initial HAUM setting was %d\n", + software_mute_orig_mode); + } + + if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", + (int)TP_EC_MUTE_BTN_NONE)) + return -EIO; + + if (result != TP_EC_MUTE_BTN_NONE) + pr_warn("Unexpected SAUM result %d\n", + result); + + /* + * In software mute mode, the standard codec controls take + * precendence, so we unmute the ThinkPad HW switch at + * startup. Just on case there are SAUM-capable ThinkPads + * with level controls, set max HW volume as well. + */ + if (tp_features.mixer_no_level_control) + result = volume_set_mute(false); + else + result = volume_set_status(TP_EC_VOLUME_MAX); + + if (result != 0) + pr_warn("Failed to unmute the HW mute switch\n"); + + return 0; +} + +static void volume_exit_software_mute(void) +{ + int r; + + if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode) + || r != software_mute_orig_mode) + pr_warn("Failed to restore mute mode\n"); +} + static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, @@ -6885,7 +7088,12 @@ static void volume_suspend(void) static void volume_resume(void) { - volume_alsa_notify_change(); + if (software_mute_active) { + if (volume_set_software_mute(false) < 0) + pr_warn("Failed to restore software mute\n"); + } else { + volume_alsa_notify_change(); + } } static void volume_shutdown(void) @@ -6901,6 +7109,9 @@ static void volume_exit(void) } tpacpi_volume_checkpoint_nvram(); + + if (software_mute_active) + volume_exit_software_mute(); } static int __init volume_create_alsa_mixer(void) @@ -7085,16 +7296,20 @@ static int __init volume_init(struct ibm_init_struct *iibm) "mute is supported, volume control is %s\n", str_supported(!tp_features.mixer_no_level_control)); - rc = volume_create_alsa_mixer(); - if (rc) { - pr_err("Could not create the ALSA mixer interface\n"); - return rc; - } + if (software_mute_requested && volume_set_software_mute(true) == 0) { + software_mute_active = true; + } else { + rc = volume_create_alsa_mixer(); + if (rc) { + pr_err("Could not create the ALSA mixer interface\n"); + return rc; + } - pr_info("Console audio control enabled, mode: %s\n", - (volume_control_allowed) ? - "override (read/write)" : - "monitor (read only)"); + pr_info("Console audio control enabled, mode: %s\n", + (volume_control_allowed) ? + "override (read/write)" : + "monitor (read only)"); + } vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, "registering volume hotkeys as change notification\n"); @@ -7925,9 +8140,8 @@ static ssize_t fan_pwm1_enable_store(struct device *dev, return count; } -static struct device_attribute dev_attr_fan_pwm1_enable = - __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, - fan_pwm1_enable_show, fan_pwm1_enable_store); +static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); /* sysfs fan pwm1 ------------------------------------------------------ */ static ssize_t fan_pwm1_show(struct device *dev, @@ -7987,9 +8201,7 @@ static ssize_t fan_pwm1_store(struct device *dev, return (rc) ? rc : count; } -static struct device_attribute dev_attr_fan_pwm1 = - __ATTR(pwm1, S_IWUSR | S_IRUGO, - fan_pwm1_show, fan_pwm1_store); +static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, fan_pwm1_show, fan_pwm1_store); /* sysfs fan fan1_input ------------------------------------------------ */ static ssize_t fan_fan1_input_show(struct device *dev, @@ -8006,9 +8218,7 @@ static ssize_t fan_fan1_input_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan1_input = - __ATTR(fan1_input, S_IRUGO, - fan_fan1_input_show, NULL); +static DEVICE_ATTR(fan1_input, S_IRUGO, fan_fan1_input_show, NULL); /* sysfs fan fan2_input ------------------------------------------------ */ static ssize_t fan_fan2_input_show(struct device *dev, @@ -8025,9 +8235,7 @@ static ssize_t fan_fan2_input_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", speed); } -static struct device_attribute dev_attr_fan_fan2_input = - __ATTR(fan2_input, S_IRUGO, - fan_fan2_input_show, NULL); +static DEVICE_ATTR(fan2_input, S_IRUGO, fan_fan2_input_show, NULL); /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ static ssize_t fan_fan_watchdog_show(struct device_driver *drv, @@ -8060,8 +8268,8 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, /* --------------------------------------------------------------------- */ static struct attribute *fan_attributes[] = { - &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, - &dev_attr_fan_fan1_input.attr, + &dev_attr_pwm1_enable.attr, &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, NULL, /* for fan2_input */ NULL }; @@ -8195,7 +8403,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) if (tp_features.second_fan) { /* attach second fan tachometer */ fan_attributes[ARRAY_SIZE(fan_attributes)-2] = - &dev_attr_fan_fan2_input.attr; + &dev_attr_fan2_input.attr; } rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group); @@ -8643,8 +8851,7 @@ static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev, return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME); } -static struct device_attribute dev_attr_thinkpad_acpi_pdev_name = - __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL); +static DEVICE_ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL); /* --------------------------------------------------------------------- */ @@ -8793,17 +9000,31 @@ static bool __pure __init tpacpi_is_fw_digit(const char c) return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); } -/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */ static bool __pure __init tpacpi_is_valid_fw_id(const char * const s, const char t) { - return s && strlen(s) >= 8 && + /* + * Most models: xxyTkkWW (#.##c) + * Ancient 570/600 and -SL lacks (#.##c) + */ + if (s && strlen(s) >= 8 && tpacpi_is_fw_digit(s[0]) && tpacpi_is_fw_digit(s[1]) && s[2] == t && (s[3] == 'T' || s[3] == 'N') && tpacpi_is_fw_digit(s[4]) && - tpacpi_is_fw_digit(s[5]); + tpacpi_is_fw_digit(s[5])) + return true; + + /* New models: xxxyTkkW (#.##c); T550 and some others */ + return s && strlen(s) >= 8 && + tpacpi_is_fw_digit(s[0]) && + tpacpi_is_fw_digit(s[1]) && + tpacpi_is_fw_digit(s[2]) && + s[3] == t && + (s[4] == 'T' || s[4] == 'N') && + tpacpi_is_fw_digit(s[5]) && + tpacpi_is_fw_digit(s[6]); } /* returns 0 - probe ok, or < 0 - probe error. @@ -9091,6 +9312,10 @@ MODULE_PARM_DESC(volume_control, "Enables software override for the console audio " "control when true"); +module_param_named(software_mute, software_mute_requested, bool, 0444); +MODULE_PARM_DESC(software_mute, + "Request full software mute control"); + /* ALSA module API parameters */ module_param_named(index, alsa_index, int, 0444); MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer"); @@ -9168,8 +9393,7 @@ static void thinkpad_acpi_module_exit(void) hwmon_device_unregister(tpacpi_hwmon); if (tp_features.sensors_pdev_attrs_registered) - device_remove_file(&tpacpi_sensors_pdev->dev, - &dev_attr_thinkpad_acpi_pdev_name); + device_remove_file(&tpacpi_sensors_pdev->dev, &dev_attr_name); if (tpacpi_sensors_pdev) platform_device_unregister(tpacpi_sensors_pdev); if (tpacpi_pdev) @@ -9290,8 +9514,7 @@ static int __init thinkpad_acpi_module_init(void) thinkpad_acpi_module_exit(); return ret; } - ret = device_create_file(&tpacpi_sensors_pdev->dev, - &dev_attr_thinkpad_acpi_pdev_name); + ret = device_create_file(&tpacpi_sensors_pdev->dev, &dev_attr_name); if (ret) { pr_err("unable to create sysfs hwmon device attributes\n"); thinkpad_acpi_module_exit(); diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index ab6151f05420..9956b9902bb4 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -1,11 +1,10 @@ /* * toshiba_acpi.c - Toshiba Laptop ACPI Extras * - * * Copyright (C) 2002-2004 John Belmonte * Copyright (C) 2008 Philip Langdale * Copyright (C) 2010 Pierre Ducroquet - * Copyright (C) 2014 Azael Avalos + * Copyright (C) 2014-2015 Azael Avalos * * 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 @@ -17,10 +16,8 @@ * 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 - * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". * * The devolpment page for this driver is located at * http://memebeam.org/toys/ToshibaAcpiDriver. @@ -30,15 +27,11 @@ * engineering the Windows drivers * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 * Rob Miller - TV out and hotkeys help - * - * - * TODO - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TOSHIBA_ACPI_VERSION "0.20" +#define TOSHIBA_ACPI_VERSION "0.21" #define PROC_INTERFACE_VERSION 1 #include <linux/kernel.h> @@ -57,7 +50,8 @@ #include <linux/i8042.h> #include <linux/acpi.h> #include <linux/dmi.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> +#include <acpi/video.h> MODULE_AUTHOR("John Belmonte"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); @@ -71,7 +65,8 @@ MODULE_LICENSE("GPL"); /* Toshiba ACPI method paths */ #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" -/* The Toshiba configuration interface is composed of the HCI and the SCI, +/* + * The Toshiba configuration interface is composed of the HCI and the SCI, * which are defined as follows: * * HCI is Toshiba's "Hardware Control Interface" which is supposed to @@ -108,6 +103,7 @@ MODULE_LICENSE("GPL"); #define TOS_FIFO_EMPTY 0x8c00 #define TOS_DATA_NOT_AVAILABLE 0x8d20 #define TOS_NOT_INITIALIZED 0x8d50 +#define TOS_NOT_INSTALLED 0x8e00 /* registers */ #define HCI_FAN 0x0004 @@ -121,18 +117,27 @@ MODULE_LICENSE("GPL"); #define HCI_KBD_ILLUMINATION 0x0095 #define HCI_ECO_MODE 0x0097 #define HCI_ACCELEROMETER2 0x00a6 +#define HCI_SYSTEM_INFO 0xc000 +#define SCI_PANEL_POWER_ON 0x010d #define SCI_ILLUMINATION 0x014e +#define SCI_USB_SLEEP_CHARGE 0x0150 #define SCI_KBD_ILLUM_STATUS 0x015c +#define SCI_USB_SLEEP_MUSIC 0x015e +#define SCI_USB_THREE 0x0169 #define SCI_TOUCHPAD 0x050e +#define SCI_KBD_FUNCTION_KEYS 0x0522 /* field definitions */ #define HCI_ACCEL_MASK 0x7fff #define HCI_HOTKEY_DISABLE 0x0b #define HCI_HOTKEY_ENABLE 0x09 +#define HCI_HOTKEY_SPECIAL_FUNCTIONS 0x10 #define HCI_LCD_BRIGHTNESS_BITS 3 #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) #define HCI_MISC_SHIFT 0x10 +#define HCI_SYSTEM_TYPE1 0x10 +#define HCI_SYSTEM_TYPE2 0x11 #define HCI_VIDEO_OUT_LCD 0x1 #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 @@ -146,6 +151,16 @@ MODULE_LICENSE("GPL"); #define SCI_KBD_MODE_ON 0x8 #define SCI_KBD_MODE_OFF 0x10 #define SCI_KBD_TIME_MAX 0x3c001a +#define SCI_USB_CHARGE_MODE_MASK 0xff +#define SCI_USB_CHARGE_DISABLED 0x00 +#define SCI_USB_CHARGE_ALTERNATE 0x09 +#define SCI_USB_CHARGE_TYPICAL 0x11 +#define SCI_USB_CHARGE_AUTO 0x21 +#define SCI_USB_CHARGE_BAT_MASK 0x7 +#define SCI_USB_CHARGE_BAT_LVL_OFF 0x1 +#define SCI_USB_CHARGE_BAT_LVL_ON 0x4 +#define SCI_USB_CHARGE_BAT_LVL 0x0200 +#define SCI_USB_CHARGE_RAPID_DSP 0x0300 struct toshiba_acpi_dev { struct acpi_device *acpi_dev; @@ -164,6 +179,9 @@ struct toshiba_acpi_dev { int kbd_type; int kbd_mode; int kbd_time; + int usbsc_bat_level; + int usbsc_mode_base; + int hotkey_event_type; unsigned int illumination_supported:1; unsigned int video_supported:1; @@ -177,6 +195,12 @@ struct toshiba_acpi_dev { unsigned int touchpad_supported:1; unsigned int eco_supported:1; unsigned int accelerometer_supported:1; + unsigned int usb_sleep_charge_supported:1; + unsigned int usb_rapid_charge_supported:1; + unsigned int usb_sleep_music_supported:1; + unsigned int kbd_function_keys_supported:1; + unsigned int panel_power_on_supported:1; + unsigned int usb_three_supported:1; unsigned int sysfs_created:1; struct mutex mutex; @@ -186,6 +210,7 @@ static struct toshiba_acpi_dev *toshiba_acpi; static const struct acpi_device_id toshiba_device_ids[] = { {"TOS6200", 0}, + {"TOS6207", 0}, {"TOS6208", 0}, {"TOS1900", 0}, {"", 0}, @@ -226,29 +251,6 @@ static const struct key_entry toshiba_acpi_keymap[] = { { KE_END, 0 }, }; -/* alternative keymap */ -static const struct dmi_system_id toshiba_alt_keymap_dmi[] = { - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "Satellite M840"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "Qosmio X75-A"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), - DMI_MATCH(DMI_PRODUCT_NAME, "TECRA A50-A"), - }, - }, - {} -}; - static const struct key_entry toshiba_acpi_alt_keymap[] = { { KE_KEY, 0x157, { KEY_MUTE } }, { KE_KEY, 0x102, { KEY_ZOOMOUT } }, @@ -263,15 +265,25 @@ static const struct key_entry toshiba_acpi_alt_keymap[] = { { KE_END, 0 }, }; -/* utility +/* + * List of models which have a broken acpi-video backlight interface and thus + * need to use the toshiba (vendor) interface instead. + */ +static const struct dmi_system_id toshiba_vendor_backlight_dmi[] = { + {} +}; + +/* + * Utility */ -static __inline__ void _set_bit(u32 * word, u32 mask, int value) +static inline void _set_bit(u32 *word, u32 mask, int value) { *word = (*word & ~mask) | (mask * value); } -/* acpi interface wrappers +/* + * ACPI interface wrappers */ static int write_acpi_int(const char *methodName, int val) @@ -282,7 +294,8 @@ static int write_acpi_int(const char *methodName, int val) return (status == AE_OK) ? 0 : -EIO; } -/* Perform a raw configuration call. Here we don't care about input or output +/* + * Perform a raw configuration call. Here we don't care about input or output * buffer format. */ static acpi_status tci_raw(struct toshiba_acpi_dev *dev, @@ -309,15 +322,15 @@ static acpi_status tci_raw(struct toshiba_acpi_dev *dev, (char *)dev->method_hci, ¶ms, &results); if ((status == AE_OK) && (out_objs->package.count <= TCI_WORDS)) { - for (i = 0; i < out_objs->package.count; ++i) { + for (i = 0; i < out_objs->package.count; ++i) out[i] = out_objs->package.elements[i].integer.value; - } } return status; } -/* common hci tasks (get or set one or two value) +/* + * Common hci tasks (get or set one or two value) * * In addition to the ACPI status, the HCI system returns a result which * may be useful (such as "not supported"). @@ -337,6 +350,7 @@ static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status)) return TOS_FAILURE; @@ -354,11 +368,13 @@ static u32 hci_write2(struct toshiba_acpi_dev *dev, u32 reg, u32 in1, u32 in2) return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE; } -static u32 hci_read2(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1, u32 *out2) +static u32 hci_read2(struct toshiba_acpi_dev *dev, + u32 reg, u32 *out1, u32 *out2) { u32 in[TCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status)) return TOS_FAILURE; @@ -368,7 +384,8 @@ static u32 hci_read2(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1, u32 *out2 return out[0]; } -/* common sci tasks +/* + * Common sci tasks */ static int sci_open(struct toshiba_acpi_dev *dev) @@ -388,6 +405,20 @@ static int sci_open(struct toshiba_acpi_dev *dev) } else if (out[0] == TOS_ALREADY_OPEN) { pr_info("Toshiba SCI already opened\n"); return 1; + } else if (out[0] == TOS_NOT_SUPPORTED) { + /* + * Some BIOSes do not have the SCI open/close functions + * implemented and return 0x8000 (Not Supported), failing to + * register some supported features. + * + * Simply return 1 if we hit those affected laptops to make the + * supported features work. + * + * In the case that some laptops really do not support the SCI, + * all the SCI dependent functions check for TOS_NOT_SUPPORTED, + * and thus, not registering support for the queried feature. + */ + return 1; } else if (out[0] == TOS_NOT_PRESENT) { pr_info("Toshiba SCI is not present\n"); } @@ -420,6 +451,7 @@ static u32 sci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) u32 in[TCI_WORDS] = { SCI_GET, reg, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status)) return TOS_FAILURE; @@ -528,10 +560,11 @@ static int toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev) return 0; } - /* Check for keyboard backlight timeout max value, + /* + * Check for keyboard backlight timeout max value, * previous kbd backlight implementation set this to * 0x3c0003, and now the new implementation set this - * to 0x3c001a, use this to distinguish between them + * to 0x3c001a, use this to distinguish between them. */ if (out[3] == SCI_KBD_TIME_MAX) dev->kbd_type = 2; @@ -666,19 +699,37 @@ static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state) static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) { acpi_status status; - u32 in[TCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 in[TCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { - pr_info("ACPI call to get ECO led failed\n"); - return 0; + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get ECO led failed\n"); + } else if (out[0] == TOS_NOT_INSTALLED) { + pr_info("ECO led not installed"); + } else if (out[0] == TOS_INPUT_DATA_ERROR) { + /* + * If we receive 0x8300 (Input Data Error), it means that the + * LED device is present, but that we just screwed the input + * parameters. + * + * Let's query the status of the LED to see if we really have a + * success response, indicating the actual presense of the LED, + * bail out otherwise. + */ + in[3] = 1; + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) + pr_err("ACPI call to get ECO led failed\n"); + else if (out[0] == TOS_SUCCESS) + return 1; } - return 1; + return 0; } -static enum led_brightness toshiba_eco_mode_get_status(struct led_classdev *cdev) +static enum led_brightness +toshiba_eco_mode_get_status(struct led_classdev *cdev) { struct toshiba_acpi_dev *dev = container_of(cdev, struct toshiba_acpi_dev, eco_led); @@ -720,7 +771,8 @@ static int toshiba_accelerometer_supported(struct toshiba_acpi_dev *dev) u32 out[TCI_WORDS]; acpi_status status; - /* Check if the accelerometer call exists, + /* + * Check if the accelerometer call exists, * this call also serves as initialization */ status = tci_raw(dev, in, out); @@ -759,6 +811,407 @@ static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, return 0; } +/* Sleep (Charge and Music) utilities support */ +static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) +{ + u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + /* Set the feature to "not supported" in case of error */ + dev->usb_sleep_charge_supported = 0; + + if (!sci_open(dev)) + return; + + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_SUCCESS) { + dev->usbsc_mode_base = out[4]; + } + + in[5] = SCI_USB_CHARGE_BAT_LVL; + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + sci_close(dev); + return; + } else if (out[0] == TOS_SUCCESS) { + dev->usbsc_bat_level = out[2]; + /* + * If we reach this point, it means that the laptop has support + * for this feature and all values are initialized. + * Set it as supported. + */ + dev->usb_sleep_charge_supported = 1; + } + + sci_close(dev); +} + +static int toshiba_usb_sleep_charge_get(struct toshiba_acpi_dev *dev, + u32 *mode) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_read(dev, SCI_USB_SLEEP_CHARGE, mode); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to set USB S&C mode failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_usb_sleep_charge_set(struct toshiba_acpi_dev *dev, + u32 mode) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_write(dev, SCI_USB_SLEEP_CHARGE, mode); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to set USB S&C mode failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev, + u32 *mode) +{ + u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + in[5] = SCI_USB_CHARGE_BAT_LVL; + status = tci_raw(dev, in, out); + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB S&C battery level failed\n"); + return -EIO; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + return -ENODEV; + } else if (out[0] == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + *mode = out[2]; + + return 0; +} + +static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev, + u32 mode) +{ + u32 in[TCI_WORDS] = { SCI_SET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + in[2] = mode; + in[5] = SCI_USB_CHARGE_BAT_LVL; + status = tci_raw(dev, in, out); + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to set USB S&C battery level failed\n"); + return -EIO; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Sleep and Charge not supported\n"); + return -ENODEV; + } else if (out[0] == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev, + u32 *state) +{ + u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + in[5] = SCI_USB_CHARGE_RAPID_DSP; + status = tci_raw(dev, in, out); + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to get USB Rapid Charge failed\n"); + return -EIO; + } else if (out[0] == TOS_NOT_SUPPORTED || + out[0] == TOS_INPUT_DATA_ERROR) { + pr_info("USB Rapid Charge not supported\n"); + return -ENODEV; + } + + *state = out[2]; + + return 0; +} + +static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev, + u32 state) +{ + u32 in[TCI_WORDS] = { SCI_SET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + in[2] = state; + in[5] = SCI_USB_CHARGE_RAPID_DSP; + status = tci_raw(dev, in, out); + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + pr_err("ACPI call to set USB Rapid Charge failed\n"); + return -EIO; + } else if (out[0] == TOS_NOT_SUPPORTED) { + pr_info("USB Rapid Charge not supported\n"); + return -ENODEV; + } else if (out[0] == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_usb_sleep_music_get(struct toshiba_acpi_dev *dev, u32 *state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_read(dev, SCI_USB_SLEEP_MUSIC, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to get Sleep and Music failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("Sleep and Music not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_usb_sleep_music_set(struct toshiba_acpi_dev *dev, u32 state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_write(dev, SCI_USB_SLEEP_MUSIC, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to set Sleep and Music failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("Sleep and Music not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +/* Keyboard function keys */ +static int toshiba_function_keys_get(struct toshiba_acpi_dev *dev, u32 *mode) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_read(dev, SCI_KBD_FUNCTION_KEYS, mode); + sci_close(dev); + if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + pr_err("ACPI call to get KBD function keys failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("KBD function keys not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_function_keys_set(struct toshiba_acpi_dev *dev, u32 mode) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_write(dev, SCI_KBD_FUNCTION_KEYS, mode); + sci_close(dev); + if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + pr_err("ACPI call to set KBD function keys failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("KBD function keys not supported\n"); + return -ENODEV; + } + + return 0; +} + +/* Panel Power ON */ +static int toshiba_panel_power_on_get(struct toshiba_acpi_dev *dev, u32 *state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_read(dev, SCI_PANEL_POWER_ON, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to get Panel Power ON failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("Panel Power on not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_panel_power_on_set(struct toshiba_acpi_dev *dev, u32 state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_write(dev, SCI_PANEL_POWER_ON, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to set Panel Power ON failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("Panel Power ON not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +/* USB Three */ +static int toshiba_usb_three_get(struct toshiba_acpi_dev *dev, u32 *state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_read(dev, SCI_USB_THREE, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to get USB 3 failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("USB 3 not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) +{ + u32 result; + + if (!sci_open(dev)) + return -EIO; + + result = sci_write(dev, SCI_USB_THREE, state); + sci_close(dev); + if (result == TOS_FAILURE) { + pr_err("ACPI call to set USB 3 failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("USB 3 not supported\n"); + return -ENODEV; + } else if (result == TOS_INPUT_DATA_ERROR) { + return -EIO; + } + + return 0; +} + +/* Hotkey Event type */ +static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, + u32 *type) +{ + u32 val1 = 0x03; + u32 val2 = 0; + u32 result; + + result = hci_read2(dev, HCI_SYSTEM_INFO, &val1, &val2); + if (result == TOS_FAILURE) { + pr_err("ACPI call to get System type failed\n"); + return -EIO; + } else if (result == TOS_NOT_SUPPORTED) { + pr_info("System type not supported\n"); + return -ENODEV; + } + + *type = val2; + + return 0; +} + /* Bluetooth rfkill handlers */ static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present) @@ -869,7 +1322,7 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) return hci_result == TOS_SUCCESS ? 0 : -EIO; } -static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; +static struct proc_dir_entry *toshiba_proc_dir /*= 0*/; static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) { @@ -880,6 +1333,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) if (dev->tr_backlight_supported) { bool enabled; int ret = get_tr_backlight_status(dev, &enabled); + if (ret) return ret; if (enabled) @@ -897,6 +1351,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) static int get_lcd_brightness(struct backlight_device *bd) { struct toshiba_acpi_dev *dev = bl_get_data(bd); + return __get_lcd_brightness(dev); } @@ -928,38 +1383,27 @@ static int lcd_proc_open(struct inode *inode, struct file *file) static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value) { - u32 in[TCI_WORDS] = { HCI_SET, HCI_LCD_BRIGHTNESS, 0, 0, 0, 0 }; - u32 out[TCI_WORDS]; - acpi_status status; + u32 hci_result; if (dev->tr_backlight_supported) { bool enable = !value; int ret = set_tr_backlight_status(dev, enable); + if (ret) return ret; if (value) value--; } - in[2] = value << HCI_LCD_BRIGHTNESS_SHIFT; - status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { - pr_err("ACPI call to set brightness failed"); - return -EIO; - } - /* Extra check for "incomplete" backlight method, where the AML code - * doesn't check for HCI_SET or HCI_GET and returns TOS_SUCCESS, - * the actual brightness, and in some cases the max brightness. - */ - if (out[2] > 0 || out[3] == 0xE000) - return -ENODEV; - - return out[0] == TOS_SUCCESS ? 0 : -EIO; + value = value << HCI_LCD_BRIGHTNESS_SHIFT; + hci_result = hci_write1(dev, HCI_LCD_BRIGHTNESS, value); + return hci_result == TOS_SUCCESS ? 0 : -EIO; } static int set_lcd_status(struct backlight_device *bd) { struct toshiba_acpi_dev *dev = bl_get_data(bd); + return set_lcd_brightness(dev, bd->props.brightness); } @@ -1017,6 +1461,7 @@ static int video_proc_show(struct seq_file *m, void *v) int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; + seq_printf(m, "lcd_out: %d\n", is_lcd); seq_printf(m, "crt_out: %d\n", is_crt); seq_printf(m, "tv_out: %d\n", is_tv); @@ -1054,9 +1499,9 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, buffer = cmd; - /* scan expression. Multiple expressions may be delimited with ; - * - * NOTE: to keep scanning simple, invalid fields are ignored + /* + * Scan expression. Multiple expressions may be delimited with ; + * NOTE: To keep scanning simple, invalid fields are ignored. */ while (remain) { if (sscanf(buffer, " lcd_out : %i", &value) == 1) @@ -1065,12 +1510,11 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, crt_out = value & 1; else if (sscanf(buffer, " tv_out : %i", &value) == 1) tv_out = value & 1; - /* advance to one character past the next ; */ + /* Advance to one character past the next ; */ do { ++buffer; --remain; - } - while (remain && *(buffer - 1) != ';'); + } while (remain && *(buffer - 1) != ';'); } kfree(cmd); @@ -1078,13 +1522,15 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, ret = get_video_status(dev, &video_out); if (!ret) { unsigned int new_video_out = video_out; + if (lcd_out != -1) _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); if (crt_out != -1) _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); if (tv_out != -1) _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); - /* To avoid unnecessary video disruption, only write the new + /* + * To avoid unnecessary video disruption, only write the new * video setting if something changed. */ if (new_video_out != video_out) ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out); @@ -1147,10 +1593,10 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, if (sscanf(cmd, " force_on : %i", &value) == 1 && value >= 0 && value <= 1) { hci_result = hci_write1(dev, HCI_FAN, value); - if (hci_result != TOS_SUCCESS) - return -EIO; - else + if (hci_result == TOS_SUCCESS) dev->force_fan = value; + else + return -EIO; } else { return -EINVAL; } @@ -1179,11 +1625,13 @@ static int keys_proc_show(struct seq_file *m, void *v) dev->key_event_valid = 1; dev->last_key_event = value; } else if (hci_result == TOS_FIFO_EMPTY) { - /* better luck next time */ + /* Better luck next time */ } else if (hci_result == TOS_NOT_SUPPORTED) { - /* This is a workaround for an unresolved issue on + /* + * This is a workaround for an unresolved issue on * some machines where system events sporadically - * become disabled. */ + * become disabled. + */ hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); pr_notice("Re-enabled hotkeys\n"); } else { @@ -1215,11 +1663,10 @@ static ssize_t keys_proc_write(struct file *file, const char __user *buf, return -EFAULT; cmd[len] = '\0'; - if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) { + if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) dev->key_event_valid = 0; - } else { + else return -EINVAL; - } return count; } @@ -1253,7 +1700,8 @@ static const struct file_operations version_proc_fops = { .release = single_release, }; -/* proc and module init +/* + * Proc and module init */ #define PROC_TOSHIBA "toshiba" @@ -1298,66 +1746,56 @@ static const struct backlight_ops toshiba_backlight_data = { /* * Sysfs files */ -static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count); -static ssize_t toshiba_kbd_bl_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf); -static ssize_t toshiba_kbd_type_show(struct device *dev, - struct device_attribute *attr, - char *buf); -static ssize_t toshiba_available_kbd_modes_show(struct device *dev, - struct device_attribute *attr, - char *buf); -static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count); -static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, - struct device_attribute *attr, - char *buf); -static ssize_t toshiba_touchpad_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count); -static ssize_t toshiba_touchpad_show(struct device *dev, - struct device_attribute *attr, - char *buf); -static ssize_t toshiba_position_show(struct device *dev, - struct device_attribute *attr, - char *buf); - -static DEVICE_ATTR(kbd_backlight_mode, S_IRUGO | S_IWUSR, - toshiba_kbd_bl_mode_show, toshiba_kbd_bl_mode_store); -static DEVICE_ATTR(kbd_type, S_IRUGO, toshiba_kbd_type_show, NULL); -static DEVICE_ATTR(available_kbd_modes, S_IRUGO, - toshiba_available_kbd_modes_show, NULL); -static DEVICE_ATTR(kbd_backlight_timeout, S_IRUGO | S_IWUSR, - toshiba_kbd_bl_timeout_show, toshiba_kbd_bl_timeout_store); -static DEVICE_ATTR(touchpad, S_IRUGO | S_IWUSR, - toshiba_touchpad_show, toshiba_touchpad_store); -static DEVICE_ATTR(position, S_IRUGO, toshiba_position_show, NULL); +static ssize_t version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", TOSHIBA_ACPI_VERSION); +} +static DEVICE_ATTR_RO(version); -static struct attribute *toshiba_attributes[] = { - &dev_attr_kbd_backlight_mode.attr, - &dev_attr_kbd_type.attr, - &dev_attr_available_kbd_modes.attr, - &dev_attr_kbd_backlight_timeout.attr, - &dev_attr_touchpad.attr, - &dev_attr_position.attr, - NULL, -}; +static ssize_t fan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 result; + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; -static umode_t toshiba_sysfs_is_visible(struct kobject *, - struct attribute *, int); + if (state != 0 && state != 1) + return -EINVAL; -static struct attribute_group toshiba_attr_group = { - .is_visible = toshiba_sysfs_is_visible, - .attrs = toshiba_attributes, -}; + result = hci_write1(toshiba, HCI_FAN, state); + if (result == TOS_FAILURE) + return -EIO; + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return count; +} + +static ssize_t fan_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 value; + int ret; -static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) + ret = get_fan_status(toshiba, &value); + if (ret) + return ret; + + return sprintf(buf, "%d\n", value); +} +static DEVICE_ATTR_RW(fan); + +static ssize_t kbd_backlight_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); int mode; @@ -1381,7 +1819,8 @@ static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, return -EINVAL; } - /* Set the Keyboard Backlight Mode where: + /* + * Set the Keyboard Backlight Mode where: * Auto - KBD backlight turns off automatically in given time * FN-Z - KBD backlight "toggles" when hotkey pressed * ON - KBD backlight is always on @@ -1406,21 +1845,15 @@ static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, if (ret) return ret; - /* Update sysfs entries on successful mode change*/ - ret = sysfs_update_group(&toshiba->acpi_dev->dev.kobj, - &toshiba_attr_group); - if (ret) - return ret; - toshiba->kbd_mode = mode; } return count; } -static ssize_t toshiba_kbd_bl_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t kbd_backlight_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); u32 time; @@ -1430,19 +1863,20 @@ static ssize_t toshiba_kbd_bl_mode_show(struct device *dev, return sprintf(buf, "%i\n", time & SCI_KBD_MODE_MASK); } +static DEVICE_ATTR_RW(kbd_backlight_mode); -static ssize_t toshiba_kbd_type_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t kbd_type_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); return sprintf(buf, "%d\n", toshiba->kbd_type); } +static DEVICE_ATTR_RO(kbd_type); -static ssize_t toshiba_available_kbd_modes_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t available_kbd_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); @@ -1453,10 +1887,11 @@ static ssize_t toshiba_available_kbd_modes_show(struct device *dev, return sprintf(buf, "%x %x %x\n", SCI_KBD_MODE_AUTO, SCI_KBD_MODE_ON, SCI_KBD_MODE_OFF); } +static DEVICE_ATTR_RO(available_kbd_modes); -static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t kbd_backlight_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); int time; @@ -1497,9 +1932,9 @@ static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev, return count; } -static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t kbd_backlight_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); u32 time; @@ -1509,10 +1944,11 @@ static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT); } +static DEVICE_ATTR_RW(kbd_backlight_timeout); -static ssize_t toshiba_touchpad_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); int state; @@ -1532,8 +1968,8 @@ static ssize_t toshiba_touchpad_store(struct device *dev, return count; } -static ssize_t toshiba_touchpad_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t touchpad_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); u32 state; @@ -1545,9 +1981,10 @@ static ssize_t toshiba_touchpad_show(struct device *dev, return sprintf(buf, "%i\n", state); } +static DEVICE_ATTR_RW(touchpad); -static ssize_t toshiba_position_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t position_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); u32 xyval, zval, tmp; @@ -1566,6 +2003,340 @@ static ssize_t toshiba_position_show(struct device *dev, return sprintf(buf, "%d %d %d\n", x, y, z); } +static DEVICE_ATTR_RO(position); + +static ssize_t usb_sleep_charge_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 mode; + int ret; + + ret = toshiba_usb_sleep_charge_get(toshiba, &mode); + if (ret < 0) + return ret; + + return sprintf(buf, "%x\n", mode & SCI_USB_CHARGE_MODE_MASK); +} + +static ssize_t usb_sleep_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 mode; + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; + /* + * Check for supported values, where: + * 0 - Disabled + * 1 - Alternate (Non USB conformant devices that require more power) + * 2 - Auto (USB conformant devices) + * 3 - Typical + */ + if (state != 0 && state != 1 && state != 2 && state != 3) + return -EINVAL; + + /* Set the USB charging mode to internal value */ + mode = toshiba->usbsc_mode_base; + if (state == 0) + mode |= SCI_USB_CHARGE_DISABLED; + else if (state == 1) + mode |= SCI_USB_CHARGE_ALTERNATE; + else if (state == 2) + mode |= SCI_USB_CHARGE_AUTO; + else if (state == 3) + mode |= SCI_USB_CHARGE_TYPICAL; + + ret = toshiba_usb_sleep_charge_set(toshiba, mode); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(usb_sleep_charge); + +static ssize_t sleep_functions_on_battery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int bat_lvl; + int status; + int ret; + int tmp; + + ret = toshiba_sleep_functions_status_get(toshiba, &state); + if (ret < 0) + return ret; + + /* Determine the status: 0x4 - Enabled | 0x1 - Disabled */ + tmp = state & SCI_USB_CHARGE_BAT_MASK; + status = (tmp == 0x4) ? 1 : 0; + /* Determine the battery level set */ + bat_lvl = state >> HCI_MISC_SHIFT; + + return sprintf(buf, "%d %d\n", status, bat_lvl); +} + +static ssize_t sleep_functions_on_battery_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 status; + int value; + int ret; + int tmp; + + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + + /* + * Set the status of the function: + * 0 - Disabled + * 1-100 - Enabled + */ + if (value < 0 || value > 100) + return -EINVAL; + + if (value == 0) { + tmp = toshiba->usbsc_bat_level << HCI_MISC_SHIFT; + status = tmp | SCI_USB_CHARGE_BAT_LVL_OFF; + } else { + tmp = value << HCI_MISC_SHIFT; + status = tmp | SCI_USB_CHARGE_BAT_LVL_ON; + } + ret = toshiba_sleep_functions_status_set(toshiba, status); + if (ret < 0) + return ret; + + toshiba->usbsc_bat_level = status >> HCI_MISC_SHIFT; + + return count; +} +static DEVICE_ATTR_RW(sleep_functions_on_battery); + +static ssize_t usb_rapid_charge_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_usb_rapid_charge_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", state); +} + +static ssize_t usb_rapid_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; + if (state != 0 && state != 1) + return -EINVAL; + + ret = toshiba_usb_rapid_charge_set(toshiba, state); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(usb_rapid_charge); + +static ssize_t usb_sleep_music_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_usb_sleep_music_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", state); +} + +static ssize_t usb_sleep_music_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; + if (state != 0 && state != 1) + return -EINVAL; + + ret = toshiba_usb_sleep_music_set(toshiba, state); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(usb_sleep_music); + +static ssize_t kbd_function_keys_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int mode; + int ret; + + ret = toshiba_function_keys_get(toshiba, &mode); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", mode); +} + +static ssize_t kbd_function_keys_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int mode; + int ret; + + ret = kstrtoint(buf, 0, &mode); + if (ret) + return ret; + /* + * Check for the function keys mode where: + * 0 - Normal operation (F{1-12} as usual and hotkeys via FN-F{1-12}) + * 1 - Special functions (Opposite of the above setting) + */ + if (mode != 0 && mode != 1) + return -EINVAL; + + ret = toshiba_function_keys_set(toshiba, mode); + if (ret) + return ret; + + pr_info("Reboot for changes to KBD Function Keys to take effect"); + + return count; +} +static DEVICE_ATTR_RW(kbd_function_keys); + +static ssize_t panel_power_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_panel_power_on_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", state); +} + +static ssize_t panel_power_on_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; + if (state != 0 && state != 1) + return -EINVAL; + + ret = toshiba_panel_power_on_set(toshiba, state); + if (ret) + return ret; + + pr_info("Reboot for changes to Panel Power ON to take effect"); + + return count; +} +static DEVICE_ATTR_RW(panel_power_on); + +static ssize_t usb_three_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_usb_three_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", state); +} + +static ssize_t usb_three_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + int ret; + + ret = kstrtoint(buf, 0, &state); + if (ret) + return ret; + /* + * Check for USB 3 mode where: + * 0 - Disabled (Acts like a USB 2 port, saving power) + * 1 - Enabled + */ + if (state != 0 && state != 1) + return -EINVAL; + + ret = toshiba_usb_three_set(toshiba, state); + if (ret) + return ret; + + pr_info("Reboot for changes to USB 3 to take effect"); + + return count; +} +static DEVICE_ATTR_RW(usb_three); + +static struct attribute *toshiba_attributes[] = { + &dev_attr_version.attr, + &dev_attr_fan.attr, + &dev_attr_kbd_backlight_mode.attr, + &dev_attr_kbd_type.attr, + &dev_attr_available_kbd_modes.attr, + &dev_attr_kbd_backlight_timeout.attr, + &dev_attr_touchpad.attr, + &dev_attr_position.attr, + &dev_attr_usb_sleep_charge.attr, + &dev_attr_sleep_functions_on_battery.attr, + &dev_attr_usb_rapid_charge.attr, + &dev_attr_usb_sleep_music.attr, + &dev_attr_kbd_function_keys.attr, + &dev_attr_panel_power_on.attr, + &dev_attr_usb_three.attr, + NULL, +}; static umode_t toshiba_sysfs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) @@ -1574,7 +2345,9 @@ static umode_t toshiba_sysfs_is_visible(struct kobject *kobj, struct toshiba_acpi_dev *drv = dev_get_drvdata(dev); bool exists = true; - if (attr == &dev_attr_kbd_backlight_mode.attr) + if (attr == &dev_attr_fan.attr) + exists = (drv->fan_supported) ? true : false; + else if (attr == &dev_attr_kbd_backlight_mode.attr) exists = (drv->kbd_illum_supported) ? true : false; else if (attr == &dev_attr_kbd_backlight_timeout.attr) exists = (drv->kbd_mode == SCI_KBD_MODE_AUTO) ? true : false; @@ -1582,14 +2355,69 @@ static umode_t toshiba_sysfs_is_visible(struct kobject *kobj, exists = (drv->touchpad_supported) ? true : false; else if (attr == &dev_attr_position.attr) exists = (drv->accelerometer_supported) ? true : false; + else if (attr == &dev_attr_usb_sleep_charge.attr) + exists = (drv->usb_sleep_charge_supported) ? true : false; + else if (attr == &dev_attr_sleep_functions_on_battery.attr) + exists = (drv->usb_sleep_charge_supported) ? true : false; + else if (attr == &dev_attr_usb_rapid_charge.attr) + exists = (drv->usb_rapid_charge_supported) ? true : false; + else if (attr == &dev_attr_usb_sleep_music.attr) + exists = (drv->usb_sleep_music_supported) ? true : false; + else if (attr == &dev_attr_kbd_function_keys.attr) + exists = (drv->kbd_function_keys_supported) ? true : false; + else if (attr == &dev_attr_panel_power_on.attr) + exists = (drv->panel_power_on_supported) ? true : false; + else if (attr == &dev_attr_usb_three.attr) + exists = (drv->usb_three_supported) ? true : false; return exists ? attr->mode : 0; } +static struct attribute_group toshiba_attr_group = { + .is_visible = toshiba_sysfs_is_visible, + .attrs = toshiba_attributes, +}; + +/* + * Hotkeys + */ +static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) +{ + acpi_status status; + u32 result; + + status = acpi_evaluate_object(dev->acpi_dev->handle, + "ENAB", NULL, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); + if (result == TOS_FAILURE) + return -EIO; + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return 0; +} + +static void toshiba_acpi_enable_special_functions(struct toshiba_acpi_dev *dev) +{ + u32 result; + + /* + * Re-activate the hotkeys, but this time, we are using the + * "Special Functions" mode. + */ + result = hci_write1(dev, HCI_HOTKEY_EVENT, + HCI_HOTKEY_SPECIAL_FUNCTIONS); + if (result != TOS_SUCCESS) + pr_err("Could not enable the Special Function mode\n"); +} + static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { - if (str & 0x20) + if (str & I8042_STR_AUXDATA) return false; if (unlikely(data == 0xe0)) @@ -1640,7 +2468,7 @@ static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev, if (scancode == 0x100) return; - /* act on key press; ignore key release */ + /* Act on key press; ignore key release */ if (scancode & 0x80) return; @@ -1648,13 +2476,61 @@ static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev, pr_info("Unknown key %x\n", scancode); } +static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) +{ + u32 hci_result, value; + int retries = 3; + int scancode; + + if (dev->info_supported) { + scancode = toshiba_acpi_query_hotkey(dev); + if (scancode < 0) + pr_err("Failed to query hotkey event\n"); + else if (scancode != 0) + toshiba_acpi_report_hotkey(dev, scancode); + } else if (dev->system_event_supported) { + do { + hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); + switch (hci_result) { + case TOS_SUCCESS: + toshiba_acpi_report_hotkey(dev, (int)value); + break; + case TOS_NOT_SUPPORTED: + /* + * This is a workaround for an unresolved + * issue on some machines where system events + * sporadically become disabled. + */ + hci_result = + hci_write1(dev, HCI_SYSTEM_EVENT, 1); + pr_notice("Re-enabled hotkeys\n"); + /* Fall through */ + default: + retries--; + break; + } + } while (retries && hci_result != TOS_FIFO_EMPTY); + } +} + static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) { - acpi_status status; + const struct key_entry *keymap = toshiba_acpi_keymap; acpi_handle ec_handle; - int error; + u32 events_type; u32 hci_result; - const struct key_entry *keymap = toshiba_acpi_keymap; + int error; + + error = toshiba_acpi_enable_hotkeys(dev); + if (error) + return error; + + error = toshiba_hotkey_event_type_get(dev, &events_type); + if (error) { + pr_err("Unable to query Hotkey Event Type\n"); + return error; + } + dev->hotkey_event_type = events_type; dev->hotkey_dev = input_allocate_device(); if (!dev->hotkey_dev) @@ -1664,8 +2540,14 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) dev->hotkey_dev->phys = "toshiba_acpi/input0"; dev->hotkey_dev->id.bustype = BUS_HOST; - if (dmi_check_system(toshiba_alt_keymap_dmi)) + if (events_type == HCI_SYSTEM_TYPE1 || + !dev->kbd_function_keys_supported) + keymap = toshiba_acpi_keymap; + else if (events_type == HCI_SYSTEM_TYPE2 || + dev->kbd_function_keys_supported) keymap = toshiba_acpi_alt_keymap; + else + pr_info("Unknown event type received %x\n", events_type); error = sparse_keymap_setup(dev->hotkey_dev, keymap, NULL); if (error) goto err_free_dev; @@ -1677,7 +2559,6 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) * supported, so if it's present set up an i8042 key filter * for this purpose. */ - status = AE_ERROR; ec_handle = ec_get_handle(); if (ec_handle && acpi_has_method(ec_handle, "NTFY")) { INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work); @@ -1708,20 +2589,12 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) goto err_remove_filter; } - status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL); - if (ACPI_FAILURE(status)) { - pr_info("Unable to enable hotkeys\n"); - error = -ENODEV; - goto err_remove_filter; - } - error = input_register_device(dev->hotkey_dev); if (error) { pr_info("Unable to register input device\n"); goto err_remove_filter; } - hci_result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); return 0; err_remove_filter: @@ -1761,11 +2634,25 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) ret = get_tr_backlight_status(dev, &enabled); dev->tr_backlight_supported = !ret; + /* + * Tell acpi-video-detect code to prefer vendor backlight on all + * systems with transflective backlight and on dmi matched systems. + */ + if (dev->tr_backlight_supported || + dmi_check_system(toshiba_vendor_backlight_dmi)) + acpi_video_dmi_promote_vendor(); + + if (acpi_video_backlight_support()) + return 0; + + /* acpi-video may have loaded before we called dmi_promote_vendor() */ + acpi_video_unregister_backlight(); + memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; - /* adding an extra level and having 0 change to transflective mode */ + /* Adding an extra level and having 0 change to transflective mode */ if (dev->tr_backlight_supported) props.max_brightness++; @@ -1810,8 +2697,7 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) rfkill_destroy(dev->bt_rfk); } - if (dev->backlight_dev) - backlight_device_unregister(dev->backlight_dev); + backlight_device_unregister(dev->backlight_dev); if (dev->illumination_supported) led_classdev_unregister(&dev->led_dev); @@ -1845,6 +2731,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) { struct toshiba_acpi_dev *dev; const char *hci_method; + u32 special_functions; u32 dummy; bool bt_present; int ret = 0; @@ -1869,6 +2756,16 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) acpi_dev->driver_data = dev; dev_set_drvdata(&acpi_dev->dev, dev); + /* Query the BIOS for supported features */ + + /* + * The "Special Functions" are always supported by the laptops + * with the new keyboard layout, query for its presence to help + * determine the keymap layout to use. + */ + ret = toshiba_function_keys_get(dev, &special_functions); + dev->kbd_function_keys_supported = !ret; + if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); @@ -1937,7 +2834,19 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = toshiba_accelerometer_supported(dev); dev->accelerometer_supported = !ret; - /* Determine whether or not BIOS supports fan and video interfaces */ + toshiba_usb_sleep_charge_available(dev); + + ret = toshiba_usb_rapid_charge_get(dev, &dummy); + dev->usb_rapid_charge_supported = !ret; + + ret = toshiba_usb_sleep_music_get(dev, &dummy); + dev->usb_sleep_music_supported = !ret; + + ret = toshiba_panel_power_on_get(dev, &dummy); + dev->panel_power_on_supported = !ret; + + ret = toshiba_usb_three_get(dev, &dummy); + dev->usb_three_supported = !ret; ret = get_video_status(dev, &dummy); dev->video_supported = !ret; @@ -1945,6 +2854,13 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = get_fan_status(dev, &dummy); dev->fan_supported = !ret; + /* + * Enable the "Special Functions" mode only if they are + * supported and if they are activated. + */ + if (dev->kbd_function_keys_supported && special_functions) + toshiba_acpi_enable_special_functions(dev); + ret = sysfs_create_group(&dev->acpi_dev->dev.kobj, &toshiba_attr_group); if (ret) { @@ -1967,42 +2883,47 @@ error: static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) { struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); - u32 hci_result, value; - int retries = 3; - int scancode; - - if (event != 0x80) - return; + int ret; - if (dev->info_supported) { - scancode = toshiba_acpi_query_hotkey(dev); - if (scancode < 0) - pr_err("Failed to query hotkey event\n"); - else if (scancode != 0) - toshiba_acpi_report_hotkey(dev, scancode); - } else if (dev->system_event_supported) { - do { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); - switch (hci_result) { - case TOS_SUCCESS: - toshiba_acpi_report_hotkey(dev, (int)value); - break; - case TOS_NOT_SUPPORTED: - /* - * This is a workaround for an unresolved - * issue on some machines where system events - * sporadically become disabled. - */ - hci_result = - hci_write1(dev, HCI_SYSTEM_EVENT, 1); - pr_notice("Re-enabled hotkeys\n"); - /* fall through */ - default: - retries--; - break; - } - } while (retries && hci_result != TOS_FIFO_EMPTY); + switch (event) { + case 0x80: /* Hotkeys and some system events */ + toshiba_acpi_process_hotkeys(dev); + break; + case 0x81: /* Dock events */ + case 0x82: + case 0x83: + pr_info("Dock event received %x\n", event); + break; + case 0x88: /* Thermal events */ + pr_info("Thermal event received\n"); + break; + case 0x8f: /* LID closed */ + case 0x90: /* LID is closed and Dock has been ejected */ + break; + case 0x8c: /* SATA power events */ + case 0x8b: + pr_info("SATA power event received %x\n", event); + break; + case 0x92: /* Keyboard backlight mode changed */ + /* Update sysfs entries */ + ret = sysfs_update_group(&acpi_dev->dev.kobj, + &toshiba_attr_group); + if (ret) + pr_err("Unable to update sysfs entries\n"); + break; + case 0x85: /* Unknown */ + case 0x8d: /* Unknown */ + case 0x8e: /* Unknown */ + case 0x94: /* Unknown */ + case 0x95: /* Unknown */ + default: + pr_info("Unknown event received %x\n", event); + break; } + + acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, + dev_name(&acpi_dev->dev), + event, 0); } #ifdef CONFIG_PM_SLEEP @@ -2020,16 +2941,12 @@ static int toshiba_acpi_suspend(struct device *device) static int toshiba_acpi_resume(struct device *device) { struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); - u32 result; - acpi_status status; + int error; if (dev->hotkey_dev) { - status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", - NULL, NULL); - if (ACPI_FAILURE(status)) + error = toshiba_acpi_enable_hotkeys(dev); + if (error) pr_info("Unable to re-enable hotkeys\n"); - - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); } return 0; diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index 2cb1ea62b4a7..249800763362 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -2,6 +2,7 @@ * Toshiba Bluetooth Enable Driver * * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com> + * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com> * * Thanks to Matthew Garrett for background info on ACPI innards which * normal people aren't meant to understand :-) @@ -25,6 +26,10 @@ #include <linux/types.h> #include <linux/acpi.h> +#define BT_KILLSWITCH_MASK 0x01 +#define BT_PLUGGED_MASK 0x40 +#define BT_POWER_MASK 0x80 + MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); MODULE_LICENSE("GPL"); @@ -57,32 +62,107 @@ static struct acpi_driver toshiba_bt_rfkill_driver = { .drv.pm = &toshiba_bt_pm, }; +static int toshiba_bluetooth_present(acpi_handle handle) +{ + acpi_status result; + u64 bt_present; + + /* + * Some Toshiba laptops may have a fake TOS6205 device in + * their ACPI BIOS, so query the _STA method to see if there + * is really anything there. + */ + result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present); + if (ACPI_FAILURE(result)) { + pr_err("ACPI call to query Bluetooth presence failed"); + return -ENXIO; + } else if (!bt_present) { + pr_info("Bluetooth device not present\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_bluetooth_status(acpi_handle handle) +{ + acpi_status result; + u64 status; + + result = acpi_evaluate_integer(handle, "BTST", NULL, &status); + if (ACPI_FAILURE(result)) { + pr_err("Could not get Bluetooth device status\n"); + return -ENXIO; + } + + pr_info("Bluetooth status %llu\n", status); + + return status; +} static int toshiba_bluetooth_enable(acpi_handle handle) { - acpi_status res1, res2; - u64 result; + acpi_status result; + bool killswitch; + bool powered; + bool plugged; + int status; /* * Query ACPI to verify RFKill switch is set to 'on'. * If not, we return silently, no need to report it as * an error. */ - res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result); - if (ACPI_FAILURE(res1)) - return res1; - if (!(result & 0x01)) - return 0; + status = toshiba_bluetooth_status(handle); + if (status < 0) + return status; + + killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; + powered = (status & BT_POWER_MASK) ? true : false; + plugged = (status & BT_PLUGGED_MASK) ? true : false; - pr_info("Re-enabling Toshiba Bluetooth\n"); - res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL); - res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL); - if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2)) + if (!killswitch) return 0; + /* + * This check ensures to only enable the device if it is powered + * off or detached, as some recent devices somehow pass the killswitch + * test, causing a loop enabling/disabling the device, see bug 93911. + */ + if (powered || plugged) + return 0; + + result = acpi_evaluate_object(handle, "AUSB", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not attach USB Bluetooth device\n"); + return -ENXIO; + } + + result = acpi_evaluate_object(handle, "BTPO", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not power ON Bluetooth device\n"); + return -ENXIO; + } + + return 0; +} + +static int toshiba_bluetooth_disable(acpi_handle handle) +{ + acpi_status result; + + result = acpi_evaluate_object(handle, "BTPF", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not power OFF Bluetooth device\n"); + return -ENXIO; + } - pr_warn("Failed to re-enable Toshiba Bluetooth\n"); + result = acpi_evaluate_object(handle, "DUSB", NULL, NULL); + if (ACPI_FAILURE(result)) { + pr_err("Could not detach USB Bluetooth device\n"); + return -ENXIO; + } - return -ENODEV; + return 0; } static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) @@ -99,23 +179,18 @@ static int toshiba_bt_resume(struct device *dev) static int toshiba_bt_rfkill_add(struct acpi_device *device) { - acpi_status status; - u64 bt_present; - int result = -ENODEV; + int result; - /* - * Some Toshiba laptops may have a fake TOS6205 device in - * their ACPI BIOS, so query the _STA method to see if there - * is really anything there, before trying to enable it. - */ - status = acpi_evaluate_integer(device->handle, "_STA", NULL, - &bt_present); + result = toshiba_bluetooth_present(device->handle); + if (result) + return result; - if (!ACPI_FAILURE(status) && bt_present) { - pr_info("Detected Toshiba ACPI Bluetooth device - " - "installing RFKill handler\n"); - result = toshiba_bluetooth_enable(device->handle); - } + pr_info("Toshiba ACPI Bluetooth device driver\n"); + + /* Enable the BT device */ + result = toshiba_bluetooth_enable(device->handle); + if (result) + return result; return result; } @@ -123,7 +198,7 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) static int toshiba_bt_rfkill_remove(struct acpi_device *device) { /* clean up */ - return 0; + return toshiba_bluetooth_disable(device->handle); } module_acpi_driver(toshiba_bt_rfkill_driver); diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 737e56d46f61..aac47573f9ed 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -45,7 +45,6 @@ MODULE_LICENSE("GPL"); #define ACPI_WMI_CLASS "wmi" -static DEFINE_MUTEX(wmi_data_lock); static LIST_HEAD(wmi_block_list); struct guid_block { @@ -240,10 +239,10 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) if (memcmp(block->guid, guid_input, 16) == 0) { if (out) *out = wblock; - return 1; + return true; } } - return 0; + return false; } static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c index 4bd17248dfc6..e46fa9cebc7d 100644 --- a/drivers/platform/x86/xo1-rfkill.c +++ b/drivers/platform/x86/xo1-rfkill.c @@ -71,7 +71,6 @@ static int xo1_rfkill_remove(struct platform_device *pdev) static struct platform_driver xo1_rfkill_driver = { .driver = { .name = "xo1-rfkill", - .owner = THIS_MODULE, }, .probe = xo1_rfkill_probe, .remove = xo1_rfkill_remove, |