summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
authorDmitry Torokhov <dmitry.torokhov@gmail.com>2015-09-03 10:01:44 -0700
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2015-09-03 10:01:44 -0700
commit01b944fe1cd4e21a2a9ed51adbdbafe2d5e905ba (patch)
tree85f506cf9fce3d8fd47cf624dd8698472bffc13f /drivers/platform
parente51e38494a8ecc18650efb0c840600637891de2c (diff)
parent58f1eae48e5372200553de07b5ecc7574803ee91 (diff)
Merge branch 'next' into for-linus
Prepare first round of input updates for 4.3 merge window.
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/Kconfig3
-rw-r--r--drivers/platform/Makefile1
-rw-r--r--drivers/platform/chrome/Kconfig31
-rw-r--r--drivers/platform/chrome/Makefile4
-rw-r--r--drivers/platform/chrome/chromeos_laptop.c39
-rw-r--r--drivers/platform/chrome/cros_ec_dev.c345
-rw-r--r--drivers/platform/chrome/cros_ec_dev.h46
-rw-r--r--drivers/platform/chrome/cros_ec_lightbar.c428
-rw-r--r--drivers/platform/chrome/cros_ec_lpc.c392
-rw-r--r--drivers/platform/chrome/cros_ec_proto.c382
-rw-r--r--drivers/platform/chrome/cros_ec_sysfs.c297
-rw-r--r--drivers/platform/goldfish/goldfish_pipe.c20
-rw-r--r--drivers/platform/goldfish/pdev_bus.c12
-rw-r--r--drivers/platform/mips/Kconfig30
-rw-r--r--drivers/platform/mips/Makefile2
-rw-r--r--drivers/platform/mips/acpi_init.c150
-rw-r--r--drivers/platform/mips/cpu_hwmon.c207
-rw-r--r--drivers/platform/x86/Kconfig51
-rw-r--r--drivers/platform/x86/Makefile2
-rw-r--r--drivers/platform/x86/acer-wmi.c10
-rw-r--r--drivers/platform/x86/acerhdf.c3
-rw-r--r--drivers/platform/x86/apple-gmux.c66
-rw-r--r--drivers/platform/x86/asus-laptop.c6
-rw-r--r--drivers/platform/x86/asus-wmi.c359
-rw-r--r--drivers/platform/x86/compal-laptop.c39
-rw-r--r--drivers/platform/x86/dell-laptop.c1541
-rw-r--r--drivers/platform/x86/dell-rbtn.c423
-rw-r--r--drivers/platform/x86/dell-rbtn.h24
-rw-r--r--drivers/platform/x86/dell-wmi.c3
-rw-r--r--drivers/platform/x86/eeepc-laptop.c5
-rw-r--r--drivers/platform/x86/fujitsu-laptop.c6
-rw-r--r--drivers/platform/x86/ideapad-laptop.c20
-rw-r--r--drivers/platform/x86/intel_oaktrail.c9
-rw-r--r--drivers/platform/x86/intel_pmc_ipc.c780
-rw-r--r--drivers/platform/x86/intel_scu_ipc.c6
-rw-r--r--drivers/platform/x86/msi-laptop.c6
-rw-r--r--drivers/platform/x86/msi-wmi.c4
-rw-r--r--drivers/platform/x86/pvpanic.c10
-rw-r--r--drivers/platform/x86/samsung-laptop.c27
-rw-r--r--drivers/platform/x86/sony-laptop.c7
-rw-r--r--drivers/platform/x86/tc1100-wmi.c2
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c348
-rw-r--r--drivers/platform/x86/toshiba_acpi.c463
-rw-r--r--drivers/platform/x86/toshiba_bluetooth.c251
-rw-r--r--drivers/platform/x86/toshiba_haps.c32
-rw-r--r--drivers/platform/x86/wmi.c5
46 files changed, 6209 insertions, 688 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..cb1329919527 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,30 @@ 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 CROS_EC_PROTO
+ ---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 && CROS_EC_PROTO && (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.
+
+config CROS_EC_PROTO
+ bool
+ help
+ ChromeOS EC communication protocol helpers.
endif # CHROMEOS_PLATFORMS
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index 2b860ca7450f..4a11b010f5d8 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -1,3 +1,7 @@
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
+obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o
diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c
index b84fdd6b629b..02072749fff3 100644
--- a/drivers/platform/chrome/chromeos_laptop.c
+++ b/drivers/platform/chrome/chromeos_laptop.c
@@ -23,7 +23,7 @@
#include <linux/dmi.h>
#include <linux/i2c.h>
-#include <linux/i2c/atmel_mxt_ts.h>
+#include <linux/platform_data/atmel_mxt_ts.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@@ -111,6 +111,7 @@ static struct mxt_platform_data atmel_224s_tp_platform_data = {
.irqflags = IRQF_TRIGGER_FALLING,
.t19_num_keys = ARRAY_SIZE(mxt_t19_keys),
.t19_keymap = mxt_t19_keys,
+ .suspend_mode = MXT_SUSPEND_T9_CTRL,
};
static struct i2c_board_info atmel_224s_tp_device = {
@@ -121,6 +122,7 @@ static struct i2c_board_info atmel_224s_tp_device = {
static struct mxt_platform_data atmel_1664s_platform_data = {
.irqflags = IRQF_TRIGGER_FALLING,
+ .suspend_mode = MXT_SUSPEND_T9_CTRL,
};
static struct i2c_board_info atmel_1664s_device = {
@@ -133,12 +135,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 +172,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 +277,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 +296,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 +309,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;
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c
new file mode 100644
index 000000000000..e8fcdc237029
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_dev.c
@@ -0,0 +1,345 @@
+/*
+ * 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/slab.h>
+#include <linux/uaccess.h>
+
+#include "cros_ec_dev.h"
+
+/* Device variables */
+#define CROS_MAX_DEV 128
+static int ec_major;
+
+static const struct attribute_group *cros_ec_groups[] = {
+ &cros_ec_attr_group,
+ &cros_ec_lightbar_attr_group,
+ NULL,
+};
+
+static struct class cros_class = {
+ .owner = THIS_MODULE,
+ .name = "chromeos",
+ .dev_groups = cros_ec_groups,
+};
+
+/* Basic communication */
+static int ec_get_version(struct cros_ec_dev *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;
+ int ret;
+
+ msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->version = 0;
+ msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
+ msg->insize = sizeof(*resp);
+ msg->outsize = 0;
+
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0)
+ goto exit;
+
+ if (msg->result != EC_RES_SUCCESS) {
+ snprintf(str, maxlen,
+ "%s\nUnknown EC version: EC returned %d\n",
+ CROS_EC_DEV_VERSION, msg->result);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ resp = (struct ec_response_get_version *)msg->data;
+ 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]);
+
+ ret = 0;
+exit:
+ kfree(msg);
+ return ret;
+}
+
+/* Device file ops */
+static int ec_device_open(struct inode *inode, struct file *filp)
+{
+ struct cros_ec_dev *ec = container_of(inode->i_cdev,
+ struct cros_ec_dev, cdev);
+ filp->private_data = ec;
+ nonseekable_open(inode, filp);
+ 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_dev *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_dev *ec, void __user *arg)
+{
+ long ret;
+ struct cros_ec_command u_cmd;
+ struct cros_ec_command *s_cmd;
+
+ if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
+ return -EFAULT;
+
+ s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize),
+ GFP_KERNEL);
+ if (!s_cmd)
+ return -ENOMEM;
+
+ if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ s_cmd->command += ec->cmd_offset;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd);
+ /* Only copy data to userland if data was received. */
+ if (ret < 0)
+ goto exit;
+
+ if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + u_cmd.insize))
+ ret = -EFAULT;
+exit:
+ kfree(s_cmd);
+ return ret;
+}
+
+static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg)
+{
+ struct cros_ec_device *ec_dev = ec->ec_dev;
+ struct cros_ec_readmem s_mem = { };
+ long num;
+
+ /* Not every platform supports direct reads */
+ if (!ec_dev->cmd_readmem)
+ return -ENOTTY;
+
+ if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
+ return -EFAULT;
+
+ num = ec_dev->cmd_readmem(ec_dev, 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_dev *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 void __remove(struct device *dev)
+{
+ struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev,
+ class_dev);
+ kfree(ec);
+}
+
+static int ec_device_probe(struct platform_device *pdev)
+{
+ int retval = -ENOMEM;
+ struct device *dev = &pdev->dev;
+ struct cros_ec_platform *ec_platform = dev_get_platdata(dev);
+ dev_t devno = MKDEV(ec_major, pdev->id);
+ struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+
+ if (!ec)
+ return retval;
+
+ dev_set_drvdata(dev, ec);
+ ec->ec_dev = dev_get_drvdata(dev->parent);
+ ec->dev = dev;
+ ec->cmd_offset = ec_platform->cmd_offset;
+ device_initialize(&ec->class_dev);
+ cdev_init(&ec->cdev, &fops);
+
+ /*
+ * Add the character device
+ * Link cdev to the class device to be sure device is not used
+ * before unbinding it.
+ */
+ ec->cdev.kobj.parent = &ec->class_dev.kobj;
+ retval = cdev_add(&ec->cdev, devno, 1);
+ if (retval) {
+ dev_err(dev, ": failed to add character device\n");
+ goto cdev_add_failed;
+ }
+
+ /*
+ * Add the class device
+ * Link to the character device for creating the /dev entry
+ * in devtmpfs.
+ */
+ ec->class_dev.devt = ec->cdev.dev;
+ ec->class_dev.class = &cros_class;
+ ec->class_dev.parent = dev;
+ ec->class_dev.release = __remove;
+
+ retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name);
+ if (retval) {
+ dev_err(dev, "dev_set_name failed => %d\n", retval);
+ goto set_named_failed;
+ }
+
+ retval = device_add(&ec->class_dev);
+ if (retval) {
+ dev_err(dev, "device_register failed => %d\n", retval);
+ goto dev_reg_failed;
+ }
+
+ return 0;
+
+dev_reg_failed:
+set_named_failed:
+ dev_set_drvdata(dev, NULL);
+ cdev_del(&ec->cdev);
+cdev_add_failed:
+ kfree(ec);
+ return retval;
+}
+
+static int ec_device_remove(struct platform_device *pdev)
+{
+ struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
+ cdev_del(&ec->cdev);
+ device_unregister(&ec->class_dev);
+ 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;
+
+ ret = class_register(&cros_class);
+ if (ret) {
+ pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
+ return ret;
+ }
+
+ /* 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_unregister(&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_unregister(&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..bfd2c84c3571
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_dev.h
@@ -0,0 +1,46 @@
+/*
+ * 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_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)
+
+#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..144e09df9b84
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_lightbar.c
@@ -0,0 +1,428 @@
+/*
+ * 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 <linux/slab.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;
+}
+
+static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
+{
+ struct cros_ec_command *msg;
+ int len;
+
+ len = max(sizeof(struct ec_params_lightbar),
+ sizeof(struct ec_response_lightbar));
+
+ msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
+ if (!msg)
+ return NULL;
+
+ msg->version = 0;
+ msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset;
+ msg->outsize = sizeof(struct ec_params_lightbar);
+ msg->insize = sizeof(struct ec_response_lightbar);
+
+ return msg;
+}
+
+static int get_lightbar_version(struct cros_ec_dev *ec,
+ uint32_t *ver_ptr, uint32_t *flg_ptr)
+{
+ struct ec_params_lightbar *param;
+ struct ec_response_lightbar *resp;
+ struct cros_ec_command *msg;
+ int ret;
+
+ msg = alloc_lightbar_cmd_msg(ec);
+ if (!msg)
+ return 0;
+
+ param = (struct ec_params_lightbar *)msg->data;
+ param->cmd = LIGHTBAR_CMD_VERSION;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0) {
+ ret = 0;
+ goto exit;
+ }
+
+ 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;
+ ret = 1;
+ goto exit;
+
+ case EC_RES_SUCCESS:
+ resp = (struct ec_response_lightbar *)msg->data;
+
+ /* Future devices w/lightbars should implement this command */
+ if (ver_ptr)
+ *ver_ptr = resp->version.num;
+ if (flg_ptr)
+ *flg_ptr = resp->version.flags;
+ ret = 1;
+ goto exit;
+ }
+
+ /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
+ ret = 0;
+exit:
+ kfree(msg);
+ return ret;
+}
+
+static ssize_t version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ uint32_t version = 0, flags = 0;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_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 cros_ec_command *msg;
+ int ret;
+ unsigned int val;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ if (kstrtouint(buf, 0, &val))
+ return -EINVAL;
+
+ msg = alloc_lightbar_cmd_msg(ec);
+ if (!msg)
+ return -ENOMEM;
+
+ param = (struct ec_params_lightbar *)msg->data;
+ param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS;
+ param->set_brightness.num = val;
+ ret = lb_throttle();
+ if (ret)
+ goto exit;
+
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0)
+ goto exit;
+
+ if (msg->result != EC_RES_SUCCESS) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ ret = count;
+exit:
+ kfree(msg);
+ return ret;
+}
+
+
+/*
+ * 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 cros_ec_command *msg;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+ unsigned int val[4];
+ int ret, i = 0, j = 0, ok = 0;
+
+ msg = alloc_lightbar_cmd_msg(ec);
+ if (!msg)
+ return -ENOMEM;
+
+ 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->data;
+ param->cmd = LIGHTBAR_CMD_SET_RGB;
+ param->set_rgb.led = val[0];
+ param->set_rgb.red = val[1];
+ param->set_rgb.green = val[2];
+ param->set_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->ec_dev, msg);
+ if (ret < 0)
+ goto exit;
+
+ if (msg->result != EC_RES_SUCCESS) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ i = 0;
+ ok = 1;
+ }
+
+ /* Skip over the number we just read */
+ while (*buf && !isspace(*buf))
+ buf++;
+
+ } while (*buf);
+
+exit:
+ kfree(msg);
+ 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;
+ int ret;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ msg = alloc_lightbar_cmd_msg(ec);
+ if (!msg)
+ return -ENOMEM;
+
+ param = (struct ec_params_lightbar *)msg->data;
+ param->cmd = LIGHTBAR_CMD_GET_SEQ;
+ ret = lb_throttle();
+ if (ret)
+ goto exit;
+
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0)
+ goto exit;
+
+ if (msg->result != EC_RES_SUCCESS) {
+ ret = scnprintf(buf, PAGE_SIZE,
+ "ERROR: EC returned %d\n", msg->result);
+ goto exit;
+ }
+
+ resp = (struct ec_response_lightbar *)msg->data;
+ if (resp->get_seq.num >= ARRAY_SIZE(seqname))
+ ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
+ else
+ ret = scnprintf(buf, PAGE_SIZE, "%s\n",
+ seqname[resp->get_seq.num]);
+
+exit:
+ kfree(msg);
+ return ret;
+}
+
+static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ec_params_lightbar *param;
+ struct cros_ec_command *msg;
+ unsigned int num;
+ int ret, len;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ msg = alloc_lightbar_cmd_msg(ec);
+ if (!msg)
+ return -ENOMEM;
+
+ 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->data;
+ param->cmd = LIGHTBAR_CMD_SEQ;
+ param->seq.num = num;
+ ret = lb_throttle();
+ if (ret)
+ return ret;
+
+ ret = cros_ec_cmd_xfer(ec->ec_dev, 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 umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj,
+ struct attribute *a, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+ struct platform_device *pdev = container_of(ec->dev,
+ struct platform_device, dev);
+ if (pdev->id != 0)
+ return 0;
+
+ /* Only instantiate this stuff if the EC has a lightbar */
+ if (get_lightbar_version(ec, NULL, NULL))
+ return a->mode;
+ else
+ return 0;
+}
+
+struct attribute_group cros_ec_lightbar_attr_group = {
+ .name = "lightbar",
+ .attrs = __lb_cmds_attrs,
+ .is_visible = cros_ec_lightbar_attrs_are_visible,
+};
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
new file mode 100644
index 000000000000..bdd77ce45f05
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_lpc.c
@@ -0,0 +1,392 @@
+/*
+ * 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_pkt_xfer_lpc(struct cros_ec_device *ec,
+ struct cros_ec_command *msg)
+{
+ struct ec_host_request *request;
+ struct ec_host_response response;
+ u8 sum = 0;
+ int i;
+ int ret = 0;
+ u8 *dout;
+
+ ret = cros_ec_prepare_tx(ec, msg);
+
+ /* Write buffer */
+ for (i = 0; i < ret; i++)
+ outb(ec->dout[i], EC_LPC_ADDR_HOST_PACKET + i);
+
+ request = (struct ec_host_request *)ec->dout;
+
+ /* Here we go */
+ outb(EC_COMMAND_PROTOCOL_3, 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);
+ ret = cros_ec_check_result(ec, msg);
+ if (ret)
+ goto done;
+
+ /* Read back response */
+ dout = (u8 *)&response;
+ for (i = 0; i < sizeof(response); i++) {
+ dout[i] = inb(EC_LPC_ADDR_HOST_PACKET + i);
+ sum += dout[i];
+ }
+
+ msg->result = response.result;
+
+ if (response.data_len > msg->insize) {
+ dev_err(ec->dev,
+ "packet too long (%d bytes, expected %d)",
+ response.data_len, msg->insize);
+ ret = -EMSGSIZE;
+ goto done;
+ }
+
+ /* Read response and process checksum */
+ for (i = 0; i < response.data_len; i++) {
+ msg->data[i] =
+ inb(EC_LPC_ADDR_HOST_PACKET + sizeof(response) + i);
+ sum += msg->data[i];
+ }
+
+ if (sum) {
+ dev_err(ec->dev,
+ "bad packet checksum %02x\n",
+ response.checksum);
+ ret = -EBADMSG;
+ goto done;
+ }
+
+ /* Return actual amount of data received */
+ ret = response.data_len;
+done:
+ return ret;
+}
+
+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->data[i], EC_LPC_ADDR_HOST_PARAM + i);
+ csum += msg->data[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->data[i] = inb(EC_LPC_ADDR_HOST_PARAM + i);
+ csum += msg->data[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->phys_name = dev_name(dev);
+ ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc;
+ ec_dev->pkt_xfer = cros_ec_pkt_xfer_lpc;
+ ec_dev->cmd_readmem = cros_ec_lpc_readmem;
+ ec_dev->din_size = sizeof(struct ec_host_response) +
+ sizeof(struct ec_response_get_protocol_info);
+ ec_dev->dout_size = sizeof(struct ec_host_request);
+
+ 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_proto.c b/drivers/platform/chrome/cros_ec_proto.c
new file mode 100644
index 000000000000..990308ca384f
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_proto.c
@@ -0,0 +1,382 @@
+/*
+ * ChromeOS EC communication protocol helper functions
+ *
+ * Copyright (C) 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.
+ *
+ */
+
+#include <linux/mfd/cros_ec.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define EC_COMMAND_RETRIES 50
+
+static int prepare_packet(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ struct ec_host_request *request;
+ u8 *out;
+ int i;
+ u8 csum = 0;
+
+ BUG_ON(ec_dev->proto_version != EC_HOST_REQUEST_VERSION);
+ BUG_ON(msg->outsize + sizeof(*request) > ec_dev->dout_size);
+
+ out = ec_dev->dout;
+ request = (struct ec_host_request *)out;
+ request->struct_version = EC_HOST_REQUEST_VERSION;
+ request->checksum = 0;
+ request->command = msg->command;
+ request->command_version = msg->version;
+ request->reserved = 0;
+ request->data_len = msg->outsize;
+
+ for (i = 0; i < sizeof(*request); i++)
+ csum += out[i];
+
+ /* Copy data and update checksum */
+ memcpy(out + sizeof(*request), msg->data, msg->outsize);
+ for (i = 0; i < msg->outsize; i++)
+ csum += msg->data[i];
+
+ request->checksum = -csum;
+
+ return sizeof(*request) + msg->outsize;
+}
+
+static int send_command(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ int ret;
+
+ if (ec_dev->proto_version > 2)
+ ret = ec_dev->pkt_xfer(ec_dev, msg);
+ else
+ ret = ec_dev->cmd_xfer(ec_dev, msg);
+
+ if (msg->result == EC_RES_IN_PROGRESS) {
+ int i;
+ struct cros_ec_command *status_msg;
+ struct ec_response_get_comms_status *status;
+
+ status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status),
+ GFP_KERNEL);
+ if (!status_msg)
+ return -ENOMEM;
+
+ status_msg->version = 0;
+ status_msg->command = EC_CMD_GET_COMMS_STATUS;
+ status_msg->insize = sizeof(*status);
+ status_msg->outsize = 0;
+
+ /*
+ * Query the EC's status until it's no longer busy or
+ * we encounter an error.
+ */
+ for (i = 0; i < EC_COMMAND_RETRIES; i++) {
+ usleep_range(10000, 11000);
+
+ ret = ec_dev->cmd_xfer(ec_dev, status_msg);
+ if (ret < 0)
+ break;
+
+ msg->result = status_msg->result;
+ if (status_msg->result != EC_RES_SUCCESS)
+ break;
+
+ status = (struct ec_response_get_comms_status *)
+ status_msg->data;
+ if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
+ break;
+ }
+
+ kfree(status_msg);
+ }
+
+ return ret;
+}
+
+int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ u8 *out;
+ u8 csum;
+ int i;
+
+ if (ec_dev->proto_version > 2)
+ return prepare_packet(ec_dev, msg);
+
+ BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE);
+ out = ec_dev->dout;
+ out[0] = EC_CMD_VERSION0 + msg->version;
+ out[1] = msg->command;
+ out[2] = msg->outsize;
+ csum = out[0] + out[1] + out[2];
+ for (i = 0; i < msg->outsize; i++)
+ csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i];
+ out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum;
+
+ return EC_MSG_TX_PROTO_BYTES + msg->outsize;
+}
+EXPORT_SYMBOL(cros_ec_prepare_tx);
+
+int cros_ec_check_result(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ switch (msg->result) {
+ case EC_RES_SUCCESS:
+ return 0;
+ case EC_RES_IN_PROGRESS:
+ dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
+ msg->command);
+ return -EAGAIN;
+ default:
+ dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
+ msg->command, msg->result);
+ return 0;
+ }
+}
+EXPORT_SYMBOL(cros_ec_check_result);
+
+static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev,
+ int devidx,
+ struct cros_ec_command *msg)
+{
+ /*
+ * Try using v3+ to query for supported protocols. If this
+ * command fails, fall back to v2. Returns the highest protocol
+ * supported by the EC.
+ * Also sets the max request/response/passthru size.
+ */
+ int ret;
+
+ if (!ec_dev->pkt_xfer)
+ return -EPROTONOSUPPORT;
+
+ memset(msg, 0, sizeof(*msg));
+ msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO;
+ msg->insize = sizeof(struct ec_response_get_protocol_info);
+
+ ret = send_command(ec_dev, msg);
+
+ if (ret < 0) {
+ dev_dbg(ec_dev->dev,
+ "failed to check for EC[%d] protocol version: %d\n",
+ devidx, ret);
+ return ret;
+ }
+
+ if (devidx > 0 && msg->result == EC_RES_INVALID_COMMAND)
+ return -ENODEV;
+ else if (msg->result != EC_RES_SUCCESS)
+ return msg->result;
+
+ return 0;
+}
+
+static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev)
+{
+ struct cros_ec_command *msg;
+ struct ec_params_hello *hello_params;
+ struct ec_response_hello *hello_response;
+ int ret;
+ int len = max(sizeof(*hello_params), sizeof(*hello_response));
+
+ msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->version = 0;
+ msg->command = EC_CMD_HELLO;
+ hello_params = (struct ec_params_hello *)msg->data;
+ msg->outsize = sizeof(*hello_params);
+ hello_response = (struct ec_response_hello *)msg->data;
+ msg->insize = sizeof(*hello_response);
+
+ hello_params->in_data = 0xa0b0c0d0;
+
+ ret = send_command(ec_dev, msg);
+
+ if (ret < 0) {
+ dev_dbg(ec_dev->dev,
+ "EC failed to respond to v2 hello: %d\n",
+ ret);
+ goto exit;
+ } else if (msg->result != EC_RES_SUCCESS) {
+ dev_err(ec_dev->dev,
+ "EC responded to v2 hello with error: %d\n",
+ msg->result);
+ ret = msg->result;
+ goto exit;
+ } else if (hello_response->out_data != 0xa1b2c3d4) {
+ dev_err(ec_dev->dev,
+ "EC responded to v2 hello with bad result: %u\n",
+ hello_response->out_data);
+ ret = -EBADMSG;
+ goto exit;
+ }
+
+ ret = 0;
+
+ exit:
+ kfree(msg);
+ return ret;
+}
+
+int cros_ec_query_all(struct cros_ec_device *ec_dev)
+{
+ struct device *dev = ec_dev->dev;
+ struct cros_ec_command *proto_msg;
+ struct ec_response_get_protocol_info *proto_info;
+ int ret;
+
+ proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info),
+ GFP_KERNEL);
+ if (!proto_msg)
+ return -ENOMEM;
+
+ /* First try sending with proto v3. */
+ ec_dev->proto_version = 3;
+ ret = cros_ec_host_command_proto_query(ec_dev, 0, proto_msg);
+
+ if (ret == 0) {
+ proto_info = (struct ec_response_get_protocol_info *)
+ proto_msg->data;
+ ec_dev->max_request = proto_info->max_request_packet_size -
+ sizeof(struct ec_host_request);
+ ec_dev->max_response = proto_info->max_response_packet_size -
+ sizeof(struct ec_host_response);
+ ec_dev->proto_version =
+ min(EC_HOST_REQUEST_VERSION,
+ fls(proto_info->protocol_versions) - 1);
+ dev_dbg(ec_dev->dev,
+ "using proto v%u\n",
+ ec_dev->proto_version);
+
+ ec_dev->din_size = ec_dev->max_response +
+ sizeof(struct ec_host_response) +
+ EC_MAX_RESPONSE_OVERHEAD;
+ ec_dev->dout_size = ec_dev->max_request +
+ sizeof(struct ec_host_request) +
+ EC_MAX_REQUEST_OVERHEAD;
+
+ /*
+ * Check for PD
+ */
+ ret = cros_ec_host_command_proto_query(ec_dev, 1, proto_msg);
+
+ if (ret) {
+ dev_dbg(ec_dev->dev, "no PD chip found: %d\n", ret);
+ ec_dev->max_passthru = 0;
+ } else {
+ dev_dbg(ec_dev->dev, "found PD chip\n");
+ ec_dev->max_passthru =
+ proto_info->max_request_packet_size -
+ sizeof(struct ec_host_request);
+ }
+ } else {
+ /* Try querying with a v2 hello message. */
+ ec_dev->proto_version = 2;
+ ret = cros_ec_host_command_proto_query_v2(ec_dev);
+
+ if (ret == 0) {
+ /* V2 hello succeeded. */
+ dev_dbg(ec_dev->dev, "falling back to proto v2\n");
+
+ ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE;
+ ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE;
+ ec_dev->max_passthru = 0;
+ ec_dev->pkt_xfer = NULL;
+ ec_dev->din_size = EC_MSG_BYTES;
+ ec_dev->dout_size = EC_MSG_BYTES;
+ } else {
+ /*
+ * It's possible for a test to occur too early when
+ * the EC isn't listening. If this happens, we'll
+ * test later when the first command is run.
+ */
+ ec_dev->proto_version = EC_PROTO_VERSION_UNKNOWN;
+ dev_dbg(ec_dev->dev, "EC query failed: %d\n", ret);
+ goto exit;
+ }
+ }
+
+ devm_kfree(dev, ec_dev->din);
+ devm_kfree(dev, ec_dev->dout);
+
+ ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
+ if (!ec_dev->din) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
+ if (!ec_dev->dout) {
+ devm_kfree(dev, ec_dev->din);
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+exit:
+ kfree(proto_msg);
+ return ret;
+}
+EXPORT_SYMBOL(cros_ec_query_all);
+
+int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ int ret;
+
+ mutex_lock(&ec_dev->lock);
+ if (ec_dev->proto_version == EC_PROTO_VERSION_UNKNOWN) {
+ ret = cros_ec_query_all(ec_dev);
+ if (ret) {
+ dev_err(ec_dev->dev,
+ "EC version unknown and query failed; aborting command\n");
+ mutex_unlock(&ec_dev->lock);
+ return ret;
+ }
+ }
+
+ if (msg->insize > ec_dev->max_response) {
+ dev_dbg(ec_dev->dev, "clamping message receive buffer\n");
+ msg->insize = ec_dev->max_response;
+ }
+
+ if (msg->command < EC_CMD_PASSTHRU_OFFSET(1)) {
+ if (msg->outsize > ec_dev->max_request) {
+ dev_err(ec_dev->dev,
+ "request of size %u is too big (max: %u)\n",
+ msg->outsize,
+ ec_dev->max_request);
+ mutex_unlock(&ec_dev->lock);
+ return -EMSGSIZE;
+ }
+ } else {
+ if (msg->outsize > ec_dev->max_passthru) {
+ dev_err(ec_dev->dev,
+ "passthru rq of size %u is too big (max: %u)\n",
+ msg->outsize,
+ ec_dev->max_passthru);
+ mutex_unlock(&ec_dev->lock);
+ return -EMSGSIZE;
+ }
+ }
+ ret = send_command(ec_dev, msg);
+ mutex_unlock(&ec_dev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cros_ec_cmd_xfer);
diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c
new file mode 100644
index 000000000000..f3baf9973989
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_sysfs.c
@@ -0,0 +1,297 @@
+/*
+ * 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/slab.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;
+ struct ec_params_reboot_ec *param;
+ int got_cmd = 0, offset = 0;
+ int i;
+ int ret;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ param = (struct ec_params_reboot_ec *)msg->data;
+
+ 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) {
+ count = -EINVAL;
+ goto exit;
+ }
+
+ msg->version = 0;
+ msg->command = EC_CMD_REBOOT_EC + ec->cmd_offset;
+ msg->outsize = sizeof(*param);
+ msg->insize = 0;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0) {
+ count = ret;
+ goto exit;
+ }
+ if (msg->result != EC_RES_SUCCESS) {
+ dev_dbg(ec->dev, "EC result %d\n", msg->result);
+ count = -EINVAL;
+ }
+exit:
+ kfree(msg);
+ 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;
+ int ret;
+ int count = 0;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ /* Get versions. RW may change. */
+ msg->version = 0;
+ msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
+ msg->insize = sizeof(*r_ver);
+ msg->outsize = 0;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0) {
+ count = ret;
+ goto exit;
+ }
+ if (msg->result != EC_RES_SUCCESS) {
+ count = scnprintf(buf, PAGE_SIZE,
+ "ERROR: EC returned %d\n", msg->result);
+ goto exit;
+ }
+
+ r_ver = (struct ec_response_get_version *)msg->data;
+ /* 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 + ec->cmd_offset;
+ msg->insize = EC_HOST_PARAM_SIZE;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, 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->data[sizeof(msg->data) - 1] = '\0';
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "Build info: %s\n", msg->data);
+ }
+
+ /* Get chip info. */
+ msg->command = EC_CMD_GET_CHIP_INFO + ec->cmd_offset;
+ msg->insize = sizeof(*r_chip);
+ ret = cros_ec_cmd_xfer(ec->ec_dev, 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->data;
+
+ 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 + ec->cmd_offset;
+ msg->insize = sizeof(*r_board);
+ ret = cros_ec_cmd_xfer(ec->ec_dev, 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->data;
+
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "Board version: %d\n",
+ r_board->board_version);
+ }
+
+exit:
+ kfree(msg);
+ 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;
+ int ret;
+ struct cros_ec_dev *ec = container_of(dev,
+ struct cros_ec_dev, class_dev);
+
+ msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ /* The flash info shouldn't ever change, but ask each time anyway. */
+ msg->version = 0;
+ msg->command = EC_CMD_FLASH_INFO + ec->cmd_offset;
+ msg->insize = sizeof(*resp);
+ msg->outsize = 0;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0)
+ goto exit;
+ if (msg->result != EC_RES_SUCCESS) {
+ ret = scnprintf(buf, PAGE_SIZE,
+ "ERROR: EC returned %d\n", msg->result);
+ goto exit;
+ }
+
+ resp = (struct ec_response_flash_info *)msg->data;
+
+ ret = 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);
+exit:
+ kfree(msg);
+ return ret;
+}
+
+/* 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,
+};
+
+struct attribute_group cros_ec_attr_group = {
+ .attrs = __ec_attrs,
+};
+
diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c
index d9a09d9637d9..e7a29e2750c6 100644
--- a/drivers/platform/goldfish/goldfish_pipe.c
+++ b/drivers/platform/goldfish/goldfish_pipe.c
@@ -158,8 +158,8 @@ static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd)
struct goldfish_pipe_dev *dev = pipe->dev;
spin_lock_irqsave(&dev->lock, flags);
- gf_write64((u64)(unsigned long)pipe, dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
+ gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
+ dev->base + PIPE_REG_CHANNEL_HIGH);
writel(cmd, dev->base + PIPE_REG_COMMAND);
status = readl(dev->base + PIPE_REG_STATUS);
spin_unlock_irqrestore(&dev->lock, flags);
@@ -172,8 +172,8 @@ static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd)
struct goldfish_pipe_dev *dev = pipe->dev;
spin_lock_irqsave(&dev->lock, flags);
- gf_write64((u64)(unsigned long)pipe, dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
+ gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
+ dev->base + PIPE_REG_CHANNEL_HIGH);
writel(cmd, dev->base + PIPE_REG_COMMAND);
spin_unlock_irqrestore(&dev->lock, flags);
}
@@ -282,7 +282,7 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
return -EIO;
/* Null reads or writes succeeds */
- if (unlikely(bufflen) == 0)
+ if (unlikely(bufflen == 0))
return 0;
/* Check the buffer range for access */
@@ -327,12 +327,12 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
spin_lock_irqsave(&dev->lock, irq_flags);
if (access_with_param(dev, CMD_WRITE_BUFFER + cmd_offset,
address, avail, pipe, &status)) {
- gf_write64((u64)(unsigned long)pipe,
- dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
+ gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
+ dev->base + PIPE_REG_CHANNEL_HIGH);
writel(avail, dev->base + PIPE_REG_SIZE);
- gf_write64(address, dev->base + PIPE_REG_ADDRESS,
- dev->base + PIPE_REG_ADDRESS_HIGH);
+ gf_write_ptr((void *)address,
+ dev->base + PIPE_REG_ADDRESS,
+ dev->base + PIPE_REG_ADDRESS_HIGH);
writel(CMD_WRITE_BUFFER + cmd_offset,
dev->base + PIPE_REG_COMMAND);
status = readl(dev->base + PIPE_REG_STATUS);
diff --git a/drivers/platform/goldfish/pdev_bus.c b/drivers/platform/goldfish/pdev_bus.c
index 8c43589c3edb..1f52462f4cdd 100644
--- a/drivers/platform/goldfish/pdev_bus.c
+++ b/drivers/platform/goldfish/pdev_bus.c
@@ -220,20 +220,10 @@ free_resources:
return ret;
}
-static int goldfish_pdev_bus_remove(struct platform_device *pdev)
-{
- iounmap(pdev_bus_base);
- free_irq(pdev_bus_irq, pdev);
- release_mem_region(pdev_bus_addr, pdev_bus_len);
- return 0;
-}
-
static struct platform_driver goldfish_pdev_bus_driver = {
.probe = goldfish_pdev_bus_probe,
- .remove = goldfish_pdev_bus_remove,
.driver = {
.name = "goldfish_pdev_bus"
}
};
-
-module_platform_driver(goldfish_pdev_bus_driver);
+builtin_platform_driver(goldfish_pdev_bus_driver);
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 97527614141b..6dc13e4de396 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -71,9 +71,10 @@ config ASUS_LAPTOP
depends on ACPI
select LEDS_CLASS
select NEW_LEDS
- select BACKLIGHT_CLASS_DEVICE
+ depends on BACKLIGHT_CLASS_DEVICE
depends on INPUT
depends on RFKILL || RFKILL = n
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP
select INPUT_POLLDEV
---help---
@@ -95,6 +96,7 @@ config DELL_LAPTOP
depends on X86
depends on DCDBAS
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL || RFKILL = n
depends on SERIO_I8042
select POWER_SUPPLY
@@ -109,6 +111,7 @@ config DELL_WMI
tristate "Dell WMI extras"
depends on ACPI_WMI
depends on INPUT
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP
---help---
Say Y here if you want to support WMI-based hotkeys on Dell laptops.
@@ -138,12 +141,29 @@ config DELL_SMO8800
To compile this driver as a module, choose M here: the module will
be called dell-smo8800.
+config DELL_RBTN
+ tristate "Dell Airplane Mode Switch driver"
+ depends on ACPI
+ depends on INPUT
+ depends on RFKILL
+ ---help---
+ Say Y here if you want to support Dell Airplane Mode Switch ACPI
+ device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN.
+ This driver register rfkill device or input hotkey device depending
+ on hardware type (hw switch slider or keyboard toggle button). For
+ rfkill devices it receive HW switch events and set correct hard
+ rfkill state.
+
+ To compile this driver as a module, choose M here: the module will
+ be called dell-rbtn.
+
config FUJITSU_LAPTOP
tristate "Fujitsu Laptop Extras"
depends on ACPI
depends on INPUT
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on LEDS_CLASS || LEDS_CLASS=n
---help---
This is a driver for laptops built by Fujitsu:
@@ -247,6 +267,7 @@ config MSI_LAPTOP
tristate "MSI Laptop Extras"
depends on ACPI
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL
depends on INPUT && SERIO_I8042
select INPUT_SPARSEKMAP
@@ -280,6 +301,7 @@ config COMPAL_LAPTOP
tristate "Compal (and others) Laptop Extras"
depends on ACPI
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL
depends on HWMON
depends on POWER_SUPPLY
@@ -296,7 +318,8 @@ config COMPAL_LAPTOP
config SONY_LAPTOP
tristate "Sony Laptop Extras"
depends on ACPI
- select BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
+ depends on BACKLIGHT_CLASS_DEVICE
depends on INPUT
depends on RFKILL
---help---
@@ -321,6 +344,7 @@ config IDEAPAD_LAPTOP
depends on RFKILL && INPUT
depends on SERIO_I8042
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP
help
This is a driver for Lenovo IdeaPad netbooks contains drivers for
@@ -331,8 +355,8 @@ config THINKPAD_ACPI
depends on ACPI
depends on INPUT
depends on RFKILL || RFKILL = n
- select BACKLIGHT_LCD_SUPPORT
- select BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
+ depends on BACKLIGHT_CLASS_DEVICE
select HWMON
select NVRAM
select NEW_LEDS
@@ -500,8 +524,9 @@ config EEEPC_LAPTOP
depends on ACPI
depends on INPUT
depends on RFKILL || RFKILL = n
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on HOTPLUG_PCI
- select BACKLIGHT_CLASS_DEVICE
+ depends on BACKLIGHT_CLASS_DEVICE
select HWMON
select LEDS_CLASS
select NEW_LEDS
@@ -587,6 +612,7 @@ config MSI_WMI
depends on ACPI_WMI
depends on INPUT
depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP
help
Say Y here if you want to support WMI-based hotkeys on MSI laptops.
@@ -612,8 +638,8 @@ config ACPI_TOSHIBA
select NEW_LEDS
depends on BACKLIGHT_CLASS_DEVICE
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---
@@ -642,6 +668,7 @@ config ACPI_TOSHIBA
config TOSHIBA_BT_RFKILL
tristate "Toshiba Bluetooth RFKill switch support"
depends on ACPI
+ depends on RFKILL || RFKILL = n
---help---
This driver adds support for Bluetooth events for the RFKill
switch on modern Toshiba laptops with full ACPI support and
@@ -659,7 +686,7 @@ config TOSHIBA_HAPS
depends on ACPI
---help---
This driver adds support for the built-in accelerometer
- found on recent Toshiba laptops equiped with HID TOS620A
+ found on recent Toshiba laptops equipped with HID TOS620A
device.
This driver receives ACPI notify events 0x80 when the sensor
@@ -668,7 +695,7 @@ config TOSHIBA_HAPS
been stabilized.
Also provides sysfs entries to get/set the desired protection
- level and reseting the HDD protection interface.
+ level and resetting the HDD protection interface.
If you have a recent Toshiba laptop with a built-in accelerometer
device, say Y.
@@ -823,6 +850,7 @@ config MXM_WMI
config INTEL_OAKTRAIL
tristate "Intel Oaktrail Platform Extras"
depends on ACPI
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL && BACKLIGHT_CLASS_DEVICE && ACPI
---help---
Intel Oaktrail platform need this driver to provide interfaces to
@@ -884,4 +912,11 @@ config PVPANIC
a paravirtualized device provided by QEMU; it lets a virtual machine
(guest) communicate panic events to the host.
+config INTEL_PMC_IPC
+ tristate "Intel PMC IPC Driver"
+ ---help---
+ This driver provides support for PMC control on some Intel platforms.
+ The PMC is an ARC processor which defines IPC commands for communication
+ with other entities in the CPU.
+
endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index f82232b1fc4d..dda95a985321 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
+obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
@@ -58,3 +59,4 @@ obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
+obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index 3ac29a1e8f92..f6b280dbfb33 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -2246,14 +2246,10 @@ static int __init acer_wmi_init(void)
set_quirks();
if (dmi_check_system(video_vendor_dmi_table))
- acpi_video_dmi_promote_vendor();
- if (acpi_video_backlight_support()) {
+ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
interface->capability &= ~ACER_CAP_BRIGHTNESS;
- pr_info("Brightness must be controlled by acpi video driver\n");
- } else {
- pr_info("Disabling ACPI video driver\n");
- acpi_video_unregister_backlight();
- }
if (wmi_has_guid(WMID_GUID3)) {
if (ec_raw_mode) {
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
index 594c918b553d..1ef02daddb60 100644
--- a/drivers/platform/x86/acerhdf.c
+++ b/drivers/platform/x86/acerhdf.c
@@ -372,7 +372,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal,
return 0;
if (thermal_zone_bind_cooling_device(thermal, 0, cdev,
- THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) {
+ THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT)) {
pr_err("error binding cooling dev\n");
return -EINVAL;
}
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
index b9429fbf1cd8..0dec3f59917a 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);
@@ -513,8 +550,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
* backlight control and supports more levels than other options.
* Disable the other backlight choices.
*/
- acpi_video_dmi_promote_vendor();
- acpi_video_unregister();
+ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
apple_bl_unregister();
gmux_data->power_state = VGA_SWITCHEROO_ON;
@@ -574,6 +610,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,13 +633,17 @@ 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);
apple_gmux_data = NULL;
kfree(gmux_data);
- acpi_video_dmi_demote_vendor();
acpi_video_register();
apple_bl_register();
}
@@ -624,19 +668,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 46b274693872..58d29c4f2840 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -54,6 +54,7 @@
#include <linux/slab.h>
#include <linux/dmi.h>
#include <linux/acpi.h>
+#include <acpi/video.h>
#define ASUS_LAPTOP_VERSION "0.42"
@@ -1884,12 +1885,11 @@ static int asus_acpi_add(struct acpi_device *device)
if (result)
goto fail_platform;
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
result = asus_backlight_init(asus);
if (result)
goto fail_backlight;
- } else
- pr_info("Backlight controlled by ACPI video driver\n");
+ }
result = asus_input_init(asus);
if (result)
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 7543a56e0f45..efbc3f0c592b 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -78,6 +78,7 @@ MODULE_LICENSE("GPL");
#define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */
#define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */
#define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */
+#define ASUS_WMI_METHODID_AGFN 0x4E464741 /* FaN? */
#define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */
#define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */
#define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */
@@ -150,12 +151,38 @@ MODULE_LICENSE("GPL");
#define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF
#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00
+#define ASUS_FAN_DESC "cpu_fan"
+#define ASUS_FAN_MFUN 0x13
+#define ASUS_FAN_SFUN_READ 0x06
+#define ASUS_FAN_SFUN_WRITE 0x07
+#define ASUS_FAN_CTRL_MANUAL 1
+#define ASUS_FAN_CTRL_AUTO 2
+
struct bios_args {
u32 arg0;
u32 arg1;
} __packed;
/*
+ * Struct that's used for all methods called via AGFN. Naming is
+ * identically to the AML code.
+ */
+struct agfn_args {
+ u16 mfun; /* probably "Multi-function" to be called */
+ u16 sfun; /* probably "Sub-function" to be called */
+ u16 len; /* size of the hole struct, including subfunction fields */
+ u8 stas; /* not used by now */
+ u8 err; /* zero on success */
+} __packed;
+
+/* struct used for calling fan read and write methods */
+struct fan_args {
+ struct agfn_args agfn; /* common fields */
+ u8 fan; /* fan number: 0: set auto mode 1: 1st fan */
+ u32 speed; /* read: RPM/100 - write: 0-255 */
+} __packed;
+
+/*
* <platform>/ - debugfs root directory
* dev_id - current dev_id
* ctrl_param - current ctrl_param
@@ -204,6 +231,10 @@ struct asus_wmi {
struct asus_rfkill gps;
struct asus_rfkill uwb;
+ bool asus_hwmon_fan_manual_mode;
+ int asus_hwmon_num_fans;
+ int asus_hwmon_pwm;
+
struct hotplug_slot *hotplug_slot;
struct mutex hotplug_lock;
struct mutex wmi_lock;
@@ -294,6 +325,36 @@ exit:
return 0;
}
+static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
+{
+ struct acpi_buffer input;
+ u64 phys_addr;
+ u32 retval;
+ u32 status = -1;
+
+ /*
+ * Copy to dma capable address otherwise memory corruption occurs as
+ * bios has to be able to access it.
+ */
+ input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL);
+ input.length = args.length;
+ if (!input.pointer)
+ return -ENOMEM;
+ phys_addr = virt_to_phys(input.pointer);
+ memcpy(input.pointer, args.pointer, args.length);
+
+ status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN,
+ phys_addr, 0, &retval);
+ if (!status)
+ memcpy(args.pointer, input.pointer, args.length);
+
+ kfree(input.pointer);
+ if (status)
+ return -ENXIO;
+
+ return retval;
+}
+
static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
{
return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval);
@@ -1022,35 +1083,228 @@ exit:
/*
* Hwmon device
*/
-static ssize_t asus_hwmon_pwm1(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
+ int *speed)
+{
+ struct fan_args args = {
+ .agfn.len = sizeof(args),
+ .agfn.mfun = ASUS_FAN_MFUN,
+ .agfn.sfun = ASUS_FAN_SFUN_READ,
+ .fan = fan,
+ .speed = 0,
+ };
+ struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+ int status;
+
+ if (fan != 1)
+ return -EINVAL;
+
+ status = asus_wmi_evaluate_method_agfn(input);
+
+ if (status || args.agfn.err)
+ return -ENXIO;
+
+ if (speed)
+ *speed = args.speed;
+
+ return 0;
+}
+
+static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan,
+ int *speed)
+{
+ struct fan_args args = {
+ .agfn.len = sizeof(args),
+ .agfn.mfun = ASUS_FAN_MFUN,
+ .agfn.sfun = ASUS_FAN_SFUN_WRITE,
+ .fan = fan,
+ .speed = speed ? *speed : 0,
+ };
+ struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+ int status;
+
+ /* 1: for setting 1st fan's speed 0: setting auto mode */
+ if (fan != 1 && fan != 0)
+ return -EINVAL;
+
+ status = asus_wmi_evaluate_method_agfn(input);
+
+ if (status || args.agfn.err)
+ return -ENXIO;
+
+ if (speed && fan == 1)
+ asus->asus_hwmon_pwm = *speed;
+
+ return 0;
+}
+
+/*
+ * Check if we can read the speed of one fan. If true we assume we can also
+ * control it.
+ */
+static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans)
+{
+ int status;
+ int speed = 0;
+
+ *num_fans = 0;
+
+ status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed);
+ if (!status)
+ *num_fans = 1;
+
+ return 0;
+}
+
+static int asus_hwmon_fan_set_auto(struct asus_wmi *asus)
+{
+ int status;
+
+ status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL);
+ if (status)
+ return -ENXIO;
+
+ asus->asus_hwmon_fan_manual_mode = false;
+
+ return 0;
+}
+
+static int asus_hwmon_fan_rpm_show(struct device *dev, int fan)
{
struct asus_wmi *asus = dev_get_drvdata(dev);
- u32 value;
+ int value;
+ int ret;
+
+ /* no speed readable on manual mode */
+ if (asus->asus_hwmon_fan_manual_mode)
+ return -ENXIO;
+
+ ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value);
+ if (ret) {
+ pr_warn("reading fan speed failed: %d\n", ret);
+ return -ENXIO;
+ }
+
+ return value;
+}
+
+static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value)
+{
int err;
- err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value);
+ if (asus->asus_hwmon_pwm >= 0) {
+ *value = asus->asus_hwmon_pwm;
+ return;
+ }
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value);
if (err < 0)
- return err;
+ return;
- value &= 0xFF;
-
- if (value == 1) /* Low Speed */
- value = 85;
- else if (value == 2)
- value = 170;
- else if (value == 3)
- value = 255;
- else if (value != 0) {
- pr_err("Unknown fan speed %#x\n", value);
- value = -1;
+ *value &= 0xFF;
+
+ if (*value == 1) /* Low Speed */
+ *value = 85;
+ else if (*value == 2)
+ *value = 170;
+ else if (*value == 3)
+ *value = 255;
+ else if (*value) {
+ pr_err("Unknown fan speed %#x\n", *value);
+ *value = -1;
}
+}
+
+static ssize_t pwm1_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int value;
+
+ asus_hwmon_pwm_show(asus, 0, &value);
return sprintf(buf, "%d\n", value);
}
+static ssize_t pwm1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count) {
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int value;
+ int state;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &value);
+
+ if (ret)
+ return ret;
+
+ value = clamp(value, 0, 255);
+
+ state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value);
+ if (state)
+ pr_warn("Setting fan speed failed: %d\n", state);
+ else
+ asus->asus_hwmon_fan_manual_mode = true;
+
+ return count;
+}
+
+static ssize_t fan1_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int value = asus_hwmon_fan_rpm_show(dev, 0);
+
+ return sprintf(buf, "%d\n", value < 0 ? -1 : value*100);
+
+}
+
+static ssize_t pwm1_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ if (asus->asus_hwmon_fan_manual_mode)
+ return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL);
+
+ return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO);
+}
+
+static ssize_t pwm1_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int status = 0;
+ int state;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &state);
+
+ if (ret)
+ return ret;
+
+ if (state == ASUS_FAN_CTRL_MANUAL)
+ asus->asus_hwmon_fan_manual_mode = true;
+ else
+ status = asus_hwmon_fan_set_auto(asus);
+
+ if (status)
+ return status;
+
+ return count;
+}
+
+static ssize_t fan1_label_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", ASUS_FAN_DESC);
+}
+
static ssize_t asus_hwmon_temp1(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -1069,11 +1323,21 @@ static ssize_t asus_hwmon_temp1(struct device *dev,
return sprintf(buf, "%d\n", value);
}
-static DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL);
+/* Fan1 */
+static DEVICE_ATTR_RW(pwm1);
+static DEVICE_ATTR_RW(pwm1_enable);
+static DEVICE_ATTR_RO(fan1_input);
+static DEVICE_ATTR_RO(fan1_label);
+
+/* Temperature */
static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
static struct attribute *hwmon_attributes[] = {
&dev_attr_pwm1.attr,
+ &dev_attr_pwm1_enable.attr,
+ &dev_attr_fan1_input.attr,
+ &dev_attr_fan1_label.attr,
+
&dev_attr_temp1_input.attr,
NULL
};
@@ -1084,19 +1348,28 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
struct device *dev = container_of(kobj, struct device, kobj);
struct platform_device *pdev = to_platform_device(dev->parent);
struct asus_wmi *asus = platform_get_drvdata(pdev);
- bool ok = true;
int dev_id = -1;
+ int fan_attr = -1;
u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
+ bool ok = true;
if (attr == &dev_attr_pwm1.attr)
dev_id = ASUS_WMI_DEVID_FAN_CTRL;
else if (attr == &dev_attr_temp1_input.attr)
dev_id = ASUS_WMI_DEVID_THERMAL_CTRL;
+
+ if (attr == &dev_attr_fan1_input.attr
+ || attr == &dev_attr_fan1_label.attr
+ || attr == &dev_attr_pwm1.attr
+ || attr == &dev_attr_pwm1_enable.attr) {
+ fan_attr = 1;
+ }
+
if (dev_id != -1) {
int err = asus_wmi_get_devstate(asus, dev_id, &value);
- if (err < 0)
+ if (err < 0 && fan_attr == -1)
return 0; /* can't return negative here */
}
@@ -1112,10 +1385,16 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000
|| (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT)))
ok = false;
+ else
+ ok = fan_attr <= asus->asus_hwmon_num_fans;
} else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
/* If value is zero, something is clearly wrong */
- if (value == 0)
+ if (!value)
ok = false;
+ } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
+ ok = true;
+ } else {
+ ok = false;
}
return ok ? attr->mode : 0;
@@ -1364,7 +1643,7 @@ static void asus_wmi_notify(u32 value, void *context)
code = ASUS_WMI_BRN_DOWN;
if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
asus_wmi_backlight_notify(asus, orig_code);
goto exit;
}
@@ -1723,6 +2002,25 @@ error_debugfs:
return -ENOMEM;
}
+static int asus_wmi_fan_init(struct asus_wmi *asus)
+{
+ int status;
+
+ asus->asus_hwmon_pwm = -1;
+ asus->asus_hwmon_num_fans = -1;
+ asus->asus_hwmon_fan_manual_mode = false;
+
+ status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
+ if (status) {
+ asus->asus_hwmon_num_fans = 0;
+ pr_warn("Could not determine number of fans: %d\n", status);
+ return -ENXIO;
+ }
+
+ pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
+ return 0;
+}
+
/*
* WMI Driver
*/
@@ -1756,6 +2054,9 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_input;
+ err = asus_wmi_fan_init(asus); /* probably no problems on error */
+ asus_hwmon_fan_set_auto(asus);
+
err = asus_wmi_hwmon_init(asus);
if (err)
goto fail_hwmon;
@@ -1772,17 +2073,16 @@ static int asus_wmi_add(struct platform_device *pdev)
stop this from showing up */
chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE);
if (chassis_type && !strcmp(chassis_type, "3"))
- acpi_video_dmi_promote_vendor();
+ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
if (asus->driver->quirks->wmi_backlight_power)
- acpi_video_dmi_promote_vendor();
- if (!acpi_video_backlight_support()) {
- pr_info("Disabling ACPI video driver\n");
- acpi_video_unregister();
+ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
err = asus_wmi_backlight_init(asus);
if (err && err != -ENODEV)
goto fail_backlight;
- } else
- pr_info("Backlight controlled by ACPI video driver\n");
+ }
status = wmi_install_notify_handler(asus->driver->event_guid,
asus_wmi_notify, asus);
@@ -1832,6 +2132,7 @@ static int asus_wmi_remove(struct platform_device *device)
asus_wmi_rfkill_exit(asus);
asus_wmi_debugfs_exit(asus);
asus_wmi_platform_exit(asus);
+ asus_hwmon_fan_set_auto(asus);
kfree(asus);
return 0;
diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c
index 15c0fab2bfa1..f2706d27adff 100644
--- a/drivers/platform/x86/compal-laptop.c
+++ b/drivers/platform/x86/compal-laptop.c
@@ -82,7 +82,7 @@
#include <linux/hwmon-sysfs.h>
#include <linux/power_supply.h>
#include <linux/fb.h>
-
+#include <acpi/video.h>
/* ======= */
/* Defines */
@@ -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:
@@ -875,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,
@@ -957,7 +959,7 @@ static int __init compal_init(void)
return -ENODEV;
}
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
@@ -1011,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;
@@ -1026,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;
@@ -1036,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);
@@ -1071,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 3d21efe11d7b..aaeeae81e3a9 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
@@ -29,9 +31,18 @@
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
+#include <acpi/video.h>
#include "../../firmware/dcdbas.h"
+#include "dell-rbtn.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 +73,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 +94,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;
@@ -267,19 +294,30 @@ 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,
+ },
{ }
};
static struct calling_interface_buffer *buffer;
-static struct page *bufferpage;
static DEFINE_MUTEX(buffer_mutex);
-static int hwswitch_state;
+static void clear_buffer(void)
+{
+ memset(buffer, 0, sizeof(struct calling_interface_buffer));
+}
static void get_buffer(void)
{
mutex_lock(&buffer_mutex);
- memset(buffer, 0, sizeof(struct calling_interface_buffer));
+ clear_buffer();
}
static void release_buffer(void)
@@ -331,17 +369,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)
@@ -362,66 +412,180 @@ dell_send_request(struct calling_interface_buffer *buffer, int class,
return buffer;
}
-/* Derived from information in DellWirelessCtl.cpp:
- Class 17, select 11 is radio control. It returns an array of 32-bit values.
-
- Input byte 0 = 0: Wireless information
-
- result[0]: return code
- result[1]:
- Bit 0: Hardware switch supported
- Bit 1: Wifi locator supported
- Bit 2: Wifi is supported
- Bit 3: Bluetooth is supported
- Bit 4: WWAN is supported
- Bit 5: Wireless keyboard supported
- Bits 6-7: Reserved
- Bit 8: Wifi is installed
- Bit 9: Bluetooth is installed
- Bit 10: WWAN is installed
- Bits 11-15: Reserved
- Bit 16: Hardware switch is on
- Bit 17: Wifi is blocked
- Bit 18: Bluetooth is blocked
- Bit 19: WWAN is blocked
- Bits 20-31: Reserved
- result[2]: NVRAM size in bytes
- result[3]: NVRAM format version number
-
- Input byte 0 = 2: Wireless switch configuration
- result[0]: return code
- result[1]:
- Bit 0: Wifi controlled by switch
- Bit 1: Bluetooth controlled by switch
- Bit 2: WWAN controlled by switch
- Bits 3-6: Reserved
- Bit 7: Wireless switch config locked
- Bit 8: Wifi locator enabled
- Bits 9-14: Reserved
- Bit 15: Wifi locator setting locked
- Bits 16-31: Reserved
-*/
+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 smbios-wireless-ctl:
+ *
+ * cbSelect 17, Value 11
+ *
+ * Return Wireless Info
+ * cbArg1, byte0 = 0x00
+ *
+ * cbRes1 Standard return codes (0, -1, -2)
+ * cbRes2 Info bit flags:
+ *
+ * 0 Hardware switch supported (1)
+ * 1 WiFi locator supported (1)
+ * 2 WLAN supported (1)
+ * 3 Bluetooth (BT) supported (1)
+ * 4 WWAN supported (1)
+ * 5 Wireless KBD supported (1)
+ * 6 Uw b supported (1)
+ * 7 WiGig supported (1)
+ * 8 WLAN installed (1)
+ * 9 BT installed (1)
+ * 10 WWAN installed (1)
+ * 11 Uw b installed (1)
+ * 12 WiGig installed (1)
+ * 13-15 Reserved (0)
+ * 16 Hardware (HW) switch is On (1)
+ * 17 WLAN disabled (1)
+ * 18 BT disabled (1)
+ * 19 WWAN disabled (1)
+ * 20 Uw b disabled (1)
+ * 21 WiGig disabled (1)
+ * 20-31 Reserved (0)
+ *
+ * cbRes3 NVRAM size in bytes
+ * cbRes4, byte 0 NVRAM format version number
+ *
+ *
+ * Set QuickSet Radio Disable Flag
+ * cbArg1, byte0 = 0x01
+ * cbArg1, byte1
+ * Radio ID value:
+ * 0 Radio Status
+ * 1 WLAN ID
+ * 2 BT ID
+ * 3 WWAN ID
+ * 4 UWB ID
+ * 5 WIGIG ID
+ * cbArg1, byte2 Flag bits:
+ * 0 QuickSet disables radio (1)
+ * 1-7 Reserved (0)
+ *
+ * cbRes1 Standard return codes (0, -1, -2)
+ * cbRes2 QuickSet (QS) radio disable bit map:
+ * 0 QS disables WLAN
+ * 1 QS disables BT
+ * 2 QS disables WWAN
+ * 3 QS disables UWB
+ * 4 QS disables WIGIG
+ * 5-31 Reserved (0)
+ *
+ * Wireless Switch Configuration
+ * cbArg1, byte0 = 0x02
+ *
+ * cbArg1, byte1
+ * Subcommand:
+ * 0 Get config
+ * 1 Set config
+ * 2 Set WiFi locator enable/disable
+ * cbArg1,byte2
+ * Switch settings (if byte 1==1):
+ * 0 WLAN sw itch control (1)
+ * 1 BT sw itch control (1)
+ * 2 WWAN sw itch control (1)
+ * 3 UWB sw itch control (1)
+ * 4 WiGig sw itch control (1)
+ * 5-7 Reserved (0)
+ * cbArg1, byte2 Enable bits (if byte 1==2):
+ * 0 Enable WiFi locator (1)
+ *
+ * cbRes1 Standard return codes (0, -1, -2)
+ * cbRes2 QuickSet radio disable bit map:
+ * 0 WLAN controlled by sw itch (1)
+ * 1 BT controlled by sw itch (1)
+ * 2 WWAN controlled by sw itch (1)
+ * 3 UWB controlled by sw itch (1)
+ * 4 WiGig controlled by sw itch (1)
+ * 5-6 Reserved (0)
+ * 7 Wireless sw itch config locked (1)
+ * 8 WiFi locator enabled (1)
+ * 9-14 Reserved (0)
+ * 15 WiFi locator setting locked (1)
+ * 16-31 Reserved (0)
+ *
+ * Read Local Config Data (LCD)
+ * cbArg1, byte0 = 0x10
+ * cbArg1, byte1 NVRAM index low byte
+ * cbArg1, byte2 NVRAM index high byte
+ * cbRes1 Standard return codes (0, -1, -2)
+ * cbRes2 4 bytes read from LCD[index]
+ * cbRes3 4 bytes read from LCD[index+4]
+ * cbRes4 4 bytes read from LCD[index+8]
+ *
+ * Write Local Config Data (LCD)
+ * cbArg1, byte0 = 0x11
+ * cbArg1, byte1 NVRAM index low byte
+ * cbArg1, byte2 NVRAM index high byte
+ * cbArg2 4 bytes to w rite at LCD[index]
+ * cbArg3 4 bytes to w rite at LCD[index+4]
+ * cbArg4 4 bytes to w rite at LCD[index+8]
+ * cbRes1 Standard return codes (0, -1, -2)
+ *
+ * Populate Local Config Data from NVRAM
+ * cbArg1, byte0 = 0x12
+ * cbRes1 Standard return codes (0, -1, -2)
+ *
+ * Commit Local Config Data to NVRAM
+ * cbArg1, byte0 = 0x13
+ * cbRes1 Standard return codes (0, -1, -2)
+ */
static int dell_rfkill_set(void *data, bool blocked)
{
int disable = blocked ? 1 : 0;
unsigned long radio = (unsigned long)data;
int hwswitch_bit = (unsigned long)data - 1;
+ int hwswitch;
+ int status;
+ int ret;
get_buffer();
+
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
+ status = buffer->output[1];
+
+ if (ret != 0)
+ goto out;
+
+ clear_buffer();
+
+ buffer->input[0] = 0x2;
+ dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
+ hwswitch = buffer->output[1];
/* If the hardware switch controls this radio, and the hardware
switch is disabled, always disable the radio */
- if ((hwswitch_state & BIT(hwswitch_bit)) &&
- !(buffer->output[1] & BIT(16)))
+ if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) &&
+ (status & BIT(0)) && !(status & BIT(16)))
disable = 1;
+ clear_buffer();
+
buffer->input[0] = (1 | (radio<<8) | (disable << 16));
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
+ out:
release_buffer();
- return 0;
+ return dell_smi_error(ret);
}
/* Must be called with the buffer held */
@@ -431,6 +595,7 @@ static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio,
if (status & BIT(0)) {
/* Has hw-switch, sync sw_state to BIOS */
int block = rfkill_blocked(rfkill);
+ clear_buffer();
buffer->input[0] = (1 | (radio << 8) | (block << 16));
dell_send_request(buffer, 17, 11);
} else {
@@ -440,23 +605,43 @@ static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio,
}
static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio,
- int status)
+ int status, int hwswitch)
{
- if (hwswitch_state & (BIT(radio - 1)))
+ if (hwswitch & (BIT(radio - 1)))
rfkill_set_hw_state(rfkill, !(status & BIT(16)));
}
static void dell_rfkill_query(struct rfkill *rfkill, void *data)
{
+ int radio = ((unsigned long)data & 0xF);
+ int hwswitch;
int status;
+ int ret;
get_buffer();
+
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
status = buffer->output[1];
- dell_rfkill_update_hw_state(rfkill, (unsigned long)data, status);
+ if (ret != 0 || !(status & BIT(0))) {
+ release_buffer();
+ return;
+ }
+
+ clear_buffer();
+
+ buffer->input[0] = 0x2;
+ dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
+ hwswitch = buffer->output[1];
release_buffer();
+
+ if (ret != 0)
+ return;
+
+ dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch);
}
static const struct rfkill_ops dell_rfkill_ops = {
@@ -468,13 +653,27 @@ static struct dentry *dell_laptop_dir;
static int dell_debugfs_show(struct seq_file *s, void *data)
{
+ int hwswitch_state;
+ int hwswitch_ret;
int status;
+ int ret;
get_buffer();
+
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
status = buffer->output[1];
+
+ clear_buffer();
+
+ buffer->input[0] = 0x2;
+ dell_send_request(buffer, 17, 11);
+ hwswitch_ret = buffer->output[0];
+ hwswitch_state = buffer->output[1];
+
release_buffer();
+ seq_printf(s, "return:\t%d\n", ret);
seq_printf(s, "status:\t0x%X\n", status);
seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n",
status & BIT(0));
@@ -488,12 +687,21 @@ static int dell_debugfs_show(struct seq_file *s, void *data)
(status & BIT(4)) >> 4);
seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n",
(status & BIT(5)) >> 5);
+ seq_printf(s, "Bit 6 : UWB supported: %lu\n",
+ (status & BIT(6)) >> 6);
+ seq_printf(s, "Bit 7 : WiGig supported: %lu\n",
+ (status & BIT(7)) >> 7);
seq_printf(s, "Bit 8 : Wifi is installed: %lu\n",
(status & BIT(8)) >> 8);
seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n",
(status & BIT(9)) >> 9);
seq_printf(s, "Bit 10: WWAN is installed: %lu\n",
(status & BIT(10)) >> 10);
+ seq_printf(s, "Bit 11: UWB installed: %lu\n",
+ (status & BIT(11)) >> 11);
+ seq_printf(s, "Bit 12: WiGig installed: %lu\n",
+ (status & BIT(12)) >> 12);
+
seq_printf(s, "Bit 16: Hardware switch is on: %lu\n",
(status & BIT(16)) >> 16);
seq_printf(s, "Bit 17: Wifi is blocked: %lu\n",
@@ -502,14 +710,23 @@ static int dell_debugfs_show(struct seq_file *s, void *data)
(status & BIT(18)) >> 18);
seq_printf(s, "Bit 19: WWAN is blocked: %lu\n",
(status & BIT(19)) >> 19);
+ seq_printf(s, "Bit 20: UWB is blocked: %lu\n",
+ (status & BIT(20)) >> 20);
+ seq_printf(s, "Bit 21: WiGig is blocked: %lu\n",
+ (status & BIT(21)) >> 21);
- seq_printf(s, "\nhwswitch_state:\t0x%X\n", hwswitch_state);
+ seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret);
+ seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state);
seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n",
hwswitch_state & BIT(0));
seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n",
(hwswitch_state & BIT(1)) >> 1);
seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n",
(hwswitch_state & BIT(2)) >> 2);
+ seq_printf(s, "Bit 3 : UWB controlled by switch: %lu\n",
+ (hwswitch_state & BIT(3)) >> 3);
+ seq_printf(s, "Bit 4 : WiGig controlled by switch: %lu\n",
+ (hwswitch_state & BIT(4)) >> 4);
seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n",
(hwswitch_state & BIT(7)) >> 7);
seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n",
@@ -535,25 +752,43 @@ static const struct file_operations dell_debugfs_fops = {
static void dell_update_rfkill(struct work_struct *ignored)
{
+ int hwswitch = 0;
int status;
+ int ret;
get_buffer();
+
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
status = buffer->output[1];
+ if (ret != 0)
+ goto out;
+
+ clear_buffer();
+
+ buffer->input[0] = 0x2;
+ dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
+
+ if (ret == 0 && (status & BIT(0)))
+ hwswitch = buffer->output[1];
+
if (wifi_rfkill) {
- dell_rfkill_update_hw_state(wifi_rfkill, 1, status);
+ dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch);
dell_rfkill_update_sw_state(wifi_rfkill, 1, status);
}
if (bluetooth_rfkill) {
- dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status);
+ dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status,
+ hwswitch);
dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status);
}
if (wwan_rfkill) {
- dell_rfkill_update_hw_state(wwan_rfkill, 3, status);
+ dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch);
dell_rfkill_update_sw_state(wwan_rfkill, 3, status);
}
+ out:
release_buffer();
}
static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill);
@@ -582,6 +817,20 @@ static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
return false;
}
+static int (*dell_rbtn_notifier_register_func)(struct notifier_block *);
+static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *);
+
+static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ schedule_delayed_work(&dell_rfkill_work, 0);
+ return NOTIFY_OK;
+}
+
+static struct notifier_block dell_laptop_rbtn_notifier = {
+ .notifier_call = dell_laptop_rbtn_notifier_call,
+};
+
static int __init dell_setup_rfkill(void)
{
int status, ret, whitelisted;
@@ -601,21 +850,17 @@ static int __init dell_setup_rfkill(void)
get_buffer();
dell_send_request(buffer, 17, 11);
+ ret = buffer->output[0];
status = buffer->output[1];
- buffer->input[0] = 0x2;
- dell_send_request(buffer, 17, 11);
- hwswitch_state = buffer->output[1];
release_buffer();
- if (!(status & BIT(0))) {
- if (force_rfkill) {
- /* No hwsitch, clear all hw-controlled bits */
- hwswitch_state &= ~7;
- } else {
- /* rfkill is only tested on laptops with a hwswitch */
- return 0;
- }
- }
+ /* dell wireless info smbios call is not supported */
+ if (ret != 0)
+ return 0;
+
+ /* rfkill is only tested on laptops with a hwswitch */
+ if (!(status & BIT(0)) && !force_rfkill)
+ return 0;
if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) {
wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev,
@@ -658,10 +903,62 @@ static int __init dell_setup_rfkill(void)
goto err_wwan;
}
- ret = i8042_install_filter(dell_laptop_i8042_filter);
- if (ret) {
- pr_warn("Unable to install key filter\n");
+ /*
+ * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices
+ * which can receive events from HW slider switch.
+ *
+ * Dell SMBIOS on whitelisted models supports controlling radio devices
+ * but does not support receiving HW button switch events. We can use
+ * i8042 filter hook function to receive keyboard data and handle
+ * keycode for HW button.
+ *
+ * So if it is possible we will use Dell Airplane Mode Switch ACPI
+ * driver for receiving HW events and Dell SMBIOS for setting rfkill
+ * states. If ACPI driver or device is not available we will fallback to
+ * i8042 filter hook function.
+ *
+ * To prevent duplicate rfkill devices which control and do same thing,
+ * dell-rbtn driver will automatically remove its own rfkill devices
+ * once function dell_rbtn_notifier_register() is called.
+ */
+
+ dell_rbtn_notifier_register_func =
+ symbol_request(dell_rbtn_notifier_register);
+ if (dell_rbtn_notifier_register_func) {
+ dell_rbtn_notifier_unregister_func =
+ symbol_request(dell_rbtn_notifier_unregister);
+ if (!dell_rbtn_notifier_unregister_func) {
+ symbol_put(dell_rbtn_notifier_register);
+ dell_rbtn_notifier_register_func = NULL;
+ }
+ }
+
+ if (dell_rbtn_notifier_register_func) {
+ ret = dell_rbtn_notifier_register_func(
+ &dell_laptop_rbtn_notifier);
+ symbol_put(dell_rbtn_notifier_register);
+ dell_rbtn_notifier_register_func = NULL;
+ if (ret != 0) {
+ symbol_put(dell_rbtn_notifier_unregister);
+ dell_rbtn_notifier_unregister_func = NULL;
+ }
+ } else {
+ pr_info("Symbols from dell-rbtn acpi driver are not available\n");
+ ret = -ENODEV;
+ }
+
+ if (ret == 0) {
+ pr_info("Using dell-rbtn acpi driver for receiving events\n");
+ } else if (ret != -ENODEV) {
+ pr_warn("Unable to register dell rbtn notifier\n");
goto err_filter;
+ } else {
+ ret = i8042_install_filter(dell_laptop_i8042_filter);
+ if (ret) {
+ pr_warn("Unable to install key filter\n");
+ goto err_filter;
+ }
+ pr_info("Using i8042 filter function for receiving events\n");
}
return 0;
@@ -684,6 +981,14 @@ err_wifi:
static void dell_cleanup_rfkill(void)
{
+ if (dell_rbtn_notifier_unregister_func) {
+ dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier);
+ symbol_put(dell_rbtn_notifier_unregister);
+ dell_rbtn_notifier_unregister_func = NULL;
+ } else {
+ i8042_remove_filter(dell_laptop_i8042_filter);
+ }
+ cancel_delayed_work_sync(&dell_rfkill_work);
if (wifi_rfkill) {
rfkill_unregister(wifi_rfkill);
rfkill_destroy(wifi_rfkill);
@@ -700,47 +1005,50 @@ static void dell_cleanup_rfkill(void)
static int dell_send_intensity(struct backlight_device *bd)
{
- int ret = 0;
+ int token;
+ int ret;
+
+ token = find_token_location(BRIGHTNESS_TOKEN);
+ if (token == -1)
+ return -ENODEV;
get_buffer();
- buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
+ buffer->input[0] = token;
buffer->input[1] = bd->props.brightness;
- if (buffer->input[0] == -1) {
- ret = -ENODEV;
- goto out;
- }
-
if (power_supply_is_system_supplied() > 0)
dell_send_request(buffer, 1, 2);
else
dell_send_request(buffer, 1, 1);
-out:
+ ret = dell_smi_error(buffer->output[0]);
+
release_buffer();
return ret;
}
static int dell_get_intensity(struct backlight_device *bd)
{
- int ret = 0;
+ int token;
+ int ret;
- get_buffer();
- buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
+ token = find_token_location(BRIGHTNESS_TOKEN);
+ if (token == -1)
+ return -ENODEV;
- if (buffer->input[0] == -1) {
- ret = -ENODEV;
- goto out;
- }
+ get_buffer();
+ buffer->input[0] = token;
if (power_supply_is_system_supplied() > 0)
dell_send_request(buffer, 0, 2);
else
dell_send_request(buffer, 0, 1);
- ret = buffer->output[1];
+ if (buffer->output[0])
+ ret = dell_smi_error(buffer->output[0]);
+ else
+ ret = buffer->output[1];
-out:
release_buffer();
return ret;
}
@@ -789,9 +1097,1022 @@ 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;
+ int token;
int ret;
if (!dmi_check_system(dell_device_table))
@@ -824,12 +2145,11 @@ static int __init dell_init(void)
* Allocate buffer below 4GB for SMI data--only 32-bit physical addr
* is passed to SMI handler.
*/
- bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32);
- if (!bufferpage) {
+ buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32);
+ if (!buffer) {
ret = -ENOMEM;
goto fail_buffer;
}
- buffer = page_address(bufferpage);
ret = dell_setup_rfkill();
@@ -841,26 +2161,25 @@ 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,
&dell_debugfs_fops);
-#ifdef CONFIG_ACPI
- /* In the event of an ACPI backlight being available, don't
- * register the platform controller.
- */
- if (acpi_video_backlight_support())
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return 0;
-#endif
- get_buffer();
- buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
- if (buffer->input[0] != -1) {
+ token = find_token_location(BRIGHTNESS_TOKEN);
+ if (token != -1) {
+ get_buffer();
+ buffer->input[0] = token;
dell_send_request(buffer, 0, 2);
- max_intensity = buffer->output[3];
+ if (buffer->output[0] == 0)
+ max_intensity = buffer->output[3];
+ release_buffer();
}
- release_buffer();
if (max_intensity) {
struct backlight_properties props;
@@ -887,11 +2206,9 @@ static int __init dell_init(void)
return 0;
fail_backlight:
- i8042_remove_filter(dell_laptop_i8042_filter);
- cancel_delayed_work_sync(&dell_rfkill_work);
dell_cleanup_rfkill();
fail_rfkill:
- free_page((unsigned long)bufferpage);
+ free_page((unsigned long)buffer);
fail_buffer:
platform_device_del(platform_device);
fail_platform_device2:
@@ -908,8 +2225,7 @@ static void __exit dell_exit(void)
debugfs_remove_recursive(dell_laptop_dir);
if (quirks && quirks->touchpad_led)
touchpad_led_exit();
- i8042_remove_filter(dell_laptop_i8042_filter);
- cancel_delayed_work_sync(&dell_rfkill_work);
+ kbd_led_exit();
backlight_device_unregister(dell_backlight_device);
dell_cleanup_rfkill();
if (platform_device) {
@@ -920,9 +2236,18 @@ static void __exit dell_exit(void)
free_page((unsigned long)buffer);
}
-module_init(dell_init);
+/* dell-rbtn.c driver export functions which will not work correctly (and could
+ * cause kernel crash) if they are called before dell-rbtn.c init code. This is
+ * not problem when dell-rbtn.c is compiled as external module. When both files
+ * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we
+ * need to ensure that dell_init() will be called after initializing dell-rbtn.
+ * This can be achieved by late_initcall() instead module_init().
+ */
+late_initcall(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-rbtn.c b/drivers/platform/x86/dell-rbtn.c
new file mode 100644
index 000000000000..cd410e392550
--- /dev/null
+++ b/drivers/platform/x86/dell-rbtn.c
@@ -0,0 +1,423 @@
+/*
+ Dell Airplane Mode Switch driver
+ Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
+
+ 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.
+*/
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+
+enum rbtn_type {
+ RBTN_UNKNOWN,
+ RBTN_TOGGLE,
+ RBTN_SLIDER,
+};
+
+struct rbtn_data {
+ enum rbtn_type type;
+ struct rfkill *rfkill;
+ struct input_dev *input_dev;
+};
+
+
+/*
+ * acpi functions
+ */
+
+static enum rbtn_type rbtn_check(struct acpi_device *device)
+{
+ unsigned long long output;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
+ if (ACPI_FAILURE(status))
+ return RBTN_UNKNOWN;
+
+ switch (output) {
+ case 0:
+ case 1:
+ return RBTN_TOGGLE;
+ case 2:
+ case 3:
+ return RBTN_SLIDER;
+ default:
+ return RBTN_UNKNOWN;
+ }
+}
+
+static int rbtn_get(struct acpi_device *device)
+{
+ unsigned long long output;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return !output;
+}
+
+static int rbtn_acquire(struct acpi_device *device, bool enable)
+{
+ struct acpi_object_list input;
+ union acpi_object param;
+ acpi_status status;
+
+ param.type = ACPI_TYPE_INTEGER;
+ param.integer.value = enable;
+ input.count = 1;
+ input.pointer = &param;
+
+ status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return 0;
+}
+
+
+/*
+ * rfkill device
+ */
+
+static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
+{
+ struct acpi_device *device = data;
+ int state;
+
+ state = rbtn_get(device);
+ if (state < 0)
+ return;
+
+ rfkill_set_states(rfkill, state, state);
+}
+
+static int rbtn_rfkill_set_block(void *data, bool blocked)
+{
+ /* NOTE: setting soft rfkill state is not supported */
+ return -EINVAL;
+}
+
+static struct rfkill_ops rbtn_ops = {
+ .query = rbtn_rfkill_query,
+ .set_block = rbtn_rfkill_set_block,
+};
+
+static int rbtn_rfkill_init(struct acpi_device *device)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+ int ret;
+
+ if (rbtn_data->rfkill)
+ return 0;
+
+ /*
+ * NOTE: rbtn controls all radio devices, not only WLAN
+ * but rfkill interface does not support "ANY" type
+ * so "WLAN" type is used
+ */
+ rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
+ RFKILL_TYPE_WLAN, &rbtn_ops, device);
+ if (!rbtn_data->rfkill)
+ return -ENOMEM;
+
+ ret = rfkill_register(rbtn_data->rfkill);
+ if (ret) {
+ rfkill_destroy(rbtn_data->rfkill);
+ rbtn_data->rfkill = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rbtn_rfkill_exit(struct acpi_device *device)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+
+ if (!rbtn_data->rfkill)
+ return;
+
+ rfkill_unregister(rbtn_data->rfkill);
+ rfkill_destroy(rbtn_data->rfkill);
+ rbtn_data->rfkill = NULL;
+}
+
+static void rbtn_rfkill_event(struct acpi_device *device)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+
+ if (rbtn_data->rfkill)
+ rbtn_rfkill_query(rbtn_data->rfkill, device);
+}
+
+
+/*
+ * input device
+ */
+
+static int rbtn_input_init(struct rbtn_data *rbtn_data)
+{
+ int ret;
+
+ rbtn_data->input_dev = input_allocate_device();
+ if (!rbtn_data->input_dev)
+ return -ENOMEM;
+
+ rbtn_data->input_dev->name = "DELL Wireless hotkeys";
+ rbtn_data->input_dev->phys = "dellabce/input0";
+ rbtn_data->input_dev->id.bustype = BUS_HOST;
+ rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
+ set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
+
+ ret = input_register_device(rbtn_data->input_dev);
+ if (ret) {
+ input_free_device(rbtn_data->input_dev);
+ rbtn_data->input_dev = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rbtn_input_exit(struct rbtn_data *rbtn_data)
+{
+ input_unregister_device(rbtn_data->input_dev);
+ rbtn_data->input_dev = NULL;
+}
+
+static void rbtn_input_event(struct rbtn_data *rbtn_data)
+{
+ input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
+ input_sync(rbtn_data->input_dev);
+ input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
+ input_sync(rbtn_data->input_dev);
+}
+
+
+/*
+ * acpi driver
+ */
+
+static int rbtn_add(struct acpi_device *device);
+static int rbtn_remove(struct acpi_device *device);
+static void rbtn_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id rbtn_ids[] = {
+ { "DELRBTN", 0 },
+ { "DELLABCE", 0 },
+ { "", 0 },
+};
+
+static struct acpi_driver rbtn_driver = {
+ .name = "dell-rbtn",
+ .ids = rbtn_ids,
+ .ops = {
+ .add = rbtn_add,
+ .remove = rbtn_remove,
+ .notify = rbtn_notify,
+ },
+ .owner = THIS_MODULE,
+};
+
+
+/*
+ * notifier export functions
+ */
+
+static bool auto_remove_rfkill = true;
+
+static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
+
+static int rbtn_inc_count(struct device *dev, void *data)
+{
+ struct acpi_device *device = to_acpi_device(dev);
+ struct rbtn_data *rbtn_data = device->driver_data;
+ int *count = data;
+
+ if (rbtn_data->type == RBTN_SLIDER)
+ (*count)++;
+
+ return 0;
+}
+
+static int rbtn_switch_dev(struct device *dev, void *data)
+{
+ struct acpi_device *device = to_acpi_device(dev);
+ struct rbtn_data *rbtn_data = device->driver_data;
+ bool enable = data;
+
+ if (rbtn_data->type != RBTN_SLIDER)
+ return 0;
+
+ if (enable)
+ rbtn_rfkill_init(device);
+ else
+ rbtn_rfkill_exit(device);
+
+ return 0;
+}
+
+int dell_rbtn_notifier_register(struct notifier_block *nb)
+{
+ bool first;
+ int count;
+ int ret;
+
+ count = 0;
+ ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
+ rbtn_inc_count);
+ if (ret || count == 0)
+ return -ENODEV;
+
+ first = !rbtn_chain_head.head;
+
+ ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);
+ if (ret != 0)
+ return ret;
+
+ if (auto_remove_rfkill && first)
+ ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+ (void *)false, rbtn_switch_dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);
+
+int dell_rbtn_notifier_unregister(struct notifier_block *nb)
+{
+ int ret;
+
+ ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);
+ if (ret != 0)
+ return ret;
+
+ if (auto_remove_rfkill && !rbtn_chain_head.head)
+ ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+ (void *)true, rbtn_switch_dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
+
+
+/*
+ * acpi driver functions
+ */
+
+static int rbtn_add(struct acpi_device *device)
+{
+ struct rbtn_data *rbtn_data;
+ enum rbtn_type type;
+ int ret = 0;
+
+ type = rbtn_check(device);
+ if (type == RBTN_UNKNOWN) {
+ dev_info(&device->dev, "Unknown device type\n");
+ return -EINVAL;
+ }
+
+ ret = rbtn_acquire(device, true);
+ if (ret < 0) {
+ dev_err(&device->dev, "Cannot enable device\n");
+ return ret;
+ }
+
+ rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
+ if (!rbtn_data)
+ return -ENOMEM;
+
+ rbtn_data->type = type;
+ device->driver_data = rbtn_data;
+
+ switch (rbtn_data->type) {
+ case RBTN_TOGGLE:
+ ret = rbtn_input_init(rbtn_data);
+ break;
+ case RBTN_SLIDER:
+ if (auto_remove_rfkill && rbtn_chain_head.head)
+ ret = 0;
+ else
+ ret = rbtn_rfkill_init(device);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+
+}
+
+static int rbtn_remove(struct acpi_device *device)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+
+ switch (rbtn_data->type) {
+ case RBTN_TOGGLE:
+ rbtn_input_exit(rbtn_data);
+ break;
+ case RBTN_SLIDER:
+ rbtn_rfkill_exit(device);
+ break;
+ default:
+ break;
+ }
+
+ rbtn_acquire(device, false);
+ device->driver_data = NULL;
+
+ return 0;
+}
+
+static void rbtn_notify(struct acpi_device *device, u32 event)
+{
+ struct rbtn_data *rbtn_data = device->driver_data;
+
+ if (event != 0x80) {
+ dev_info(&device->dev, "Received unknown event (0x%x)\n",
+ event);
+ return;
+ }
+
+ switch (rbtn_data->type) {
+ case RBTN_TOGGLE:
+ rbtn_input_event(rbtn_data);
+ break;
+ case RBTN_SLIDER:
+ rbtn_rfkill_event(device);
+ atomic_notifier_call_chain(&rbtn_chain_head, event, device);
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+ * module functions
+ */
+
+module_acpi_driver(rbtn_driver);
+
+module_param(auto_remove_rfkill, bool, 0444);
+
+MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "
+ "other modules start receiving events "
+ "from this module and re-add them when "
+ "the last module stops receiving events "
+ "(default true)");
+MODULE_DEVICE_TABLE(acpi, rbtn_ids);
+MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h
new file mode 100644
index 000000000000..c59cc6b8ec2b
--- /dev/null
+++ b/drivers/platform/x86/dell-rbtn.h
@@ -0,0 +1,24 @@
+/*
+ Dell Airplane Mode Switch driver
+ Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com>
+
+ 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.
+*/
+
+#ifndef _DELL_RBTN_H_
+#define _DELL_RBTN_H_
+
+struct notifier_block;
+
+int dell_rbtn_notifier_register(struct notifier_block *nb);
+int dell_rbtn_notifier_unregister(struct notifier_block *nb);
+
+#endif
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c
index 6512a06bc053..f2d77fe696ac 100644
--- a/drivers/platform/x86/dell-wmi.c
+++ b/drivers/platform/x86/dell-wmi.c
@@ -35,6 +35,7 @@
#include <linux/acpi.h>
#include <linux/string.h>
#include <linux/dmi.h>
+#include <acpi/video.h>
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
@@ -397,7 +398,7 @@ static int __init dell_wmi_init(void)
}
dmi_walk(find_hk_type, NULL);
- acpi_video = acpi_video_backlight_support();
+ acpi_video = acpi_video_get_backlight_type() != acpi_backlight_vendor;
err = dell_wmi_input_setup();
if (err)
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index 844c2096bde9..8cdf315f9730 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -37,6 +37,7 @@
#include <linux/pci_hotplug.h>
#include <linux/leds.h>
#include <linux/dmi.h>
+#include <acpi/video.h>
#define EEEPC_LAPTOP_VERSION "0.1"
#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver"
@@ -1433,12 +1434,10 @@ static int eeepc_acpi_add(struct acpi_device *device)
if (result)
goto fail_platform;
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
result = eeepc_backlight_init(eeepc);
if (result)
goto fail_backlight;
- } else {
- pr_info("Backlight controlled by ACPI video driver\n");
}
result = eeepc_input_init(eeepc);
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
index 2a9afa261c61..1c62caff93fd 100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -72,6 +72,7 @@
#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
#include <linux/leds.h>
#endif
+#include <acpi/video.h>
#define FUJITSU_DRIVER_VERSION "0.6.0"
@@ -1099,7 +1100,7 @@ static int __init fujitsu_init(void)
/* Register backlight stuff */
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
@@ -1137,8 +1138,7 @@ static int __init fujitsu_init(void)
}
/* Sync backlight power status (needs FUJ02E3 device, hence deferred) */
-
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
fujitsu->bl_device->props.power = FB_BLANK_POWERDOWN;
else
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
index b3d419a84723..76b57388d01b 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -38,6 +38,7 @@
#include <linux/i8042.h>
#include <linux/dmi.h>
#include <linux/device.h>
+#include <acpi/video.h>
#define IDEAPAD_RFKILL_DEV_NUM (3)
@@ -464,8 +465,9 @@ static const struct ideapad_rfk_data ideapad_rfk_data[] = {
static int ideapad_rfk_set(void *data, bool blocked)
{
struct ideapad_rfk_priv *priv = data;
+ int opcode = ideapad_rfk_data[priv->dev].opcode;
- return write_ec_cmd(priv->priv->adev->handle, priv->dev, !blocked);
+ return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked);
}
static struct rfkill_ops ideapad_rfk_ops = {
@@ -830,6 +832,20 @@ 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 G50-30",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
+ },
+ },
+ {
.ident = "Lenovo Yoga 2 11 / 13 / Pro",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@@ -896,7 +912,7 @@ static int ideapad_acpi_add(struct platform_device *pdev)
ideapad_sync_rfk_state(priv);
ideapad_sync_touchpad_state(priv);
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
ret = ideapad_backlight_init(priv);
if (ret && ret != -ENODEV)
goto backlight_failed;
diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c
index a4a4258f6134..6aa33c4a809f 100644
--- a/drivers/platform/x86/intel_oaktrail.c
+++ b/drivers/platform/x86/intel_oaktrail.c
@@ -50,6 +50,7 @@
#include <linux/platform_device.h>
#include <linux/dmi.h>
#include <linux/rfkill.h>
+#include <acpi/video.h>
#define DRIVER_NAME "intel_oaktrail"
#define DRIVER_VERSION "0.4ac1"
@@ -62,7 +63,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
@@ -343,13 +344,11 @@ static int __init oaktrail_init(void)
goto err_device_add;
}
- if (!acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
ret = oaktrail_backlight_init();
if (ret)
goto err_backlight;
-
- } else
- pr_info("Backlight controlled by ACPI video driver\n");
+ }
ret = oaktrail_rfkill_init();
if (ret) {
diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c
new file mode 100644
index 000000000000..105cfffe82c6
--- /dev/null
+++ b/drivers/platform/x86/intel_pmc_ipc.c
@@ -0,0 +1,780 @@
+/*
+ * intel_pmc_ipc.c: Driver for the Intel PMC IPC mechanism
+ *
+ * (C) Copyright 2014-2015 Intel Corporation
+ *
+ * This driver is based on Intel SCU IPC driver(intel_scu_opc.c) by
+ * Sreedhara DS <sreedhara.ds@intel.com>
+ *
+ * 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; version 2
+ * of the License.
+ *
+ * PMC running in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad PMC.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/pm_qos.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/sched.h>
+#include <linux/atomic.h>
+#include <linux/notifier.h>
+#include <linux/suspend.h>
+#include <linux/acpi.h>
+#include <asm/intel_pmc_ipc.h>
+#include <linux/mfd/lpc_ich.h>
+
+/*
+ * IPC registers
+ * The IA write to IPC_CMD command register triggers an interrupt to the ARC,
+ * The ARC handles the interrupt and services it, writing optional data to
+ * the IPC1 registers, updates the IPC_STS response register with the status.
+ */
+#define IPC_CMD 0x0
+#define IPC_CMD_MSI 0x100
+#define IPC_CMD_SIZE 16
+#define IPC_CMD_SUBCMD 12
+#define IPC_STATUS 0x04
+#define IPC_STATUS_IRQ 0x4
+#define IPC_STATUS_ERR 0x2
+#define IPC_STATUS_BUSY 0x1
+#define IPC_SPTR 0x08
+#define IPC_DPTR 0x0C
+#define IPC_WRITE_BUFFER 0x80
+#define IPC_READ_BUFFER 0x90
+
+/*
+ * 16-byte buffer for sending data associated with IPC command.
+ */
+#define IPC_DATA_BUFFER_SIZE 16
+
+#define IPC_LOOP_CNT 3000000
+#define IPC_MAX_SEC 3
+
+#define IPC_TRIGGER_MODE_IRQ true
+
+/* exported resources from IFWI */
+#define PLAT_RESOURCE_IPC_INDEX 0
+#define PLAT_RESOURCE_IPC_SIZE 0x1000
+#define PLAT_RESOURCE_GCR_SIZE 0x1000
+#define PLAT_RESOURCE_PUNIT_DATA_INDEX 1
+#define PLAT_RESOURCE_PUNIT_INTER_INDEX 2
+#define PLAT_RESOURCE_ACPI_IO_INDEX 0
+
+/*
+ * BIOS does not create an ACPI device for each PMC function,
+ * but exports multiple resources from one ACPI device(IPC) for
+ * multiple functions. This driver is responsible to create a
+ * platform device and to export resources for those functions.
+ */
+#define TCO_DEVICE_NAME "iTCO_wdt"
+#define SMI_EN_OFFSET 0x30
+#define SMI_EN_SIZE 4
+#define TCO_BASE_OFFSET 0x60
+#define TCO_REGS_SIZE 16
+#define PUNIT_DEVICE_NAME "intel_punit_ipc"
+
+static const int iTCO_version = 3;
+
+static struct intel_pmc_ipc_dev {
+ struct device *dev;
+ void __iomem *ipc_base;
+ bool irq_mode;
+ int irq;
+ int cmd;
+ struct completion cmd_complete;
+
+ /* The following PMC BARs share the same ACPI device with the IPC */
+ resource_size_t acpi_io_base;
+ int acpi_io_size;
+ struct platform_device *tco_dev;
+
+ /* gcr */
+ resource_size_t gcr_base;
+ int gcr_size;
+
+ /* punit */
+ resource_size_t punit_base;
+ int punit_size;
+ resource_size_t punit_base2;
+ int punit_size2;
+ struct platform_device *punit_dev;
+} ipcdev;
+
+static char *ipc_err_sources[] = {
+ [IPC_ERR_NONE] =
+ "no error",
+ [IPC_ERR_CMD_NOT_SUPPORTED] =
+ "command not supported",
+ [IPC_ERR_CMD_NOT_SERVICED] =
+ "command not serviced",
+ [IPC_ERR_UNABLE_TO_SERVICE] =
+ "unable to service",
+ [IPC_ERR_CMD_INVALID] =
+ "command invalid",
+ [IPC_ERR_CMD_FAILED] =
+ "command failed",
+ [IPC_ERR_EMSECURITY] =
+ "Invalid Battery",
+ [IPC_ERR_UNSIGNEDKERNEL] =
+ "Unsigned kernel",
+};
+
+/* Prevent concurrent calls to the PMC */
+static DEFINE_MUTEX(ipclock);
+
+static inline void ipc_send_command(u32 cmd)
+{
+ ipcdev.cmd = cmd;
+ if (ipcdev.irq_mode) {
+ reinit_completion(&ipcdev.cmd_complete);
+ cmd |= IPC_CMD_MSI;
+ }
+ writel(cmd, ipcdev.ipc_base + IPC_CMD);
+}
+
+static inline u32 ipc_read_status(void)
+{
+ return readl(ipcdev.ipc_base + IPC_STATUS);
+}
+
+static inline void ipc_data_writel(u32 data, u32 offset)
+{
+ writel(data, ipcdev.ipc_base + IPC_WRITE_BUFFER + offset);
+}
+
+static inline u8 ipc_data_readb(u32 offset)
+{
+ return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u32 ipc_data_readl(u32 offset)
+{
+ return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static int intel_pmc_ipc_check_status(void)
+{
+ int status;
+ int ret = 0;
+
+ if (ipcdev.irq_mode) {
+ if (0 == wait_for_completion_timeout(
+ &ipcdev.cmd_complete, IPC_MAX_SEC * HZ))
+ ret = -ETIMEDOUT;
+ } else {
+ int loop_count = IPC_LOOP_CNT;
+
+ while ((ipc_read_status() & IPC_STATUS_BUSY) && --loop_count)
+ udelay(1);
+ if (loop_count == 0)
+ ret = -ETIMEDOUT;
+ }
+
+ status = ipc_read_status();
+ if (ret == -ETIMEDOUT) {
+ dev_err(ipcdev.dev,
+ "IPC timed out, TS=0x%x, CMD=0x%x\n",
+ status, ipcdev.cmd);
+ return ret;
+ }
+
+ if (status & IPC_STATUS_ERR) {
+ int i;
+
+ ret = -EIO;
+ i = (status >> IPC_CMD_SIZE) & 0xFF;
+ if (i < ARRAY_SIZE(ipc_err_sources))
+ dev_err(ipcdev.dev,
+ "IPC failed: %s, STS=0x%x, CMD=0x%x\n",
+ ipc_err_sources[i], status, ipcdev.cmd);
+ else
+ dev_err(ipcdev.dev,
+ "IPC failed: unknown, STS=0x%x, CMD=0x%x\n",
+ status, ipcdev.cmd);
+ if ((i == IPC_ERR_UNSIGNEDKERNEL) || (i == IPC_ERR_EMSECURITY))
+ ret = -EACCES;
+ }
+
+ return ret;
+}
+
+/**
+ * intel_pmc_ipc_simple_command() - Simple IPC command
+ * @cmd: IPC command code.
+ * @sub: IPC command sub type.
+ *
+ * Send a simple IPC command to PMC when don't need to specify
+ * input/output data and source/dest pointers.
+ *
+ * Return: an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_simple_command(int cmd, int sub)
+{
+ int ret;
+
+ mutex_lock(&ipclock);
+ if (ipcdev.dev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+ ipc_send_command(sub << IPC_CMD_SUBCMD | cmd);
+ ret = intel_pmc_ipc_check_status();
+ mutex_unlock(&ipclock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_simple_command);
+
+/**
+ * intel_pmc_ipc_raw_cmd() - IPC command with data and pointers
+ * @cmd: IPC command code.
+ * @sub: IPC command sub type.
+ * @in: input data of this IPC command.
+ * @inlen: input data length in bytes.
+ * @out: output data of this IPC command.
+ * @outlen: output data length in dwords.
+ * @sptr: data writing to SPTR register.
+ * @dptr: data writing to DPTR register.
+ *
+ * Send an IPC command to PMC with input/output data and source/dest pointers.
+ *
+ * Return: an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_raw_cmd(u32 cmd, u32 sub, u8 *in, u32 inlen, u32 *out,
+ u32 outlen, u32 dptr, u32 sptr)
+{
+ u32 wbuf[4] = { 0 };
+ int ret;
+ int i;
+
+ if (inlen > IPC_DATA_BUFFER_SIZE || outlen > IPC_DATA_BUFFER_SIZE / 4)
+ return -EINVAL;
+
+ mutex_lock(&ipclock);
+ if (ipcdev.dev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+ memcpy(wbuf, in, inlen);
+ writel(dptr, ipcdev.ipc_base + IPC_DPTR);
+ writel(sptr, ipcdev.ipc_base + IPC_SPTR);
+ /* The input data register is 32bit register and inlen is in Byte */
+ for (i = 0; i < ((inlen + 3) / 4); i++)
+ ipc_data_writel(wbuf[i], 4 * i);
+ ipc_send_command((inlen << IPC_CMD_SIZE) |
+ (sub << IPC_CMD_SUBCMD) | cmd);
+ ret = intel_pmc_ipc_check_status();
+ if (!ret) {
+ /* out is read from 32bit register and outlen is in 32bit */
+ for (i = 0; i < outlen; i++)
+ *out++ = ipc_data_readl(4 * i);
+ }
+ mutex_unlock(&ipclock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_raw_cmd);
+
+/**
+ * intel_pmc_ipc_command() - IPC command with input/output data
+ * @cmd: IPC command code.
+ * @sub: IPC command sub type.
+ * @in: input data of this IPC command.
+ * @inlen: input data length in bytes.
+ * @out: output data of this IPC command.
+ * @outlen: output data length in dwords.
+ *
+ * Send an IPC command to PMC with input/output data.
+ *
+ * Return: an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
+ u32 *out, u32 outlen)
+{
+ return intel_pmc_ipc_raw_cmd(cmd, sub, in, inlen, out, outlen, 0, 0);
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_command);
+
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+ int status;
+
+ if (ipcdev.irq_mode) {
+ status = ipc_read_status();
+ writel(status | IPC_STATUS_IRQ, ipcdev.ipc_base + IPC_STATUS);
+ }
+ complete(&ipcdev.cmd_complete);
+
+ return IRQ_HANDLED;
+}
+
+static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ resource_size_t pci_resource;
+ int ret;
+ int len;
+
+ ipcdev.dev = &pci_dev_get(pdev)->dev;
+ ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ;
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ ret = pci_request_regions(pdev, "intel_pmc_ipc");
+ if (ret)
+ return ret;
+
+ pci_resource = pci_resource_start(pdev, 0);
+ len = pci_resource_len(pdev, 0);
+ if (!pci_resource || !len) {
+ dev_err(&pdev->dev, "Failed to get resource\n");
+ return -ENOMEM;
+ }
+
+ init_completion(&ipcdev.cmd_complete);
+
+ if (request_irq(pdev->irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) {
+ dev_err(&pdev->dev, "Failed to request irq\n");
+ return -EBUSY;
+ }
+
+ ipcdev.ipc_base = ioremap_nocache(pci_resource, len);
+ if (!ipcdev.ipc_base) {
+ dev_err(&pdev->dev, "Failed to ioremap ipc base\n");
+ free_irq(pdev->irq, &ipcdev);
+ ret = -ENOMEM;
+ }
+
+ return ret;
+}
+
+static void ipc_pci_remove(struct pci_dev *pdev)
+{
+ free_irq(pdev->irq, &ipcdev);
+ pci_release_regions(pdev);
+ pci_dev_put(pdev);
+ iounmap(ipcdev.ipc_base);
+ ipcdev.dev = NULL;
+}
+
+static const struct pci_device_id ipc_pci_ids[] = {
+ {PCI_VDEVICE(INTEL, 0x0a94), 0},
+ {PCI_VDEVICE(INTEL, 0x1a94), 0},
+ { 0,}
+};
+MODULE_DEVICE_TABLE(pci, ipc_pci_ids);
+
+static struct pci_driver ipc_pci_driver = {
+ .name = "intel_pmc_ipc",
+ .id_table = ipc_pci_ids,
+ .probe = ipc_pci_probe,
+ .remove = ipc_pci_remove,
+};
+
+static ssize_t intel_pmc_ipc_simple_cmd_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int subcmd;
+ int cmd;
+ int ret;
+
+ ret = sscanf(buf, "%d %d", &cmd, &subcmd);
+ if (ret != 2) {
+ dev_err(dev, "Error args\n");
+ return -EINVAL;
+ }
+
+ ret = intel_pmc_ipc_simple_command(cmd, subcmd);
+ if (ret) {
+ dev_err(dev, "command %d error with %d\n", cmd, ret);
+ return ret;
+ }
+ return (ssize_t)count;
+}
+
+static ssize_t intel_pmc_ipc_northpeak_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long val;
+ int subcmd;
+ int ret;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ if (val)
+ subcmd = 1;
+ else
+ subcmd = 0;
+ ret = intel_pmc_ipc_simple_command(PMC_IPC_NORTHPEAK_CTRL, subcmd);
+ if (ret) {
+ dev_err(dev, "command north %d error with %d\n", subcmd, ret);
+ return ret;
+ }
+ return (ssize_t)count;
+}
+
+static DEVICE_ATTR(simplecmd, S_IWUSR,
+ NULL, intel_pmc_ipc_simple_cmd_store);
+static DEVICE_ATTR(northpeak, S_IWUSR,
+ NULL, intel_pmc_ipc_northpeak_store);
+
+static struct attribute *intel_ipc_attrs[] = {
+ &dev_attr_northpeak.attr,
+ &dev_attr_simplecmd.attr,
+ NULL
+};
+
+static const struct attribute_group intel_ipc_group = {
+ .attrs = intel_ipc_attrs,
+};
+
+#define PUNIT_RESOURCE_INTER 1
+static struct resource punit_res[] = {
+ /* Punit */
+ {
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+#define TCO_RESOURCE_ACPI_IO 0
+#define TCO_RESOURCE_SMI_EN_IO 1
+#define TCO_RESOURCE_GCR_MEM 2
+static struct resource tco_res[] = {
+ /* ACPI - TCO */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* ACPI - SMI */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* GCS */
+ {
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct lpc_ich_info tco_info = {
+ .name = "Apollo Lake SoC",
+ .iTCO_version = 3,
+};
+
+static int ipc_create_punit_device(void)
+{
+ struct platform_device *pdev;
+ struct resource *res;
+ int ret;
+
+ pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1);
+ if (!pdev) {
+ dev_err(ipcdev.dev, "Failed to alloc punit platform device\n");
+ return -ENOMEM;
+ }
+
+ pdev->dev.parent = ipcdev.dev;
+
+ res = punit_res;
+ res->start = ipcdev.punit_base;
+ res->end = res->start + ipcdev.punit_size - 1;
+
+ res = punit_res + PUNIT_RESOURCE_INTER;
+ res->start = ipcdev.punit_base2;
+ res->end = res->start + ipcdev.punit_size2 - 1;
+
+ ret = platform_device_add_resources(pdev, punit_res,
+ ARRAY_SIZE(punit_res));
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add platform punit resources\n");
+ goto err;
+ }
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add punit platform device\n");
+ goto err;
+ }
+ ipcdev.punit_dev = pdev;
+
+ return 0;
+err:
+ platform_device_put(pdev);
+ return ret;
+}
+
+static int ipc_create_tco_device(void)
+{
+ struct platform_device *pdev;
+ struct resource *res;
+ int ret;
+
+ pdev = platform_device_alloc(TCO_DEVICE_NAME, -1);
+ if (!pdev) {
+ dev_err(ipcdev.dev, "Failed to alloc tco platform device\n");
+ return -ENOMEM;
+ }
+
+ pdev->dev.parent = ipcdev.dev;
+
+ res = tco_res + TCO_RESOURCE_ACPI_IO;
+ res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET;
+ res->end = res->start + TCO_REGS_SIZE - 1;
+
+ res = tco_res + TCO_RESOURCE_SMI_EN_IO;
+ res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET;
+ res->end = res->start + SMI_EN_SIZE - 1;
+
+ res = tco_res + TCO_RESOURCE_GCR_MEM;
+ res->start = ipcdev.gcr_base;
+ res->end = res->start + ipcdev.gcr_size - 1;
+
+ ret = platform_device_add_resources(pdev, tco_res, ARRAY_SIZE(tco_res));
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add tco platform resources\n");
+ goto err;
+ }
+
+ ret = platform_device_add_data(pdev, &tco_info,
+ sizeof(struct lpc_ich_info));
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add tco platform data\n");
+ goto err;
+ }
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add tco platform device\n");
+ goto err;
+ }
+ ipcdev.tco_dev = pdev;
+
+ return 0;
+err:
+ platform_device_put(pdev);
+ return ret;
+}
+
+static int ipc_create_pmc_devices(void)
+{
+ int ret;
+
+ ret = ipc_create_tco_device();
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add tco platform device\n");
+ return ret;
+ }
+ ret = ipc_create_punit_device();
+ if (ret) {
+ dev_err(ipcdev.dev, "Failed to add punit platform device\n");
+ platform_device_unregister(ipcdev.tco_dev);
+ }
+ return ret;
+}
+
+static int ipc_plat_get_res(struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *addr;
+ int size;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO,
+ PLAT_RESOURCE_ACPI_IO_INDEX);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get io resource\n");
+ return -ENXIO;
+ }
+ size = resource_size(res);
+ ipcdev.acpi_io_base = res->start;
+ ipcdev.acpi_io_size = size;
+ dev_info(&pdev->dev, "io res: %llx %x\n",
+ (long long)res->start, (int)resource_size(res));
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ PLAT_RESOURCE_PUNIT_DATA_INDEX);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get punit resource\n");
+ return -ENXIO;
+ }
+ size = resource_size(res);
+ ipcdev.punit_base = res->start;
+ ipcdev.punit_size = size;
+ dev_info(&pdev->dev, "punit data res: %llx %x\n",
+ (long long)res->start, (int)resource_size(res));
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ PLAT_RESOURCE_PUNIT_INTER_INDEX);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get punit inter resource\n");
+ return -ENXIO;
+ }
+ size = resource_size(res);
+ ipcdev.punit_base2 = res->start;
+ ipcdev.punit_size2 = size;
+ dev_info(&pdev->dev, "punit interface res: %llx %x\n",
+ (long long)res->start, (int)resource_size(res));
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ PLAT_RESOURCE_IPC_INDEX);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get ipc resource\n");
+ return -ENXIO;
+ }
+ size = PLAT_RESOURCE_IPC_SIZE;
+ if (!request_mem_region(res->start, size, pdev->name)) {
+ dev_err(&pdev->dev, "Failed to request ipc resource\n");
+ return -EBUSY;
+ }
+ addr = ioremap_nocache(res->start, size);
+ if (!addr) {
+ dev_err(&pdev->dev, "I/O memory remapping failed\n");
+ release_mem_region(res->start, size);
+ return -ENOMEM;
+ }
+ ipcdev.ipc_base = addr;
+
+ ipcdev.gcr_base = res->start + size;
+ ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE;
+ dev_info(&pdev->dev, "ipc res: %llx %x\n",
+ (long long)res->start, (int)resource_size(res));
+
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id ipc_acpi_ids[] = {
+ { "INT34D2", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, ipc_acpi_ids);
+#endif
+
+static int ipc_plat_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ ipcdev.dev = &pdev->dev;
+ ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ;
+ init_completion(&ipcdev.cmd_complete);
+
+ ipcdev.irq = platform_get_irq(pdev, 0);
+ if (ipcdev.irq < 0) {
+ dev_err(&pdev->dev, "Failed to get irq\n");
+ return -EINVAL;
+ }
+
+ ret = ipc_plat_get_res(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request resource\n");
+ return ret;
+ }
+
+ ret = ipc_create_pmc_devices();
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create pmc devices\n");
+ goto err_device;
+ }
+
+ if (request_irq(ipcdev.irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) {
+ dev_err(&pdev->dev, "Failed to request irq\n");
+ ret = -EBUSY;
+ goto err_irq;
+ }
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &intel_ipc_group);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create sysfs group %d\n",
+ ret);
+ goto err_sys;
+ }
+
+ return 0;
+err_sys:
+ free_irq(ipcdev.irq, &ipcdev);
+err_irq:
+ platform_device_unregister(ipcdev.tco_dev);
+ platform_device_unregister(ipcdev.punit_dev);
+err_device:
+ iounmap(ipcdev.ipc_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ PLAT_RESOURCE_IPC_INDEX);
+ if (res)
+ release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+ return ret;
+}
+
+static int ipc_plat_remove(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group);
+ free_irq(ipcdev.irq, &ipcdev);
+ platform_device_unregister(ipcdev.tco_dev);
+ platform_device_unregister(ipcdev.punit_dev);
+ iounmap(ipcdev.ipc_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ PLAT_RESOURCE_IPC_INDEX);
+ if (res)
+ release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+ ipcdev.dev = NULL;
+ return 0;
+}
+
+static struct platform_driver ipc_plat_driver = {
+ .remove = ipc_plat_remove,
+ .probe = ipc_plat_probe,
+ .driver = {
+ .name = "pmc-ipc-plat",
+ .acpi_match_table = ACPI_PTR(ipc_acpi_ids),
+ },
+};
+
+static int __init intel_pmc_ipc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&ipc_plat_driver);
+ if (ret) {
+ pr_err("Failed to register PMC ipc platform driver\n");
+ return ret;
+ }
+ ret = pci_register_driver(&ipc_pci_driver);
+ if (ret) {
+ pr_err("Failed to register PMC ipc pci driver\n");
+ platform_driver_unregister(&ipc_plat_driver);
+ return ret;
+ }
+ return ret;
+}
+
+static void __exit intel_pmc_ipc_exit(void)
+{
+ pci_unregister_driver(&ipc_pci_driver);
+ platform_driver_unregister(&ipc_plat_driver);
+}
+
+MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
+MODULE_DESCRIPTION("Intel PMC IPC driver");
+MODULE_LICENSE("GPL");
+
+/* Some modules are dependent on this, so init earlier */
+fs_initcall(intel_pmc_ipc_init);
+module_exit(intel_pmc_ipc_exit);
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
index 001b199a8c33..187d1086d15c 100644
--- a/drivers/platform/x86/intel_scu_ipc.c
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -216,13 +216,13 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
int nc;
u32 offset = 0;
int err;
- u8 cbuf[IPC_WWBUF_SIZE] = { };
+ u8 cbuf[IPC_WWBUF_SIZE];
u32 *wbuf = (u32 *)&cbuf;
- mutex_lock(&ipclock);
-
memset(cbuf, 0, sizeof(cbuf));
+ mutex_lock(&ipclock);
+
if (ipcdev.pdev == NULL) {
mutex_unlock(&ipclock);
return -ENODEV;
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
index 085987730aab..42317704629d 100644
--- a/drivers/platform/x86/msi-laptop.c
+++ b/drivers/platform/x86/msi-laptop.c
@@ -64,6 +64,7 @@
#include <linux/i8042.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <acpi/video.h>
#define MSI_DRIVER_VERSION "0.5"
@@ -1069,9 +1070,8 @@ static int __init msi_init(void)
/* Register backlight stuff */
- if (!quirks->old_ec_model || acpi_video_backlight_support()) {
- pr_info("Brightness ignored, must be controlled by ACPI video driver\n");
- } else {
+ if (quirks->old_ec_model ||
+ acpi_video_get_backlight_type() == acpi_backlight_vendor) {
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
index 6d2bac0c463c..978e6d640572 100644
--- a/drivers/platform/x86/msi-wmi.c
+++ b/drivers/platform/x86/msi-wmi.c
@@ -29,6 +29,7 @@
#include <linux/backlight.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <acpi/video.h>
MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
@@ -320,7 +321,8 @@ static int __init msi_wmi_init(void)
break;
}
- if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) {
+ if (wmi_has_guid(MSIWMI_BIOS_GUID) &&
+ acpi_video_get_backlight_type() == acpi_backlight_vendor) {
err = msi_wmi_backlight_setup();
if (err) {
pr_err("Unable to setup backlight device\n");
diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c
index 073a90a63dbc..fd86daba7ffd 100644
--- a/drivers/platform/x86/pvpanic.c
+++ b/drivers/platform/x86/pvpanic.c
@@ -92,13 +92,13 @@ pvpanic_walk_resources(struct acpi_resource *res, void *context)
static int pvpanic_add(struct acpi_device *device)
{
- acpi_status status;
- u64 ret;
+ int ret;
- status = acpi_evaluate_integer(device->handle, "_STA", NULL,
- &ret);
+ ret = acpi_bus_get_status(device);
+ if (ret < 0)
+ return ret;
- if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B)
+ if (!device->status.enabled || !device->status.functional)
return -ENODEV;
acpi_walk_resources(device->handle, METHOD_NAME__CRS,
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
index 9e701b2256f9..8c146e2b6727 100644
--- a/drivers/platform/x86/samsung-laptop.c
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -1720,27 +1720,14 @@ static int __init samsung_init(void)
samsung->handle_backlight = true;
samsung->quirks = quirks;
-
#ifdef CONFIG_ACPI
if (samsung->quirks->broken_acpi_video)
- acpi_video_dmi_promote_vendor();
-
- /* Don't handle backlight here if the acpi video already handle it */
- if (acpi_video_backlight_support()) {
- samsung->handle_backlight = false;
- } else if (samsung->quirks->broken_acpi_video) {
- pr_info("Disabling ACPI video driver\n");
- acpi_video_unregister();
- }
+ acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+ if (samsung->quirks->use_native_backlight)
+ acpi_video_set_dmi_backlight_type(acpi_backlight_native);
- 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 */
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
samsung->handle_backlight = false;
- }
#endif
ret = samsung_platform_init(samsung);
@@ -1751,12 +1738,6 @@ static int __init samsung_init(void)
if (ret)
goto error_sabi;
-#ifdef CONFIG_ACPI
- /* Only log that if we are really on a sabi platform */
- if (acpi_video_backlight_support())
- pr_info("Backlight controlled by ACPI video driver\n");
-#endif
-
ret = samsung_sysfs_init(samsung);
if (ret)
goto error_sysfs;
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index e51c1e753607..aeb80d1c2b07 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -69,6 +69,7 @@
#include <linux/miscdevice.h>
#endif
#include <asm/uaccess.h>
+#include <acpi/video.h>
#define dprintk(fmt, ...) \
do { \
@@ -3198,12 +3199,8 @@ static int sony_nc_add(struct acpi_device *device)
sony_nc_function_setup(device, sony_pf_device);
}
- /* setup input devices and helper fifo */
- if (acpi_video_backlight_support()) {
- pr_info("brightness ignored, must be controlled by ACPI video driver\n");
- } else {
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
sony_nc_backlight_setup();
- }
/* create sony_pf sysfs attributes related to the SNC device */
for (item = sony_nc_values; item->name; ++item) {
diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c
index e36542564131..89aa976f0ab2 100644
--- a/drivers/platform/x86/tc1100-wmi.c
+++ b/drivers/platform/x86/tc1100-wmi.c
@@ -82,7 +82,7 @@ static int get_state(u32 *out, u8 instance)
tmp = 0;
}
- if (result.length > 0 && result.pointer)
+ if (result.length > 0)
kfree(result.pointer);
switch (instance) {
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 3b8ceee7c5cb..33e488cf5569 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -83,6 +83,7 @@
#include <sound/control.h>
#include <sound/initval.h>
#include <asm/uaccess.h>
+#include <acpi/video.h>
/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
@@ -319,6 +320,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 {
@@ -1911,6 +1913,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
};
@@ -2093,7 +2116,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 &
@@ -2647,9 +2670,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,
@@ -2685,9 +2706,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,
@@ -2697,8 +2716,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,
@@ -2710,8 +2728,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,
@@ -2722,8 +2739,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,
@@ -2735,9 +2751,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
@@ -2792,9 +2806,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,
@@ -2826,9 +2838,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 */
@@ -2849,8 +2859,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)
{
@@ -2872,8 +2881,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)
{
@@ -2890,8 +2898,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)
{
@@ -2907,9 +2914,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)
{
@@ -2917,14 +2923,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,
@@ -3118,6 +3175,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 */
@@ -3174,6 +3238,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 */
},
};
@@ -3227,6 +3320,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));
@@ -3381,7 +3488,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
/* Do not issue duplicate brightness change events to
* userspace. tpacpi_detect_brightness_capabilities() must have
* been called before this point */
- if (acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
pr_info("This ThinkPad has standard ACPI backlight "
"brightness control, supported by the ACPI "
"video driver\n");
@@ -3437,6 +3544,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;
@@ -3449,14 +3559,6 @@ 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
-};
-
static const int adaptive_keyboard_modes[] = {
HOME_MODE,
/* WEB_BROWSER_MODE = 2,
@@ -3466,6 +3568,8 @@ static 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.
@@ -3473,6 +3577,32 @@ static 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;
@@ -3493,8 +3623,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:
@@ -3502,43 +3633,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, &current_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;
}
}
@@ -3836,28 +3975,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 +4004,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 +4207,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 +4395,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 +5173,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);
/* --------------------------------------------------------------------- */
@@ -6368,7 +6492,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
return 1;
}
- if (acpi_video_backlight_support()) {
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
if (brightness_enable > 1) {
pr_info("Standard ACPI backlight interface "
"available, not loading native one\n");
@@ -8017,9 +8141,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,
@@ -8079,9 +8202,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,
@@ -8098,9 +8219,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,
@@ -8117,9 +8236,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,
@@ -8152,8 +8269,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
};
@@ -8287,7 +8404,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);
@@ -8735,8 +8852,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);
/* --------------------------------------------------------------------- */
@@ -9278,8 +9394,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)
@@ -9400,8 +9515,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 dbcb7a8915b8..3ad7b1fa24ce 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -31,7 +31,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#define TOSHIBA_ACPI_VERSION "0.21"
+#define TOSHIBA_ACPI_VERSION "0.22"
#define PROC_INTERFACE_VERSION 1
#include <linux/kernel.h>
@@ -41,7 +41,6 @@
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/backlight.h>
-#include <linux/rfkill.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/leds.h>
@@ -51,6 +50,7 @@
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/uaccess.h>
+#include <acpi/video.h>
MODULE_AUTHOR("John Belmonte");
MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver");
@@ -81,7 +81,7 @@ MODULE_LICENSE("GPL");
#define TCI_WORDS 6
-/* operations */
+/* Operations */
#define HCI_SET 0xff00
#define HCI_GET 0xfe00
#define SCI_OPEN 0xf100
@@ -89,7 +89,7 @@ MODULE_LICENSE("GPL");
#define SCI_GET 0xf300
#define SCI_SET 0xf400
-/* return codes */
+/* Return codes */
#define TOS_SUCCESS 0x0000
#define TOS_OPEN_CLOSE_OK 0x0044
#define TOS_FAILURE 0x1000
@@ -104,7 +104,7 @@ MODULE_LICENSE("GPL");
#define TOS_NOT_INITIALIZED 0x8d50
#define TOS_NOT_INSTALLED 0x8e00
-/* registers */
+/* Registers */
#define HCI_FAN 0x0004
#define HCI_TR_BACKLIGHT 0x0005
#define HCI_SYSTEM_EVENT 0x0016
@@ -116,6 +116,7 @@ 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
@@ -125,14 +126,17 @@ MODULE_LICENSE("GPL");
#define SCI_TOUCHPAD 0x050e
#define SCI_KBD_FUNCTION_KEYS 0x0522
-/* field definitions */
+/* 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
@@ -147,9 +151,10 @@ MODULE_LICENSE("GPL");
#define SCI_KBD_MODE_OFF 0x10
#define SCI_KBD_TIME_MAX 0x3c001a
#define SCI_USB_CHARGE_MODE_MASK 0xff
-#define SCI_USB_CHARGE_DISABLED 0x30000
-#define SCI_USB_CHARGE_ALTERNATE 0x30009
-#define SCI_USB_CHARGE_AUTO 0x30021
+#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
@@ -159,7 +164,6 @@ MODULE_LICENSE("GPL");
struct toshiba_acpi_dev {
struct acpi_device *acpi_dev;
const char *method_hci;
- struct rfkill *bt_rfk;
struct input_dev *hotkey_dev;
struct work_struct hotkey_work;
struct backlight_device *backlight_dev;
@@ -174,6 +178,8 @@ struct toshiba_acpi_dev {
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;
@@ -194,8 +200,6 @@ struct toshiba_acpi_dev {
unsigned int panel_power_on_supported:1;
unsigned int usb_three_supported:1;
unsigned int sysfs_created:1;
-
- struct mutex mutex;
};
static struct toshiba_acpi_dev *toshiba_acpi;
@@ -243,29 +247,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 } },
@@ -281,6 +262,14 @@ static const struct key_entry toshiba_acpi_alt_keymap[] = {
};
/*
+ * 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
*/
@@ -337,13 +326,13 @@ static acpi_status tci_raw(struct toshiba_acpi_dev *dev,
}
/*
- * Common hci tasks (get or set one or two value)
+ * Common hci tasks
*
* In addition to the ACPI status, the HCI system returns a result which
* may be useful (such as "not supported").
*/
-static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
+static u32 hci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
{
u32 in[TCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 };
u32 out[TCI_WORDS];
@@ -352,7 +341,7 @@ static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
}
-static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
+static u32 hci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
{
u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 };
u32 out[TCI_WORDS];
@@ -366,31 +355,6 @@ static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
return out[0];
}
-static u32 hci_write2(struct toshiba_acpi_dev *dev, u32 reg, u32 in1, u32 in2)
-{
- u32 in[TCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 };
- u32 out[TCI_WORDS];
- acpi_status status = tci_raw(dev, in, out);
-
- return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
-}
-
-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;
-
- *out1 = out[2];
- *out2 = out[3];
-
- return out[0];
-}
-
/*
* Common sci tasks
*/
@@ -402,7 +366,7 @@ static int sci_open(struct toshiba_acpi_dev *dev)
acpi_status status;
status = tci_raw(dev, in, out);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to open SCI failed\n");
return 0;
}
@@ -440,7 +404,7 @@ static void sci_close(struct toshiba_acpi_dev *dev)
acpi_status status;
status = tci_raw(dev, in, out);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to close SCI failed\n");
return;
}
@@ -488,7 +452,7 @@ static int toshiba_illumination_available(struct toshiba_acpi_dev *dev)
status = tci_raw(dev, in, out);
sci_close(dev);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to query Illumination support failed\n");
return 0;
} else if (out[0] == TOS_NOT_SUPPORTED) {
@@ -529,7 +493,7 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev)
struct toshiba_acpi_dev, led_dev);
u32 state, result;
- /* First request : initialize communication. */
+ /* First request : initialize communication. */
if (!sci_open(dev))
return LED_OFF;
@@ -632,7 +596,7 @@ static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev)
u32 state, result;
/* Check the keyboard backlight state */
- result = hci_read1(dev, HCI_KBD_ILLUMINATION, &state);
+ result = hci_read(dev, HCI_KBD_ILLUMINATION, &state);
if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) {
pr_err("ACPI call to get the keyboard backlight failed\n");
return LED_OFF;
@@ -653,7 +617,7 @@ static void toshiba_kbd_backlight_set(struct led_classdev *cdev,
/* Set the keyboard backlight state */
state = brightness ? 1 : 0;
- result = hci_write1(dev, HCI_KBD_ILLUMINATION, state);
+ result = hci_write(dev, HCI_KBD_ILLUMINATION, state);
if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) {
pr_err("ACPI call to set KBD Illumination mode failed\n");
return;
@@ -710,7 +674,7 @@ static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev)
u32 out[TCI_WORDS];
status = tci_raw(dev, in, out);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to get ECO led failed\n");
} else if (out[0] == TOS_NOT_INSTALLED) {
pr_info("ECO led not installed");
@@ -819,6 +783,54 @@ static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev,
}
/* 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)) {
+ 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)) {
+ 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)
{
@@ -878,7 +890,7 @@ static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev,
in[5] = SCI_USB_CHARGE_BAT_LVL;
status = tci_raw(dev, in, out);
sci_close(dev);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to get USB S&C battery level failed\n");
return -EIO;
} else if (out[0] == TOS_NOT_SUPPORTED) {
@@ -907,7 +919,7 @@ static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev,
in[5] = SCI_USB_CHARGE_BAT_LVL;
status = tci_raw(dev, in, out);
sci_close(dev);
- if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) {
+ if (ACPI_FAILURE(status)) {
pr_err("ACPI call to set USB S&C battery level failed\n");
return -EIO;
} else if (out[0] == TOS_NOT_SUPPORTED) {
@@ -933,12 +945,12 @@ static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev,
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 S&C battery level failed\n");
+ if (ACPI_FAILURE(status)) {
+ 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 Sleep and Charge not supported\n");
+ pr_info("USB Rapid Charge not supported\n");
return -ENODEV;
}
@@ -961,11 +973,11 @@ static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev,
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 S&C battery level failed\n");
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to set USB Rapid Charge failed\n");
return -EIO;
} else if (out[0] == TOS_NOT_SUPPORTED) {
- pr_info("USB Sleep and Charge not supported\n");
+ pr_info("USB Rapid Charge not supported\n");
return -ENODEV;
} else if (out[0] == TOS_INPUT_DATA_ERROR) {
return -EIO;
@@ -984,10 +996,10 @@ static int toshiba_usb_sleep_music_get(struct toshiba_acpi_dev *dev, u32 *state)
result = sci_read(dev, SCI_USB_SLEEP_MUSIC, state);
sci_close(dev);
if (result == TOS_FAILURE) {
- pr_err("ACPI call to set USB S&C mode failed\n");
+ pr_err("ACPI call to get Sleep and Music failed\n");
return -EIO;
} else if (result == TOS_NOT_SUPPORTED) {
- pr_info("USB Sleep and Charge not supported\n");
+ pr_info("Sleep and Music not supported\n");
return -ENODEV;
} else if (result == TOS_INPUT_DATA_ERROR) {
return -EIO;
@@ -1006,10 +1018,10 @@ static int toshiba_usb_sleep_music_set(struct toshiba_acpi_dev *dev, u32 state)
result = sci_write(dev, SCI_USB_SLEEP_MUSIC, state);
sci_close(dev);
if (result == TOS_FAILURE) {
- pr_err("ACPI call to set USB S&C mode failed\n");
+ pr_err("ACPI call to set Sleep and Music failed\n");
return -EIO;
} else if (result == TOS_NOT_SUPPORTED) {
- pr_info("USB Sleep and Charge not supported\n");
+ pr_info("Sleep and Music not supported\n");
return -ENODEV;
} else if (result == TOS_INPUT_DATA_ERROR) {
return -EIO;
@@ -1149,103 +1161,35 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state)
return 0;
}
-/* Bluetooth rfkill handlers */
-
-static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present)
-{
- u32 hci_result;
- u32 value, value2;
-
- value = 0;
- value2 = 0;
- hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2);
- if (hci_result == TOS_SUCCESS)
- *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false;
-
- return hci_result;
-}
-
-static u32 hci_get_radio_state(struct toshiba_acpi_dev *dev, bool *radio_state)
+/* Hotkey Event type */
+static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev,
+ u32 *type)
{
- u32 hci_result;
- u32 value, value2;
-
- value = 0;
- value2 = 0x0001;
- hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2);
-
- *radio_state = value & HCI_WIRELESS_KILL_SWITCH;
- return hci_result;
-}
-
-static int bt_rfkill_set_block(void *data, bool blocked)
-{
- struct toshiba_acpi_dev *dev = data;
- u32 result1, result2;
- u32 value;
- int err;
- bool radio_state;
-
- value = (blocked == false);
-
- mutex_lock(&dev->mutex);
- if (hci_get_radio_state(dev, &radio_state) != TOS_SUCCESS) {
- err = -EIO;
- goto out;
- }
-
- if (!radio_state) {
- err = 0;
- goto out;
- }
-
- result1 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER);
- result2 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH);
-
- if (result1 != TOS_SUCCESS || result2 != TOS_SUCCESS)
- err = -EIO;
- else
- err = 0;
- out:
- mutex_unlock(&dev->mutex);
- return err;
-}
-
-static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
-{
- bool new_rfk_state;
- bool value;
- u32 hci_result;
- struct toshiba_acpi_dev *dev = data;
-
- mutex_lock(&dev->mutex);
+ u32 in[TCI_WORDS] = { HCI_GET, HCI_SYSTEM_INFO, 0x03, 0, 0, 0 };
+ u32 out[TCI_WORDS];
+ acpi_status status;
- hci_result = hci_get_radio_state(dev, &value);
- if (hci_result != TOS_SUCCESS) {
- /* Can't do anything useful */
- mutex_unlock(&dev->mutex);
- return;
+ status = tci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to get System type failed\n");
+ return -EIO;
+ } else if (out[0] == TOS_NOT_SUPPORTED) {
+ pr_info("System type not supported\n");
+ return -ENODEV;
}
- new_rfk_state = value;
+ *type = out[3];
- mutex_unlock(&dev->mutex);
-
- if (rfkill_set_hw_state(rfkill, !new_rfk_state))
- bt_rfkill_set_block(data, true);
+ return 0;
}
-static const struct rfkill_ops toshiba_rfk_ops = {
- .set_block = bt_rfkill_set_block,
- .poll = bt_rfkill_poll,
-};
-
+/* Transflective Backlight */
static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled)
{
u32 hci_result;
u32 status;
- hci_result = hci_read1(dev, HCI_TR_BACKLIGHT, &status);
+ hci_result = hci_read(dev, HCI_TR_BACKLIGHT, &status);
*enabled = !status;
return hci_result == TOS_SUCCESS ? 0 : -EIO;
}
@@ -1255,12 +1199,13 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable)
u32 hci_result;
u32 value = !enable;
- hci_result = hci_write1(dev, HCI_TR_BACKLIGHT, value);
+ hci_result = hci_write(dev, HCI_TR_BACKLIGHT, value);
return hci_result == TOS_SUCCESS ? 0 : -EIO;
}
-static struct proc_dir_entry *toshiba_proc_dir /*= 0*/;
+static struct proc_dir_entry *toshiba_proc_dir;
+/* LCD Brightness */
static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
{
u32 hci_result;
@@ -1278,7 +1223,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
brightness++;
}
- hci_result = hci_read1(dev, HCI_LCD_BRIGHTNESS, &value);
+ hci_result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value);
if (hci_result == TOS_SUCCESS)
return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT);
@@ -1333,7 +1278,7 @@ static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value)
}
value = value << HCI_LCD_BRIGHTNESS_SHIFT;
- hci_result = hci_write1(dev, HCI_LCD_BRIGHTNESS, value);
+ hci_result = hci_write(dev, HCI_LCD_BRIGHTNESS, value);
return hci_result == TOS_SUCCESS ? 0 : -EIO;
}
@@ -1383,7 +1328,7 @@ static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status)
{
u32 hci_result;
- hci_result = hci_read1(dev, HCI_VIDEO_OUT, status);
+ hci_result = hci_read(dev, HCI_VIDEO_OUT, status);
return hci_result == TOS_SUCCESS ? 0 : -EIO;
}
@@ -1468,7 +1413,8 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf,
_set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out);
/*
* To avoid unnecessary video disruption, only write the new
- * video setting if something changed. */
+ * video setting if something changed.
+ */
if (new_video_out != video_out)
ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out);
}
@@ -1489,7 +1435,7 @@ static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status)
{
u32 hci_result;
- hci_result = hci_read1(dev, HCI_FAN, status);
+ hci_result = hci_read(dev, HCI_FAN, status);
return hci_result == TOS_SUCCESS ? 0 : -EIO;
}
@@ -1529,7 +1475,7 @@ 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);
+ hci_result = hci_write(dev, HCI_FAN, value);
if (hci_result == TOS_SUCCESS)
dev->force_fan = value;
else
@@ -1557,7 +1503,7 @@ static int keys_proc_show(struct seq_file *m, void *v)
u32 value;
if (!dev->key_event_valid && dev->system_event_supported) {
- hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value);
+ hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value);
if (hci_result == TOS_SUCCESS) {
dev->key_event_valid = 1;
dev->last_key_event = value;
@@ -1569,7 +1515,7 @@ static int keys_proc_show(struct seq_file *m, void *v)
* some machines where system events sporadically
* become disabled.
*/
- hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1);
+ hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1);
pr_notice("Re-enabled hotkeys\n");
} else {
pr_err("Error reading hotkey status\n");
@@ -1706,7 +1652,7 @@ static ssize_t fan_store(struct device *dev,
if (state != 0 && state != 1)
return -EINVAL;
- result = hci_write1(toshiba, HCI_FAN, state);
+ result = hci_write(toshiba, HCI_FAN, state);
if (result == TOS_FAILURE)
return -EIO;
else if (result == TOS_NOT_SUPPORTED)
@@ -1973,17 +1919,21 @@ static ssize_t usb_sleep_charge_store(struct device *dev,
* 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)
+ 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;
+ mode |= SCI_USB_CHARGE_DISABLED;
else if (state == 1)
- mode = SCI_USB_CHARGE_ALTERNATE;
+ mode |= SCI_USB_CHARGE_ALTERNATE;
else if (state == 2)
- mode = SCI_USB_CHARGE_AUTO;
+ mode |= SCI_USB_CHARGE_AUTO;
+ else if (state == 3)
+ mode |= SCI_USB_CHARGE_TYPICAL;
ret = toshiba_usb_sleep_charge_set(toshiba, mode);
if (ret)
@@ -2324,7 +2274,7 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
if (ACPI_FAILURE(status))
return -ENODEV;
- result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE);
+ result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE);
if (result == TOS_FAILURE)
return -EIO;
else if (result == TOS_NOT_SUPPORTED)
@@ -2333,6 +2283,20 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
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_write(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)
{
@@ -2409,7 +2373,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
toshiba_acpi_report_hotkey(dev, scancode);
} else if (dev->system_event_supported) {
do {
- hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value);
+ hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value);
switch (hci_result) {
case TOS_SUCCESS:
toshiba_acpi_report_hotkey(dev, (int)value);
@@ -2421,7 +2385,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
* sporadically become disabled.
*/
hci_result =
- hci_write1(dev, HCI_SYSTEM_EVENT, 1);
+ hci_write(dev, HCI_SYSTEM_EVENT, 1);
pr_notice("Re-enabled hotkeys\n");
/* Fall through */
default:
@@ -2434,10 +2398,22 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
{
+ 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)
@@ -2447,8 +2423,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;
@@ -2480,7 +2462,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
if (acpi_has_method(dev->acpi_dev->handle, "INFO"))
dev->info_supported = 1;
else {
- hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1);
+ hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1);
if (hci_result == TOS_SUCCESS)
dev->system_event_supported = 1;
}
@@ -2490,12 +2472,6 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
goto err_remove_filter;
}
- error = toshiba_acpi_enable_hotkeys(dev);
- if (error) {
- pr_info("Unable to enable hotkeys\n");
- goto err_remove_filter;
- }
-
error = input_register_device(dev->hotkey_dev);
if (error) {
pr_info("Unable to register input device\n");
@@ -2541,6 +2517,17 @@ 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_set_dmi_backlight_type(acpi_backlight_vendor);
+
+ if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+ return 0;
+
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
@@ -2585,11 +2572,6 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
sparse_keymap_free(dev->hotkey_dev);
}
- if (dev->bt_rfk) {
- rfkill_unregister(dev->bt_rfk);
- rfkill_destroy(dev->bt_rfk);
- }
-
backlight_device_unregister(dev->backlight_dev);
if (dev->illumination_supported)
@@ -2624,8 +2606,8 @@ 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;
if (toshiba_acpi)
@@ -2648,36 +2630,23 @@ 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");
- mutex_init(&dev->mutex);
-
ret = toshiba_acpi_setup_backlight(dev);
if (ret)
goto error;
- /* Register rfkill switch for Bluetooth */
- if (hci_get_bt_present(dev, &bt_present) == TOS_SUCCESS && bt_present) {
- dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth",
- &acpi_dev->dev,
- RFKILL_TYPE_BLUETOOTH,
- &toshiba_rfk_ops,
- dev);
- if (!dev->bt_rfk) {
- pr_err("unable to allocate rfkill device\n");
- ret = -ENOMEM;
- goto error;
- }
-
- ret = rfkill_register(dev->bt_rfk);
- if (ret) {
- pr_err("unable to register rfkill device\n");
- rfkill_destroy(dev->bt_rfk);
- goto error;
- }
- }
-
if (toshiba_illumination_available(dev)) {
dev->led_dev.name = "toshiba::illumination";
dev->led_dev.max_brightness = 1;
@@ -2716,8 +2685,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
ret = toshiba_accelerometer_supported(dev);
dev->accelerometer_supported = !ret;
- ret = toshiba_usb_sleep_charge_get(dev, &dummy);
- dev->usb_sleep_charge_supported = !ret;
+ toshiba_usb_sleep_charge_available(dev);
ret = toshiba_usb_rapid_charge_get(dev, &dummy);
dev->usb_rapid_charge_supported = !ret;
@@ -2725,23 +2693,25 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
ret = toshiba_usb_sleep_music_get(dev, &dummy);
dev->usb_sleep_music_supported = !ret;
- ret = toshiba_function_keys_get(dev, &dummy);
- dev->kbd_function_keys_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;
- /* Determine whether or not BIOS supports fan and video interfaces */
-
ret = get_video_status(dev, &dummy);
dev->video_supported = !ret;
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) {
@@ -2770,6 +2740,21 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 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,
@@ -2777,17 +2762,19 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
if (ret)
pr_err("Unable to update sysfs entries\n");
break;
- case 0x81: /* Unknown */
- case 0x82: /* Unknown */
- case 0x83: /* Unknown */
- case 0x8c: /* Unknown */
+ case 0x85: /* Unknown */
+ case 0x8d: /* Unknown */
case 0x8e: /* Unknown */
- case 0x8f: /* Unknown */
- case 0x90: /* 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
@@ -2797,7 +2784,7 @@ static int toshiba_acpi_suspend(struct device *device)
u32 result;
if (dev->hotkey_dev)
- result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE);
+ result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE);
return 0;
}
diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c
index 2cb1ea62b4a7..c5e45089ac51 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 :-)
@@ -9,12 +10,6 @@
* 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
* published by the Free Software Foundation.
- *
- * Note the Toshiba Bluetooth RFKill switch seems to be a strange
- * fish. It only provides a BT event when the switch is flipped to
- * the 'on' position. When flipping it to 'off', the USB device is
- * simply pulled away underneath us, without any BT event being
- * delivered.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -24,11 +19,25 @@
#include <linux/init.h>
#include <linux/types.h>
#include <linux/acpi.h>
+#include <linux/rfkill.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");
+struct toshiba_bluetooth_dev {
+ struct acpi_device *acpi_dev;
+ struct rfkill *rfk;
+
+ bool killswitch;
+ bool plugged;
+ bool powered;
+};
+
static int toshiba_bt_rfkill_add(struct acpi_device *device);
static int toshiba_bt_rfkill_remove(struct acpi_device *device);
static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
@@ -57,64 +66,215 @@ static struct acpi_driver toshiba_bt_rfkill_driver = {
.drv.pm = &toshiba_bt_pm,
};
-
-static int toshiba_bluetooth_enable(acpi_handle handle)
+static int toshiba_bluetooth_present(acpi_handle handle)
{
- acpi_status res1, res2;
- u64 result;
+ acpi_status result;
+ u64 bt_present;
/*
- * Query ACPI to verify RFKill switch is set to 'on'.
- * If not, we return silently, no need to report it as
- * an error.
+ * 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.
*/
- res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result);
- if (ACPI_FAILURE(res1))
- return res1;
- if (!(result & 0x01))
- return 0;
+ 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;
- 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))
+ result = acpi_evaluate_integer(handle, "BTST", NULL, &status);
+ if (ACPI_FAILURE(result)) {
+ pr_err("Could not get Bluetooth device status\n");
+ return -ENXIO;
+ }
+
+ return status;
+}
+
+static int toshiba_bluetooth_enable(acpi_handle handle)
+{
+ acpi_status result;
+
+ 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;
+ }
+
+ result = acpi_evaluate_object(handle, "DUSB", NULL, NULL);
+ if (ACPI_FAILURE(result)) {
+ pr_err("Could not detach USB Bluetooth device\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+/* Helper function */
+static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev)
+{
+ int status;
+
+ status = toshiba_bluetooth_status(bt_dev->acpi_dev->handle);
+ if (status < 0) {
+ pr_err("Could not sync bluetooth device status\n");
+ return status;
+ }
+
+ bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false;
+ bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false;
+ bt_dev->powered = (status & BT_POWER_MASK) ? true : false;
+
+ pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n",
+ status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered);
+
+ return 0;
+}
+
+/* RFKill handlers */
+static int bt_rfkill_set_block(void *data, bool blocked)
+{
+ struct toshiba_bluetooth_dev *bt_dev = data;
+ int ret;
+
+ ret = toshiba_bluetooth_sync_status(bt_dev);
+ if (ret)
+ return ret;
+
+ if (!bt_dev->killswitch)
return 0;
- pr_warn("Failed to re-enable Toshiba Bluetooth\n");
+ if (blocked)
+ ret = toshiba_bluetooth_disable(bt_dev->acpi_dev->handle);
+ else
+ ret = toshiba_bluetooth_enable(bt_dev->acpi_dev->handle);
- return -ENODEV;
+ return ret;
}
+static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+ struct toshiba_bluetooth_dev *bt_dev = data;
+
+ if (toshiba_bluetooth_sync_status(bt_dev))
+ return;
+
+ /*
+ * Note the Toshiba Bluetooth RFKill switch seems to be a strange
+ * fish. It only provides a BT event when the switch is flipped to
+ * the 'on' position. When flipping it to 'off', the USB device is
+ * simply pulled away underneath us, without any BT event being
+ * delivered.
+ */
+ rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+}
+
+static const struct rfkill_ops rfk_ops = {
+ .set_block = bt_rfkill_set_block,
+ .poll = bt_rfkill_poll,
+};
+
+/* ACPI driver functions */
static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
{
- toshiba_bluetooth_enable(device->handle);
+ struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+
+ if (toshiba_bluetooth_sync_status(bt_dev))
+ return;
+
+ rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
}
#ifdef CONFIG_PM_SLEEP
static int toshiba_bt_resume(struct device *dev)
{
- return toshiba_bluetooth_enable(to_acpi_device(dev)->handle);
+ struct toshiba_bluetooth_dev *bt_dev;
+ int ret;
+
+ bt_dev = acpi_driver_data(to_acpi_device(dev));
+
+ ret = toshiba_bluetooth_sync_status(bt_dev);
+ if (ret)
+ return ret;
+
+ rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+
+ return 0;
}
#endif
static int toshiba_bt_rfkill_add(struct acpi_device *device)
{
- acpi_status status;
- u64 bt_present;
- int result = -ENODEV;
+ struct toshiba_bluetooth_dev *bt_dev;
+ 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;
+
+ pr_info("Toshiba ACPI Bluetooth device driver\n");
+
+ bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL);
+ if (!bt_dev)
+ return -ENOMEM;
+ bt_dev->acpi_dev = device;
+ device->driver_data = bt_dev;
+ dev_set_drvdata(&device->dev, bt_dev);
- if (!ACPI_FAILURE(status) && bt_present) {
- pr_info("Detected Toshiba ACPI Bluetooth device - "
- "installing RFKill handler\n");
- result = toshiba_bluetooth_enable(device->handle);
+ result = toshiba_bluetooth_sync_status(bt_dev);
+ if (result) {
+ kfree(bt_dev);
+ return result;
+ }
+
+ bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
+ &device->dev,
+ RFKILL_TYPE_BLUETOOTH,
+ &rfk_ops,
+ bt_dev);
+ if (!bt_dev->rfk) {
+ pr_err("Unable to allocate rfkill device\n");
+ kfree(bt_dev);
+ return -ENOMEM;
+ }
+
+ rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+
+ result = rfkill_register(bt_dev->rfk);
+ if (result) {
+ pr_err("Unable to register rfkill device\n");
+ rfkill_destroy(bt_dev->rfk);
+ kfree(bt_dev);
}
return result;
@@ -122,8 +282,17 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device)
static int toshiba_bt_rfkill_remove(struct acpi_device *device)
{
+ struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+
/* clean up */
- return 0;
+ if (bt_dev->rfk) {
+ rfkill_unregister(bt_dev->rfk);
+ rfkill_destroy(bt_dev->rfk);
+ }
+
+ kfree(bt_dev);
+
+ return toshiba_bluetooth_disable(device->handle);
}
module_acpi_driver(toshiba_bt_rfkill_driver);
diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c
index 65300b6a84b9..7f2afc6b5eb9 100644
--- a/drivers/platform/x86/toshiba_haps.c
+++ b/drivers/platform/x86/toshiba_haps.c
@@ -78,15 +78,20 @@ static ssize_t protection_level_store(struct device *dev,
const char *buf, size_t count)
{
struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
- int level, ret;
-
- if (sscanf(buf, "%d", &level) != 1 || level < 0 || level > 3)
- return -EINVAL;
+ int level;
+ int ret;
- /* Set the sensor level.
- * Acceptable levels are:
+ ret = kstrtoint(buf, 0, &level);
+ if (ret)
+ return ret;
+ /*
+ * Check for supported levels, which can be:
* 0 - Disabled | 1 - Low | 2 - Medium | 3 - High
*/
+ if (level < 0 || level > 3)
+ return -EINVAL;
+
+ /* Set the sensor level */
ret = toshiba_haps_protection_level(haps->acpi_dev->handle, level);
if (ret != 0)
return ret;
@@ -95,15 +100,21 @@ static ssize_t protection_level_store(struct device *dev,
return count;
}
+static DEVICE_ATTR_RW(protection_level);
static ssize_t reset_protection_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
- int reset, ret;
+ int reset;
+ int ret;
- if (sscanf(buf, "%d", &reset) != 1 || reset != 1)
+ ret = kstrtoint(buf, 0, &reset);
+ if (ret)
+ return ret;
+ /* The only accepted value is 1 */
+ if (reset != 1)
return -EINVAL;
/* Reset the protection interface */
@@ -113,10 +124,7 @@ static ssize_t reset_protection_store(struct device *dev,
return count;
}
-
-static DEVICE_ATTR(protection_level, S_IRUGO | S_IWUSR,
- protection_level_show, protection_level_store);
-static DEVICE_ATTR(reset_protection, S_IWUSR, NULL, reset_protection_store);
+static DEVICE_ATTR_WO(reset_protection);
static struct attribute *haps_attributes[] = {
&dev_attr_protection_level.attr,
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)