diff options
Diffstat (limited to 'kernel/drivers/platform')
52 files changed, 4316 insertions, 1414 deletions
diff --git a/kernel/drivers/platform/chrome/Kconfig b/kernel/drivers/platform/chrome/Kconfig index 2a6531a5f..d03df4a60 100644 --- a/kernel/drivers/platform/chrome/Kconfig +++ b/kernel/drivers/platform/chrome/Kconfig @@ -4,7 +4,7 @@ menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" - depends on X86 || ARM + depends on X86 || ARM || ARM64 || COMPILE_TEST ---help--- Say Y here to get to see options for platform support for various Chromebooks and Chromeboxes. This option alone does @@ -59,4 +59,9 @@ config CROS_EC_LPC 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/kernel/drivers/platform/chrome/Makefile b/kernel/drivers/platform/chrome/Makefile index bd8d8601e..bc498bda8 100644 --- a/kernel/drivers/platform/chrome/Makefile +++ b/kernel/drivers/platform/chrome/Makefile @@ -1,6 +1,8 @@ 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 +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o \ + cros_ec_lightbar.o cros_ec_vbc.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/kernel/drivers/platform/chrome/chromeos_laptop.c b/kernel/drivers/platform/chrome/chromeos_laptop.c index a04019ab9..2b441e9ae 100644 --- a/kernel/drivers/platform/chrome/chromeos_laptop.c +++ b/kernel/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> @@ -47,8 +47,8 @@ static const char *i2c_adapter_names[] = { "SMBus I801 adapter", "i915 gmbus vga", "i915 gmbus panel", - "i2c-designware-pci", - "i2c-designware-pci", + "Synopsys DesignWare I2C adapter", + "Synopsys DesignWare I2C adapter", }; /* Keep this enum consistent with i2c_adapter_names */ @@ -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 = { diff --git a/kernel/drivers/platform/chrome/cros_ec_dev.c b/kernel/drivers/platform/chrome/cros_ec_dev.c index 6090d0b28..d45cd254e 100644 --- a/kernel/drivers/platform/chrome/cros_ec_dev.c +++ b/kernel/drivers/platform/chrome/cros_ec_dev.c @@ -20,44 +20,60 @@ #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 struct class *cros_class; static int ec_major; +static const struct attribute_group *cros_ec_groups[] = { + &cros_ec_attr_group, + &cros_ec_lightbar_attr_group, + &cros_ec_vbc_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_device *ec, char *str, int maxlen) +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 = { - .version = 0, - .command = EC_CMD_GET_VERSION, - .outdata = { 0 }, - .outsize = 0, - .indata = { 0 }, - .insize = sizeof(*resp), - }; + struct cros_ec_command *msg; int ret; - ret = cros_ec_cmd_xfer(ec, &msg); + 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) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) { + if (msg->result != EC_RES_SUCCESS) { snprintf(str, maxlen, "%s\nUnknown EC version: EC returned %d\n", - CROS_EC_DEV_VERSION, msg.result); - return 0; + CROS_EC_DEV_VERSION, msg->result); + ret = -EINVAL; + goto exit; } - resp = (struct ec_response_get_version *)msg.indata; + resp = (struct ec_response_get_version *)msg->data; if (resp->current_image >= ARRAY_SIZE(current_image_name)) resp->current_image = 3; /* invalid */ @@ -65,14 +81,19 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) resp->version_string_ro, resp->version_string_rw, current_image_name[resp->current_image]); - return 0; + ret = 0; +exit: + kfree(msg); + return ret; } /* Device file ops */ static int ec_device_open(struct inode *inode, struct file *filp) { - filp->private_data = container_of(inode->i_cdev, - struct cros_ec_device, cdev); + 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; } @@ -84,7 +105,7 @@ static int ec_device_release(struct inode *inode, struct file *filp) static ssize_t ec_device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) { - struct cros_ec_device *ec = filp->private_data; + struct cros_ec_dev *ec = filp->private_data; char msg[sizeof(struct ec_response_get_version) + sizeof(CROS_EC_DEV_VERSION)]; size_t count; @@ -107,38 +128,53 @@ static ssize_t ec_device_read(struct file *filp, char __user *buffer, } /* Ioctls */ -static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) +static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) { long ret; - struct cros_ec_command s_cmd = { }; + struct cros_ec_command u_cmd; + struct cros_ec_command *s_cmd; - if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) + if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) return -EFAULT; - ret = cros_ec_cmd_xfer(ec, &s_cmd); + 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) - return ret; - - if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) - return -EFAULT; + goto exit; - return 0; + 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_device *ec, void __user *arg) +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->cmd_readmem) + if (!ec_dev->cmd_readmem) return -ENOTTY; if (copy_from_user(&s_mem, arg, sizeof(s_mem))) return -EFAULT; - num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); + num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, + s_mem.buffer); if (num <= 0) return num; @@ -151,7 +187,7 @@ static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) static long ec_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - struct cros_ec_device *ec = filp->private_data; + struct cros_ec_dev *ec = filp->private_data; if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) return -ENOTTY; @@ -174,48 +210,90 @@ static const struct file_operations fops = { .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) { - struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); - int retval = -ENOTTY; - dev_t devno = MKDEV(ec_major, 0); + 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); - /* Instantiate it (and remember the EC) */ + 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(&pdev->dev, ": failed to add character device\n"); - return retval; + dev_err(dev, ": failed to add character device\n"); + goto cdev_add_failed; } - ec->vdev = device_create(cros_class, NULL, devno, ec, - CROS_EC_DEV_NAME); - if (IS_ERR(ec->vdev)) { - retval = PTR_ERR(ec->vdev); - dev_err(&pdev->dev, ": failed to create device\n"); - cdev_del(&ec->cdev); - return retval; + /* + * 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; } - /* Initialize extra interfaces */ - ec_dev_sysfs_init(ec); - ec_dev_lightbar_init(ec); + 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_device *ec = dev_get_drvdata(pdev->dev.parent); - - ec_dev_lightbar_remove(ec); - ec_dev_sysfs_remove(ec); - device_destroy(cros_class, MKDEV(ec_major, 0)); + struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); cdev_del(&ec->cdev); + device_unregister(&ec->class_dev); return 0; } +static const struct platform_device_id cros_ec_id[] = { + { "cros-ec-ctl", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, cros_ec_id); + static struct platform_driver cros_ec_dev_driver = { .driver = { .name = "cros-ec-ctl", @@ -229,10 +307,10 @@ static int __init cros_ec_dev_init(void) int ret; dev_t dev = 0; - cros_class = class_create(THIS_MODULE, "chromeos"); - if (IS_ERR(cros_class)) { + ret = class_register(&cros_class); + if (ret) { pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); - return PTR_ERR(cros_class); + return ret; } /* Get a range of minor numbers (starting with 0) to work with */ @@ -254,7 +332,7 @@ static int __init cros_ec_dev_init(void) failed_devreg: unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); failed_chrdevreg: - class_destroy(cros_class); + class_unregister(&cros_class); return ret; } @@ -262,7 +340,7 @@ static void __exit cros_ec_dev_exit(void) { platform_driver_unregister(&cros_ec_dev_driver); unregister_chrdev(ec_major, CROS_EC_DEV_NAME); - class_destroy(cros_class); + class_unregister(&cros_class); } module_init(cros_ec_dev_init); diff --git a/kernel/drivers/platform/chrome/cros_ec_dev.h b/kernel/drivers/platform/chrome/cros_ec_dev.h index 45d67f7e5..bfd2c84c3 100644 --- a/kernel/drivers/platform/chrome/cros_ec_dev.h +++ b/kernel/drivers/platform/chrome/cros_ec_dev.h @@ -24,7 +24,6 @@ #include <linux/types.h> #include <linux/mfd/cros_ec.h> -#define CROS_EC_DEV_NAME "cros_ec" #define CROS_EC_DEV_VERSION "1.0.0" /* @@ -44,10 +43,4 @@ struct cros_ec_readmem { #define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) #define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) -void ec_dev_sysfs_init(struct cros_ec_device *); -void ec_dev_sysfs_remove(struct cros_ec_device *); - -void ec_dev_lightbar_init(struct cros_ec_device *); -void ec_dev_lightbar_remove(struct cros_ec_device *); - #endif /* _CROS_EC_DEV_H_ */ diff --git a/kernel/drivers/platform/chrome/cros_ec_lightbar.c b/kernel/drivers/platform/chrome/cros_ec_lightbar.c index b4ff47a90..ff7640575 100644 --- a/kernel/drivers/platform/chrome/cros_ec_lightbar.c +++ b/kernel/drivers/platform/chrome/cros_ec_lightbar.c @@ -31,6 +31,7 @@ #include <linux/sched.h> #include <linux/types.h> #include <linux/uaccess.h> +#include <linux/slab.h> #include "cros_ec_dev.h" @@ -91,55 +92,81 @@ out: return ret; } -#define INIT_MSG(P, R) { \ - .command = EC_CMD_LIGHTBAR_CMD, \ - .outsize = sizeof(*P), \ - .insize = sizeof(*R), \ - } +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_device *ec, +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 = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; - param = (struct ec_params_lightbar *)msg.outdata; - param->cmd = LIGHTBAR_CMD_VERSION; - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) return 0; - switch (msg.result) { + 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; - return 1; + ret = 1; + goto exit; case EC_RES_SUCCESS: - resp = (struct ec_response_lightbar *)msg.indata; + 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; - return 1; + ret = 1; + goto exit; } /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ - return 0; + ret = 0; +exit: + kfree(msg); + return ret; } static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { - uint32_t version, flags; - struct cros_ec_device *ec = dev_get_drvdata(dev); + 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(); @@ -158,30 +185,39 @@ static ssize_t brightness_store(struct device *dev, const char *buf, size_t count) { struct ec_params_lightbar *param; - struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; unsigned int val; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); if (kstrtouint(buf, 0, &val)) return -EINVAL; - param = (struct ec_params_lightbar *)msg.outdata; - param->cmd = LIGHTBAR_CMD_BRIGHTNESS; - param->brightness.num = val; + 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) - return ret; + goto exit; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return -EINVAL; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto exit; + } - return count; + ret = count; +exit: + kfree(msg); + return ret; } @@ -196,12 +232,16 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; - struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); - struct cros_ec_device *ec = dev_get_drvdata(dev); + 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)) @@ -212,15 +252,15 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, ret = sscanf(buf, "%i", &val[i++]); if (ret == 0) - return -EINVAL; + goto exit; if (i == 4) { - param = (struct ec_params_lightbar *)msg.outdata; - param->cmd = LIGHTBAR_CMD_RGB; - param->rgb.led = val[0]; - param->rgb.red = val[1]; - param->rgb.green = val[2]; - param->rgb.blue = val[3]; + 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. @@ -228,15 +268,15 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, if ((j++ % 4) == 0) { ret = lb_throttle(); if (ret) - return ret; + goto exit; } - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return -EINVAL; + if (msg->result != EC_RES_SUCCESS) + goto exit; i = 0; ok = 1; @@ -248,6 +288,8 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, } while (*buf); +exit: + kfree(msg); return (ok && i == 0) ? count : -EINVAL; } @@ -261,41 +303,52 @@ static ssize_t sequence_show(struct device *dev, { struct ec_params_lightbar *param; struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); - param = (struct ec_params_lightbar *)msg.outdata; + 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) - return ret; + goto exit; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); + 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.indata; + resp = (struct ec_response_lightbar *)msg->data; if (resp->get_seq.num >= ARRAY_SIZE(seqname)) - return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); else - return scnprintf(buf, PAGE_SIZE, "%s\n", - seqname[resp->get_seq.num]); + 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 ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; unsigned int num; int ret, len; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); for (len = 0; len < count; len++) if (!isalnum(buf[len])) @@ -311,21 +364,30 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, return ret; } - param = (struct ec_params_lightbar *)msg.outdata; + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SEQ; param->seq.num = num; ret = lb_throttle(); if (ret) - return ret; + goto exit; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return -EINVAL; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto exit; + } - return count; + ret = count; +exit: + kfree(msg); + return ret; } /* Module initialization */ @@ -343,25 +405,27 @@ static struct attribute *__lb_cmds_attrs[] = { &dev_attr_sequence.attr, NULL, }; -static struct attribute_group lb_cmds_attr_group = { - .name = "lightbar", - .attrs = __lb_cmds_attrs, -}; -void ec_dev_lightbar_init(struct cros_ec_device *ec) +static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) { - int ret = 0; + 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; - - ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); - if (ret) - pr_warn("sysfs_create_group() failed: %d\n", ret); + if (get_lightbar_version(ec, NULL, NULL)) + return a->mode; + else + return 0; } -void ec_dev_lightbar_remove(struct cros_ec_device *ec) -{ - sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); -} +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/kernel/drivers/platform/chrome/cros_ec_lpc.c b/kernel/drivers/platform/chrome/cros_ec_lpc.c index 8f9ac4d7b..f9a245465 100644 --- a/kernel/drivers/platform/chrome/cros_ec_lpc.c +++ b/kernel/drivers/platform/chrome/cros_ec_lpc.c @@ -46,6 +46,77 @@ static int ec_response_timed_out(void) 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) { @@ -73,8 +144,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, /* Copy data and update checksum */ for (i = 0; i < msg->outsize; i++) { - outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->outdata[i]; + outb(msg->data[i], EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->data[i]; } /* Finalize checksum and write args */ @@ -95,19 +166,9 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, /* 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); + ret = cros_ec_check_result(ec, msg); + if (ret) 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); @@ -129,8 +190,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, /* Read response and update checksum */ for (i = 0; i < args.data_size; i++) { - msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->indata[i]; + msg->data[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->data[i]; } /* Verify checksum */ @@ -212,11 +273,13 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ec_dev); ec_dev->dev = dev; - ec_dev->ec_name = pdev->name; ec_dev->phys_name = dev_name(dev); - ec_dev->parent = dev; ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; + ec_dev->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) { @@ -257,6 +320,13 @@ static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { }, }, { + /* x86-samus, the Chromebook Pixel 2. */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Samus"), + }, + }, + { /* x86-peppy, the Acer C720 Chromebook. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), diff --git a/kernel/drivers/platform/chrome/cros_ec_proto.c b/kernel/drivers/platform/chrome/cros_ec_proto.c new file mode 100644 index 000000000..990308ca3 --- /dev/null +++ b/kernel/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/kernel/drivers/platform/chrome/cros_ec_sysfs.c b/kernel/drivers/platform/chrome/cros_ec_sysfs.c index fb62ab6cc..f3baf9973 100644 --- a/kernel/drivers/platform/chrome/cros_ec_sysfs.c +++ b/kernel/drivers/platform/chrome/cros_ec_sysfs.c @@ -29,6 +29,7 @@ #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> @@ -66,13 +67,19 @@ static ssize_t store_ec_reboot(struct device *dev, {"hibernate", EC_REBOOT_HIBERNATE, 0}, {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, }; - struct cros_ec_command msg = { 0 }; - struct ec_params_reboot_ec *param = - (struct ec_params_reboot_ec *)msg.outdata; + struct cros_ec_command *msg; + struct ec_params_reboot_ec *param; int got_cmd = 0, offset = 0; int i; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + 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) { @@ -100,19 +107,26 @@ static ssize_t store_ec_reboot(struct device *dev, offset++; } - if (!got_cmd) - return -EINVAL; - - msg.command = EC_CMD_REBOOT_EC; - msg.outsize = sizeof(param); - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) - return ret; - if (msg.result != EC_RES_SUCCESS) { - dev_dbg(ec->dev, "EC result %d\n", msg.result); - return -EINVAL; + 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; } @@ -123,22 +137,33 @@ static ssize_t show_ec_version(struct device *dev, struct ec_response_get_version *r_ver; struct ec_response_get_chip_info *r_chip; struct ec_response_board_version *r_board; - struct cros_ec_command msg = { 0 }; + struct cros_ec_command *msg; int ret; int count = 0; - struct cros_ec_device *ec = dev_get_drvdata(dev); + 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.command = EC_CMD_GET_VERSION; - msg.insize = sizeof(*r_ver); - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) - return ret; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); + 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.indata; + 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'; @@ -152,33 +177,33 @@ static ssize_t show_ec_version(struct device *dev, image_names[r_ver->current_image] : "?")); /* Get build info. */ - msg.command = EC_CMD_GET_BUILD_INFO; - msg.insize = sizeof(msg.indata); - ret = cros_ec_cmd_xfer(ec, &msg); + 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) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Build info: EC error %d\n", msg.result); + "Build info: EC error %d\n", msg->result); else { - msg.indata[sizeof(msg.indata) - 1] = '\0'; + msg->data[sizeof(msg->data) - 1] = '\0'; count += scnprintf(buf + count, PAGE_SIZE - count, - "Build info: %s\n", msg.indata); + "Build info: %s\n", msg->data); } /* Get chip info. */ - msg.command = EC_CMD_GET_CHIP_INFO; - msg.insize = sizeof(*r_chip); - ret = cros_ec_cmd_xfer(ec, &msg); + 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) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Chip info: EC error %d\n", msg.result); + "Chip info: EC error %d\n", msg->result); else { - r_chip = (struct ec_response_get_chip_info *)msg.indata; + 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'; @@ -192,23 +217,25 @@ static ssize_t show_ec_version(struct device *dev, } /* Get board version */ - msg.command = EC_CMD_GET_BOARD_VERSION; - msg.insize = sizeof(*r_board); - ret = cros_ec_cmd_xfer(ec, &msg); + 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) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Board version: EC error %d\n", msg.result); + "Board version: EC error %d\n", msg->result); else { - r_board = (struct ec_response_board_version *)msg.indata; + 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; } @@ -216,27 +243,39 @@ static ssize_t show_ec_flashinfo(struct device *dev, struct device_attribute *attr, char *buf) { struct ec_response_flash_info *resp; - struct cros_ec_command msg = { 0 }; + struct cros_ec_command *msg; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + 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.command = EC_CMD_FLASH_INFO; - msg.insize = sizeof(*resp); - ret = cros_ec_cmd_xfer(ec, &msg); + 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) - return ret; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); - - resp = (struct ec_response_flash_info *)msg.indata; - - return scnprintf(buf, PAGE_SIZE, - "FlashSize %d\nWriteSize %d\n" - "EraseSize %d\nProtectSize %d\n", - resp->flash_size, resp->write_block_size, - resp->erase_block_size, resp->protect_block_size); + 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 */ @@ -252,20 +291,7 @@ static struct attribute *__ec_attrs[] = { NULL, }; -static struct attribute_group ec_attr_group = { +struct attribute_group cros_ec_attr_group = { .attrs = __ec_attrs, }; -void ec_dev_sysfs_init(struct cros_ec_device *ec) -{ - int error; - - error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); - if (error) - pr_warn("failed to create group: %d\n", error); -} - -void ec_dev_sysfs_remove(struct cros_ec_device *ec) -{ - sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); -} diff --git a/kernel/drivers/platform/chrome/cros_ec_vbc.c b/kernel/drivers/platform/chrome/cros_ec_vbc.c new file mode 100644 index 000000000..564a0d08c --- /dev/null +++ b/kernel/drivers/platform/chrome/cros_ec_vbc.c @@ -0,0 +1,137 @@ +/* + * cros_ec_vbc - Expose the vboot context nvram to userspace + * + * Copyright (C) 2015 Collabora Ltd. + * + * based on vendor driver, + * + * Copyright (C) 2012 The Chromium OS Authors + * + * 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/of.h> +#include <linux/platform_device.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/slab.h> + +static ssize_t vboot_context_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *att, char *buf, + loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, + class_dev); + struct cros_ec_device *ecdev = ec->ec_dev; + struct ec_params_vbnvcontext *params; + struct cros_ec_command *msg; + int err; + const size_t para_sz = sizeof(params->op); + const size_t resp_sz = sizeof(struct ec_response_vbnvcontext); + const size_t payload = max(para_sz, resp_sz); + + msg = kmalloc(sizeof(*msg) + payload, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + /* NB: we only kmalloc()ated enough space for the op field */ + params = (struct ec_params_vbnvcontext *)msg->data; + params->op = EC_VBNV_CONTEXT_OP_READ; + + msg->version = EC_VER_VBNV_CONTEXT; + msg->command = EC_CMD_VBNV_CONTEXT; + msg->outsize = para_sz; + msg->insize = resp_sz; + + err = cros_ec_cmd_xfer(ecdev, msg); + if (err < 0) { + dev_err(dev, "Error sending read request: %d\n", err); + kfree(msg); + return err; + } + + memcpy(buf, msg->data, resp_sz); + + kfree(msg); + return resp_sz; +} + +static ssize_t vboot_context_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, + class_dev); + struct cros_ec_device *ecdev = ec->ec_dev; + struct ec_params_vbnvcontext *params; + struct cros_ec_command *msg; + int err; + const size_t para_sz = sizeof(*params); + const size_t data_sz = sizeof(params->block); + + /* Only write full values */ + if (count != data_sz) + return -EINVAL; + + msg = kmalloc(sizeof(*msg) + para_sz, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + params = (struct ec_params_vbnvcontext *)msg->data; + params->op = EC_VBNV_CONTEXT_OP_WRITE; + memcpy(params->block, buf, data_sz); + + msg->version = EC_VER_VBNV_CONTEXT; + msg->command = EC_CMD_VBNV_CONTEXT; + msg->outsize = para_sz; + msg->insize = 0; + + err = cros_ec_cmd_xfer(ecdev, msg); + if (err < 0) { + dev_err(dev, "Error sending write request: %d\n", err); + kfree(msg); + return err; + } + + kfree(msg); + return data_sz; +} + +static umode_t cros_ec_vbc_is_visible(struct kobject *kobj, + struct bin_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 device_node *np = ec->ec_dev->dev->of_node; + + if (IS_ENABLED(CONFIG_OF) && np) { + if (of_property_read_bool(np, "google,has-vbc-nvram")) + return a->attr.mode; + } + + return 0; +} + +static BIN_ATTR_RW(vboot_context, 16); + +static struct bin_attribute *cros_ec_vbc_bin_attrs[] = { + &bin_attr_vboot_context, + NULL +}; + +struct attribute_group cros_ec_vbc_attr_group = { + .name = "vbc", + .bin_attrs = cros_ec_vbc_bin_attrs, + .is_bin_visible = cros_ec_vbc_is_visible, +}; diff --git a/kernel/drivers/platform/goldfish/goldfish_pipe.c b/kernel/drivers/platform/goldfish/goldfish_pipe.c index d9a09d963..e7a29e275 100644 --- a/kernel/drivers/platform/goldfish/goldfish_pipe.c +++ b/kernel/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/kernel/drivers/platform/goldfish/pdev_bus.c b/kernel/drivers/platform/goldfish/pdev_bus.c index 8c43589c3..1f52462f4 100644 --- a/kernel/drivers/platform/goldfish/pdev_bus.c +++ b/kernel/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/kernel/drivers/platform/olpc/olpc-ec.c b/kernel/drivers/platform/olpc/olpc-ec.c index f9119525f..f99b183d5 100644 --- a/kernel/drivers/platform/olpc/olpc-ec.c +++ b/kernel/drivers/platform/olpc/olpc-ec.c @@ -192,18 +192,15 @@ static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf, for (i = 0; i <= ec_cmd_bytes; i++) ec_cmd[i] = ec_cmd_int[i]; - pr_debug("olpc-ec: debugfs cmd 0x%02x with %d args %02x %02x %02x %02x %02x, want %d returns\n", - ec_cmd[0], ec_cmd_bytes, ec_cmd[1], ec_cmd[2], - ec_cmd[3], ec_cmd[4], ec_cmd[5], ec_dbgfs_resp_bytes); + pr_debug("olpc-ec: debugfs cmd 0x%02x with %d args %5ph, want %d returns\n", + ec_cmd[0], ec_cmd_bytes, ec_cmd + 1, + ec_dbgfs_resp_bytes); olpc_ec_cmd(ec_cmd[0], (ec_cmd_bytes == 0) ? NULL : &ec_cmd[1], ec_cmd_bytes, ec_dbgfs_resp, ec_dbgfs_resp_bytes); - pr_debug("olpc-ec: response %02x %02x %02x %02x %02x %02x %02x %02x (%d bytes expected)\n", - ec_dbgfs_resp[0], ec_dbgfs_resp[1], ec_dbgfs_resp[2], - ec_dbgfs_resp[3], ec_dbgfs_resp[4], ec_dbgfs_resp[5], - ec_dbgfs_resp[6], ec_dbgfs_resp[7], - ec_dbgfs_resp_bytes); + pr_debug("olpc-ec: response %8ph (%d bytes expected)\n", + ec_dbgfs_resp, ec_dbgfs_resp_bytes); out: mutex_unlock(&ec_dbgfs_lock); diff --git a/kernel/drivers/platform/x86/Kconfig b/kernel/drivers/platform/x86/Kconfig index f9f205cb1..1089eaa02 100644 --- a/kernel/drivers/platform/x86/Kconfig +++ b/kernel/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 @@ -287,8 +309,8 @@ config COMPAL_LAPTOP This is a driver for laptops built by Compal, and some models by other brands (e.g. Dell, Toshiba). - It adds support for rfkill, Bluetooth, WLAN and LCD brightness - control. + It adds support for rfkill, Bluetooth, WLAN, LCD brightness, hwmon + and battery charging level control. For a (possibly incomplete) list of supported laptops, please refer to: Documentation/platform/x86-laptop-drivers.txt @@ -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,8 @@ config IDEAPAD_LAPTOP depends on RFKILL && INPUT depends on SERIO_I8042 depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on ACPI_WMI || ACPI_WMI = n select INPUT_SPARSEKMAP help This is a driver for Lenovo IdeaPad netbooks contains drivers for @@ -331,8 +356,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 +525,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 +613,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,7 +639,6 @@ 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 @@ -643,6 +669,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 @@ -660,7 +687,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 @@ -669,11 +696,29 @@ 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. +config TOSHIBA_WMI + tristate "Toshiba WMI Hotkeys Driver (EXPERIMENTAL)" + default n + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + ---help--- + This driver adds hotkey monitoring support to some Toshiba models + that manage the hotkeys via WMI events. + + WARNING: This driver is incomplete as it lacks a proper keymap and the + *notify function only prints the ACPI event type value. Be warned that + you will need to provide some information if you have a Toshiba model + with WMI event hotkeys and want to help with the develpment of this + driver. + + If you have a WMI-based hotkeys Toshiba laptop, say Y or M here. + config ACPI_CMPC tristate "CMPC Laptop Extras" depends on X86 && ACPI @@ -824,6 +869,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 @@ -885,4 +931,17 @@ 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" + depends on ACPI + ---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. + +config SURFACE_PRO3_BUTTON + tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3 tablet" + depends on ACPI && INPUT + ---help--- + This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3 tablet. endif # X86_PLATFORM_DEVICES diff --git a/kernel/drivers/platform/x86/Makefile b/kernel/drivers/platform/x86/Makefile index f82232b1f..3ca78a3eb 100644 --- a/kernel/drivers/platform/x86/Makefile +++ b/kernel/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 @@ -39,6 +40,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o +obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o @@ -58,3 +60,5 @@ 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 +obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/kernel/drivers/platform/x86/acer-wmi.c b/kernel/drivers/platform/x86/acer-wmi.c index 3ac29a1e8..1062fa42f 100644 --- a/kernel/drivers/platform/x86/acer-wmi.c +++ b/kernel/drivers/platform/x86/acer-wmi.c @@ -807,6 +807,7 @@ static const struct acpi_device_id norfkill_ids[] __initconst = { { "IBM0068", 0}, { "LEN0068", 0}, { "SNY5001", 0}, /* sony-laptop in charge */ + { "HPQ6601", 0}, { "", 0}, }; @@ -1661,58 +1662,6 @@ static void acer_rfkill_exit(void) return; } -/* - * sysfs interface - */ -static ssize_t show_bool_threeg(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u32 result; \ - acpi_status status; - - pr_info("This threeg sysfs will be removed in 2014 - used by: %s\n", - current->comm); - status = get_u32(&result, ACER_CAP_THREEG); - if (ACPI_SUCCESS(status)) - return sprintf(buf, "%u\n", result); - return sprintf(buf, "Read error\n"); -} - -static ssize_t set_bool_threeg(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - u32 tmp = simple_strtoul(buf, NULL, 10); - acpi_status status = set_u32(tmp, ACER_CAP_THREEG); - pr_info("This threeg sysfs will be removed in 2014 - used by: %s\n", - current->comm); - if (ACPI_FAILURE(status)) - return -EINVAL; - return count; -} -static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg, - set_bool_threeg); - -static ssize_t show_interface(struct device *dev, struct device_attribute *attr, - char *buf) -{ - pr_info("This interface sysfs will be removed in 2014 - used by: %s\n", - current->comm); - switch (interface->type) { - case ACER_AMW0: - return sprintf(buf, "AMW0\n"); - case ACER_AMW0_V2: - return sprintf(buf, "AMW0 v2\n"); - case ACER_WMID: - return sprintf(buf, "WMID\n"); - case ACER_WMID_v2: - return sprintf(buf, "WMID v2\n"); - default: - return sprintf(buf, "Error!\n"); - } -} - -static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL); - static void acer_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -2126,39 +2075,6 @@ static struct platform_driver acer_platform_driver = { static struct platform_device *acer_platform_device; -static int remove_sysfs(struct platform_device *device) -{ - if (has_cap(ACER_CAP_THREEG)) - device_remove_file(&device->dev, &dev_attr_threeg); - - device_remove_file(&device->dev, &dev_attr_interface); - - return 0; -} - -static int __init create_sysfs(void) -{ - int retval = -ENOMEM; - - if (has_cap(ACER_CAP_THREEG)) { - retval = device_create_file(&acer_platform_device->dev, - &dev_attr_threeg); - if (retval) - goto error_sysfs; - } - - retval = device_create_file(&acer_platform_device->dev, - &dev_attr_interface); - if (retval) - goto error_sysfs; - - return 0; - -error_sysfs: - remove_sysfs(acer_platform_device); - return retval; -} - static void remove_debugfs(void) { debugfs_remove(interface->debug.devices); @@ -2246,14 +2162,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) { @@ -2293,10 +2205,6 @@ static int __init acer_wmi_init(void) if (err) goto error_device_add; - err = create_sysfs(); - if (err) - goto error_create_sys; - if (wmi_has_guid(WMID_GUID2)) { interface->debug.wmid_devices = get_wmid_devices(); err = create_debugfs(); @@ -2310,8 +2218,6 @@ static int __init acer_wmi_init(void) return 0; error_create_debugfs: - remove_sysfs(acer_platform_device); -error_create_sys: platform_device_del(acer_platform_device); error_device_add: platform_device_put(acer_platform_device); @@ -2334,7 +2240,6 @@ static void __exit acer_wmi_exit(void) if (has_cap(ACER_CAP_ACCEL)) acer_wmi_accel_destroy(); - remove_sysfs(acer_platform_device); remove_debugfs(); platform_device_unregister(acer_platform_device); platform_driver_unregister(&acer_platform_driver); diff --git a/kernel/drivers/platform/x86/acerhdf.c b/kernel/drivers/platform/x86/acerhdf.c index 594c918b5..460fa6708 100644 --- a/kernel/drivers/platform/x86/acerhdf.c +++ b/kernel/drivers/platform/x86/acerhdf.c @@ -346,8 +346,7 @@ static void acerhdf_check_param(struct thermal_zone_device *thermal) * as late as the polling interval is since we can't do that in the respective * accessors of the module parameters. */ -static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, - unsigned long *t) +static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, int *t) { int temp, err = 0; @@ -372,7 +371,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; } @@ -452,7 +452,7 @@ static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip, } static int acerhdf_get_trip_hyst(struct thermal_zone_device *thermal, int trip, - unsigned long *temp) + int *temp) { if (trip != 0) return -EINVAL; @@ -463,7 +463,7 @@ static int acerhdf_get_trip_hyst(struct thermal_zone_device *thermal, int trip, } static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip, - unsigned long *temp) + int *temp) { if (trip == 0) *temp = fanon; @@ -476,7 +476,7 @@ static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip, } static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, - unsigned long *temperature) + int *temperature) { *temperature = ACERHDF_TEMP_CRIT; return 0; diff --git a/kernel/drivers/platform/x86/apple-gmux.c b/kernel/drivers/platform/x86/apple-gmux.c index 680871500..976efeb3f 100644 --- a/kernel/drivers/platform/x86/apple-gmux.c +++ b/kernel/drivers/platform/x86/apple-gmux.c @@ -346,7 +346,7 @@ gmux_active_client(struct apple_gmux_data *gmux_data) return VGA_SWITCHEROO_DIS; } -static struct vga_switcheroo_handler gmux_handler = { +static const struct vga_switcheroo_handler gmux_handler = { .switchto = gmux_switchto, .power_state = gmux_set_power_state, .get_client_id = gmux_get_client_id, @@ -550,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; @@ -645,7 +644,6 @@ static void gmux_remove(struct pnp_dev *pnp) apple_gmux_data = NULL; kfree(gmux_data); - acpi_video_dmi_demote_vendor(); acpi_video_register(); apple_bl_register(); } diff --git a/kernel/drivers/platform/x86/asus-laptop.c b/kernel/drivers/platform/x86/asus-laptop.c index 46b274693..f2b5d0a8a 100644 --- a/kernel/drivers/platform/x86/asus-laptop.c +++ b/kernel/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" @@ -331,6 +332,7 @@ static const struct key_entry asus_keymap[] = { {KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */ {KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */ {KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */ + {KE_KEY, 0x6A, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad Fn + F9 */ {KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad */ {KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */ {KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */ @@ -1884,12 +1886,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/kernel/drivers/platform/x86/asus-nb-wmi.c b/kernel/drivers/platform/x86/asus-nb-wmi.c index abdaed34c..131fee2b0 100644 --- a/kernel/drivers/platform/x86/asus-nb-wmi.c +++ b/kernel/drivers/platform/x86/asus-nb-wmi.c @@ -128,6 +128,24 @@ static const struct dmi_system_id asus_quirks[] = { }, { .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X456UA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X456UA"), + }, + .driver_data = &quirk_asus_wapf4, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X456UF", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X456UF"), + }, + .driver_data = &quirk_asus_wapf4, + }, + { + .callback = dmi_matched, .ident = "ASUSTeK COMPUTER INC. X501U", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), diff --git a/kernel/drivers/platform/x86/asus-wmi.c b/kernel/drivers/platform/x86/asus-wmi.c index 7543a56e0..f96f7b865 100644 --- a/kernel/drivers/platform/x86/asus-wmi.c +++ b/kernel/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); @@ -521,7 +582,7 @@ static void asus_wmi_led_exit(struct asus_wmi *asus) static int asus_wmi_led_init(struct asus_wmi *asus) { - int rv = 0; + int rv = 0, led_val; asus->led_workqueue = create_singlethread_workqueue("led_workqueue"); if (!asus->led_workqueue) @@ -541,9 +602,11 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } - if (kbd_led_read(asus, NULL, NULL) >= 0) { + led_val = kbd_led_read(asus, NULL, NULL); + if (led_val >= 0) { INIT_WORK(&asus->kbd_led_work, kbd_led_update); + asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.brightness_set = kbd_led_set; asus->kbd_led.brightness_get = kbd_led_get; @@ -1022,35 +1085,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) @@ -1064,16 +1320,26 @@ static ssize_t asus_hwmon_temp1(struct device *dev, if (err < 0) return err; - value = KELVIN_TO_CELSIUS((value & 0xFFFF)) * 1000; + value = DECI_KELVIN_TO_CELSIUS((value & 0xFFFF)) * 1000; 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 +1350,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 +1387,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 +1645,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; } @@ -1401,7 +1682,7 @@ static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid, int rv, err, value; value = asus_wmi_get_devstate_simple(asus, devid); - if (value == -ENODEV) /* Check device presence */ + if (value < 0) return value; rv = parse_arg(buf, count, &value); @@ -1723,6 +2004,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 +2056,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 +2075,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 +2134,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; @@ -1859,6 +2162,16 @@ static int asus_hotk_thaw(struct device *device) return 0; } +static int asus_hotk_resume(struct device *device) +{ + struct asus_wmi *asus = dev_get_drvdata(device); + + if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) + queue_work(asus->led_workqueue, &asus->kbd_led_work); + + return 0; +} + static int asus_hotk_restore(struct device *device) { struct asus_wmi *asus = dev_get_drvdata(device); @@ -1889,6 +2202,8 @@ static int asus_hotk_restore(struct device *device) bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_UWB); rfkill_set_sw_state(asus->uwb.rfkill, bl); } + if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) + queue_work(asus->led_workqueue, &asus->kbd_led_work); return 0; } @@ -1896,6 +2211,7 @@ static int asus_hotk_restore(struct device *device) static const struct dev_pm_ops asus_pm_ops = { .thaw = asus_hotk_thaw, .restore = asus_hotk_restore, + .resume = asus_hotk_resume, }; static int asus_wmi_probe(struct platform_device *pdev) diff --git a/kernel/drivers/platform/x86/compal-laptop.c b/kernel/drivers/platform/x86/compal-laptop.c index b4e94471f..e1c2b6d4b 100644 --- a/kernel/drivers/platform/x86/compal-laptop.c +++ b/kernel/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 */ @@ -151,6 +151,8 @@ #define BAT_STATUS2 0xF1 #define BAT_STOP_CHARGE1 0xF2 #define BAT_STOP_CHARGE2 0xF3 +#define BAT_CHARGE_LIMIT 0x03 +#define BAT_CHARGE_LIMIT_MAX 100 #define BAT_S0_DISCHARGE (1 << 0) #define BAT_S0_DISCHRG_CRITICAL (1 << 2) @@ -601,6 +603,12 @@ static int bat_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_NOW: val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000; break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = ec_read_u8(BAT_CHARGE_LIMIT); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = BAT_CHARGE_LIMIT_MAX; + break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = ec_read_u8(BAT_CAPACITY); break; @@ -634,6 +642,36 @@ static int bat_get_property(struct power_supply *psy, return 0; } +static int bat_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int level; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + level = val->intval; + if (level < 0 || level > BAT_CHARGE_LIMIT_MAX) + return -EINVAL; + if (ec_write(BAT_CHARGE_LIMIT, level) < 0) + return -EIO; + break; + default: + break; + } + return 0; +} + +static int bat_writeable_property(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return 1; + default: + return 0; + } +} @@ -726,6 +764,8 @@ static enum power_supply_property compal_bat_properties[] = { POWER_SUPPLY_PROP_POWER_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_TEMP, @@ -880,11 +920,12 @@ static const struct power_supply_desc psy_bat_desc = { .properties = compal_bat_properties, .num_properties = ARRAY_SIZE(compal_bat_properties), .get_property = bat_get_property, + .set_property = bat_set_property, + .property_is_writeable = bat_writeable_property, }; static void initialize_power_supply_data(struct compal_data *data) { - ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR, data->bat_manufacturer_name, BAT_MANUFACTURER_NAME_LEN); @@ -959,7 +1000,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; diff --git a/kernel/drivers/platform/x86/dell-laptop.c b/kernel/drivers/platform/x86/dell-laptop.c index 2c1d5f543..aaeeae81e 100644 --- a/kernel/drivers/platform/x86/dell-laptop.c +++ b/kernel/drivers/platform/x86/dell-laptop.c @@ -31,7 +31,9 @@ #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 @@ -307,12 +309,15 @@ static const struct dmi_system_id dell_quirks[] __initconst = { static struct calling_interface_buffer *buffer; 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) @@ -421,66 +426,166 @@ static inline int dell_smi_error(int value) } } -/* 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 -*/ +/* + * 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 */ @@ -490,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 { @@ -499,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 = { @@ -527,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)); @@ -547,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", @@ -561,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", @@ -594,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); @@ -641,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; @@ -660,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, @@ -717,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; @@ -743,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); @@ -759,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; } @@ -1863,6 +2112,7 @@ static void kbd_led_exit(void) static int __init dell_init(void) { int max_intensity = 0; + int token; int ret; if (!dmi_check_system(dell_device_table)) @@ -1918,21 +2168,18 @@ static int __init dell_init(void) 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; @@ -1959,8 +2206,6 @@ 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)buffer); @@ -1981,8 +2226,6 @@ static void __exit dell_exit(void) if (quirks && quirks->touchpad_led) touchpad_led_exit(); kbd_led_exit(); - i8042_remove_filter(dell_laptop_i8042_filter); - cancel_delayed_work_sync(&dell_rfkill_work); backlight_device_unregister(dell_backlight_device); dell_cleanup_rfkill(); if (platform_device) { @@ -1993,7 +2236,14 @@ 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>"); diff --git a/kernel/drivers/platform/x86/dell-rbtn.c b/kernel/drivers/platform/x86/dell-rbtn.c new file mode 100644 index 000000000..cd410e392 --- /dev/null +++ b/kernel/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 = ¶m; + + 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/kernel/drivers/platform/x86/dell-rbtn.h b/kernel/drivers/platform/x86/dell-rbtn.h new file mode 100644 index 000000000..c59cc6b8e --- /dev/null +++ b/kernel/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/kernel/drivers/platform/x86/dell-wmi.c b/kernel/drivers/platform/x86/dell-wmi.c index 6512a06bc..f2d77fe69 100644 --- a/kernel/drivers/platform/x86/dell-wmi.c +++ b/kernel/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/kernel/drivers/platform/x86/eeepc-laptop.c b/kernel/drivers/platform/x86/eeepc-laptop.c index 844c2096b..8cdf315f9 100644 --- a/kernel/drivers/platform/x86/eeepc-laptop.c +++ b/kernel/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/kernel/drivers/platform/x86/fujitsu-laptop.c b/kernel/drivers/platform/x86/fujitsu-laptop.c index 2a9afa261..1c62caff9 100644 --- a/kernel/drivers/platform/x86/fujitsu-laptop.c +++ b/kernel/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/kernel/drivers/platform/x86/hp-wireless.c b/kernel/drivers/platform/x86/hp-wireless.c index 4e4cc8bd7..988eedbd7 100644 --- a/kernel/drivers/platform/x86/hp-wireless.c +++ b/kernel/drivers/platform/x86/hp-wireless.c @@ -114,14 +114,9 @@ static int __init hpwl_init(void) pr_info("Initializing HPQ6001 module\n"); err = acpi_bus_register_driver(&hpwl_driver); - if (err) { + if (err) pr_err("Unable to register HP wireless control driver.\n"); - goto error_acpi_register; - } - - return 0; -error_acpi_register: return err; } diff --git a/kernel/drivers/platform/x86/ibm_rtl.c b/kernel/drivers/platform/x86/ibm_rtl.c index 97c2be195..c62e5e11c 100644 --- a/kernel/drivers/platform/x86/ibm_rtl.c +++ b/kernel/drivers/platform/x86/ibm_rtl.c @@ -33,7 +33,7 @@ #include <linux/mutex.h> #include <asm/bios_ebda.h> -#include <asm-generic/io-64-nonatomic-lo-hi.h> +#include <linux/io-64-nonatomic-lo-hi.h> static bool force; module_param(force, bool, 0); diff --git a/kernel/drivers/platform/x86/ideapad-laptop.c b/kernel/drivers/platform/x86/ideapad-laptop.c index cd78f1166..d78ee151c 100644 --- a/kernel/drivers/platform/x86/ideapad-laptop.c +++ b/kernel/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) @@ -46,6 +47,10 @@ #define CFG_WIFI_BIT (18) #define CFG_CAMERA_BIT (19) +#if IS_ENABLED(CONFIG_ACPI_WMI) +static const char ideapad_wmi_fnesc_event[] = "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6"; +#endif + enum { VPCCMD_R_VPC1 = 0x10, VPCCMD_R_BL_MAX, @@ -566,6 +571,8 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 65, { KEY_PROG4 } }, { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 128, { KEY_ESC } }, + { KE_END, 0 }, }; @@ -824,6 +831,19 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) } } +#if IS_ENABLED(CONFIG_ACPI_WMI) +static void ideapad_wmi_notify(u32 value, void *context) +{ + switch (value) { + case 128: + ideapad_input_report(context, value); + break; + default: + pr_info("Unknown WMI event %u\n", value); + } +} +#endif + /* * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF * always results in 0 on these models, causing ideapad_laptop to wrongly @@ -845,6 +865,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { }, }, { + .ident = "Lenovo ideapad Y700-17ISK", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"), + }, + }, + { .ident = "Lenovo Yoga 2 11 / 13 / Pro", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), @@ -852,17 +879,38 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { }, }, { - .ident = "Lenovo Yoga 3 14", + .ident = "Lenovo Yoga 2 11 / 13 / Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "Yoga2"), + }, + }, + { + .ident = "Lenovo Yoga 3 1170 / 1470", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3 14"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"), }, }, { .ident = "Lenovo Yoga 3 Pro 1370", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3 Pro-1370"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"), + }, + }, + { + .ident = "Lenovo Yoga 700", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"), + }, + }, + { + .ident = "Lenovo Yoga 900", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"), }, }, {} @@ -918,7 +966,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; @@ -927,8 +975,18 @@ static int ideapad_acpi_add(struct platform_device *pdev) ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); if (ret) goto notification_failed; +#if IS_ENABLED(CONFIG_ACPI_WMI) + ret = wmi_install_notify_handler(ideapad_wmi_fnesc_event, ideapad_wmi_notify, priv); + if (ret != AE_OK && ret != AE_NOT_EXIST) + goto notification_failed_wmi; +#endif return 0; +#if IS_ENABLED(CONFIG_ACPI_WMI) +notification_failed_wmi: + acpi_remove_notify_handler(priv->adev->handle, + ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); +#endif notification_failed: ideapad_backlight_exit(priv); backlight_failed: @@ -947,6 +1005,9 @@ static int ideapad_acpi_remove(struct platform_device *pdev) struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); int i; +#if IS_ENABLED(CONFIG_ACPI_WMI) + wmi_remove_notify_handler(ideapad_wmi_fnesc_event); +#endif acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); ideapad_backlight_exit(priv); diff --git a/kernel/drivers/platform/x86/intel_ips.c b/kernel/drivers/platform/x86/intel_ips.c index e2065e06a..55663b3d7 100644 --- a/kernel/drivers/platform/x86/intel_ips.c +++ b/kernel/drivers/platform/x86/intel_ips.c @@ -78,7 +78,7 @@ #include <asm/processor.h> #include "intel_ips.h" -#include <asm-generic/io-64-nonatomic-lo-hi.h> +#include <linux/io-64-nonatomic-lo-hi.h> #define PCI_DEVICE_ID_INTEL_THERMAL_SENSOR 0x3b32 diff --git a/kernel/drivers/platform/x86/intel_menlow.c b/kernel/drivers/platform/x86/intel_menlow.c index e8b46d2c4..0a919d816 100644 --- a/kernel/drivers/platform/x86/intel_menlow.c +++ b/kernel/drivers/platform/x86/intel_menlow.c @@ -315,7 +315,7 @@ static ssize_t aux0_show(struct device *dev, result = sensor_get_auxtrip(attr->handle, 0, &value); - return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value)); + return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value)); } static ssize_t aux1_show(struct device *dev, @@ -327,7 +327,7 @@ static ssize_t aux1_show(struct device *dev, result = sensor_get_auxtrip(attr->handle, 1, &value); - return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value)); + return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value)); } static ssize_t aux0_store(struct device *dev, @@ -345,7 +345,7 @@ static ssize_t aux0_store(struct device *dev, if (value < 0) return -EINVAL; - result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_KELVIN(value)); + result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_DECI_KELVIN(value)); return result ? result : count; } @@ -364,7 +364,7 @@ static ssize_t aux1_store(struct device *dev, if (value < 0) return -EINVAL; - result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_KELVIN(value)); + result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_DECI_KELVIN(value)); return result ? result : count; } diff --git a/kernel/drivers/platform/x86/intel_mid_powerbtn.c b/kernel/drivers/platform/x86/intel_mid_powerbtn.c index 22606d6b2..1fc0de870 100644 --- a/kernel/drivers/platform/x86/intel_mid_powerbtn.c +++ b/kernel/drivers/platform/x86/intel_mid_powerbtn.c @@ -24,6 +24,7 @@ #include <linux/platform_device.h> #include <linux/input.h> #include <linux/mfd/intel_msic.h> +#include <linux/pm_wakeirq.h> #define DRIVER_NAME "msic_power_btn" @@ -76,14 +77,17 @@ static int mfld_pb_probe(struct platform_device *pdev) input_set_capability(input, EV_KEY, KEY_POWER); - error = request_threaded_irq(irq, NULL, mfld_pb_isr, IRQF_NO_SUSPEND, - DRIVER_NAME, input); + error = request_threaded_irq(irq, NULL, mfld_pb_isr, 0, + DRIVER_NAME, input); if (error) { dev_err(&pdev->dev, "Unable to request irq %d for mfld power" "button\n", irq); goto err_free_input; } + device_init_wakeup(&pdev->dev, true); + dev_pm_set_wake_irq(&pdev->dev, irq); + error = input_register_device(input); if (error) { dev_err(&pdev->dev, "Unable to register input dev, error " @@ -124,6 +128,8 @@ static int mfld_pb_remove(struct platform_device *pdev) struct input_dev *input = platform_get_drvdata(pdev); int irq = platform_get_irq(pdev, 0); + dev_pm_clear_wake_irq(&pdev->dev); + device_init_wakeup(&pdev->dev, false); free_irq(irq, input); input_unregister_device(input); diff --git a/kernel/drivers/platform/x86/intel_mid_thermal.c b/kernel/drivers/platform/x86/intel_mid_thermal.c index 0944e834a..9f713b832 100644 --- a/kernel/drivers/platform/x86/intel_mid_thermal.c +++ b/kernel/drivers/platform/x86/intel_mid_thermal.c @@ -132,7 +132,7 @@ static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) * to achieve very close approximate temp value with less than * 0.5C error */ -static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) +static int adc_to_temp(int direct, uint16_t adc_val, int *tp) { int temp; @@ -174,14 +174,13 @@ static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) * * Can sleep */ -static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) +static int mid_read_temp(struct thermal_zone_device *tzd, int *temp) { struct thermal_device_info *td_info = tzd->devdata; uint16_t adc_val, addr; uint8_t data = 0; int ret; - unsigned long curr_temp; - + int curr_temp; addr = td_info->chnl_addr; @@ -453,7 +452,7 @@ static SIMPLE_DEV_PM_OPS(mid_thermal_pm, * * Can sleep */ -static int read_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp) +static int read_curr_temp(struct thermal_zone_device *tzd, int *temp) { WARN_ON(tzd == NULL); return mid_read_temp(tzd, temp); diff --git a/kernel/drivers/platform/x86/intel_oaktrail.c b/kernel/drivers/platform/x86/intel_oaktrail.c index 8037c8b46..6aa33c4a8 100644 --- a/kernel/drivers/platform/x86/intel_oaktrail.c +++ b/kernel/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" @@ -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/kernel/drivers/platform/x86/intel_pmc_ipc.c b/kernel/drivers/platform/x86/intel_pmc_ipc.c new file mode 100644 index 000000000..28b2a12bb --- /dev/null +++ b/kernel/drivers/platform/x86/intel_pmc_ipc.c @@ -0,0 +1,779 @@ +/* + * 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/platform_data/itco_wdt.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 itco_wdt_platform_data tco_info = { + .name = "Apollo Lake SoC", + .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(tco_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/kernel/drivers/platform/x86/intel_scu_ipc.c b/kernel/drivers/platform/x86/intel_scu_ipc.c index 001b199a8..f94b73054 100644 --- a/kernel/drivers/platform/x86/intel_scu_ipc.c +++ b/kernel/drivers/platform/x86/intel_scu_ipc.c @@ -92,11 +92,8 @@ static struct intel_scu_ipc_pdata_t intel_scu_ipc_tangier_pdata = { .irq_mode = 0, }; -static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id); -static void ipc_remove(struct pci_dev *pdev); - struct intel_scu_ipc_dev { - struct pci_dev *pdev; + struct device *dev; void __iomem *ipc_base; void __iomem *i2c_base; struct completion cmd_complete; @@ -118,28 +115,30 @@ static struct intel_scu_ipc_dev ipcdev; /* Only one for now */ static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */ /* + * Send ipc command * Command Register (Write Only): * A write to this register results in an interrupt to the SCU core processor * Format: * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| */ -static inline void ipc_command(u32 cmd) /* Send ipc command */ +static inline void ipc_command(struct intel_scu_ipc_dev *scu, u32 cmd) { - if (ipcdev.irq_mode) { - reinit_completion(&ipcdev.cmd_complete); - writel(cmd | IPC_IOC, ipcdev.ipc_base); + if (scu->irq_mode) { + reinit_completion(&scu->cmd_complete); + writel(cmd | IPC_IOC, scu->ipc_base); } - writel(cmd, ipcdev.ipc_base); + writel(cmd, scu->ipc_base); } /* + * Write ipc data * IPC Write Buffer (Write Only): * 16-byte buffer for sending data associated with IPC command to * SCU. Size of the data is specified in the IPC_COMMAND_REG register */ -static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */ +static inline void ipc_data_writel(struct intel_scu_ipc_dev *scu, u32 data, u32 offset) { - writel(data, ipcdev.ipc_base + 0x80 + offset); + writel(data, scu->ipc_base + 0x80 + offset); } /* @@ -149,35 +148,37 @@ static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */ * Format: * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| */ -static inline u8 ipc_read_status(void) +static inline u8 ipc_read_status(struct intel_scu_ipc_dev *scu) { - return __raw_readl(ipcdev.ipc_base + 0x04); + return __raw_readl(scu->ipc_base + 0x04); } -static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */ +/* Read ipc byte data */ +static inline u8 ipc_data_readb(struct intel_scu_ipc_dev *scu, u32 offset) { - return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset); + return readb(scu->ipc_base + IPC_READ_BUFFER + offset); } -static inline u32 ipc_data_readl(u32 offset) /* Read ipc u32 data */ +/* Read ipc u32 data */ +static inline u32 ipc_data_readl(struct intel_scu_ipc_dev *scu, u32 offset) { - return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset); + return readl(scu->ipc_base + IPC_READ_BUFFER + offset); } /* Wait till scu status is busy */ -static inline int busy_loop(void) +static inline int busy_loop(struct intel_scu_ipc_dev *scu) { - u32 status = ipc_read_status(); + u32 status = ipc_read_status(scu); u32 loop_count = 100000; /* break if scu doesn't reset busy bit after huge retry */ while ((status & BIT(0)) && --loop_count) { udelay(1); /* scu processing time is in few u secods */ - status = ipc_read_status(); + status = ipc_read_status(scu); } if (status & BIT(0)) { - dev_err(&ipcdev.pdev->dev, "IPC timed out"); + dev_err(scu->dev, "IPC timed out"); return -ETIMEDOUT; } @@ -188,42 +189,42 @@ static inline int busy_loop(void) } /* Wait till ipc ioc interrupt is received or timeout in 3 HZ */ -static inline int ipc_wait_for_interrupt(void) +static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu) { int status; - if (!wait_for_completion_timeout(&ipcdev.cmd_complete, 3 * HZ)) { - struct device *dev = &ipcdev.pdev->dev; - dev_err(dev, "IPC timed out\n"); + if (!wait_for_completion_timeout(&scu->cmd_complete, 3 * HZ)) { + dev_err(scu->dev, "IPC timed out\n"); return -ETIMEDOUT; } - status = ipc_read_status(); + status = ipc_read_status(scu); if (status & BIT(1)) return -EIO; return 0; } -static int intel_scu_ipc_check_status(void) +static int intel_scu_ipc_check_status(struct intel_scu_ipc_dev *scu) { - return ipcdev.irq_mode ? ipc_wait_for_interrupt() : busy_loop(); + return scu->irq_mode ? ipc_wait_for_interrupt(scu) : busy_loop(scu); } /* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) { + struct intel_scu_ipc_dev *scu = &ipcdev; 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)); - if (ipcdev.pdev == NULL) { + mutex_lock(&ipclock); + + if (scu->dev == NULL) { mutex_unlock(&ipclock); return -ENODEV; } @@ -235,27 +236,27 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) if (id == IPC_CMD_PCNTRL_R) { for (nc = 0, offset = 0; nc < count; nc++, offset += 4) - ipc_data_writel(wbuf[nc], offset); - ipc_command((count * 2) << 16 | id << 12 | 0 << 8 | op); + ipc_data_writel(scu, wbuf[nc], offset); + ipc_command(scu, (count * 2) << 16 | id << 12 | 0 << 8 | op); } else if (id == IPC_CMD_PCNTRL_W) { for (nc = 0; nc < count; nc++, offset += 1) cbuf[offset] = data[nc]; for (nc = 0, offset = 0; nc < count; nc++, offset += 4) - ipc_data_writel(wbuf[nc], offset); - ipc_command((count * 3) << 16 | id << 12 | 0 << 8 | op); + ipc_data_writel(scu, wbuf[nc], offset); + ipc_command(scu, (count * 3) << 16 | id << 12 | 0 << 8 | op); } else if (id == IPC_CMD_PCNTRL_M) { cbuf[offset] = data[0]; cbuf[offset + 1] = data[1]; - ipc_data_writel(wbuf[0], 0); /* Write wbuff */ - ipc_command(4 << 16 | id << 12 | 0 << 8 | op); + ipc_data_writel(scu, wbuf[0], 0); /* Write wbuff */ + ipc_command(scu, 4 << 16 | id << 12 | 0 << 8 | op); } - err = intel_scu_ipc_check_status(); + err = intel_scu_ipc_check_status(scu); if (!err && id == IPC_CMD_PCNTRL_R) { /* Read rbuf */ /* Workaround: values are read as 0 without memcpy_fromio */ - memcpy_fromio(cbuf, ipcdev.ipc_base + 0x90, 16); + memcpy_fromio(cbuf, scu->ipc_base + 0x90, 16); for (nc = 0; nc < count; nc++) - data[nc] = ipc_data_readb(nc); + data[nc] = ipc_data_readb(scu, nc); } mutex_unlock(&ipclock); return err; @@ -436,15 +437,16 @@ EXPORT_SYMBOL(intel_scu_ipc_update_register); */ int intel_scu_ipc_simple_command(int cmd, int sub) { + struct intel_scu_ipc_dev *scu = &ipcdev; int err; mutex_lock(&ipclock); - if (ipcdev.pdev == NULL) { + if (scu->dev == NULL) { mutex_unlock(&ipclock); return -ENODEV; } - ipc_command(sub << 12 | cmd); - err = intel_scu_ipc_check_status(); + ipc_command(scu, sub << 12 | cmd); + err = intel_scu_ipc_check_status(scu); mutex_unlock(&ipclock); return err; } @@ -465,23 +467,24 @@ EXPORT_SYMBOL(intel_scu_ipc_simple_command); int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, u32 *out, int outlen) { + struct intel_scu_ipc_dev *scu = &ipcdev; int i, err; mutex_lock(&ipclock); - if (ipcdev.pdev == NULL) { + if (scu->dev == NULL) { mutex_unlock(&ipclock); return -ENODEV; } for (i = 0; i < inlen; i++) - ipc_data_writel(*in++, 4 * i); + ipc_data_writel(scu, *in++, 4 * i); - ipc_command((inlen << 16) | (sub << 12) | cmd); - err = intel_scu_ipc_check_status(); + ipc_command(scu, (inlen << 16) | (sub << 12) | cmd); + err = intel_scu_ipc_check_status(scu); if (!err) { for (i = 0; i < outlen; i++) - *out++ = ipc_data_readl(4 * i); + *out++ = ipc_data_readl(scu, 4 * i); } mutex_unlock(&ipclock); @@ -507,25 +510,26 @@ EXPORT_SYMBOL(intel_scu_ipc_command); */ int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data) { + struct intel_scu_ipc_dev *scu = &ipcdev; u32 cmd = 0; mutex_lock(&ipclock); - if (ipcdev.pdev == NULL) { + if (scu->dev == NULL) { mutex_unlock(&ipclock); return -ENODEV; } cmd = (addr >> 24) & 0xFF; if (cmd == IPC_I2C_READ) { - writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR); + writel(addr, scu->i2c_base + IPC_I2C_CNTRL_ADDR); /* Write not getting updated without delay */ mdelay(1); - *data = readl(ipcdev.i2c_base + I2C_DATA_ADDR); + *data = readl(scu->i2c_base + I2C_DATA_ADDR); } else if (cmd == IPC_I2C_WRITE) { - writel(*data, ipcdev.i2c_base + I2C_DATA_ADDR); + writel(*data, scu->i2c_base + I2C_DATA_ADDR); mdelay(1); - writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR); + writel(addr, scu->i2c_base + IPC_I2C_CNTRL_ADDR); } else { - dev_err(&ipcdev.pdev->dev, + dev_err(scu->dev, "intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd); mutex_unlock(&ipclock); @@ -545,63 +549,65 @@ EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl); */ static irqreturn_t ioc(int irq, void *dev_id) { - if (ipcdev.irq_mode) - complete(&ipcdev.cmd_complete); + struct intel_scu_ipc_dev *scu = dev_id; + + if (scu->irq_mode) + complete(&scu->cmd_complete); return IRQ_HANDLED; } /** * ipc_probe - probe an Intel SCU IPC - * @dev: the PCI device matching + * @pdev: the PCI device matching * @id: entry in the match table * * Enable and install an intel SCU IPC. This appears in the PCI space * but uses some hard coded addresses as well. */ -static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) +static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id) { + int platform; /* Platform type */ int err; + struct intel_scu_ipc_dev *scu = &ipcdev; struct intel_scu_ipc_pdata_t *pdata; - resource_size_t base; - if (ipcdev.pdev) /* We support only one SCU */ + platform = intel_mid_identify_cpu(); + if (platform == 0) + return -ENODEV; + + if (scu->dev) /* We support only one SCU */ return -EBUSY; pdata = (struct intel_scu_ipc_pdata_t *)id->driver_data; - ipcdev.pdev = pci_dev_get(dev); - ipcdev.irq_mode = pdata->irq_mode; + scu->dev = &pdev->dev; + scu->irq_mode = pdata->irq_mode; - err = pci_enable_device(dev); + err = pcim_enable_device(pdev); if (err) return err; - err = pci_request_regions(dev, "intel_scu_ipc"); + err = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev)); if (err) return err; - base = pci_resource_start(dev, 0); - if (!base) - return -ENOMEM; + init_completion(&scu->cmd_complete); - init_completion(&ipcdev.cmd_complete); + err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc", + scu); + if (err) + return err; - if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev)) - return -EBUSY; + scu->ipc_base = pcim_iomap_table(pdev)[0]; - ipcdev.ipc_base = ioremap_nocache(base, pci_resource_len(dev, 0)); - if (!ipcdev.ipc_base) + scu->i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len); + if (!scu->i2c_base) return -ENOMEM; - ipcdev.i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len); - if (!ipcdev.i2c_base) { - iounmap(ipcdev.ipc_base); - return -ENOMEM; - } - intel_scu_devices_create(); + pci_set_drvdata(pdev, scu); return 0; } @@ -617,12 +623,13 @@ static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) */ static void ipc_remove(struct pci_dev *pdev) { - free_irq(pdev->irq, &ipcdev); - pci_release_regions(pdev); - pci_dev_put(ipcdev.pdev); - iounmap(ipcdev.ipc_base); - iounmap(ipcdev.i2c_base); - ipcdev.pdev = NULL; + struct intel_scu_ipc_dev *scu = pci_get_drvdata(pdev); + + mutex_lock(&ipclock); + scu->dev = NULL; + mutex_unlock(&ipclock); + + iounmap(scu->i2c_base); intel_scu_devices_destroy(); } @@ -652,24 +659,8 @@ static struct pci_driver ipc_driver = { .remove = ipc_remove, }; -static int __init intel_scu_ipc_init(void) -{ - int platform; /* Platform type */ - - platform = intel_mid_identify_cpu(); - if (platform == 0) - return -ENODEV; - return pci_register_driver(&ipc_driver); -} - -static void __exit intel_scu_ipc_exit(void) -{ - pci_unregister_driver(&ipc_driver); -} +module_pci_driver(ipc_driver); MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>"); MODULE_DESCRIPTION("Intel SCU IPC driver"); MODULE_LICENSE("GPL"); - -module_init(intel_scu_ipc_init); -module_exit(intel_scu_ipc_exit); diff --git a/kernel/drivers/platform/x86/intel_scu_ipcutil.c b/kernel/drivers/platform/x86/intel_scu_ipcutil.c index 02bc5a634..aa4542414 100644 --- a/kernel/drivers/platform/x86/intel_scu_ipcutil.c +++ b/kernel/drivers/platform/x86/intel_scu_ipcutil.c @@ -49,7 +49,7 @@ struct scu_ipc_data { static int scu_reg_access(u32 cmd, struct scu_ipc_data *data) { - int count = data->count; + unsigned int count = data->count; if (count == 0 || count == 3 || count > 4) return -EINVAL; diff --git a/kernel/drivers/platform/x86/msi-laptop.c b/kernel/drivers/platform/x86/msi-laptop.c index 085987730..423177046 100644 --- a/kernel/drivers/platform/x86/msi-laptop.c +++ b/kernel/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/kernel/drivers/platform/x86/msi-wmi.c b/kernel/drivers/platform/x86/msi-wmi.c index 6d2bac0c4..978e6d640 100644 --- a/kernel/drivers/platform/x86/msi-wmi.c +++ b/kernel/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/kernel/drivers/platform/x86/pvpanic.c b/kernel/drivers/platform/x86/pvpanic.c index 073a90a63..fd86daba7 100644 --- a/kernel/drivers/platform/x86/pvpanic.c +++ b/kernel/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/kernel/drivers/platform/x86/samsung-laptop.c b/kernel/drivers/platform/x86/samsung-laptop.c index 9e701b225..8c146e2b6 100644 --- a/kernel/drivers/platform/x86/samsung-laptop.c +++ b/kernel/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/kernel/drivers/platform/x86/sony-laptop.c b/kernel/drivers/platform/x86/sony-laptop.c index e51c1e753..f73c29558 100644 --- a/kernel/drivers/platform/x86/sony-laptop.c +++ b/kernel/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 { \ @@ -1203,6 +1204,8 @@ static void sony_nc_notify(struct acpi_device *device, u32 event) { u32 real_ev = event; u8 ev_type = 0; + int ret; + dprintk("sony_nc_notify, event: 0x%.2x\n", event); if (event >= 0x90) { @@ -1224,13 +1227,12 @@ static void sony_nc_notify(struct acpi_device *device, u32 event) case 0x0100: case 0x0127: ev_type = HOTKEY; - real_ev = sony_nc_hotkeys_decode(event, handle); + ret = sony_nc_hotkeys_decode(event, handle); - if (real_ev > 0) - sony_laptop_report_input_event(real_ev); - else - /* restore the original event for reporting */ - real_ev = event; + if (ret > 0) { + sony_laptop_report_input_event(ret); + real_ev = ret; + } break; @@ -3198,12 +3200,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/kernel/drivers/platform/x86/surfacepro3_button.c b/kernel/drivers/platform/x86/surfacepro3_button.c new file mode 100644 index 000000000..f7dade3fd --- /dev/null +++ b/kernel/drivers/platform/x86/surfacepro3_button.c @@ -0,0 +1,216 @@ +/* + * power/home/volume button support for + * Microsoft Surface Pro 3 tablet. + * + * Copyright (c) 2015 Intel Corporation. + * All rights reserved. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/acpi.h> +#include <acpi/button.h> + +#define SURFACE_BUTTON_HID "MSHW0028" +#define SURFACE_BUTTON_OBJ_NAME "VGBI" +#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3 Buttons" + +#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 +#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 + +#define SURFACE_BUTTON_NOTIFY_PRESS_HOME 0xc4 +#define SURFACE_BUTTON_NOTIFY_RELEASE_HOME 0xc5 + +#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0 +#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1 + +#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2 +#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3 + +ACPI_MODULE_NAME("surface pro 3 button"); + +MODULE_AUTHOR("Chen Yu"); +MODULE_DESCRIPTION("Surface Pro3 Button Driver"); +MODULE_LICENSE("GPL v2"); + +/* + * Power button, Home button, Volume buttons support is supposed to + * be covered by drivers/input/misc/soc_button_array.c, which is implemented + * according to "Windows ACPI Design Guide for SoC Platforms". + * However surface pro3 seems not to obey the specs, instead it uses + * device VGBI(MSHW0028) for dispatching the events. + * We choose acpi_driver rather than platform_driver/i2c_driver because + * although VGBI has an i2c resource connected to i2c controller, it + * is not embedded in any i2c controller's scope, thus neither platform_device + * will be created, nor i2c_client will be enumerated, we have to use + * acpi_driver. + */ +static const struct acpi_device_id surface_button_device_ids[] = { + {SURFACE_BUTTON_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, surface_button_device_ids); + +struct surface_button { + unsigned int type; + struct input_dev *input; + char phys[32]; /* for input device */ + unsigned long pushed; + bool suspended; +}; + +static void surface_button_notify(struct acpi_device *device, u32 event) +{ + struct surface_button *button = acpi_driver_data(device); + struct input_dev *input; + int key_code = KEY_RESERVED; + bool pressed = false; + + switch (event) { + /* Power button press,release handle */ + case SURFACE_BUTTON_NOTIFY_PRESS_POWER: + pressed = true; + /*fall through*/ + case SURFACE_BUTTON_NOTIFY_RELEASE_POWER: + key_code = KEY_POWER; + break; + /* Home button press,release handle */ + case SURFACE_BUTTON_NOTIFY_PRESS_HOME: + pressed = true; + /*fall through*/ + case SURFACE_BUTTON_NOTIFY_RELEASE_HOME: + key_code = KEY_LEFTMETA; + break; + /* Volume up button press,release handle */ + case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP: + pressed = true; + /*fall through*/ + case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP: + key_code = KEY_VOLUMEUP; + break; + /* Volume down button press,release handle */ + case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN: + pressed = true; + /*fall through*/ + case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN: + key_code = KEY_VOLUMEDOWN; + break; + default: + dev_info_ratelimited(&device->dev, + "Unsupported event [0x%x]\n", event); + break; + } + input = button->input; + if (KEY_RESERVED == key_code) + return; + if (pressed) + pm_wakeup_event(&device->dev, 0); + if (button->suspended) + return; + input_report_key(input, key_code, pressed?1:0); + input_sync(input); +} + +#ifdef CONFIG_PM_SLEEP +static int surface_button_suspend(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct surface_button *button = acpi_driver_data(device); + + button->suspended = true; + return 0; +} + +static int surface_button_resume(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct surface_button *button = acpi_driver_data(device); + + button->suspended = false; + return 0; +} +#endif + +static int surface_button_add(struct acpi_device *device) +{ + struct surface_button *button; + struct input_dev *input; + const char *hid = acpi_device_hid(device); + char *name; + int error; + + if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, + strlen(SURFACE_BUTTON_OBJ_NAME))) + return -ENODEV; + + button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); + if (!button) + return -ENOMEM; + + device->driver_data = button; + button->input = input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto err_free_button; + } + + name = acpi_device_name(device); + strcpy(name, SURFACE_BUTTON_DEVICE_NAME); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); + + input->name = name; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &device->dev; + input_set_capability(input, EV_KEY, KEY_POWER); + input_set_capability(input, EV_KEY, KEY_LEFTMETA); + input_set_capability(input, EV_KEY, KEY_VOLUMEUP); + input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); + + error = input_register_device(input); + if (error) + goto err_free_input; + dev_info(&device->dev, + "%s [%s]\n", name, acpi_device_bid(device)); + return 0; + + err_free_input: + input_free_device(input); + err_free_button: + kfree(button); + return error; +} + +static int surface_button_remove(struct acpi_device *device) +{ + struct surface_button *button = acpi_driver_data(device); + + input_unregister_device(button->input); + kfree(button); + return 0; +} + +static SIMPLE_DEV_PM_OPS(surface_button_pm, + surface_button_suspend, surface_button_resume); + +static struct acpi_driver surface_button_driver = { + .name = "surface_pro3_button", + .class = "SurfacePro3", + .ids = surface_button_device_ids, + .ops = { + .add = surface_button_add, + .remove = surface_button_remove, + .notify = surface_button_notify, + }, + .drv.pm = &surface_button_pm, +}; + +module_acpi_driver(surface_button_driver); diff --git a/kernel/drivers/platform/x86/tc1100-wmi.c b/kernel/drivers/platform/x86/tc1100-wmi.c index e36542564..89aa976f0 100644 --- a/kernel/drivers/platform/x86/tc1100-wmi.c +++ b/kernel/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/kernel/drivers/platform/x86/thinkpad_acpi.c b/kernel/drivers/platform/x86/thinkpad_acpi.c index 28f328136..0bed4733c 100644 --- a/kernel/drivers/platform/x86/thinkpad_acpi.c +++ b/kernel/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 @@ -401,7 +402,7 @@ static const char *str_supported(int is_supported); #else static inline const char *str_supported(int is_supported) { return ""; } #define vdbg_printk(a_dbg_level, format, arg...) \ - no_printk(format, ##arg) + do { if (0) no_printk(format, ##arg); } while (0) #endif static void tpacpi_log_usertask(const char * const what) @@ -3487,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"); @@ -6458,8 +6459,7 @@ static void __init tpacpi_detect_brightness_capabilities(void) pr_info("detected a 8-level brightness capable ThinkPad\n"); break; default: - pr_err("Unsupported brightness interface, " - "please contact %s\n", TPACPI_MAIL); + pr_info("Unsupported brightness interface\n"); tp_features.bright_unkfw = 1; bright_maxlvl = b - 1; } @@ -6491,7 +6491,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"); diff --git a/kernel/drivers/platform/x86/toshiba-wmi.c b/kernel/drivers/platform/x86/toshiba-wmi.c new file mode 100644 index 000000000..feac4576b --- /dev/null +++ b/kernel/drivers/platform/x86/toshiba-wmi.c @@ -0,0 +1,138 @@ +/* + * toshiba_wmi.c - Toshiba WMI Hotkey Driver + * + * Copyright (C) 2015 Azael Avalos <coproscefalo@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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> + +MODULE_AUTHOR("Azael Avalos"); +MODULE_DESCRIPTION("Toshiba WMI Hotkey Driver"); +MODULE_LICENSE("GPL"); + +#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100" + +MODULE_ALIAS("wmi:"TOSHIBA_WMI_EVENT_GUID); + +static struct input_dev *toshiba_wmi_input_dev; + +static const struct key_entry toshiba_wmi_keymap[] __initconst = { + /* TODO: Add keymap values once found... */ + /*{ KE_KEY, 0x00, { KEY_ } },*/ + { KE_END, 0 } +}; + +static void toshiba_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (ACPI_FAILURE(status)) { + pr_err("Bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + if (!obj) + return; + + /* TODO: Add proper checks once we have data */ + pr_debug("Unknown event received, obj type %x\n", obj->type); + + kfree(response.pointer); +} + +static int __init toshiba_wmi_input_setup(void) +{ + acpi_status status; + int err; + + toshiba_wmi_input_dev = input_allocate_device(); + if (!toshiba_wmi_input_dev) + return -ENOMEM; + + toshiba_wmi_input_dev->name = "Toshiba WMI hotkeys"; + toshiba_wmi_input_dev->phys = "wmi/input0"; + toshiba_wmi_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(toshiba_wmi_input_dev, + toshiba_wmi_keymap, NULL); + if (err) + goto err_free_dev; + + status = wmi_install_notify_handler(TOSHIBA_WMI_EVENT_GUID, + toshiba_wmi_notify, NULL); + if (ACPI_FAILURE(status)) { + err = -EIO; + goto err_free_keymap; + } + + err = input_register_device(toshiba_wmi_input_dev); + if (err) + goto err_remove_notifier; + + return 0; + + err_remove_notifier: + wmi_remove_notify_handler(TOSHIBA_WMI_EVENT_GUID); + err_free_keymap: + sparse_keymap_free(toshiba_wmi_input_dev); + err_free_dev: + input_free_device(toshiba_wmi_input_dev); + return err; +} + +static void toshiba_wmi_input_destroy(void) +{ + wmi_remove_notify_handler(TOSHIBA_WMI_EVENT_GUID); + sparse_keymap_free(toshiba_wmi_input_dev); + input_unregister_device(toshiba_wmi_input_dev); +} + +static int __init toshiba_wmi_init(void) +{ + int ret; + + if (!wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + return -ENODEV; + + ret = toshiba_wmi_input_setup(); + if (ret) { + pr_err("Failed to setup input device\n"); + return ret; + } + + pr_info("Toshiba WMI Hotkey Driver\n"); + + return 0; +} + +static void __exit toshiba_wmi_exit(void) +{ + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + toshiba_wmi_input_destroy(); +} + +module_init(toshiba_wmi_init); +module_exit(toshiba_wmi_exit); diff --git a/kernel/drivers/platform/x86/toshiba_acpi.c b/kernel/drivers/platform/x86/toshiba_acpi.c index 9956b9902..b0f62141e 100644 --- a/kernel/drivers/platform/x86/toshiba_acpi.c +++ b/kernel/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.23" #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,8 @@ #include <linux/acpi.h> #include <linux/dmi.h> #include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/toshiba.h> #include <acpi/video.h> MODULE_AUTHOR("John Belmonte"); @@ -82,7 +83,7 @@ MODULE_LICENSE("GPL"); #define TCI_WORDS 6 -/* operations */ +/* Operations */ #define HCI_SET 0xff00 #define HCI_GET 0xfe00 #define SCI_OPEN 0xf100 @@ -90,8 +91,9 @@ MODULE_LICENSE("GPL"); #define SCI_GET 0xf300 #define SCI_SET 0xf400 -/* return codes */ +/* Return codes */ #define TOS_SUCCESS 0x0000 +#define TOS_SUCCESS2 0x0001 #define TOS_OPEN_CLOSE_OK 0x0044 #define TOS_FAILURE 0x1000 #define TOS_NOT_SUPPORTED 0x8000 @@ -105,14 +107,13 @@ 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 #define HCI_VIDEO_OUT 0x001c #define HCI_HOTKEY_EVENT 0x001e #define HCI_LCD_BRIGHTNESS 0x002a -#define HCI_WIRELESS 0x0056 #define HCI_ACCELEROMETER 0x006d #define HCI_KBD_ILLUMINATION 0x0095 #define HCI_ECO_MODE 0x0097 @@ -127,10 +128,10 @@ 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_ENABLE 0x01 #define HCI_HOTKEY_SPECIAL_FUNCTIONS 0x10 #define HCI_LCD_BRIGHTNESS_BITS 3 #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) @@ -141,10 +142,6 @@ MODULE_LICENSE("GPL"); #define HCI_VIDEO_OUT_LCD 0x1 #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 -#define HCI_WIRELESS_KILL_SWITCH 0x01 -#define HCI_WIRELESS_BT_PRESENT 0x0f -#define HCI_WIRELESS_BT_ATTACH 0x40 -#define HCI_WIRELESS_BT_POWER 0x80 #define SCI_KBD_MODE_MASK 0x1f #define SCI_KBD_MODE_FNZ 0x1 #define SCI_KBD_MODE_AUTO 0x2 @@ -165,13 +162,13 @@ 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; struct led_classdev led_dev; struct led_classdev kbd_led; struct led_classdev eco_led; + struct miscdevice miscdev; int force_fan; int last_key_event; @@ -191,7 +188,6 @@ struct toshiba_acpi_dev { unsigned int info_supported:1; unsigned int tr_backlight_supported:1; unsigned int kbd_illum_supported:1; - unsigned int kbd_led_registered:1; unsigned int touchpad_supported:1; unsigned int eco_supported:1; unsigned int accelerometer_supported:1; @@ -202,8 +198,11 @@ struct toshiba_acpi_dev { unsigned int panel_power_on_supported:1; unsigned int usb_three_supported:1; unsigned int sysfs_created:1; + unsigned int special_functions; - struct mutex mutex; + bool kbd_led_registered; + bool illumination_led_registered; + bool eco_led_registered; }; static struct toshiba_acpi_dev *toshiba_acpi; @@ -252,16 +251,16 @@ static const struct key_entry toshiba_acpi_keymap[] = { }; static const struct key_entry toshiba_acpi_alt_keymap[] = { - { KE_KEY, 0x157, { KEY_MUTE } }, { KE_KEY, 0x102, { KEY_ZOOMOUT } }, { KE_KEY, 0x103, { KEY_ZOOMIN } }, { KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } }, { KE_KEY, 0x139, { KEY_ZOOMRESET } }, - { KE_KEY, 0x13e, { KEY_SWITCHVIDEOMODE } }, { KE_KEY, 0x13c, { KEY_BRIGHTNESSDOWN } }, { KE_KEY, 0x13d, { KEY_BRIGHTNESSUP } }, - { KE_KEY, 0x158, { KEY_WLAN } }, + { KE_KEY, 0x13e, { KEY_SWITCHVIDEOMODE } }, { KE_KEY, 0x13f, { KEY_TOUCHPAD_TOGGLE } }, + { KE_KEY, 0x157, { KEY_MUTE } }, + { KE_KEY, 0x158, { KEY_WLAN } }, { KE_END, 0 }, }; @@ -330,13 +329,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]; @@ -345,7 +344,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]; @@ -359,31 +358,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 */ @@ -395,7 +369,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; } @@ -433,7 +407,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; } @@ -470,26 +444,24 @@ static u32 sci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) } /* Illumination support */ -static int toshiba_illumination_available(struct toshiba_acpi_dev *dev) +static void toshiba_illumination_available(struct toshiba_acpi_dev *dev) { u32 in[TCI_WORDS] = { SCI_GET, SCI_ILLUMINATION, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status; + dev->illumination_supported = 0; + dev->illumination_led_registered = false; + if (!sci_open(dev)) - return 0; + return; 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) { - pr_info("Illumination device not available\n"); - return 0; - } - - return 1; + else if (out[0] == TOS_SUCCESS) + dev->illumination_supported = 1; } static void toshiba_illumination_set(struct led_classdev *cdev, @@ -497,7 +469,8 @@ static void toshiba_illumination_set(struct led_classdev *cdev, { struct toshiba_acpi_dev *dev = container_of(cdev, struct toshiba_acpi_dev, led_dev); - u32 state, result; + u32 result; + u32 state; /* First request : initialize communication. */ if (!sci_open(dev)) @@ -507,13 +480,8 @@ static void toshiba_illumination_set(struct led_classdev *cdev, state = brightness ? 1 : 0; result = sci_write(dev, SCI_ILLUMINATION, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call for illumination failed\n"); - return; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Illumination not supported\n"); - return; - } } static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) @@ -522,18 +490,17 @@ 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; /* Check the illumination */ result = sci_read(dev, SCI_ILLUMINATION, &state); sci_close(dev); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + if (result == TOS_FAILURE) { pr_err("ACPI call for illumination failed\n"); return LED_OFF; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Illumination not supported\n"); + } else if (result != TOS_SUCCESS) { return LED_OFF; } @@ -541,41 +508,40 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) } /* KBD Illumination */ -static int toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev) +static void toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev) { u32 in[TCI_WORDS] = { SCI_GET, SCI_KBD_ILLUM_STATUS, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status; + dev->kbd_illum_supported = 0; + dev->kbd_led_registered = false; + if (!sci_open(dev)) - return 0; + return; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to query kbd illumination support failed\n"); - return 0; - } else if (out[0] == TOS_NOT_SUPPORTED) { - pr_info("Keyboard illumination not available\n"); - return 0; + } else if (out[0] == TOS_SUCCESS) { + /* + * Check for keyboard backlight timeout max value, + * previous kbd backlight implementation set this to + * 0x3c0003, and now the new implementation set this + * to 0x3c001a, use this to distinguish between them. + */ + if (out[3] == SCI_KBD_TIME_MAX) + dev->kbd_type = 2; + else + dev->kbd_type = 1; + /* Get the current keyboard backlight mode */ + dev->kbd_mode = out[2] & SCI_KBD_MODE_MASK; + /* Get the current time (1-60 seconds) */ + dev->kbd_time = out[2] >> HCI_MISC_SHIFT; + /* Flag as supported */ + dev->kbd_illum_supported = 1; } - - /* - * Check for keyboard backlight timeout max value, - * previous kbd backlight implementation set this to - * 0x3c0003, and now the new implementation set this - * to 0x3c001a, use this to distinguish between them. - */ - if (out[3] == SCI_KBD_TIME_MAX) - dev->kbd_type = 2; - else - dev->kbd_type = 1; - /* Get the current keyboard backlight mode */ - dev->kbd_mode = out[2] & SCI_KBD_MODE_MASK; - /* Get the current time (1-60 seconds) */ - dev->kbd_time = out[2] >> HCI_MISC_SHIFT; - - return 1; } static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time) @@ -587,15 +553,12 @@ static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time) result = sci_write(dev, SCI_KBD_ILLUM_STATUS, time); sci_close(dev); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + if (result == TOS_FAILURE) pr_err("ACPI call to set KBD backlight status failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Keyboard backlight status not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time) @@ -607,30 +570,27 @@ static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time) result = sci_read(dev, SCI_KBD_ILLUM_STATUS, time); sci_close(dev); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + if (result == TOS_FAILURE) pr_err("ACPI call to get KBD backlight status failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Keyboard backlight status not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev) { struct toshiba_acpi_dev *dev = container_of(cdev, struct toshiba_acpi_dev, kbd_led); - u32 state, result; + u32 result; + u32 state; /* Check the keyboard backlight state */ - result = hci_read1(dev, HCI_KBD_ILLUMINATION, &state); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + result = hci_read(dev, HCI_KBD_ILLUMINATION, &state); + if (result == TOS_FAILURE) { pr_err("ACPI call to get the keyboard backlight failed\n"); return LED_OFF; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Keyboard backlight not supported\n"); + } else if (result != TOS_SUCCESS) { return LED_OFF; } @@ -642,18 +602,14 @@ static void toshiba_kbd_backlight_set(struct led_classdev *cdev, { struct toshiba_acpi_dev *dev = container_of(cdev, struct toshiba_acpi_dev, kbd_led); - u32 state, result; + u32 result; + u32 state; /* Set the keyboard backlight state */ state = brightness ? 1 : 0; - result = hci_write1(dev, HCI_KBD_ILLUMINATION, state); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + result = hci_write(dev, HCI_KBD_ILLUMINATION, state); + if (result == TOS_FAILURE) pr_err("ACPI call to set KBD Illumination mode failed\n"); - return; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Keyboard backlight not supported\n"); - return; - } } /* TouchPad support */ @@ -666,14 +622,12 @@ static int toshiba_touchpad_set(struct toshiba_acpi_dev *dev, u32 state) result = sci_write(dev, SCI_TOUCHPAD, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to set the touchpad failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state) @@ -685,28 +639,27 @@ static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state) result = sci_read(dev, SCI_TOUCHPAD, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to query the touchpad failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } /* Eco Mode support */ -static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) +static void toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) { acpi_status status; u32 in[TCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; + dev->eco_supported = 0; + dev->eco_led_registered = false; + 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"); } else if (out[0] == TOS_INPUT_DATA_ERROR) { /* * If we receive 0x8300 (Input Data Error), it means that the @@ -719,13 +672,11 @@ static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) */ in[3] = 1; 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_SUCCESS) - return 1; + dev->eco_supported = 1; } - - return 0; } static enum led_brightness @@ -738,9 +689,11 @@ toshiba_eco_mode_get_status(struct led_classdev *cdev) acpi_status status; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get ECO led failed\n"); return LED_OFF; + } else if (out[0] != TOS_SUCCESS) { + return LED_OFF; } return out[2] ? LED_FULL : LED_OFF; @@ -758,41 +711,32 @@ static void toshiba_eco_mode_set_status(struct led_classdev *cdev, /* Switch the Eco Mode led on/off */ in[2] = (brightness) ? 1 : 0; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { + if (ACPI_FAILURE(status)) pr_err("ACPI call to set ECO led failed\n"); - return; - } } /* Accelerometer support */ -static int toshiba_accelerometer_supported(struct toshiba_acpi_dev *dev) +static void toshiba_accelerometer_available(struct toshiba_acpi_dev *dev) { u32 in[TCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER2, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; acpi_status status; + dev->accelerometer_supported = 0; + /* * Check if the accelerometer call exists, * this call also serves as initialization */ status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { + if (ACPI_FAILURE(status)) pr_err("ACPI call to query the accelerometer failed\n"); - return -EIO; - } else if (out[0] == TOS_DATA_NOT_AVAILABLE || - out[0] == TOS_NOT_INITIALIZED) { - pr_err("Accelerometer not initialized\n"); - return -EIO; - } else if (out[0] == TOS_NOT_SUPPORTED) { - pr_info("Accelerometer not supported\n"); - return -ENODEV; - } - - return 0; + else if (out[0] == TOS_SUCCESS) + dev->accelerometer_supported = 1; } static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, - u32 *xy, u32 *z) + u32 *xy, u32 *z) { u32 in[TCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER, 0, 1, 0, 0 }; u32 out[TCI_WORDS]; @@ -800,15 +744,18 @@ static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, /* Check the Accelerometer status */ status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_INPUT_DATA_ERROR) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to query the accelerometer failed\n"); return -EIO; + } else if (out[0] == TOS_NOT_SUPPORTED) { + return -ENODEV; + } else if (out[0] == TOS_SUCCESS) { + *xy = out[2]; + *z = out[4]; + return 0; } - *xy = out[2]; - *z = out[4]; - - return 0; + return -EIO; } /* Sleep (Charge and Music) utilities support */ @@ -818,19 +765,17 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) u32 out[TCI_WORDS]; acpi_status status; - /* Set the feature to "not supported" in case of error */ dev->usb_sleep_charge_supported = 0; if (!sci_open(dev)) return; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + 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) { @@ -839,25 +784,15 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + sci_close(dev); + 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. - */ + /* Flag as supported */ dev->usb_sleep_charge_supported = 1; } - sci_close(dev); } static int toshiba_usb_sleep_charge_get(struct toshiba_acpi_dev *dev, @@ -870,17 +805,12 @@ static int toshiba_usb_sleep_charge_get(struct toshiba_acpi_dev *dev, result = sci_read(dev, SCI_USB_SLEEP_CHARGE, mode); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to set USB S&C mode failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB Sleep and Charge not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_usb_sleep_charge_set(struct toshiba_acpi_dev *dev, @@ -893,17 +823,12 @@ static int toshiba_usb_sleep_charge_set(struct toshiba_acpi_dev *dev, result = sci_write(dev, SCI_USB_SLEEP_CHARGE, mode); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to set USB S&C mode failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB Sleep and Charge not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev, @@ -919,19 +844,16 @@ 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) { - pr_info("USB Sleep and Charge not supported\n"); return -ENODEV; - } else if (out[0] == TOS_INPUT_DATA_ERROR) { - return -EIO; + } else if (out[0] == TOS_SUCCESS) { + *mode = out[2]; + return 0; } - *mode = out[2]; - - return 0; + return -EIO; } static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev, @@ -948,17 +870,12 @@ 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) { - pr_info("USB Sleep and Charge not supported\n"); + else if (out[0] == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (out[0] == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return out[0] == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev, @@ -974,18 +891,16 @@ 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) { + 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 Rapid Charge not supported\n"); + } else if (out[0] == TOS_NOT_SUPPORTED) { return -ENODEV; + } else if (out[0] == TOS_SUCCESS || out[0] == TOS_SUCCESS2) { + *state = out[2]; + return 0; } - *state = out[2]; - - return 0; + return -EIO; } static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev, @@ -1002,17 +917,12 @@ 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) { + 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 Rapid Charge not supported\n"); + else if (out[0] == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (out[0] == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return (out[0] == TOS_SUCCESS || out[0] == TOS_SUCCESS2) ? 0 : -EIO; } static int toshiba_usb_sleep_music_get(struct toshiba_acpi_dev *dev, u32 *state) @@ -1024,17 +934,12 @@ 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) { + if (result == TOS_FAILURE) pr_err("ACPI call to get Sleep and Music failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Sleep and Music not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_usb_sleep_music_set(struct toshiba_acpi_dev *dev, u32 state) @@ -1046,17 +951,12 @@ 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) { + if (result == TOS_FAILURE) pr_err("ACPI call to set Sleep and Music failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Sleep and Music not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } /* Keyboard function keys */ @@ -1069,15 +969,12 @@ static int toshiba_function_keys_get(struct toshiba_acpi_dev *dev, u32 *mode) result = sci_read(dev, SCI_KBD_FUNCTION_KEYS, mode); sci_close(dev); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + if (result == TOS_FAILURE) pr_err("ACPI call to get KBD function keys failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("KBD function keys not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO; } static int toshiba_function_keys_set(struct toshiba_acpi_dev *dev, u32 mode) @@ -1089,15 +986,12 @@ static int toshiba_function_keys_set(struct toshiba_acpi_dev *dev, u32 mode) result = sci_write(dev, SCI_KBD_FUNCTION_KEYS, mode); sci_close(dev); - if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { + if (result == TOS_FAILURE) pr_err("ACPI call to set KBD function keys failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("KBD function keys not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } - return 0; + return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO; } /* Panel Power ON */ @@ -1110,17 +1004,12 @@ static int toshiba_panel_power_on_get(struct toshiba_acpi_dev *dev, u32 *state) result = sci_read(dev, SCI_PANEL_POWER_ON, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to get Panel Power ON failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Panel Power on not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } static int toshiba_panel_power_on_set(struct toshiba_acpi_dev *dev, u32 state) @@ -1132,17 +1021,12 @@ static int toshiba_panel_power_on_set(struct toshiba_acpi_dev *dev, u32 state) result = sci_write(dev, SCI_PANEL_POWER_ON, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to set Panel Power ON failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("Panel Power ON not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return result == TOS_SUCCESS ? 0 : -EIO; } /* USB Three */ @@ -1155,17 +1039,12 @@ static int toshiba_usb_three_get(struct toshiba_acpi_dev *dev, u32 *state) result = sci_read(dev, SCI_USB_THREE, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to get USB 3 failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB 3 not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO; } static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) @@ -1177,172 +1056,85 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) result = sci_write(dev, SCI_USB_THREE, state); sci_close(dev); - if (result == TOS_FAILURE) { + if (result == TOS_FAILURE) pr_err("ACPI call to set USB 3 failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("USB 3 not supported\n"); + else if (result == TOS_NOT_SUPPORTED) return -ENODEV; - } else if (result == TOS_INPUT_DATA_ERROR) { - return -EIO; - } - return 0; + return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO; } /* Hotkey Event type */ static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, u32 *type) { - u32 val1 = 0x03; - u32 val2 = 0; - u32 result; + u32 in[TCI_WORDS] = { HCI_GET, HCI_SYSTEM_INFO, 0x03, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; - result = hci_read2(dev, HCI_SYSTEM_INFO, &val1, &val2); - if (result == TOS_FAILURE) { + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get System type failed\n"); - return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { - pr_info("System type not supported\n"); + } else if (out[0] == TOS_NOT_SUPPORTED) { return -ENODEV; + } else if (out[0] == TOS_SUCCESS) { + *type = out[3]; + return 0; } - *type = val2; - - 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) -{ - 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; + return -EIO; } -static void bt_rfkill_poll(struct rfkill *rfkill, void *data) +/* Transflective Backlight */ +static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 *status) { - bool new_rfk_state; - bool value; - u32 hci_result; - struct toshiba_acpi_dev *dev = data; - - mutex_lock(&dev->mutex); - - hci_result = hci_get_radio_state(dev, &value); - if (hci_result != TOS_SUCCESS) { - /* Can't do anything useful */ - mutex_unlock(&dev->mutex); - return; - } + u32 result = hci_read(dev, HCI_TR_BACKLIGHT, status); - new_rfk_state = value; - - mutex_unlock(&dev->mutex); + if (result == TOS_FAILURE) + pr_err("ACPI call to get Transflective Backlight failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; - if (rfkill_set_hw_state(rfkill, !new_rfk_state)) - bt_rfkill_set_block(data, true); + return result == TOS_SUCCESS ? 0 : -EIO; } -static const struct rfkill_ops toshiba_rfk_ops = { - .set_block = bt_rfkill_set_block, - .poll = bt_rfkill_poll, -}; - -static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) +static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 status) { - u32 hci_result; - u32 status; + u32 result = hci_write(dev, HCI_TR_BACKLIGHT, !status); - hci_result = hci_read1(dev, HCI_TR_BACKLIGHT, &status); - *enabled = !status; - return hci_result == TOS_SUCCESS ? 0 : -EIO; -} - -static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) -{ - u32 hci_result; - u32 value = !enable; + if (result == TOS_FAILURE) + pr_err("ACPI call to set Transflective Backlight failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; - hci_result = hci_write1(dev, HCI_TR_BACKLIGHT, value); - return hci_result == TOS_SUCCESS ? 0 : -EIO; + return 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; + u32 result; u32 value; int brightness = 0; if (dev->tr_backlight_supported) { - bool enabled; - int ret = get_tr_backlight_status(dev, &enabled); + int ret = get_tr_backlight_status(dev, &value); if (ret) return ret; - if (enabled) + if (value) return 0; brightness++; } - hci_result = hci_read1(dev, HCI_LCD_BRIGHTNESS, &value); - if (hci_result == TOS_SUCCESS) + result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value); + if (result == TOS_FAILURE) + pr_err("ACPI call to get LCD Brightness failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + if (result == TOS_SUCCESS) return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT); return -EIO; @@ -1358,8 +1150,8 @@ static int get_lcd_brightness(struct backlight_device *bd) static int lcd_proc_show(struct seq_file *m, void *v) { struct toshiba_acpi_dev *dev = m->private; - int value; int levels; + int value; if (!dev->backlight_dev) return -ENODEV; @@ -1373,6 +1165,7 @@ static int lcd_proc_show(struct seq_file *m, void *v) } pr_err("Error reading LCD brightness\n"); + return -EIO; } @@ -1383,11 +1176,10 @@ static int lcd_proc_open(struct inode *inode, struct file *file) static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value) { - u32 hci_result; + u32 result; if (dev->tr_backlight_supported) { - bool enable = !value; - int ret = set_tr_backlight_status(dev, enable); + int ret = set_tr_backlight_status(dev, !value); if (ret) return ret; @@ -1396,8 +1188,13 @@ 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); - return hci_result == TOS_SUCCESS ? 0 : -EIO; + result = hci_write(dev, HCI_LCD_BRIGHTNESS, value); + if (result == TOS_FAILURE) + pr_err("ACPI call to set LCD Brightness failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return result == TOS_SUCCESS ? 0 : -EIO; } static int set_lcd_status(struct backlight_device *bd) @@ -1413,24 +1210,22 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf, struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); char cmd[42]; size_t len; - int value; - int ret; int levels = dev->backlight_dev->props.max_brightness + 1; + int value; len = min(count, sizeof(cmd) - 1); if (copy_from_user(cmd, buf, len)) return -EFAULT; cmd[len] = '\0'; - if (sscanf(cmd, " brightness : %i", &value) == 1 && - value >= 0 && value < levels) { - ret = set_lcd_brightness(dev, value); - if (ret == 0) - ret = count; - } else { - ret = -EINVAL; - } - return ret; + if (sscanf(cmd, " brightness : %i", &value) != 1 && + value < 0 && value > levels) + return -EINVAL; + + if (set_lcd_brightness(dev, value)) + return -EIO; + + return count; } static const struct file_operations lcd_proc_fops = { @@ -1442,22 +1237,25 @@ static const struct file_operations lcd_proc_fops = { .write = lcd_proc_write, }; +/* Video-Out */ static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status) { - u32 hci_result; + u32 result = hci_read(dev, HCI_VIDEO_OUT, status); - hci_result = hci_read1(dev, HCI_VIDEO_OUT, status); - return hci_result == TOS_SUCCESS ? 0 : -EIO; + if (result == TOS_FAILURE) + pr_err("ACPI call to get Video-Out failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return result == TOS_SUCCESS ? 0 : -EIO; } static int video_proc_show(struct seq_file *m, void *v) { struct toshiba_acpi_dev *dev = m->private; u32 value; - int ret; - ret = get_video_status(dev, &value); - if (!ret) { + if (!get_video_status(dev, &value)) { int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; @@ -1465,9 +1263,10 @@ static int video_proc_show(struct seq_file *m, void *v) seq_printf(m, "lcd_out: %d\n", is_lcd); seq_printf(m, "crt_out: %d\n", is_crt); seq_printf(m, "tv_out: %d\n", is_tv); + return 0; } - return ret; + return -EIO; } static int video_proc_open(struct inode *inode, struct file *file) @@ -1479,13 +1278,14 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); - char *cmd, *buffer; - int ret; - int value; + char *buffer; + char *cmd; int remain = count; int lcd_out = -1; int crt_out = -1; int tv_out = -1; + int value; + int ret; u32 video_out; cmd = kmalloc(count + 1, GFP_KERNEL); @@ -1531,12 +1331,13 @@ 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); } - return ret ? ret : count; + return ret ? -EIO : count; } static const struct file_operations video_proc_fops = { @@ -1548,27 +1349,43 @@ static const struct file_operations video_proc_fops = { .write = video_proc_write, }; +/* Fan status */ static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status) { - u32 hci_result; + u32 result = hci_read(dev, HCI_FAN, status); - hci_result = hci_read1(dev, HCI_FAN, status); - return hci_result == TOS_SUCCESS ? 0 : -EIO; + if (result == TOS_FAILURE) + pr_err("ACPI call to get Fan status failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return result == TOS_SUCCESS ? 0 : -EIO; +} + +static int set_fan_status(struct toshiba_acpi_dev *dev, u32 status) +{ + u32 result = hci_write(dev, HCI_FAN, status); + + if (result == TOS_FAILURE) + pr_err("ACPI call to set Fan status failed\n"); + else if (result == TOS_NOT_SUPPORTED) + return -ENODEV; + + return result == TOS_SUCCESS ? 0 : -EIO; } static int fan_proc_show(struct seq_file *m, void *v) { struct toshiba_acpi_dev *dev = m->private; - int ret; u32 value; - ret = get_fan_status(dev, &value); - if (!ret) { - seq_printf(m, "running: %d\n", (value > 0)); - seq_printf(m, "force_on: %d\n", dev->force_fan); - } + if (get_fan_status(dev, &value)) + return -EIO; - return ret; + seq_printf(m, "running: %d\n", (value > 0)); + seq_printf(m, "force_on: %d\n", dev->force_fan); + + return 0; } static int fan_proc_open(struct inode *inode, struct file *file) @@ -1583,23 +1400,20 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, char cmd[42]; size_t len; int value; - u32 hci_result; len = min(count, sizeof(cmd) - 1); if (copy_from_user(cmd, buf, len)) return -EFAULT; cmd[len] = '\0'; - if (sscanf(cmd, " force_on : %i", &value) == 1 && - value >= 0 && value <= 1) { - hci_result = hci_write1(dev, HCI_FAN, value); - if (hci_result == TOS_SUCCESS) - dev->force_fan = value; - else - return -EIO; - } else { + if (sscanf(cmd, " force_on : %i", &value) != 1 && + value != 0 && value != 1) return -EINVAL; - } + + if (set_fan_status(dev, value)) + return -EIO; + + dev->force_fan = value; return count; } @@ -1616,32 +1430,10 @@ static const struct file_operations fan_proc_fops = { static int keys_proc_show(struct seq_file *m, void *v) { struct toshiba_acpi_dev *dev = m->private; - u32 hci_result; - u32 value; - - if (!dev->key_event_valid && dev->system_event_supported) { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); - if (hci_result == TOS_SUCCESS) { - dev->key_event_valid = 1; - dev->last_key_event = value; - } else if (hci_result == TOS_FIFO_EMPTY) { - /* Better luck next time */ - } else if (hci_result == TOS_NOT_SUPPORTED) { - /* - * This is a workaround for an unresolved issue on - * some machines where system events sporadically - * become disabled. - */ - hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); - pr_notice("Re-enabled hotkeys\n"); - } else { - pr_err("Error reading hotkey status\n"); - return -EIO; - } - } seq_printf(m, "hotkey_ready: %d\n", dev->key_event_valid); seq_printf(m, "hotkey: 0x%04x\n", dev->last_key_event); + return 0; } @@ -1758,7 +1550,6 @@ static ssize_t fan_store(struct device *dev, const char *buf, size_t count) { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); - u32 result; int state; int ret; @@ -1769,11 +1560,9 @@ static ssize_t fan_store(struct device *dev, if (state != 0 && state != 1) return -EINVAL; - result = hci_write1(toshiba, HCI_FAN, state); - if (result == TOS_FAILURE) - return -EIO; - else if (result == TOS_NOT_SUPPORTED) - return -ENODEV; + ret = set_fan_status(toshiba, state); + if (ret) + return ret; return count; } @@ -1799,7 +1588,6 @@ static ssize_t kbd_backlight_mode_store(struct device *dev, { struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); int mode; - int time; int ret; @@ -1830,7 +1618,7 @@ static ssize_t kbd_backlight_mode_store(struct device *dev, /* Only make a change if the actual mode has changed */ if (toshiba->kbd_mode != mode) { /* Shift the time to "base time" (0x3c0000 == 60 seconds) */ - time = toshiba->kbd_time << HCI_MISC_SHIFT; + int time = toshiba->kbd_time << HCI_MISC_SHIFT; /* OR the "base time" to the actual method format */ if (toshiba->kbd_type == 1) { @@ -1881,10 +1669,10 @@ static ssize_t available_kbd_modes_show(struct device *dev, struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); if (toshiba->kbd_type == 1) - return sprintf(buf, "%x %x\n", + return sprintf(buf, "0x%x 0x%x\n", SCI_KBD_MODE_FNZ, SCI_KBD_MODE_AUTO); - return sprintf(buf, "%x %x %x\n", + return sprintf(buf, "0x%x 0x%x 0x%x\n", SCI_KBD_MODE_AUTO, SCI_KBD_MODE_ON, SCI_KBD_MODE_OFF); } static DEVICE_ATTR_RO(available_kbd_modes); @@ -2379,6 +2167,81 @@ static struct attribute_group toshiba_attr_group = { }; /* + * Misc device + */ +static int toshiba_acpi_smm_bridge(SMMRegisters *regs) +{ + u32 in[TCI_WORDS] = { regs->eax, regs->ebx, regs->ecx, + regs->edx, regs->esi, regs->edi }; + u32 out[TCI_WORDS]; + acpi_status status; + + status = tci_raw(toshiba_acpi, in, out); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call to query SMM registers failed\n"); + return -EIO; + } + + /* Fillout the SMM struct with the TCI call results */ + regs->eax = out[0]; + regs->ebx = out[1]; + regs->ecx = out[2]; + regs->edx = out[3]; + regs->esi = out[4]; + regs->edi = out[5]; + + return 0; +} + +static long toshiba_acpi_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + SMMRegisters __user *argp = (SMMRegisters __user *)arg; + SMMRegisters regs; + int ret; + + if (!argp) + return -EINVAL; + + switch (cmd) { + case TOSH_SMM: + if (copy_from_user(®s, argp, sizeof(SMMRegisters))) + return -EFAULT; + ret = toshiba_acpi_smm_bridge(®s); + if (ret) + return ret; + if (copy_to_user(argp, ®s, sizeof(SMMRegisters))) + return -EFAULT; + break; + case TOSHIBA_ACPI_SCI: + if (copy_from_user(®s, argp, sizeof(SMMRegisters))) + return -EFAULT; + /* Ensure we are being called with a SCI_{GET, SET} register */ + if (regs.eax != SCI_GET && regs.eax != SCI_SET) + return -EINVAL; + if (!sci_open(toshiba_acpi)) + return -EIO; + ret = toshiba_acpi_smm_bridge(®s); + sci_close(toshiba_acpi); + if (ret) + return ret; + if (copy_to_user(argp, ®s, sizeof(SMMRegisters))) + return -EFAULT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct file_operations toshiba_acpi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = toshiba_acpi_ioctl, + .llseek = noop_llseek, +}; + +/* * Hotkeys */ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) @@ -2391,7 +2254,16 @@ 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); + /* + * Enable the "Special Functions" mode only if they are + * supported and if they are activated. + */ + if (dev->kbd_function_keys_supported && dev->special_functions) + result = hci_write(dev, HCI_HOTKEY_EVENT, + HCI_HOTKEY_SPECIAL_FUNCTIONS); + else + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); + if (result == TOS_FAILURE) return -EIO; else if (result == TOS_NOT_SUPPORTED) @@ -2400,20 +2272,6 @@ 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_write1(dev, HCI_HOTKEY_EVENT, - HCI_HOTKEY_SPECIAL_FUNCTIONS); - if (result != TOS_SUCCESS) - pr_err("Could not enable the Special Function mode\n"); -} - static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { @@ -2478,22 +2336,28 @@ static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev, static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) { - u32 hci_result, value; - int retries = 3; - int scancode; - if (dev->info_supported) { - scancode = toshiba_acpi_query_hotkey(dev); - if (scancode < 0) + int scancode = toshiba_acpi_query_hotkey(dev); + + if (scancode < 0) { pr_err("Failed to query hotkey event\n"); - else if (scancode != 0) + } else if (scancode != 0) { toshiba_acpi_report_hotkey(dev, scancode); + dev->key_event_valid = 1; + dev->last_key_event = scancode; + } } else if (dev->system_event_supported) { + u32 result; + u32 value; + int retries = 3; + do { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); - switch (hci_result) { + result = hci_read(dev, HCI_SYSTEM_EVENT, &value); + switch (result) { case TOS_SUCCESS: toshiba_acpi_report_hotkey(dev, (int)value); + dev->key_event_valid = 1; + dev->last_key_event = value; break; case TOS_NOT_SUPPORTED: /* @@ -2501,15 +2365,15 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) * issue on some machines where system events * sporadically become disabled. */ - hci_result = - hci_write1(dev, HCI_SYSTEM_EVENT, 1); - pr_notice("Re-enabled hotkeys\n"); + result = hci_write(dev, HCI_SYSTEM_EVENT, 1); + if (result == TOS_SUCCESS) + pr_notice("Re-enabled hotkeys\n"); /* Fall through */ default: retries--; break; } - } while (retries && hci_result != TOS_FIFO_EMPTY); + } while (retries && result != TOS_FIFO_EMPTY); } } @@ -2517,20 +2381,19 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) { const struct key_entry *keymap = toshiba_acpi_keymap; acpi_handle ec_handle; - u32 events_type; - u32 hci_result; int error; + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) { + pr_info("WMI event detected, hotkeys will not be monitored\n"); + return 0; + } + 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; + if (toshiba_hotkey_event_type_get(dev, &dev->hotkey_event_type)) + pr_notice("Unable to query Hotkey Event Type\n"); dev->hotkey_dev = input_allocate_device(); if (!dev->hotkey_dev) @@ -2540,14 +2403,15 @@ 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 (events_type == HCI_SYSTEM_TYPE1 || + if (dev->hotkey_event_type == HCI_SYSTEM_TYPE1 || !dev->kbd_function_keys_supported) keymap = toshiba_acpi_keymap; - else if (events_type == HCI_SYSTEM_TYPE2 || + else if (dev->hotkey_event_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); + pr_info("Unknown event type received %x\n", + dev->hotkey_event_type); error = sparse_keymap_setup(dev->hotkey_dev, keymap, NULL); if (error) goto err_free_dev; @@ -2578,11 +2442,8 @@ 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); - if (hci_result == TOS_SUCCESS) - dev->system_event_supported = 1; - } + else if (hci_write(dev, HCI_SYSTEM_EVENT, 1) == TOS_SUCCESS) + dev->system_event_supported = 1; if (!dev->info_supported && !dev->system_event_supported) { pr_warn("No hotkey query interface found\n"); @@ -2613,7 +2474,6 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) struct backlight_properties props; int brightness; int ret; - bool enabled; /* * Some machines don't support the backlight methods at all, and @@ -2624,30 +2484,31 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) brightness = __get_lcd_brightness(dev); if (brightness < 0) return 0; + /* + * If transflective backlight is supported and the brightness is zero + * (lowest brightness level), the set_lcd_brightness function will + * activate the transflective backlight, making the LCD appear to be + * turned off, simply increment the brightness level to avoid that. + */ + if (dev->tr_backlight_supported && brightness == 0) + brightness++; ret = set_lcd_brightness(dev, brightness); if (ret) { pr_debug("Backlight method is read-only, disabling backlight support\n"); return 0; } - /* Determine whether or not BIOS supports transflective backlight */ - ret = get_tr_backlight_status(dev, &enabled); - dev->tr_backlight_supported = !ret; - /* * Tell acpi-video-detect code to prefer vendor backlight on all * systems with transflective backlight and on dmi matched systems. */ if (dev->tr_backlight_supported || dmi_check_system(toshiba_vendor_backlight_dmi)) - acpi_video_dmi_promote_vendor(); + acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); - if (acpi_video_backlight_support()) + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return 0; - /* acpi-video may have loaded before we called dmi_promote_vendor() */ - acpi_video_unregister_backlight(); - memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; @@ -2672,10 +2533,52 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) return 0; } +static void print_supported_features(struct toshiba_acpi_dev *dev) +{ + pr_info("Supported laptop features:"); + + if (dev->hotkey_dev) + pr_cont(" hotkeys"); + if (dev->backlight_dev) + pr_cont(" backlight"); + if (dev->video_supported) + pr_cont(" video-out"); + if (dev->fan_supported) + pr_cont(" fan"); + if (dev->tr_backlight_supported) + pr_cont(" transflective-backlight"); + if (dev->illumination_supported) + pr_cont(" illumination"); + if (dev->kbd_illum_supported) + pr_cont(" keyboard-backlight"); + if (dev->touchpad_supported) + pr_cont(" touchpad"); + if (dev->eco_supported) + pr_cont(" eco-led"); + if (dev->accelerometer_supported) + pr_cont(" accelerometer-axes"); + if (dev->usb_sleep_charge_supported) + pr_cont(" usb-sleep-charge"); + if (dev->usb_rapid_charge_supported) + pr_cont(" usb-rapid-charge"); + if (dev->usb_sleep_music_supported) + pr_cont(" usb-sleep-music"); + if (dev->kbd_function_keys_supported) + pr_cont(" special-function-keys"); + if (dev->panel_power_on_supported) + pr_cont(" panel-power-on"); + if (dev->usb_three_supported) + pr_cont(" usb3"); + + pr_cont("\n"); +} + static int toshiba_acpi_remove(struct acpi_device *acpi_dev) { struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + misc_deregister(&dev->miscdev); + remove_toshiba_proc_entries(dev); if (dev->sysfs_created) @@ -2692,20 +2595,15 @@ 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) + if (dev->illumination_led_registered) led_classdev_unregister(&dev->led_dev); if (dev->kbd_led_registered) led_classdev_unregister(&dev->kbd_led); - if (dev->eco_supported) + if (dev->eco_led_registered) led_classdev_unregister(&dev->eco_led); if (toshiba_acpi) @@ -2731,9 +2629,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) { struct toshiba_acpi_dev *dev; const char *hci_method; - u32 special_functions; u32 dummy; - bool bt_present; int ret = 0; if (toshiba_acpi) @@ -2753,6 +2649,17 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) return -ENOMEM; dev->acpi_dev = acpi_dev; dev->method_hci = hci_method; + dev->miscdev.minor = MISC_DYNAMIC_MINOR; + dev->miscdev.name = "toshiba_acpi"; + dev->miscdev.fops = &toshiba_acpi_fops; + + ret = misc_register(&dev->miscdev); + if (ret) { + pr_err("Failed to register miscdevice\n"); + kfree(dev); + return ret; + } + acpi_dev->driver_data = dev; dev_set_drvdata(&acpi_dev->dev, dev); @@ -2763,58 +2670,42 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) * 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); + ret = toshiba_function_keys_get(dev, &dev->special_functions); dev->kbd_function_keys_supported = !ret; + dev->hotkey_event_type = 0; if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); - mutex_init(&dev->mutex); + /* Determine whether or not BIOS supports transflective backlight */ + ret = get_tr_backlight_status(dev, &dummy); + dev->tr_backlight_supported = !ret; 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)) { + toshiba_illumination_available(dev); + if (dev->illumination_supported) { dev->led_dev.name = "toshiba::illumination"; dev->led_dev.max_brightness = 1; dev->led_dev.brightness_set = toshiba_illumination_set; dev->led_dev.brightness_get = toshiba_illumination_get; if (!led_classdev_register(&acpi_dev->dev, &dev->led_dev)) - dev->illumination_supported = 1; + dev->illumination_led_registered = true; } - if (toshiba_eco_mode_available(dev)) { + toshiba_eco_mode_available(dev); + if (dev->eco_supported) { dev->eco_led.name = "toshiba::eco_mode"; dev->eco_led.max_brightness = 1; dev->eco_led.brightness_set = toshiba_eco_mode_set_status; dev->eco_led.brightness_get = toshiba_eco_mode_get_status; if (!led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led)) - dev->eco_supported = 1; + dev->eco_led_registered = true; } - dev->kbd_illum_supported = toshiba_kbd_illum_available(dev); + toshiba_kbd_illum_available(dev); /* * Only register the LED if KBD illumination is supported * and the keyboard backlight operation mode is set to FN-Z @@ -2825,14 +2716,13 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->kbd_led.brightness_set = toshiba_kbd_backlight_set; dev->kbd_led.brightness_get = toshiba_kbd_backlight_get; if (!led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led)) - dev->kbd_led_registered = 1; + dev->kbd_led_registered = true; } ret = toshiba_touchpad_get(dev, &dummy); dev->touchpad_supported = !ret; - ret = toshiba_accelerometer_supported(dev); - dev->accelerometer_supported = !ret; + toshiba_accelerometer_available(dev); toshiba_usb_sleep_charge_available(dev); @@ -2854,12 +2744,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) ret = get_fan_status(dev, &dummy); dev->fan_supported = !ret; - /* - * Enable the "Special Functions" mode only if they are - * supported and if they are activated. - */ - if (dev->kbd_function_keys_supported && special_functions) - toshiba_acpi_enable_special_functions(dev); + print_supported_features(dev); ret = sysfs_create_group(&dev->acpi_dev->dev.kobj, &toshiba_attr_group); @@ -2887,6 +2772,14 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) switch (event) { case 0x80: /* Hotkeys and some system events */ + /* + * Machines with this WMI GUID aren't supported due to bugs in + * their AML. + * + * Return silently to avoid triggering a netlink event. + */ + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + return; toshiba_acpi_process_hotkeys(dev); break; case 0x81: /* Dock events */ @@ -2930,10 +2823,14 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) static int toshiba_acpi_suspend(struct device *device) { struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); - u32 result; - if (dev->hotkey_dev) - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); + if (dev->hotkey_dev) { + u32 result; + + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); + if (result != TOS_SUCCESS) + pr_info("Unable to disable hotkeys\n"); + } return 0; } @@ -2941,10 +2838,10 @@ static int toshiba_acpi_suspend(struct device *device) static int toshiba_acpi_resume(struct device *device) { struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); - int error; if (dev->hotkey_dev) { - error = toshiba_acpi_enable_hotkeys(dev); + int error = toshiba_acpi_enable_hotkeys(dev); + if (error) pr_info("Unable to re-enable hotkeys\n"); } @@ -2973,14 +2870,6 @@ static int __init toshiba_acpi_init(void) { int ret; - /* - * Machines with this WMI guid aren't supported due to bugs in - * their AML. This check relies on wmi initializing before - * toshiba_acpi to guarantee guids have been identified. - */ - if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) - return -ENODEV; - toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); if (!toshiba_proc_dir) { pr_err("Unable to create proc dir " PROC_TOSHIBA "\n"); diff --git a/kernel/drivers/platform/x86/toshiba_bluetooth.c b/kernel/drivers/platform/x86/toshiba_bluetooth.c index 249800763..c5e45089a 100644 --- a/kernel/drivers/platform/x86/toshiba_bluetooth.c +++ b/kernel/drivers/platform/x86/toshiba_bluetooth.c @@ -10,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 @@ -25,6 +19,7 @@ #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 @@ -34,6 +29,15 @@ 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); @@ -95,41 +99,12 @@ static int toshiba_bluetooth_status(acpi_handle handle) return -ENXIO; } - pr_info("Bluetooth status %llu\n", status); - return status; } static int toshiba_bluetooth_enable(acpi_handle handle) { acpi_status result; - bool killswitch; - bool powered; - bool plugged; - int status; - - /* - * Query ACPI to verify RFKill switch is set to 'on'. - * If not, we return silently, no need to report it as - * an error. - */ - status = toshiba_bluetooth_status(handle); - if (status < 0) - return status; - - killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; - powered = (status & BT_POWER_MASK) ? true : false; - plugged = (status & BT_PLUGGED_MASK) ? true : false; - - if (!killswitch) - return 0; - /* - * This check ensures to only enable the device if it is powered - * off or detached, as some recent devices somehow pass the killswitch - * test, causing a loop enabling/disabling the device, see bug 93911. - */ - if (powered || plugged) - return 0; result = acpi_evaluate_object(handle, "AUSB", NULL, NULL); if (ACPI_FAILURE(result)) { @@ -165,20 +140,102 @@ static int toshiba_bluetooth_disable(acpi_handle handle) 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; + + if (blocked) + ret = toshiba_bluetooth_disable(bt_dev->acpi_dev->handle); + else + ret = toshiba_bluetooth_enable(bt_dev->acpi_dev->handle); + + 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) { + struct toshiba_bluetooth_dev *bt_dev; int result; result = toshiba_bluetooth_present(device->handle); @@ -187,17 +244,54 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) pr_info("Toshiba ACPI Bluetooth device driver\n"); - /* Enable the BT device */ - result = toshiba_bluetooth_enable(device->handle); - if (result) + 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); + + 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; } static int toshiba_bt_rfkill_remove(struct acpi_device *device) { + struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); + /* clean up */ + if (bt_dev->rfk) { + rfkill_unregister(bt_dev->rfk); + rfkill_destroy(bt_dev->rfk); + } + + kfree(bt_dev); + return toshiba_bluetooth_disable(device->handle); } diff --git a/kernel/drivers/platform/x86/toshiba_haps.c b/kernel/drivers/platform/x86/toshiba_haps.c index 65300b6a8..7f2afc6b5 100644 --- a/kernel/drivers/platform/x86/toshiba_haps.c +++ b/kernel/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/kernel/drivers/platform/x86/wmi.c b/kernel/drivers/platform/x86/wmi.c index aac47573f..eb391a281 100644 --- a/kernel/drivers/platform/x86/wmi.c +++ b/kernel/drivers/platform/x86/wmi.c @@ -194,34 +194,6 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest) return true; } -/* - * Convert a raw GUID to the ACII string representation - */ -static int wmi_gtoa(const char *in, char *out) -{ - int i; - - for (i = 3; i >= 0; i--) - out += sprintf(out, "%02X", in[i] & 0xFF); - - out += sprintf(out, "-"); - out += sprintf(out, "%02X", in[5] & 0xFF); - out += sprintf(out, "%02X", in[4] & 0xFF); - out += sprintf(out, "-"); - out += sprintf(out, "%02X", in[7] & 0xFF); - out += sprintf(out, "%02X", in[6] & 0xFF); - out += sprintf(out, "-"); - out += sprintf(out, "%02X", in[8] & 0xFF); - out += sprintf(out, "%02X", in[9] & 0xFF); - out += sprintf(out, "-"); - - for (i = 10; i <= 15; i++) - out += sprintf(out, "%02X", in[i] & 0xFF); - - *out = '\0'; - return 0; -} - static bool find_guid(const char *guid_string, struct wmi_block **out) { char tmp[16], guid_input[16]; @@ -457,11 +429,7 @@ EXPORT_SYMBOL_GPL(wmi_set_block); static void wmi_dump_wdg(const struct guid_block *g) { - char guid_string[37]; - - wmi_gtoa(g->guid, guid_string); - - pr_info("%s:\n", guid_string); + pr_info("%pUL:\n", g->guid); pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]); pr_info("\tnotify_id: %02X\n", g->notify_id); pr_info("\treserved: %02X\n", g->reserved); @@ -661,7 +629,6 @@ EXPORT_SYMBOL_GPL(wmi_has_guid); static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { - char guid_string[37]; struct wmi_block *wblock; wblock = dev_get_drvdata(dev); @@ -670,9 +637,7 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, return strlen(buf); } - wmi_gtoa(wblock->gblock.guid, guid_string); - - return sprintf(buf, "wmi:%s\n", guid_string); + return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid); } static DEVICE_ATTR_RO(modalias); @@ -695,7 +660,7 @@ static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) if (!wblock) return -ENOMEM; - wmi_gtoa(wblock->gblock.guid, guid_string); + sprintf(guid_string, "%pUL", wblock->gblock.guid); strcpy(&env->buf[env->buflen - 1], "wmi:"); memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36); @@ -721,12 +686,9 @@ static struct class wmi_class = { static int wmi_create_device(const struct guid_block *gblock, struct wmi_block *wblock, acpi_handle handle) { - char guid_string[37]; - wblock->dev.class = &wmi_class; - wmi_gtoa(gblock->guid, guid_string); - dev_set_name(&wblock->dev, "%s", guid_string); + dev_set_name(&wblock->dev, "%pUL", gblock->guid); dev_set_drvdata(&wblock->dev, wblock); @@ -877,7 +839,6 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) struct guid_block *block; struct wmi_block *wblock; struct list_head *p; - char guid_string[37]; list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); @@ -888,8 +849,8 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) if (wblock->handler) wblock->handler(event, wblock->handler_data); if (debug_event) { - wmi_gtoa(wblock->gblock.guid, guid_string); - pr_info("DEBUG Event GUID: %s\n", guid_string); + pr_info("DEBUG Event GUID: %pUL\n", + wblock->gblock.guid); } acpi_bus_generate_netlink_event( |