Commit 58ac7aa0c308d8b7e38e92840de59da7a39834d8

Authored by David Woodhouse
1 parent f6cec0ae58

Add Lenovo ideapad driver

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>

Showing 3 changed files with 290 additions and 0 deletions Side-by-side Diff

drivers/platform/x86/Kconfig
... ... @@ -219,6 +219,13 @@
219 219 ---help---
220 220 Build the sonypi driver compatibility code into the sony-laptop driver.
221 221  
  222 +config IDEAPAD_ACPI
  223 + tristate "Lenovo IdeaPad ACPI Laptop Extras"
  224 + depends on ACPI
  225 + depends on RFKILL
  226 + help
  227 + This is a driver for the rfkill switches on Lenovo IdeaPad netbooks.
  228 +
222 229 config THINKPAD_ACPI
223 230 tristate "ThinkPad ACPI Laptop Extras"
224 231 depends on ACPI
drivers/platform/x86/Makefile
... ... @@ -15,6 +15,7 @@
15 15 obj-$(CONFIG_HP_WMI) += hp-wmi.o
16 16 obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
17 17 obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
  18 +obj-$(CONFIG_IDEAPAD_ACPI) += ideapad_acpi.o
18 19 obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
19 20 obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
20 21 obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
drivers/platform/x86/ideapad_acpi.c
  1 +/*
  2 + * ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
  3 + *
  4 + * Copyright © 2010 Intel Corporation
  5 + * Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
  6 + *
  7 + * This program is free software; you can redistribute it and/or modify
  8 + * it under the terms of the GNU General Public License as published by
  9 + * the Free Software Foundation; either version 2 of the License, or
  10 + * (at your option) any later version.
  11 + *
  12 + * This program is distributed in the hope that it will be useful,
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15 + * GNU General Public License for more details.
  16 + *
  17 + * You should have received a copy of the GNU General Public License
  18 + * along with this program; if not, write to the Free Software
  19 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20 + * 02110-1301, USA.
  21 + */
  22 +
  23 +#include <linux/kernel.h>
  24 +#include <linux/module.h>
  25 +#include <linux/init.h>
  26 +#include <linux/types.h>
  27 +#include <acpi/acpi_bus.h>
  28 +#include <acpi/acpi_drivers.h>
  29 +#include <linux/rfkill.h>
  30 +
  31 +#define IDEAPAD_DEV_CAMERA 0
  32 +#define IDEAPAD_DEV_WLAN 1
  33 +#define IDEAPAD_DEV_BLUETOOTH 2
  34 +#define IDEAPAD_DEV_3G 3
  35 +#define IDEAPAD_DEV_KILLSW 4
  36 +
  37 +static struct rfkill *ideapad_rfkill[5];
  38 +
  39 +static const char *ideapad_rfk_names[] = {
  40 + "ideapad_camera", "ideapad_wlan", "ideapad_bluetooth", "ideapad_3g", "ideapad_rfkill"
  41 +};
  42 +static const int ideapad_rfk_types[] = {
  43 + 0, RFKILL_TYPE_WLAN, RFKILL_TYPE_BLUETOOTH, RFKILL_TYPE_WWAN, RFKILL_TYPE_WLAN
  44 +};
  45 +
  46 +static int ideapad_dev_exists(int device)
  47 +{
  48 + acpi_status status;
  49 + union acpi_object in_param;
  50 + struct acpi_object_list input = { 1, &in_param };
  51 + struct acpi_buffer output;
  52 + union acpi_object out_obj;
  53 +
  54 + output.length = sizeof(out_obj);
  55 + output.pointer = &out_obj;
  56 +
  57 + in_param.type = ACPI_TYPE_INTEGER;
  58 + in_param.integer.value = device + 1;
  59 +
  60 + status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output);
  61 + if (ACPI_FAILURE(status)) {
  62 + printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status);
  63 + return -ENODEV;
  64 + }
  65 + if (out_obj.type != ACPI_TYPE_INTEGER) {
  66 + printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n");
  67 + return -ENODEV;
  68 + }
  69 + return out_obj.integer.value;
  70 +}
  71 +
  72 +static int ideapad_dev_get_state(int device)
  73 +{
  74 + acpi_status status;
  75 + union acpi_object in_param;
  76 + struct acpi_object_list input = { 1, &in_param };
  77 + struct acpi_buffer output;
  78 + union acpi_object out_obj;
  79 +
  80 + output.length = sizeof(out_obj);
  81 + output.pointer = &out_obj;
  82 +
  83 + in_param.type = ACPI_TYPE_INTEGER;
  84 + in_param.integer.value = device + 1;
  85 +
  86 + status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
  87 + if (ACPI_FAILURE(status)) {
  88 + printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
  89 + return -ENODEV;
  90 + }
  91 + if (out_obj.type != ACPI_TYPE_INTEGER) {
  92 + printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
  93 + return -ENODEV;
  94 + }
  95 + return out_obj.integer.value;
  96 +}
  97 +
  98 +static int ideapad_dev_set_state(int device, int state)
  99 +{
  100 + acpi_status status;
  101 + union acpi_object in_params[2];
  102 + struct acpi_object_list input = { 2, in_params };
  103 +
  104 + in_params[0].type = ACPI_TYPE_INTEGER;
  105 + in_params[0].integer.value = device + 1;
  106 + in_params[1].type = ACPI_TYPE_INTEGER;
  107 + in_params[1].integer.value = state;
  108 +
  109 + status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
  110 + if (ACPI_FAILURE(status)) {
  111 + printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
  112 + return -ENODEV;
  113 + }
  114 + return 0;
  115 +}
  116 +static ssize_t show_ideapad_cam(struct device *dev,
  117 + struct device_attribute *attr,
  118 + char *buf)
  119 +{
  120 + int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
  121 + if (state < 0)
  122 + return state;
  123 +
  124 + return sprintf(buf, "%d\n", state);
  125 +}
  126 +
  127 +static ssize_t store_ideapad_cam(struct device *dev,
  128 + struct device_attribute *attr,
  129 + const char *buf, size_t count)
  130 +{
  131 + int ret, state;
  132 +
  133 + if (!count)
  134 + return 0;
  135 + if (sscanf(buf, "%i", &state) != 1)
  136 + return -EINVAL;
  137 + ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, state);
  138 + if (ret < 0)
  139 + return ret;
  140 + return count;
  141 +}
  142 +
  143 +static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
  144 +
  145 +static int ideapad_rfk_set(void *data, bool blocked)
  146 +{
  147 + int device = (unsigned long)data;
  148 +
  149 + if (device == IDEAPAD_DEV_KILLSW)
  150 + return -EINVAL;
  151 + return ideapad_dev_set_state(device, !blocked);
  152 +}
  153 +
  154 +static struct rfkill_ops ideapad_rfk_ops = {
  155 + .set_block = ideapad_rfk_set,
  156 +};
  157 +
  158 +static void ideapad_sync_rfk_state(void)
  159 +{
  160 + int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
  161 + int i;
  162 +
  163 + rfkill_set_hw_state(ideapad_rfkill[IDEAPAD_DEV_KILLSW], hw_blocked);
  164 + for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
  165 + if (ideapad_rfkill[i])
  166 + rfkill_set_hw_state(ideapad_rfkill[i], hw_blocked);
  167 + if (hw_blocked)
  168 + return;
  169 +
  170 + for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
  171 + if (ideapad_rfkill[i])
  172 + rfkill_set_sw_state(ideapad_rfkill[i], !ideapad_dev_get_state(i));
  173 +}
  174 +
  175 +static int ideapad_register_rfkill(struct acpi_device *device, int dev)
  176 +{
  177 + int ret;
  178 +
  179 + ideapad_rfkill[dev] = rfkill_alloc(ideapad_rfk_names[dev], &device->dev,
  180 + ideapad_rfk_types[dev], &ideapad_rfk_ops,
  181 + (void *)(long)dev);
  182 + if (!ideapad_rfkill[dev])
  183 + return -ENOMEM;
  184 +
  185 + ret = rfkill_register(ideapad_rfkill[dev]);
  186 + if (ret) {
  187 + rfkill_destroy(ideapad_rfkill[dev]);
  188 + return ret;
  189 + }
  190 + return 0;
  191 +}
  192 +
  193 +static void ideapad_unregister_rfkill(int dev)
  194 +{
  195 + if (!ideapad_rfkill[dev])
  196 + return;
  197 +
  198 + rfkill_unregister(ideapad_rfkill[dev]);
  199 + rfkill_destroy(ideapad_rfkill[dev]);
  200 +}
  201 +
  202 +static const struct acpi_device_id ideapad_device_ids[] = {
  203 + { "VPC2004", 0},
  204 + { "", 0},
  205 +};
  206 +MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
  207 +
  208 +static int ideapad_acpi_add(struct acpi_device *device)
  209 +{
  210 + int i;
  211 + int devs_present[5];
  212 +
  213 + for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
  214 + devs_present[i] = ideapad_dev_exists(i);
  215 + if (devs_present[i] < 0)
  216 + return devs_present[i];
  217 + }
  218 +
  219 + /* The hardware switch is always present */
  220 + devs_present[IDEAPAD_DEV_KILLSW] = 1;
  221 +
  222 + if (devs_present[IDEAPAD_DEV_CAMERA]) {
  223 + int ret = device_create_file(&device->dev, &dev_attr_camera_power);
  224 + if (ret)
  225 + return ret;
  226 + }
  227 +
  228 + for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
  229 + if (!devs_present[i])
  230 + continue;
  231 +
  232 + ideapad_register_rfkill(device, i);
  233 + }
  234 + ideapad_sync_rfk_state();
  235 + return 0;
  236 +}
  237 +
  238 +static int ideapad_acpi_remove(struct acpi_device *device, int type)
  239 +{
  240 + int i;
  241 + device_remove_file(&device->dev, &dev_attr_camera_power);
  242 + for (i = 0; i < 5; i++)
  243 + ideapad_unregister_rfkill(i);
  244 + return 0;
  245 +}
  246 +
  247 +static void ideapad_acpi_notify(struct acpi_device *device, u32 event)
  248 +{
  249 + ideapad_sync_rfk_state();
  250 +}
  251 +
  252 +static struct acpi_driver ideapad_acpi_driver = {
  253 + .name = "ideapad_acpi",
  254 + .class = "IdeaPad",
  255 + .ids = ideapad_device_ids,
  256 + .ops.add = ideapad_acpi_add,
  257 + .ops.remove = ideapad_acpi_remove,
  258 + .ops.notify = ideapad_acpi_notify,
  259 + .owner = THIS_MODULE,
  260 +};
  261 +
  262 +
  263 +static int __init ideapad_acpi_module_init(void)
  264 +{
  265 + acpi_bus_register_driver(&ideapad_acpi_driver);
  266 +
  267 + return 0;
  268 +}
  269 +
  270 +
  271 +static void __exit ideapad_acpi_module_exit(void)
  272 +{
  273 + acpi_bus_unregister_driver(&ideapad_acpi_driver);
  274 +
  275 +}
  276 +
  277 +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
  278 +MODULE_DESCRIPTION("IdeaPad ACPI Extras");
  279 +MODULE_LICENSE("GPL");
  280 +
  281 +module_init(ideapad_acpi_module_init);
  282 +module_exit(ideapad_acpi_module_exit);