diff options
Diffstat (limited to 'drivers/platform')
38 files changed, 3054 insertions, 710 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 2a6531a5f..3271cd1ab 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -4,7 +4,6 @@ menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" - depends on X86 || ARM ---help--- Say Y here to get to see options for platform support for various Chromebooks and Chromeboxes. This option alone does @@ -40,7 +39,7 @@ config CHROMEOS_PSTORE config CROS_EC_CHARDEV tristate "Chrome OS Embedded Controller userspace device interface" - depends on MFD_CROS_EC + depends on CROS_EC_PROTO ---help--- This driver adds support to talk with the ChromeOS EC from userspace. @@ -49,7 +48,7 @@ config CROS_EC_CHARDEV config CROS_EC_LPC tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC && (X86 || COMPILE_TEST) + depends on MFD_CROS_EC && CROS_EC_PROTO && (X86 || COMPILE_TEST) help If you say Y here, you get support for talking to the ChromeOS EC over an LPC bus. This uses a simple byte-level protocol with a @@ -59,4 +58,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/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index bd8d8601e..4a11b010f 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o +obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6090d0b28..e8fcdc237 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -20,44 +20,59 @@ #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, + 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 +80,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 +104,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 +127,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 +186,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,45 +209,81 @@ 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); + + if (!ec) + return retval; - /* Instantiate it (and remember the EC) */ + 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; } @@ -229,10 +300,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 +325,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 +333,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/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index 45d67f7e5..bfd2c84c3 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/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/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index b4ff47a90..144e09df9 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/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)) @@ -215,12 +255,12 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return -EINVAL; if (i == 4) { - param = (struct ec_params_lightbar *)msg.outdata; - param->cmd = LIGHTBAR_CMD_RGB; - param->rgb.led = val[0]; - param->rgb.red = val[1]; - param->rgb.green = val[2]; - param->rgb.blue = val[3]; + 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. @@ -231,12 +271,14 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return ret; } - 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; + } i = 0; ok = 1; @@ -248,6 +290,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 +305,56 @@ 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); + + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; - param = (struct ec_params_lightbar *)msg.outdata; + 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); + + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; for (len = 0; len < count; len++) if (!isalnum(buf[len])) @@ -311,18 +370,18 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, return ret; } - param = (struct ec_params_lightbar *)msg.outdata; + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SEQ; param->seq.num = num; ret = lb_throttle(); if (ret) return ret; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) return ret; - if (msg.result != EC_RES_SUCCESS) + if (msg->result != EC_RES_SUCCESS) return -EINVAL; return count; @@ -343,25 +402,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/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 8f9ac4d7b..bdd77ce45 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/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 */ @@ -129,8 +200,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 +283,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) { diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c new file mode 100644 index 000000000..990308ca3 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -0,0 +1,382 @@ +/* + * ChromeOS EC communication protocol helper functions + * + * Copyright (C) 2015 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/mfd/cros_ec.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> + +#define EC_COMMAND_RETRIES 50 + +static int prepare_packet(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + struct ec_host_request *request; + u8 *out; + int i; + u8 csum = 0; + + BUG_ON(ec_dev->proto_version != EC_HOST_REQUEST_VERSION); + BUG_ON(msg->outsize + sizeof(*request) > ec_dev->dout_size); + + out = ec_dev->dout; + request = (struct ec_host_request *)out; + request->struct_version = EC_HOST_REQUEST_VERSION; + request->checksum = 0; + request->command = msg->command; + request->command_version = msg->version; + request->reserved = 0; + request->data_len = msg->outsize; + + for (i = 0; i < sizeof(*request); i++) + csum += out[i]; + + /* Copy data and update checksum */ + memcpy(out + sizeof(*request), msg->data, msg->outsize); + for (i = 0; i < msg->outsize; i++) + csum += msg->data[i]; + + request->checksum = -csum; + + return sizeof(*request) + msg->outsize; +} + +static int send_command(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + if (ec_dev->proto_version > 2) + ret = ec_dev->pkt_xfer(ec_dev, msg); + else + ret = ec_dev->cmd_xfer(ec_dev, msg); + + if (msg->result == EC_RES_IN_PROGRESS) { + int i; + struct cros_ec_command *status_msg; + struct ec_response_get_comms_status *status; + + status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), + GFP_KERNEL); + if (!status_msg) + return -ENOMEM; + + status_msg->version = 0; + status_msg->command = EC_CMD_GET_COMMS_STATUS; + status_msg->insize = sizeof(*status); + status_msg->outsize = 0; + + /* + * Query the EC's status until it's no longer busy or + * we encounter an error. + */ + for (i = 0; i < EC_COMMAND_RETRIES; i++) { + usleep_range(10000, 11000); + + ret = ec_dev->cmd_xfer(ec_dev, status_msg); + if (ret < 0) + break; + + msg->result = status_msg->result; + if (status_msg->result != EC_RES_SUCCESS) + break; + + status = (struct ec_response_get_comms_status *) + status_msg->data; + if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) + break; + } + + kfree(status_msg); + } + + return ret; +} + +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + u8 *out; + u8 csum; + int i; + + if (ec_dev->proto_version > 2) + return prepare_packet(ec_dev, msg); + + BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); + out = ec_dev->dout; + out[0] = EC_CMD_VERSION0 + msg->version; + out[1] = msg->command; + out[2] = msg->outsize; + csum = out[0] + out[1] + out[2]; + for (i = 0; i < msg->outsize; i++) + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; + out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum; + + return EC_MSG_TX_PROTO_BYTES + msg->outsize; +} +EXPORT_SYMBOL(cros_ec_prepare_tx); + +int cros_ec_check_result(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + switch (msg->result) { + case EC_RES_SUCCESS: + return 0; + case EC_RES_IN_PROGRESS: + dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", + msg->command); + return -EAGAIN; + default: + dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + return 0; + } +} +EXPORT_SYMBOL(cros_ec_check_result); + +static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev, + int devidx, + struct cros_ec_command *msg) +{ + /* + * Try using v3+ to query for supported protocols. If this + * command fails, fall back to v2. Returns the highest protocol + * supported by the EC. + * Also sets the max request/response/passthru size. + */ + int ret; + + if (!ec_dev->pkt_xfer) + return -EPROTONOSUPPORT; + + memset(msg, 0, sizeof(*msg)); + msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO; + msg->insize = sizeof(struct ec_response_get_protocol_info); + + ret = send_command(ec_dev, msg); + + if (ret < 0) { + dev_dbg(ec_dev->dev, + "failed to check for EC[%d] protocol version: %d\n", + devidx, ret); + return ret; + } + + if (devidx > 0 && msg->result == EC_RES_INVALID_COMMAND) + return -ENODEV; + else if (msg->result != EC_RES_SUCCESS) + return msg->result; + + return 0; +} + +static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev) +{ + struct cros_ec_command *msg; + struct ec_params_hello *hello_params; + struct ec_response_hello *hello_response; + int ret; + int len = max(sizeof(*hello_params), sizeof(*hello_response)); + + msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = 0; + msg->command = EC_CMD_HELLO; + hello_params = (struct ec_params_hello *)msg->data; + msg->outsize = sizeof(*hello_params); + hello_response = (struct ec_response_hello *)msg->data; + msg->insize = sizeof(*hello_response); + + hello_params->in_data = 0xa0b0c0d0; + + ret = send_command(ec_dev, msg); + + if (ret < 0) { + dev_dbg(ec_dev->dev, + "EC failed to respond to v2 hello: %d\n", + ret); + goto exit; + } else if (msg->result != EC_RES_SUCCESS) { + dev_err(ec_dev->dev, + "EC responded to v2 hello with error: %d\n", + msg->result); + ret = msg->result; + goto exit; + } else if (hello_response->out_data != 0xa1b2c3d4) { + dev_err(ec_dev->dev, + "EC responded to v2 hello with bad result: %u\n", + hello_response->out_data); + ret = -EBADMSG; + goto exit; + } + + ret = 0; + + exit: + kfree(msg); + return ret; +} + +int cros_ec_query_all(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + struct cros_ec_command *proto_msg; + struct ec_response_get_protocol_info *proto_info; + int ret; + + proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info), + GFP_KERNEL); + if (!proto_msg) + return -ENOMEM; + + /* First try sending with proto v3. */ + ec_dev->proto_version = 3; + ret = cros_ec_host_command_proto_query(ec_dev, 0, proto_msg); + + if (ret == 0) { + proto_info = (struct ec_response_get_protocol_info *) + proto_msg->data; + ec_dev->max_request = proto_info->max_request_packet_size - + sizeof(struct ec_host_request); + ec_dev->max_response = proto_info->max_response_packet_size - + sizeof(struct ec_host_response); + ec_dev->proto_version = + min(EC_HOST_REQUEST_VERSION, + fls(proto_info->protocol_versions) - 1); + dev_dbg(ec_dev->dev, + "using proto v%u\n", + ec_dev->proto_version); + + ec_dev->din_size = ec_dev->max_response + + sizeof(struct ec_host_response) + + EC_MAX_RESPONSE_OVERHEAD; + ec_dev->dout_size = ec_dev->max_request + + sizeof(struct ec_host_request) + + EC_MAX_REQUEST_OVERHEAD; + + /* + * Check for PD + */ + ret = cros_ec_host_command_proto_query(ec_dev, 1, proto_msg); + + if (ret) { + dev_dbg(ec_dev->dev, "no PD chip found: %d\n", ret); + ec_dev->max_passthru = 0; + } else { + dev_dbg(ec_dev->dev, "found PD chip\n"); + ec_dev->max_passthru = + proto_info->max_request_packet_size - + sizeof(struct ec_host_request); + } + } else { + /* Try querying with a v2 hello message. */ + ec_dev->proto_version = 2; + ret = cros_ec_host_command_proto_query_v2(ec_dev); + + if (ret == 0) { + /* V2 hello succeeded. */ + dev_dbg(ec_dev->dev, "falling back to proto v2\n"); + + ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE; + ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; + ec_dev->max_passthru = 0; + ec_dev->pkt_xfer = NULL; + ec_dev->din_size = EC_MSG_BYTES; + ec_dev->dout_size = EC_MSG_BYTES; + } else { + /* + * It's possible for a test to occur too early when + * the EC isn't listening. If this happens, we'll + * test later when the first command is run. + */ + ec_dev->proto_version = EC_PROTO_VERSION_UNKNOWN; + dev_dbg(ec_dev->dev, "EC query failed: %d\n", ret); + goto exit; + } + } + + devm_kfree(dev, ec_dev->din); + devm_kfree(dev, ec_dev->dout); + + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) { + ret = -ENOMEM; + goto exit; + } + + ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) { + devm_kfree(dev, ec_dev->din); + ret = -ENOMEM; + goto exit; + } + +exit: + kfree(proto_msg); + return ret; +} +EXPORT_SYMBOL(cros_ec_query_all); + +int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + mutex_lock(&ec_dev->lock); + if (ec_dev->proto_version == EC_PROTO_VERSION_UNKNOWN) { + ret = cros_ec_query_all(ec_dev); + if (ret) { + dev_err(ec_dev->dev, + "EC version unknown and query failed; aborting command\n"); + mutex_unlock(&ec_dev->lock); + return ret; + } + } + + if (msg->insize > ec_dev->max_response) { + dev_dbg(ec_dev->dev, "clamping message receive buffer\n"); + msg->insize = ec_dev->max_response; + } + + if (msg->command < EC_CMD_PASSTHRU_OFFSET(1)) { + if (msg->outsize > ec_dev->max_request) { + dev_err(ec_dev->dev, + "request of size %u is too big (max: %u)\n", + msg->outsize, + ec_dev->max_request); + mutex_unlock(&ec_dev->lock); + return -EMSGSIZE; + } + } else { + if (msg->outsize > ec_dev->max_passthru) { + dev_err(ec_dev->dev, + "passthru rq of size %u is too big (max: %u)\n", + msg->outsize, + ec_dev->max_passthru); + mutex_unlock(&ec_dev->lock); + return -EMSGSIZE; + } + } + ret = send_command(ec_dev, msg); + mutex_unlock(&ec_dev->lock); + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer); diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index fb62ab6cc..f3baf9973 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/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/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index d9a09d963..e7a29e275 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -158,8 +158,8 @@ static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd) struct goldfish_pipe_dev *dev = pipe->dev; spin_lock_irqsave(&dev->lock, flags); - gf_write64((u64)(unsigned long)pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); + gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, + dev->base + PIPE_REG_CHANNEL_HIGH); writel(cmd, dev->base + PIPE_REG_COMMAND); status = readl(dev->base + PIPE_REG_STATUS); spin_unlock_irqrestore(&dev->lock, flags); @@ -172,8 +172,8 @@ static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd) struct goldfish_pipe_dev *dev = pipe->dev; spin_lock_irqsave(&dev->lock, flags); - gf_write64((u64)(unsigned long)pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); + gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, + dev->base + PIPE_REG_CHANNEL_HIGH); writel(cmd, dev->base + PIPE_REG_COMMAND); spin_unlock_irqrestore(&dev->lock, flags); } @@ -282,7 +282,7 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, return -EIO; /* Null reads or writes succeeds */ - if (unlikely(bufflen) == 0) + if (unlikely(bufflen == 0)) return 0; /* Check the buffer range for access */ @@ -327,12 +327,12 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, spin_lock_irqsave(&dev->lock, irq_flags); if (access_with_param(dev, CMD_WRITE_BUFFER + cmd_offset, address, avail, pipe, &status)) { - gf_write64((u64)(unsigned long)pipe, - dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); + gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, + dev->base + PIPE_REG_CHANNEL_HIGH); writel(avail, dev->base + PIPE_REG_SIZE); - gf_write64(address, dev->base + PIPE_REG_ADDRESS, - dev->base + PIPE_REG_ADDRESS_HIGH); + gf_write_ptr((void *)address, + dev->base + PIPE_REG_ADDRESS, + dev->base + PIPE_REG_ADDRESS_HIGH); writel(CMD_WRITE_BUFFER + cmd_offset, dev->base + PIPE_REG_COMMAND); status = readl(dev->base + PIPE_REG_STATUS); diff --git a/drivers/platform/goldfish/pdev_bus.c b/drivers/platform/goldfish/pdev_bus.c index 8c43589c3..1f52462f4 100644 --- a/drivers/platform/goldfish/pdev_bus.c +++ b/drivers/platform/goldfish/pdev_bus.c @@ -220,20 +220,10 @@ free_resources: return ret; } -static int goldfish_pdev_bus_remove(struct platform_device *pdev) -{ - iounmap(pdev_bus_base); - free_irq(pdev_bus_irq, pdev); - release_mem_region(pdev_bus_addr, pdev_bus_len); - return 0; -} - static struct platform_driver goldfish_pdev_bus_driver = { .probe = goldfish_pdev_bus_probe, - .remove = goldfish_pdev_bus_remove, .driver = { .name = "goldfish_pdev_bus" } }; - -module_platform_driver(goldfish_pdev_bus_driver); +builtin_platform_driver(goldfish_pdev_bus_driver); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 47c9510eb..8fca8364f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -71,9 +71,10 @@ config ASUS_LAPTOP depends on ACPI select LEDS_CLASS select NEW_LEDS - select BACKLIGHT_CLASS_DEVICE + depends on BACKLIGHT_CLASS_DEVICE depends on INPUT depends on RFKILL || RFKILL = n + depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_SPARSEKMAP select INPUT_POLLDEV ---help--- @@ -95,6 +96,7 @@ config DELL_LAPTOP depends on X86 depends on DCDBAS depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL || RFKILL = n depends on SERIO_I8042 select POWER_SUPPLY @@ -109,6 +111,7 @@ config DELL_WMI tristate "Dell WMI extras" depends on ACPI_WMI depends on INPUT + depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_SPARSEKMAP ---help--- Say Y here if you want to support WMI-based hotkeys on Dell laptops. @@ -138,12 +141,29 @@ config DELL_SMO8800 To compile this driver as a module, choose M here: the module will be called dell-smo8800. +config DELL_RBTN + tristate "Dell Airplane Mode Switch driver" + depends on ACPI + depends on INPUT + depends on RFKILL + ---help--- + Say Y here if you want to support Dell Airplane Mode Switch ACPI + device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. + This driver register rfkill device or input hotkey device depending + on hardware type (hw switch slider or keyboard toggle button). For + rfkill devices it receive HW switch events and set correct hard + rfkill state. + + To compile this driver as a module, choose M here: the module will + be called dell-rbtn. + config FUJITSU_LAPTOP tristate "Fujitsu Laptop Extras" depends on ACPI depends on INPUT depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n depends on LEDS_CLASS || LEDS_CLASS=n ---help--- This is a driver for laptops built by Fujitsu: @@ -247,6 +267,7 @@ config MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL depends on INPUT && SERIO_I8042 select INPUT_SPARSEKMAP @@ -280,6 +301,7 @@ config COMPAL_LAPTOP tristate "Compal (and others) Laptop Extras" depends on ACPI depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL depends on HWMON depends on POWER_SUPPLY @@ -296,7 +318,8 @@ config COMPAL_LAPTOP config SONY_LAPTOP tristate "Sony Laptop Extras" depends on ACPI - select BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on BACKLIGHT_CLASS_DEVICE depends on INPUT depends on RFKILL ---help--- @@ -321,6 +344,7 @@ config IDEAPAD_LAPTOP depends on RFKILL && INPUT depends on SERIO_I8042 depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_SPARSEKMAP help This is a driver for Lenovo IdeaPad netbooks contains drivers for @@ -331,8 +355,8 @@ config THINKPAD_ACPI depends on ACPI depends on INPUT depends on RFKILL || RFKILL = n - select BACKLIGHT_LCD_SUPPORT - select BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on BACKLIGHT_CLASS_DEVICE select HWMON select NVRAM select NEW_LEDS @@ -521,8 +545,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 @@ -608,6 +633,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. @@ -633,7 +659,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 @@ -664,6 +689,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 @@ -681,7 +707,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 @@ -690,7 +716,7 @@ config TOSHIBA_HAPS been stabilized. Also provides sysfs entries to get/set the desired protection - level and reseting the HDD protection interface. + level and resetting the HDD protection interface. If you have a recent Toshiba laptop with a built-in accelerometer device, say Y. @@ -845,6 +871,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 @@ -906,4 +933,11 @@ config PVPANIC a paravirtualized device provided by QEMU; it lets a virtual machine (guest) communicate panic events to the host. +config INTEL_PMC_IPC + tristate "Intel PMC IPC Driver" + ---help--- + This driver provides support for PMC control on some Intel platforms. + The PMC is an ARC processor which defines IPC commands for communication + with other entities in the CPU. + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 00f156aed..3f5750397 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o @@ -60,3 +61,4 @@ obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o +obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 3ac29a1e8..f6b280dbf 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -2246,14 +2246,10 @@ static int __init acer_wmi_init(void) set_quirks(); if (dmi_check_system(video_vendor_dmi_table)) - acpi_video_dmi_promote_vendor(); - if (acpi_video_backlight_support()) { + acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); + + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) interface->capability &= ~ACER_CAP_BRIGHTNESS; - pr_info("Brightness must be controlled by acpi video driver\n"); - } else { - pr_info("Disabling ACPI video driver\n"); - acpi_video_unregister_backlight(); - } if (wmi_has_guid(WMID_GUID3)) { if (ec_raw_mode) { diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 594c918b5..1ef02dadd 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -372,7 +372,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal, return 0; if (thermal_zone_bind_cooling_device(thermal, 0, cdev, - THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) { + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT, + THERMAL_WEIGHT_DEFAULT)) { pr_err("error binding cooling dev\n"); return -EINVAL; } diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 680871500..0dec3f599 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -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/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 46b274693..58d29c4f2 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -54,6 +54,7 @@ #include <linux/slab.h> #include <linux/dmi.h> #include <linux/acpi.h> +#include <acpi/video.h> #define ASUS_LAPTOP_VERSION "0.42" @@ -1884,12 +1885,11 @@ static int asus_acpi_add(struct acpi_device *device) if (result) goto fail_platform; - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { result = asus_backlight_init(asus); if (result) goto fail_backlight; - } else - pr_info("Backlight controlled by ACPI video driver\n"); + } result = asus_input_init(asus); if (result) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 7543a56e0..efbc3f0c5 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -78,6 +78,7 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */ #define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */ #define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */ +#define ASUS_WMI_METHODID_AGFN 0x4E464741 /* FaN? */ #define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */ #define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */ #define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */ @@ -150,12 +151,38 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 +#define ASUS_FAN_DESC "cpu_fan" +#define ASUS_FAN_MFUN 0x13 +#define ASUS_FAN_SFUN_READ 0x06 +#define ASUS_FAN_SFUN_WRITE 0x07 +#define ASUS_FAN_CTRL_MANUAL 1 +#define ASUS_FAN_CTRL_AUTO 2 + struct bios_args { u32 arg0; u32 arg1; } __packed; /* + * Struct that's used for all methods called via AGFN. Naming is + * identically to the AML code. + */ +struct agfn_args { + u16 mfun; /* probably "Multi-function" to be called */ + u16 sfun; /* probably "Sub-function" to be called */ + u16 len; /* size of the hole struct, including subfunction fields */ + u8 stas; /* not used by now */ + u8 err; /* zero on success */ +} __packed; + +/* struct used for calling fan read and write methods */ +struct fan_args { + struct agfn_args agfn; /* common fields */ + u8 fan; /* fan number: 0: set auto mode 1: 1st fan */ + u32 speed; /* read: RPM/100 - write: 0-255 */ +} __packed; + +/* * <platform>/ - debugfs root directory * dev_id - current dev_id * ctrl_param - current ctrl_param @@ -204,6 +231,10 @@ struct asus_wmi { struct asus_rfkill gps; struct asus_rfkill uwb; + bool asus_hwmon_fan_manual_mode; + int asus_hwmon_num_fans; + int asus_hwmon_pwm; + struct hotplug_slot *hotplug_slot; struct mutex hotplug_lock; struct mutex wmi_lock; @@ -294,6 +325,36 @@ exit: return 0; } +static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) +{ + struct acpi_buffer input; + u64 phys_addr; + u32 retval; + u32 status = -1; + + /* + * Copy to dma capable address otherwise memory corruption occurs as + * bios has to be able to access it. + */ + input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL); + input.length = args.length; + if (!input.pointer) + return -ENOMEM; + phys_addr = virt_to_phys(input.pointer); + memcpy(input.pointer, args.pointer, args.length); + + status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN, + phys_addr, 0, &retval); + if (!status) + memcpy(args.pointer, input.pointer, args.length); + + kfree(input.pointer); + if (status) + return -ENXIO; + + return retval; +} + static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) { return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval); @@ -1022,35 +1083,228 @@ exit: /* * Hwmon device */ -static ssize_t asus_hwmon_pwm1(struct device *dev, - struct device_attribute *attr, - char *buf) +static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, + int *speed) +{ + struct fan_args args = { + .agfn.len = sizeof(args), + .agfn.mfun = ASUS_FAN_MFUN, + .agfn.sfun = ASUS_FAN_SFUN_READ, + .fan = fan, + .speed = 0, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + int status; + + if (fan != 1) + return -EINVAL; + + status = asus_wmi_evaluate_method_agfn(input); + + if (status || args.agfn.err) + return -ENXIO; + + if (speed) + *speed = args.speed; + + return 0; +} + +static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan, + int *speed) +{ + struct fan_args args = { + .agfn.len = sizeof(args), + .agfn.mfun = ASUS_FAN_MFUN, + .agfn.sfun = ASUS_FAN_SFUN_WRITE, + .fan = fan, + .speed = speed ? *speed : 0, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + int status; + + /* 1: for setting 1st fan's speed 0: setting auto mode */ + if (fan != 1 && fan != 0) + return -EINVAL; + + status = asus_wmi_evaluate_method_agfn(input); + + if (status || args.agfn.err) + return -ENXIO; + + if (speed && fan == 1) + asus->asus_hwmon_pwm = *speed; + + return 0; +} + +/* + * Check if we can read the speed of one fan. If true we assume we can also + * control it. + */ +static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans) +{ + int status; + int speed = 0; + + *num_fans = 0; + + status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed); + if (!status) + *num_fans = 1; + + return 0; +} + +static int asus_hwmon_fan_set_auto(struct asus_wmi *asus) +{ + int status; + + status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL); + if (status) + return -ENXIO; + + asus->asus_hwmon_fan_manual_mode = false; + + return 0; +} + +static int asus_hwmon_fan_rpm_show(struct device *dev, int fan) { struct asus_wmi *asus = dev_get_drvdata(dev); - u32 value; + int value; + int ret; + + /* no speed readable on manual mode */ + if (asus->asus_hwmon_fan_manual_mode) + return -ENXIO; + + ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value); + if (ret) { + pr_warn("reading fan speed failed: %d\n", ret); + return -ENXIO; + } + + return value; +} + +static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value) +{ int err; - err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); + if (asus->asus_hwmon_pwm >= 0) { + *value = asus->asus_hwmon_pwm; + return; + } + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value); if (err < 0) - return err; + return; - value &= 0xFF; - - if (value == 1) /* Low Speed */ - value = 85; - else if (value == 2) - value = 170; - else if (value == 3) - value = 255; - else if (value != 0) { - pr_err("Unknown fan speed %#x\n", value); - value = -1; + *value &= 0xFF; + + if (*value == 1) /* Low Speed */ + *value = 85; + else if (*value == 2) + *value = 170; + else if (*value == 3) + *value = 255; + else if (*value) { + pr_err("Unknown fan speed %#x\n", *value); + *value = -1; } +} + +static ssize_t pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + int value; + + asus_hwmon_pwm_show(asus, 0, &value); return sprintf(buf, "%d\n", value); } +static ssize_t pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { + struct asus_wmi *asus = dev_get_drvdata(dev); + int value; + int state; + int ret; + + ret = kstrtouint(buf, 10, &value); + + if (ret) + return ret; + + value = clamp(value, 0, 255); + + state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value); + if (state) + pr_warn("Setting fan speed failed: %d\n", state); + else + asus->asus_hwmon_fan_manual_mode = true; + + return count; +} + +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int value = asus_hwmon_fan_rpm_show(dev, 0); + + return sprintf(buf, "%d\n", value < 0 ? -1 : value*100); + +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + + if (asus->asus_hwmon_fan_manual_mode) + return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL); + + return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + int status = 0; + int state; + int ret; + + ret = kstrtouint(buf, 10, &state); + + if (ret) + return ret; + + if (state == ASUS_FAN_CTRL_MANUAL) + asus->asus_hwmon_fan_manual_mode = true; + else + status = asus_hwmon_fan_set_auto(asus); + + if (status) + return status; + + return count; +} + +static ssize_t fan1_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", ASUS_FAN_DESC); +} + static ssize_t asus_hwmon_temp1(struct device *dev, struct device_attribute *attr, char *buf) @@ -1069,11 +1323,21 @@ static ssize_t asus_hwmon_temp1(struct device *dev, return sprintf(buf, "%d\n", value); } -static DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL); +/* Fan1 */ +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR_RO(fan1_label); + +/* Temperature */ static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL); static struct attribute *hwmon_attributes[] = { &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_label.attr, + &dev_attr_temp1_input.attr, NULL }; @@ -1084,19 +1348,28 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, struct device *dev = container_of(kobj, struct device, kobj); struct platform_device *pdev = to_platform_device(dev->parent); struct asus_wmi *asus = platform_get_drvdata(pdev); - bool ok = true; int dev_id = -1; + int fan_attr = -1; u32 value = ASUS_WMI_UNSUPPORTED_METHOD; + bool ok = true; if (attr == &dev_attr_pwm1.attr) dev_id = ASUS_WMI_DEVID_FAN_CTRL; else if (attr == &dev_attr_temp1_input.attr) dev_id = ASUS_WMI_DEVID_THERMAL_CTRL; + + if (attr == &dev_attr_fan1_input.attr + || attr == &dev_attr_fan1_label.attr + || attr == &dev_attr_pwm1.attr + || attr == &dev_attr_pwm1_enable.attr) { + fan_attr = 1; + } + if (dev_id != -1) { int err = asus_wmi_get_devstate(asus, dev_id, &value); - if (err < 0) + if (err < 0 && fan_attr == -1) return 0; /* can't return negative here */ } @@ -1112,10 +1385,16 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))) ok = false; + else + ok = fan_attr <= asus->asus_hwmon_num_fans; } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) { /* If value is zero, something is clearly wrong */ - if (value == 0) + if (!value) ok = false; + } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) { + ok = true; + } else { + ok = false; } return ok ? attr->mode : 0; @@ -1364,7 +1643,7 @@ static void asus_wmi_notify(u32 value, void *context) code = ASUS_WMI_BRN_DOWN; if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) { - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { asus_wmi_backlight_notify(asus, orig_code); goto exit; } @@ -1723,6 +2002,25 @@ error_debugfs: return -ENOMEM; } +static int asus_wmi_fan_init(struct asus_wmi *asus) +{ + int status; + + asus->asus_hwmon_pwm = -1; + asus->asus_hwmon_num_fans = -1; + asus->asus_hwmon_fan_manual_mode = false; + + status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans); + if (status) { + asus->asus_hwmon_num_fans = 0; + pr_warn("Could not determine number of fans: %d\n", status); + return -ENXIO; + } + + pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans); + return 0; +} + /* * WMI Driver */ @@ -1756,6 +2054,9 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_input; + err = asus_wmi_fan_init(asus); /* probably no problems on error */ + asus_hwmon_fan_set_auto(asus); + err = asus_wmi_hwmon_init(asus); if (err) goto fail_hwmon; @@ -1772,17 +2073,16 @@ static int asus_wmi_add(struct platform_device *pdev) stop this from showing up */ chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); if (chassis_type && !strcmp(chassis_type, "3")) - acpi_video_dmi_promote_vendor(); + acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); + if (asus->driver->quirks->wmi_backlight_power) - acpi_video_dmi_promote_vendor(); - if (!acpi_video_backlight_support()) { - pr_info("Disabling ACPI video driver\n"); - acpi_video_unregister(); + acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); + + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { err = asus_wmi_backlight_init(asus); if (err && err != -ENODEV) goto fail_backlight; - } else - pr_info("Backlight controlled by ACPI video driver\n"); + } status = wmi_install_notify_handler(asus->driver->event_guid, asus_wmi_notify, asus); @@ -1832,6 +2132,7 @@ static int asus_wmi_remove(struct platform_device *device) asus_wmi_rfkill_exit(asus); asus_wmi_debugfs_exit(asus); asus_wmi_platform_exit(asus); + asus_hwmon_fan_set_auto(asus); kfree(asus); return 0; diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index b4e94471f..f2706d27a 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -82,7 +82,7 @@ #include <linux/hwmon-sysfs.h> #include <linux/power_supply.h> #include <linux/fb.h> - +#include <acpi/video.h> /* ======= */ /* Defines */ @@ -959,7 +959,7 @@ static int __init compal_init(void) return -ENODEV; } - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { struct backlight_properties props; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_PLATFORM; diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 2c1d5f543..aaeeae81e 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/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/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c new file mode 100644 index 000000000..cd410e392 --- /dev/null +++ b/drivers/platform/x86/dell-rbtn.c @@ -0,0 +1,423 @@ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/rfkill.h> +#include <linux/input.h> + +enum rbtn_type { + RBTN_UNKNOWN, + RBTN_TOGGLE, + RBTN_SLIDER, +}; + +struct rbtn_data { + enum rbtn_type type; + struct rfkill *rfkill; + struct input_dev *input_dev; +}; + + +/* + * acpi functions + */ + +static enum rbtn_type rbtn_check(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return RBTN_UNKNOWN; + + switch (output) { + case 0: + case 1: + return RBTN_TOGGLE; + case 2: + case 3: + return RBTN_SLIDER; + default: + return RBTN_UNKNOWN; + } +} + +static int rbtn_get(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return !output; +} + +static int rbtn_acquire(struct acpi_device *device, bool enable) +{ + struct acpi_object_list input; + union acpi_object param; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = enable; + input.count = 1; + input.pointer = ¶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/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h new file mode 100644 index 000000000..c59cc6b8e --- /dev/null +++ b/drivers/platform/x86/dell-rbtn.h @@ -0,0 +1,24 @@ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifndef _DELL_RBTN_H_ +#define _DELL_RBTN_H_ + +struct notifier_block; + +int dell_rbtn_notifier_register(struct notifier_block *nb); +int dell_rbtn_notifier_unregister(struct notifier_block *nb); + +#endif diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 6512a06bc..f2d77fe69 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -35,6 +35,7 @@ #include <linux/acpi.h> #include <linux/string.h> #include <linux/dmi.h> +#include <acpi/video.h> MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); @@ -397,7 +398,7 @@ static int __init dell_wmi_init(void) } dmi_walk(find_hk_type, NULL); - acpi_video = acpi_video_backlight_support(); + acpi_video = acpi_video_get_backlight_type() != acpi_backlight_vendor; err = dell_wmi_input_setup(); if (err) diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 844c2096b..8cdf315f9 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -37,6 +37,7 @@ #include <linux/pci_hotplug.h> #include <linux/leds.h> #include <linux/dmi.h> +#include <acpi/video.h> #define EEEPC_LAPTOP_VERSION "0.1" #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" @@ -1433,12 +1434,10 @@ static int eeepc_acpi_add(struct acpi_device *device) if (result) goto fail_platform; - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { result = eeepc_backlight_init(eeepc); if (result) goto fail_backlight; - } else { - pr_info("Backlight controlled by ACPI video driver\n"); } result = eeepc_input_init(eeepc); diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 2a9afa261..1c62caff9 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -72,6 +72,7 @@ #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) #include <linux/leds.h> #endif +#include <acpi/video.h> #define FUJITSU_DRIVER_VERSION "0.6.0" @@ -1099,7 +1100,7 @@ static int __init fujitsu_init(void) /* Register backlight stuff */ - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { struct backlight_properties props; memset(&props, 0, sizeof(struct backlight_properties)); @@ -1137,8 +1138,7 @@ static int __init fujitsu_init(void) } /* Sync backlight power status (needs FUJ02E3 device, hence deferred) */ - - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) fujitsu->bl_device->props.power = FB_BLANK_POWERDOWN; else diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index cb7cd8d79..76b57388d 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -38,6 +38,7 @@ #include <linux/i8042.h> #include <linux/dmi.h> #include <linux/device.h> +#include <acpi/video.h> #define IDEAPAD_RFKILL_DEV_NUM (3) @@ -911,7 +912,7 @@ static int ideapad_acpi_add(struct platform_device *pdev) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { ret = ideapad_backlight_init(priv); if (ret && ret != -ENODEV) goto backlight_failed; diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c index 8037c8b46..6aa33c4a8 100644 --- a/drivers/platform/x86/intel_oaktrail.c +++ b/drivers/platform/x86/intel_oaktrail.c @@ -50,6 +50,7 @@ #include <linux/platform_device.h> #include <linux/dmi.h> #include <linux/rfkill.h> +#include <acpi/video.h> #define DRIVER_NAME "intel_oaktrail" #define DRIVER_VERSION "0.4ac1" @@ -343,13 +344,11 @@ static int __init oaktrail_init(void) goto err_device_add; } - if (!acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { ret = oaktrail_backlight_init(); if (ret) goto err_backlight; - - } else - pr_info("Backlight controlled by ACPI video driver\n"); + } ret = oaktrail_rfkill_init(); if (ret) { diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c new file mode 100644 index 000000000..105cfffe8 --- /dev/null +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -0,0 +1,780 @@ +/* + * intel_pmc_ipc.c: Driver for the Intel PMC IPC mechanism + * + * (C) Copyright 2014-2015 Intel Corporation + * + * This driver is based on Intel SCU IPC driver(intel_scu_opc.c) by + * Sreedhara DS <sreedhara.ds@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * PMC running in ARC processor communicates with other entity running in IA + * core through IPC mechanism which in turn messaging between IA core ad PMC. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/pm.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/pm_qos.h> +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/sched.h> +#include <linux/atomic.h> +#include <linux/notifier.h> +#include <linux/suspend.h> +#include <linux/acpi.h> +#include <asm/intel_pmc_ipc.h> +#include <linux/mfd/lpc_ich.h> + +/* + * IPC registers + * The IA write to IPC_CMD command register triggers an interrupt to the ARC, + * The ARC handles the interrupt and services it, writing optional data to + * the IPC1 registers, updates the IPC_STS response register with the status. + */ +#define IPC_CMD 0x0 +#define IPC_CMD_MSI 0x100 +#define IPC_CMD_SIZE 16 +#define IPC_CMD_SUBCMD 12 +#define IPC_STATUS 0x04 +#define IPC_STATUS_IRQ 0x4 +#define IPC_STATUS_ERR 0x2 +#define IPC_STATUS_BUSY 0x1 +#define IPC_SPTR 0x08 +#define IPC_DPTR 0x0C +#define IPC_WRITE_BUFFER 0x80 +#define IPC_READ_BUFFER 0x90 + +/* + * 16-byte buffer for sending data associated with IPC command. + */ +#define IPC_DATA_BUFFER_SIZE 16 + +#define IPC_LOOP_CNT 3000000 +#define IPC_MAX_SEC 3 + +#define IPC_TRIGGER_MODE_IRQ true + +/* exported resources from IFWI */ +#define PLAT_RESOURCE_IPC_INDEX 0 +#define PLAT_RESOURCE_IPC_SIZE 0x1000 +#define PLAT_RESOURCE_GCR_SIZE 0x1000 +#define PLAT_RESOURCE_PUNIT_DATA_INDEX 1 +#define PLAT_RESOURCE_PUNIT_INTER_INDEX 2 +#define PLAT_RESOURCE_ACPI_IO_INDEX 0 + +/* + * BIOS does not create an ACPI device for each PMC function, + * but exports multiple resources from one ACPI device(IPC) for + * multiple functions. This driver is responsible to create a + * platform device and to export resources for those functions. + */ +#define TCO_DEVICE_NAME "iTCO_wdt" +#define SMI_EN_OFFSET 0x30 +#define SMI_EN_SIZE 4 +#define TCO_BASE_OFFSET 0x60 +#define TCO_REGS_SIZE 16 +#define PUNIT_DEVICE_NAME "intel_punit_ipc" + +static const int iTCO_version = 3; + +static struct intel_pmc_ipc_dev { + struct device *dev; + void __iomem *ipc_base; + bool irq_mode; + int irq; + int cmd; + struct completion cmd_complete; + + /* The following PMC BARs share the same ACPI device with the IPC */ + resource_size_t acpi_io_base; + int acpi_io_size; + struct platform_device *tco_dev; + + /* gcr */ + resource_size_t gcr_base; + int gcr_size; + + /* punit */ + resource_size_t punit_base; + int punit_size; + resource_size_t punit_base2; + int punit_size2; + struct platform_device *punit_dev; +} ipcdev; + +static char *ipc_err_sources[] = { + [IPC_ERR_NONE] = + "no error", + [IPC_ERR_CMD_NOT_SUPPORTED] = + "command not supported", + [IPC_ERR_CMD_NOT_SERVICED] = + "command not serviced", + [IPC_ERR_UNABLE_TO_SERVICE] = + "unable to service", + [IPC_ERR_CMD_INVALID] = + "command invalid", + [IPC_ERR_CMD_FAILED] = + "command failed", + [IPC_ERR_EMSECURITY] = + "Invalid Battery", + [IPC_ERR_UNSIGNEDKERNEL] = + "Unsigned kernel", +}; + +/* Prevent concurrent calls to the PMC */ +static DEFINE_MUTEX(ipclock); + +static inline void ipc_send_command(u32 cmd) +{ + ipcdev.cmd = cmd; + if (ipcdev.irq_mode) { + reinit_completion(&ipcdev.cmd_complete); + cmd |= IPC_CMD_MSI; + } + writel(cmd, ipcdev.ipc_base + IPC_CMD); +} + +static inline u32 ipc_read_status(void) +{ + return readl(ipcdev.ipc_base + IPC_STATUS); +} + +static inline void ipc_data_writel(u32 data, u32 offset) +{ + writel(data, ipcdev.ipc_base + IPC_WRITE_BUFFER + offset); +} + +static inline u8 ipc_data_readb(u32 offset) +{ + return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset); +} + +static inline u32 ipc_data_readl(u32 offset) +{ + return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset); +} + +static int intel_pmc_ipc_check_status(void) +{ + int status; + int ret = 0; + + if (ipcdev.irq_mode) { + if (0 == wait_for_completion_timeout( + &ipcdev.cmd_complete, IPC_MAX_SEC * HZ)) + ret = -ETIMEDOUT; + } else { + int loop_count = IPC_LOOP_CNT; + + while ((ipc_read_status() & IPC_STATUS_BUSY) && --loop_count) + udelay(1); + if (loop_count == 0) + ret = -ETIMEDOUT; + } + + status = ipc_read_status(); + if (ret == -ETIMEDOUT) { + dev_err(ipcdev.dev, + "IPC timed out, TS=0x%x, CMD=0x%x\n", + status, ipcdev.cmd); + return ret; + } + + if (status & IPC_STATUS_ERR) { + int i; + + ret = -EIO; + i = (status >> IPC_CMD_SIZE) & 0xFF; + if (i < ARRAY_SIZE(ipc_err_sources)) + dev_err(ipcdev.dev, + "IPC failed: %s, STS=0x%x, CMD=0x%x\n", + ipc_err_sources[i], status, ipcdev.cmd); + else + dev_err(ipcdev.dev, + "IPC failed: unknown, STS=0x%x, CMD=0x%x\n", + status, ipcdev.cmd); + if ((i == IPC_ERR_UNSIGNEDKERNEL) || (i == IPC_ERR_EMSECURITY)) + ret = -EACCES; + } + + return ret; +} + +/** + * intel_pmc_ipc_simple_command() - Simple IPC command + * @cmd: IPC command code. + * @sub: IPC command sub type. + * + * Send a simple IPC command to PMC when don't need to specify + * input/output data and source/dest pointers. + * + * Return: an IPC error code or 0 on success. + */ +int intel_pmc_ipc_simple_command(int cmd, int sub) +{ + int ret; + + mutex_lock(&ipclock); + if (ipcdev.dev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + ipc_send_command(sub << IPC_CMD_SUBCMD | cmd); + ret = intel_pmc_ipc_check_status(); + mutex_unlock(&ipclock); + + return ret; +} +EXPORT_SYMBOL_GPL(intel_pmc_ipc_simple_command); + +/** + * intel_pmc_ipc_raw_cmd() - IPC command with data and pointers + * @cmd: IPC command code. + * @sub: IPC command sub type. + * @in: input data of this IPC command. + * @inlen: input data length in bytes. + * @out: output data of this IPC command. + * @outlen: output data length in dwords. + * @sptr: data writing to SPTR register. + * @dptr: data writing to DPTR register. + * + * Send an IPC command to PMC with input/output data and source/dest pointers. + * + * Return: an IPC error code or 0 on success. + */ +int intel_pmc_ipc_raw_cmd(u32 cmd, u32 sub, u8 *in, u32 inlen, u32 *out, + u32 outlen, u32 dptr, u32 sptr) +{ + u32 wbuf[4] = { 0 }; + int ret; + int i; + + if (inlen > IPC_DATA_BUFFER_SIZE || outlen > IPC_DATA_BUFFER_SIZE / 4) + return -EINVAL; + + mutex_lock(&ipclock); + if (ipcdev.dev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + memcpy(wbuf, in, inlen); + writel(dptr, ipcdev.ipc_base + IPC_DPTR); + writel(sptr, ipcdev.ipc_base + IPC_SPTR); + /* The input data register is 32bit register and inlen is in Byte */ + for (i = 0; i < ((inlen + 3) / 4); i++) + ipc_data_writel(wbuf[i], 4 * i); + ipc_send_command((inlen << IPC_CMD_SIZE) | + (sub << IPC_CMD_SUBCMD) | cmd); + ret = intel_pmc_ipc_check_status(); + if (!ret) { + /* out is read from 32bit register and outlen is in 32bit */ + for (i = 0; i < outlen; i++) + *out++ = ipc_data_readl(4 * i); + } + mutex_unlock(&ipclock); + + return ret; +} +EXPORT_SYMBOL_GPL(intel_pmc_ipc_raw_cmd); + +/** + * intel_pmc_ipc_command() - IPC command with input/output data + * @cmd: IPC command code. + * @sub: IPC command sub type. + * @in: input data of this IPC command. + * @inlen: input data length in bytes. + * @out: output data of this IPC command. + * @outlen: output data length in dwords. + * + * Send an IPC command to PMC with input/output data. + * + * Return: an IPC error code or 0 on success. + */ +int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen, + u32 *out, u32 outlen) +{ + return intel_pmc_ipc_raw_cmd(cmd, sub, in, inlen, out, outlen, 0, 0); +} +EXPORT_SYMBOL_GPL(intel_pmc_ipc_command); + +static irqreturn_t ioc(int irq, void *dev_id) +{ + int status; + + if (ipcdev.irq_mode) { + status = ipc_read_status(); + writel(status | IPC_STATUS_IRQ, ipcdev.ipc_base + IPC_STATUS); + } + complete(&ipcdev.cmd_complete); + + return IRQ_HANDLED; +} + +static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + resource_size_t pci_resource; + int ret; + int len; + + ipcdev.dev = &pci_dev_get(pdev)->dev; + ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + + ret = pci_request_regions(pdev, "intel_pmc_ipc"); + if (ret) + return ret; + + pci_resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + if (!pci_resource || !len) { + dev_err(&pdev->dev, "Failed to get resource\n"); + return -ENOMEM; + } + + init_completion(&ipcdev.cmd_complete); + + if (request_irq(pdev->irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) { + dev_err(&pdev->dev, "Failed to request irq\n"); + return -EBUSY; + } + + ipcdev.ipc_base = ioremap_nocache(pci_resource, len); + if (!ipcdev.ipc_base) { + dev_err(&pdev->dev, "Failed to ioremap ipc base\n"); + free_irq(pdev->irq, &ipcdev); + ret = -ENOMEM; + } + + return ret; +} + +static void ipc_pci_remove(struct pci_dev *pdev) +{ + free_irq(pdev->irq, &ipcdev); + pci_release_regions(pdev); + pci_dev_put(pdev); + iounmap(ipcdev.ipc_base); + ipcdev.dev = NULL; +} + +static const struct pci_device_id ipc_pci_ids[] = { + {PCI_VDEVICE(INTEL, 0x0a94), 0}, + {PCI_VDEVICE(INTEL, 0x1a94), 0}, + { 0,} +}; +MODULE_DEVICE_TABLE(pci, ipc_pci_ids); + +static struct pci_driver ipc_pci_driver = { + .name = "intel_pmc_ipc", + .id_table = ipc_pci_ids, + .probe = ipc_pci_probe, + .remove = ipc_pci_remove, +}; + +static ssize_t intel_pmc_ipc_simple_cmd_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int subcmd; + int cmd; + int ret; + + ret = sscanf(buf, "%d %d", &cmd, &subcmd); + if (ret != 2) { + dev_err(dev, "Error args\n"); + return -EINVAL; + } + + ret = intel_pmc_ipc_simple_command(cmd, subcmd); + if (ret) { + dev_err(dev, "command %d error with %d\n", cmd, ret); + return ret; + } + return (ssize_t)count; +} + +static ssize_t intel_pmc_ipc_northpeak_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long val; + int subcmd; + int ret; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val) + subcmd = 1; + else + subcmd = 0; + ret = intel_pmc_ipc_simple_command(PMC_IPC_NORTHPEAK_CTRL, subcmd); + if (ret) { + dev_err(dev, "command north %d error with %d\n", subcmd, ret); + return ret; + } + return (ssize_t)count; +} + +static DEVICE_ATTR(simplecmd, S_IWUSR, + NULL, intel_pmc_ipc_simple_cmd_store); +static DEVICE_ATTR(northpeak, S_IWUSR, + NULL, intel_pmc_ipc_northpeak_store); + +static struct attribute *intel_ipc_attrs[] = { + &dev_attr_northpeak.attr, + &dev_attr_simplecmd.attr, + NULL +}; + +static const struct attribute_group intel_ipc_group = { + .attrs = intel_ipc_attrs, +}; + +#define PUNIT_RESOURCE_INTER 1 +static struct resource punit_res[] = { + /* Punit */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, +}; + +#define TCO_RESOURCE_ACPI_IO 0 +#define TCO_RESOURCE_SMI_EN_IO 1 +#define TCO_RESOURCE_GCR_MEM 2 +static struct resource tco_res[] = { + /* ACPI - TCO */ + { + .flags = IORESOURCE_IO, + }, + /* ACPI - SMI */ + { + .flags = IORESOURCE_IO, + }, + /* GCS */ + { + .flags = IORESOURCE_MEM, + }, +}; + +static struct lpc_ich_info tco_info = { + .name = "Apollo Lake SoC", + .iTCO_version = 3, +}; + +static int ipc_create_punit_device(void) +{ + struct platform_device *pdev; + struct resource *res; + int ret; + + pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1); + if (!pdev) { + dev_err(ipcdev.dev, "Failed to alloc punit platform device\n"); + return -ENOMEM; + } + + pdev->dev.parent = ipcdev.dev; + + res = punit_res; + res->start = ipcdev.punit_base; + res->end = res->start + ipcdev.punit_size - 1; + + res = punit_res + PUNIT_RESOURCE_INTER; + res->start = ipcdev.punit_base2; + res->end = res->start + ipcdev.punit_size2 - 1; + + ret = platform_device_add_resources(pdev, punit_res, + ARRAY_SIZE(punit_res)); + if (ret) { + dev_err(ipcdev.dev, "Failed to add platform punit resources\n"); + goto err; + } + + ret = platform_device_add(pdev); + if (ret) { + dev_err(ipcdev.dev, "Failed to add punit platform device\n"); + goto err; + } + ipcdev.punit_dev = pdev; + + return 0; +err: + platform_device_put(pdev); + return ret; +} + +static int ipc_create_tco_device(void) +{ + struct platform_device *pdev; + struct resource *res; + int ret; + + pdev = platform_device_alloc(TCO_DEVICE_NAME, -1); + if (!pdev) { + dev_err(ipcdev.dev, "Failed to alloc tco platform device\n"); + return -ENOMEM; + } + + pdev->dev.parent = ipcdev.dev; + + res = tco_res + TCO_RESOURCE_ACPI_IO; + res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET; + res->end = res->start + TCO_REGS_SIZE - 1; + + res = tco_res + TCO_RESOURCE_SMI_EN_IO; + res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET; + res->end = res->start + SMI_EN_SIZE - 1; + + res = tco_res + TCO_RESOURCE_GCR_MEM; + res->start = ipcdev.gcr_base; + res->end = res->start + ipcdev.gcr_size - 1; + + ret = platform_device_add_resources(pdev, tco_res, ARRAY_SIZE(tco_res)); + if (ret) { + dev_err(ipcdev.dev, "Failed to add tco platform resources\n"); + goto err; + } + + ret = platform_device_add_data(pdev, &tco_info, + sizeof(struct lpc_ich_info)); + if (ret) { + dev_err(ipcdev.dev, "Failed to add tco platform data\n"); + goto err; + } + + ret = platform_device_add(pdev); + if (ret) { + dev_err(ipcdev.dev, "Failed to add tco platform device\n"); + goto err; + } + ipcdev.tco_dev = pdev; + + return 0; +err: + platform_device_put(pdev); + return ret; +} + +static int ipc_create_pmc_devices(void) +{ + int ret; + + ret = ipc_create_tco_device(); + if (ret) { + dev_err(ipcdev.dev, "Failed to add tco platform device\n"); + return ret; + } + ret = ipc_create_punit_device(); + if (ret) { + dev_err(ipcdev.dev, "Failed to add punit platform device\n"); + platform_device_unregister(ipcdev.tco_dev); + } + return ret; +} + +static int ipc_plat_get_res(struct platform_device *pdev) +{ + struct resource *res; + void __iomem *addr; + int size; + + res = platform_get_resource(pdev, IORESOURCE_IO, + PLAT_RESOURCE_ACPI_IO_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get io resource\n"); + return -ENXIO; + } + size = resource_size(res); + ipcdev.acpi_io_base = res->start; + ipcdev.acpi_io_size = size; + dev_info(&pdev->dev, "io res: %llx %x\n", + (long long)res->start, (int)resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_PUNIT_DATA_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get punit resource\n"); + return -ENXIO; + } + size = resource_size(res); + ipcdev.punit_base = res->start; + ipcdev.punit_size = size; + dev_info(&pdev->dev, "punit data res: %llx %x\n", + (long long)res->start, (int)resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_PUNIT_INTER_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get punit inter resource\n"); + return -ENXIO; + } + size = resource_size(res); + ipcdev.punit_base2 = res->start; + ipcdev.punit_size2 = size; + dev_info(&pdev->dev, "punit interface res: %llx %x\n", + (long long)res->start, (int)resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_IPC_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get ipc resource\n"); + return -ENXIO; + } + size = PLAT_RESOURCE_IPC_SIZE; + if (!request_mem_region(res->start, size, pdev->name)) { + dev_err(&pdev->dev, "Failed to request ipc resource\n"); + return -EBUSY; + } + addr = ioremap_nocache(res->start, size); + if (!addr) { + dev_err(&pdev->dev, "I/O memory remapping failed\n"); + release_mem_region(res->start, size); + return -ENOMEM; + } + ipcdev.ipc_base = addr; + + ipcdev.gcr_base = res->start + size; + ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE; + dev_info(&pdev->dev, "ipc res: %llx %x\n", + (long long)res->start, (int)resource_size(res)); + + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ipc_acpi_ids[] = { + { "INT34D2", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, ipc_acpi_ids); +#endif + +static int ipc_plat_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + ipcdev.dev = &pdev->dev; + ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ; + init_completion(&ipcdev.cmd_complete); + + ipcdev.irq = platform_get_irq(pdev, 0); + if (ipcdev.irq < 0) { + dev_err(&pdev->dev, "Failed to get irq\n"); + return -EINVAL; + } + + ret = ipc_plat_get_res(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to request resource\n"); + return ret; + } + + ret = ipc_create_pmc_devices(); + if (ret) { + dev_err(&pdev->dev, "Failed to create pmc devices\n"); + goto err_device; + } + + if (request_irq(ipcdev.irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) { + dev_err(&pdev->dev, "Failed to request irq\n"); + ret = -EBUSY; + goto err_irq; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &intel_ipc_group); + if (ret) { + dev_err(&pdev->dev, "Failed to create sysfs group %d\n", + ret); + goto err_sys; + } + + return 0; +err_sys: + free_irq(ipcdev.irq, &ipcdev); +err_irq: + platform_device_unregister(ipcdev.tco_dev); + platform_device_unregister(ipcdev.punit_dev); +err_device: + iounmap(ipcdev.ipc_base); + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_IPC_INDEX); + if (res) + release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE); + return ret; +} + +static int ipc_plat_remove(struct platform_device *pdev) +{ + struct resource *res; + + sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group); + free_irq(ipcdev.irq, &ipcdev); + platform_device_unregister(ipcdev.tco_dev); + platform_device_unregister(ipcdev.punit_dev); + iounmap(ipcdev.ipc_base); + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_IPC_INDEX); + if (res) + release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE); + ipcdev.dev = NULL; + return 0; +} + +static struct platform_driver ipc_plat_driver = { + .remove = ipc_plat_remove, + .probe = ipc_plat_probe, + .driver = { + .name = "pmc-ipc-plat", + .acpi_match_table = ACPI_PTR(ipc_acpi_ids), + }, +}; + +static int __init intel_pmc_ipc_init(void) +{ + int ret; + + ret = platform_driver_register(&ipc_plat_driver); + if (ret) { + pr_err("Failed to register PMC ipc platform driver\n"); + return ret; + } + ret = pci_register_driver(&ipc_pci_driver); + if (ret) { + pr_err("Failed to register PMC ipc pci driver\n"); + platform_driver_unregister(&ipc_plat_driver); + return ret; + } + return ret; +} + +static void __exit intel_pmc_ipc_exit(void) +{ + pci_unregister_driver(&ipc_pci_driver); + platform_driver_unregister(&ipc_plat_driver); +} + +MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>"); +MODULE_DESCRIPTION("Intel PMC IPC driver"); +MODULE_LICENSE("GPL"); + +/* Some modules are dependent on this, so init earlier */ +fs_initcall(intel_pmc_ipc_init); +module_exit(intel_pmc_ipc_exit); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index 001b199a8..187d1086d 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -216,13 +216,13 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) int nc; u32 offset = 0; int err; - u8 cbuf[IPC_WWBUF_SIZE] = { }; + u8 cbuf[IPC_WWBUF_SIZE]; u32 *wbuf = (u32 *)&cbuf; - mutex_lock(&ipclock); - memset(cbuf, 0, sizeof(cbuf)); + mutex_lock(&ipclock); + if (ipcdev.pdev == NULL) { mutex_unlock(&ipclock); return -ENODEV; diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 085987730..423177046 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -64,6 +64,7 @@ #include <linux/i8042.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> +#include <acpi/video.h> #define MSI_DRIVER_VERSION "0.5" @@ -1069,9 +1070,8 @@ static int __init msi_init(void) /* Register backlight stuff */ - if (!quirks->old_ec_model || acpi_video_backlight_support()) { - pr_info("Brightness ignored, must be controlled by ACPI video driver\n"); - } else { + if (quirks->old_ec_model || + acpi_video_get_backlight_type() == acpi_backlight_vendor) { struct backlight_properties props; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_PLATFORM; diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 6d2bac0c4..978e6d640 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -29,6 +29,7 @@ #include <linux/backlight.h> #include <linux/slab.h> #include <linux/module.h> +#include <acpi/video.h> MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>"); MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver"); @@ -320,7 +321,8 @@ static int __init msi_wmi_init(void) break; } - if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) { + if (wmi_has_guid(MSIWMI_BIOS_GUID) && + acpi_video_get_backlight_type() == acpi_backlight_vendor) { err = msi_wmi_backlight_setup(); if (err) { pr_err("Unable to setup backlight device\n"); diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c index 073a90a63..fd86daba7 100644 --- a/drivers/platform/x86/pvpanic.c +++ b/drivers/platform/x86/pvpanic.c @@ -92,13 +92,13 @@ pvpanic_walk_resources(struct acpi_resource *res, void *context) static int pvpanic_add(struct acpi_device *device) { - acpi_status status; - u64 ret; + int ret; - status = acpi_evaluate_integer(device->handle, "_STA", NULL, - &ret); + ret = acpi_bus_get_status(device); + if (ret < 0) + return ret; - if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B) + if (!device->status.enabled || !device->status.functional) return -ENODEV; acpi_walk_resources(device->handle, METHOD_NAME__CRS, diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 9e701b225..8c146e2b6 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -1720,27 +1720,14 @@ static int __init samsung_init(void) samsung->handle_backlight = true; samsung->quirks = quirks; - #ifdef CONFIG_ACPI if (samsung->quirks->broken_acpi_video) - acpi_video_dmi_promote_vendor(); - - /* Don't handle backlight here if the acpi video already handle it */ - if (acpi_video_backlight_support()) { - samsung->handle_backlight = false; - } else if (samsung->quirks->broken_acpi_video) { - pr_info("Disabling ACPI video driver\n"); - acpi_video_unregister(); - } + acpi_video_set_dmi_backlight_type(acpi_backlight_vendor); + if (samsung->quirks->use_native_backlight) + acpi_video_set_dmi_backlight_type(acpi_backlight_native); - if (samsung->quirks->use_native_backlight) { - pr_info("Using native backlight driver\n"); - /* Tell acpi-video to not handle the backlight */ - acpi_video_dmi_promote_vendor(); - acpi_video_unregister(); - /* And also do not handle it ourselves */ + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) samsung->handle_backlight = false; - } #endif ret = samsung_platform_init(samsung); @@ -1751,12 +1738,6 @@ static int __init samsung_init(void) if (ret) goto error_sabi; -#ifdef CONFIG_ACPI - /* Only log that if we are really on a sabi platform */ - if (acpi_video_backlight_support()) - pr_info("Backlight controlled by ACPI video driver\n"); -#endif - ret = samsung_sysfs_init(samsung); if (ret) goto error_sysfs; diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index e51c1e753..aeb80d1c2 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -69,6 +69,7 @@ #include <linux/miscdevice.h> #endif #include <asm/uaccess.h> +#include <acpi/video.h> #define dprintk(fmt, ...) \ do { \ @@ -3198,12 +3199,8 @@ static int sony_nc_add(struct acpi_device *device) sony_nc_function_setup(device, sony_pf_device); } - /* setup input devices and helper fifo */ - if (acpi_video_backlight_support()) { - pr_info("brightness ignored, must be controlled by ACPI video driver\n"); - } else { + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) sony_nc_backlight_setup(); - } /* create sony_pf sysfs attributes related to the SNC device */ for (item = sony_nc_values; item->name; ++item) { diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index e36542564..89aa976f0 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -82,7 +82,7 @@ static int get_state(u32 *out, u8 instance) tmp = 0; } - if (result.length > 0 && result.pointer) + if (result.length > 0) kfree(result.pointer); switch (instance) { diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 28f328136..33e488cf5 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -83,6 +83,7 @@ #include <sound/control.h> #include <sound/initval.h> #include <asm/uaccess.h> +#include <acpi/video.h> /* ThinkPad CMOS commands */ #define TP_CMOS_VOLUME_DOWN 0 @@ -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"); @@ -6491,7 +6492,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm) return 1; } - if (acpi_video_backlight_support()) { + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) { if (brightness_enable > 1) { pr_info("Standard ACPI backlight interface " "available, not loading native one\n"); diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 9956b9902..3ad7b1fa2 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -31,7 +31,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TOSHIBA_ACPI_VERSION "0.21" +#define TOSHIBA_ACPI_VERSION "0.22" #define PROC_INTERFACE_VERSION 1 #include <linux/kernel.h> @@ -41,7 +41,6 @@ #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/backlight.h> -#include <linux/rfkill.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/leds.h> @@ -82,7 +81,7 @@ MODULE_LICENSE("GPL"); #define TCI_WORDS 6 -/* operations */ +/* Operations */ #define HCI_SET 0xff00 #define HCI_GET 0xfe00 #define SCI_OPEN 0xf100 @@ -90,7 +89,7 @@ MODULE_LICENSE("GPL"); #define SCI_GET 0xf300 #define SCI_SET 0xf400 -/* return codes */ +/* Return codes */ #define TOS_SUCCESS 0x0000 #define TOS_OPEN_CLOSE_OK 0x0044 #define TOS_FAILURE 0x1000 @@ -105,7 +104,7 @@ MODULE_LICENSE("GPL"); #define TOS_NOT_INITIALIZED 0x8d50 #define TOS_NOT_INSTALLED 0x8e00 -/* registers */ +/* Registers */ #define HCI_FAN 0x0004 #define HCI_TR_BACKLIGHT 0x0005 #define HCI_SYSTEM_EVENT 0x0016 @@ -127,7 +126,7 @@ 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 @@ -165,7 +164,6 @@ MODULE_LICENSE("GPL"); struct toshiba_acpi_dev { struct acpi_device *acpi_dev; const char *method_hci; - struct rfkill *bt_rfk; struct input_dev *hotkey_dev; struct work_struct hotkey_work; struct backlight_device *backlight_dev; @@ -202,8 +200,6 @@ struct toshiba_acpi_dev { unsigned int panel_power_on_supported:1; unsigned int usb_three_supported:1; unsigned int sysfs_created:1; - - struct mutex mutex; }; static struct toshiba_acpi_dev *toshiba_acpi; @@ -330,13 +326,13 @@ static acpi_status tci_raw(struct toshiba_acpi_dev *dev, } /* - * Common hci tasks (get or set one or two value) + * Common hci tasks * * In addition to the ACPI status, the HCI system returns a result which * may be useful (such as "not supported"). */ -static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) +static u32 hci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) { u32 in[TCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; u32 out[TCI_WORDS]; @@ -345,7 +341,7 @@ static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE; } -static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) +static u32 hci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) { u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; @@ -359,31 +355,6 @@ static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) return out[0]; } -static u32 hci_write2(struct toshiba_acpi_dev *dev, u32 reg, u32 in1, u32 in2) -{ - u32 in[TCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; - u32 out[TCI_WORDS]; - acpi_status status = tci_raw(dev, in, out); - - return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE; -} - -static u32 hci_read2(struct toshiba_acpi_dev *dev, - u32 reg, u32 *out1, u32 *out2) -{ - u32 in[TCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; - u32 out[TCI_WORDS]; - acpi_status status = tci_raw(dev, in, out); - - if (ACPI_FAILURE(status)) - return TOS_FAILURE; - - *out1 = out[2]; - *out2 = out[3]; - - return out[0]; -} - /* * Common sci tasks */ @@ -395,7 +366,7 @@ static int sci_open(struct toshiba_acpi_dev *dev) acpi_status status; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to open SCI failed\n"); return 0; } @@ -433,7 +404,7 @@ static void sci_close(struct toshiba_acpi_dev *dev) acpi_status status; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to close SCI failed\n"); return; } @@ -481,7 +452,7 @@ static int toshiba_illumination_available(struct toshiba_acpi_dev *dev) status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to query Illumination support failed\n"); return 0; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -522,7 +493,7 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) struct toshiba_acpi_dev, led_dev); u32 state, result; - /* First request : initialize communication. */ + /* First request : initialize communication. */ if (!sci_open(dev)) return LED_OFF; @@ -625,7 +596,7 @@ static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev) u32 state, result; /* Check the keyboard backlight state */ - result = hci_read1(dev, HCI_KBD_ILLUMINATION, &state); + result = hci_read(dev, HCI_KBD_ILLUMINATION, &state); if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { pr_err("ACPI call to get the keyboard backlight failed\n"); return LED_OFF; @@ -646,7 +617,7 @@ static void toshiba_kbd_backlight_set(struct led_classdev *cdev, /* Set the keyboard backlight state */ state = brightness ? 1 : 0; - result = hci_write1(dev, HCI_KBD_ILLUMINATION, state); + result = hci_write(dev, HCI_KBD_ILLUMINATION, state); if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { pr_err("ACPI call to set KBD Illumination mode failed\n"); return; @@ -703,7 +674,7 @@ static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) u32 out[TCI_WORDS]; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get ECO led failed\n"); } else if (out[0] == TOS_NOT_INSTALLED) { pr_info("ECO led not installed"); @@ -825,7 +796,7 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *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; @@ -839,7 +810,7 @@ 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) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); sci_close(dev); return; @@ -919,7 +890,7 @@ static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB S&C battery level failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -948,7 +919,7 @@ static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to set USB S&C battery level failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -974,7 +945,7 @@ 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 || @@ -1002,7 +973,7 @@ 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) { @@ -1194,121 +1165,31 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) 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) { + } else if (out[0] == TOS_NOT_SUPPORTED) { pr_info("System type not supported\n"); return -ENODEV; } - *type = val2; + *type = out[3]; 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; -} - -static void bt_rfkill_poll(struct rfkill *rfkill, void *data) -{ - bool new_rfk_state; - bool value; - u32 hci_result; - struct toshiba_acpi_dev *dev = data; - - mutex_lock(&dev->mutex); - - hci_result = hci_get_radio_state(dev, &value); - if (hci_result != TOS_SUCCESS) { - /* Can't do anything useful */ - mutex_unlock(&dev->mutex); - return; - } - - new_rfk_state = value; - - mutex_unlock(&dev->mutex); - - if (rfkill_set_hw_state(rfkill, !new_rfk_state)) - bt_rfkill_set_block(data, true); -} - -static const struct rfkill_ops toshiba_rfk_ops = { - .set_block = bt_rfkill_set_block, - .poll = bt_rfkill_poll, -}; - +/* Transflective Backlight */ static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) { u32 hci_result; u32 status; - hci_result = hci_read1(dev, HCI_TR_BACKLIGHT, &status); + hci_result = hci_read(dev, HCI_TR_BACKLIGHT, &status); *enabled = !status; return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1318,12 +1199,13 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) u32 hci_result; u32 value = !enable; - hci_result = hci_write1(dev, HCI_TR_BACKLIGHT, value); + hci_result = hci_write(dev, HCI_TR_BACKLIGHT, value); return hci_result == TOS_SUCCESS ? 0 : -EIO; } -static struct proc_dir_entry *toshiba_proc_dir /*= 0*/; +static struct proc_dir_entry *toshiba_proc_dir; +/* LCD Brightness */ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) { u32 hci_result; @@ -1341,7 +1223,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) brightness++; } - hci_result = hci_read1(dev, HCI_LCD_BRIGHTNESS, &value); + hci_result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value); if (hci_result == TOS_SUCCESS) return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT); @@ -1396,7 +1278,7 @@ static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value) } value = value << HCI_LCD_BRIGHTNESS_SHIFT; - hci_result = hci_write1(dev, HCI_LCD_BRIGHTNESS, value); + hci_result = hci_write(dev, HCI_LCD_BRIGHTNESS, value); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1446,7 +1328,7 @@ static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; - hci_result = hci_read1(dev, HCI_VIDEO_OUT, status); + hci_result = hci_read(dev, HCI_VIDEO_OUT, status); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1531,7 +1413,8 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); /* * To avoid unnecessary video disruption, only write the new - * video setting if something changed. */ + * video setting if something changed. + */ if (new_video_out != video_out) ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out); } @@ -1552,7 +1435,7 @@ static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; - hci_result = hci_read1(dev, HCI_FAN, status); + hci_result = hci_read(dev, HCI_FAN, status); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1592,7 +1475,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, if (sscanf(cmd, " force_on : %i", &value) == 1 && value >= 0 && value <= 1) { - hci_result = hci_write1(dev, HCI_FAN, value); + hci_result = hci_write(dev, HCI_FAN, value); if (hci_result == TOS_SUCCESS) dev->force_fan = value; else @@ -1620,7 +1503,7 @@ static int keys_proc_show(struct seq_file *m, void *v) u32 value; if (!dev->key_event_valid && dev->system_event_supported) { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); + hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value); if (hci_result == TOS_SUCCESS) { dev->key_event_valid = 1; dev->last_key_event = value; @@ -1632,7 +1515,7 @@ static int keys_proc_show(struct seq_file *m, void *v) * some machines where system events sporadically * become disabled. */ - hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1); pr_notice("Re-enabled hotkeys\n"); } else { pr_err("Error reading hotkey status\n"); @@ -1769,7 +1652,7 @@ static ssize_t fan_store(struct device *dev, if (state != 0 && state != 1) return -EINVAL; - result = hci_write1(toshiba, HCI_FAN, state); + result = hci_write(toshiba, HCI_FAN, state); if (result == TOS_FAILURE) return -EIO; else if (result == TOS_NOT_SUPPORTED) @@ -2391,7 +2274,7 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) if (ACPI_FAILURE(status)) return -ENODEV; - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); if (result == TOS_FAILURE) return -EIO; else if (result == TOS_NOT_SUPPORTED) @@ -2408,8 +2291,8 @@ static void toshiba_acpi_enable_special_functions(struct toshiba_acpi_dev *dev) * 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); + result = hci_write(dev, HCI_HOTKEY_EVENT, + HCI_HOTKEY_SPECIAL_FUNCTIONS); if (result != TOS_SUCCESS) pr_err("Could not enable the Special Function mode\n"); } @@ -2490,7 +2373,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) toshiba_acpi_report_hotkey(dev, scancode); } else if (dev->system_event_supported) { do { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); + hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value); switch (hci_result) { case TOS_SUCCESS: toshiba_acpi_report_hotkey(dev, (int)value); @@ -2502,7 +2385,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) * sporadically become disabled. */ hci_result = - hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_write(dev, HCI_SYSTEM_EVENT, 1); pr_notice("Re-enabled hotkeys\n"); /* Fall through */ default: @@ -2579,7 +2462,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) if (acpi_has_method(dev->acpi_dev->handle, "INFO")) dev->info_supported = 1; else { - hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1); if (hci_result == TOS_SUCCESS) dev->system_event_supported = 1; } @@ -2640,14 +2523,11 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) */ 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; @@ -2692,11 +2572,6 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) sparse_keymap_free(dev->hotkey_dev); } - if (dev->bt_rfk) { - rfkill_unregister(dev->bt_rfk); - rfkill_destroy(dev->bt_rfk); - } - backlight_device_unregister(dev->backlight_dev); if (dev->illumination_supported) @@ -2733,7 +2608,6 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) const char *hci_method; u32 special_functions; u32 dummy; - bool bt_present; int ret = 0; if (toshiba_acpi) @@ -2769,33 +2643,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); - mutex_init(&dev->mutex); - ret = toshiba_acpi_setup_backlight(dev); if (ret) goto error; - /* Register rfkill switch for Bluetooth */ - if (hci_get_bt_present(dev, &bt_present) == TOS_SUCCESS && bt_present) { - dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth", - &acpi_dev->dev, - RFKILL_TYPE_BLUETOOTH, - &toshiba_rfk_ops, - dev); - if (!dev->bt_rfk) { - pr_err("unable to allocate rfkill device\n"); - ret = -ENOMEM; - goto error; - } - - ret = rfkill_register(dev->bt_rfk); - if (ret) { - pr_err("unable to register rfkill device\n"); - rfkill_destroy(dev->bt_rfk); - goto error; - } - } - if (toshiba_illumination_available(dev)) { dev->led_dev.name = "toshiba::illumination"; dev->led_dev.max_brightness = 1; @@ -2933,7 +2784,7 @@ static int toshiba_acpi_suspend(struct device *device) u32 result; if (dev->hotkey_dev) - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); return 0; } diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index 249800763..c5e45089a 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/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/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index 65300b6a8..7f2afc6b5 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -78,15 +78,20 @@ static ssize_t protection_level_store(struct device *dev, const char *buf, size_t count) { struct toshiba_haps_dev *haps = dev_get_drvdata(dev); - int level, ret; - - if (sscanf(buf, "%d", &level) != 1 || level < 0 || level > 3) - return -EINVAL; + int level; + int ret; - /* Set the sensor level. - * Acceptable levels are: + ret = kstrtoint(buf, 0, &level); + if (ret) + return ret; + /* + * Check for supported levels, which can be: * 0 - Disabled | 1 - Low | 2 - Medium | 3 - High */ + if (level < 0 || level > 3) + return -EINVAL; + + /* Set the sensor level */ ret = toshiba_haps_protection_level(haps->acpi_dev->handle, level); if (ret != 0) return ret; @@ -95,15 +100,21 @@ static ssize_t protection_level_store(struct device *dev, return count; } +static DEVICE_ATTR_RW(protection_level); static ssize_t reset_protection_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct toshiba_haps_dev *haps = dev_get_drvdata(dev); - int reset, ret; + int reset; + int ret; - if (sscanf(buf, "%d", &reset) != 1 || reset != 1) + ret = kstrtoint(buf, 0, &reset); + if (ret) + return ret; + /* The only accepted value is 1 */ + if (reset != 1) return -EINVAL; /* Reset the protection interface */ @@ -113,10 +124,7 @@ static ssize_t reset_protection_store(struct device *dev, return count; } - -static DEVICE_ATTR(protection_level, S_IRUGO | S_IWUSR, - protection_level_show, protection_level_store); -static DEVICE_ATTR(reset_protection, S_IWUSR, NULL, reset_protection_store); +static DEVICE_ATTR_WO(reset_protection); static struct attribute *haps_attributes[] = { &dev_attr_protection_level.attr, |