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 37struct ideapad_private { 38 struct rfkill *rfk[5]; 39}; 40 41static struct { 42 char *name; 43 int type; 44} ideapad_rfk_data[] = { 45 /* camera has no rfkill */ 46 { "ideapad_wlan", RFKILL_TYPE_WLAN }, 47 { "ideapad_bluetooth", RFKILL_TYPE_BLUETOOTH }, 48 { "ideapad_3g", RFKILL_TYPE_WWAN }, 49 { "ideapad_killsw", RFKILL_TYPE_WLAN } 50}; 51 52static int ideapad_dev_exists(int device) 53{ 54 acpi_status status; 55 union acpi_object in_param; 56 struct acpi_object_list input = { 1, &in_param }; 57 struct acpi_buffer output; 58 union acpi_object out_obj; 59 60 output.length = sizeof(out_obj); 61 output.pointer = &out_obj; 62 63 in_param.type = ACPI_TYPE_INTEGER; 64 in_param.integer.value = device + 1; 65 66 status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output); 67 if (ACPI_FAILURE(status)) { 68 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status); 69 return -ENODEV; 70 } 71 if (out_obj.type != ACPI_TYPE_INTEGER) { 72 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n"); 73 return -ENODEV; 74 } 75 return out_obj.integer.value; 76} 77 78static int ideapad_dev_get_state(int device) 79{ 80 acpi_status status; 81 union acpi_object in_param; 82 struct acpi_object_list input = { 1, &in_param }; 83 struct acpi_buffer output; 84 union acpi_object out_obj; 85 86 output.length = sizeof(out_obj); 87 output.pointer = &out_obj; 88 89 in_param.type = ACPI_TYPE_INTEGER; 90 in_param.integer.value = device + 1; 91 92 status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output); 93 if (ACPI_FAILURE(status)) { 94 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status); 95 return -ENODEV; 96 } 97 if (out_obj.type != ACPI_TYPE_INTEGER) { 98 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n"); 99 return -ENODEV; 100 } 101 return out_obj.integer.value; 102} 103 104static int ideapad_dev_set_state(int device, int state) 105{ 106 acpi_status status; 107 union acpi_object in_params[2]; 108 struct acpi_object_list input = { 2, in_params }; 109 110 in_params[0].type = ACPI_TYPE_INTEGER; 111 in_params[0].integer.value = device + 1; 112 in_params[1].type = ACPI_TYPE_INTEGER; 113 in_params[1].integer.value = state; 114 115 status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL); 116 if (ACPI_FAILURE(status)) { 117 printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status); 118 return -ENODEV; 119 } 120 return 0; 121} 122static ssize_t show_ideapad_cam(struct device *dev, 123 struct device_attribute *attr, 124 char *buf) 125{ 126 int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA); 127 if (state < 0) 128 return state; 129 130 return sprintf(buf, "%d\n", state); 131} 132 133static ssize_t store_ideapad_cam(struct device *dev, 134 struct device_attribute *attr, 135 const char *buf, size_t count) 136{ 137 int ret, state; 138 139 if (!count) 140 return 0; 141 if (sscanf(buf, "%i", &state) != 1) 142 return -EINVAL; 143 ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state); 144 if (ret < 0) 145 return ret; 146 return count; 147} 148 149static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 150 151static int ideapad_rfk_set(void *data, bool blocked) 152{ 153 int device = (unsigned long)data; 154 155 if (device == IDEAPAD_DEV_KILLSW) 156 return -EINVAL; 157 return ideapad_dev_set_state(device, !blocked); 158} 159 160static struct rfkill_ops ideapad_rfk_ops = { 161 .set_block = ideapad_rfk_set, 162}; 163 164static void ideapad_sync_rfk_state(struct acpi_device *adevice) 165{ 166 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 167 int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW); 168 int i; 169 170 rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked); 171 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) 172 if (priv->rfk[i]) 173 rfkill_set_hw_state(priv->rfk[i], hw_blocked); 174 if (hw_blocked) 175 return; 176 177 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) 178 if (priv->rfk[i]) 179 rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i)); 180} 181 182static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) 183{ 184 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 185 int ret; 186 187 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev, 188 ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops, 189 (void *)(long)dev); 190 if (!priv->rfk[dev]) 191 return -ENOMEM; 192 193 ret = rfkill_register(priv->rfk[dev]); 194 if (ret) { 195 rfkill_destroy(priv->rfk[dev]); 196 return ret; 197 } 198 return 0; 199} 200 201static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) 202{ 203 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 204 205 if (!priv->rfk[dev]) 206 return; 207 208 rfkill_unregister(priv->rfk[dev]); 209 rfkill_destroy(priv->rfk[dev]); 210} 211 212static const struct acpi_device_id ideapad_device_ids[] = { 213 { "VPC2004", 0}, 214 { "", 0}, 215}; 216MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 217 218static int ideapad_acpi_add(struct acpi_device *adevice) 219{ 220 int i; 221 int devs_present[5]; 222 struct ideapad_private *priv; 223 224 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) { 225 devs_present[i] = ideapad_dev_exists(i); 226 if (devs_present[i] < 0) 227 return devs_present[i]; 228 } 229 230 /* The hardware switch is always present */ 231 devs_present[IDEAPAD_DEV_KILLSW] = 1; 232 233 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 234 if (!priv) 235 return -ENOMEM; 236 237 if (devs_present[IDEAPAD_DEV_CAMERA]) { 238 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power); 239 if (ret) { 240 kfree(priv); 241 return ret; 242 } 243 } 244 245 dev_set_drvdata(&adevice->dev, priv); 246 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) { 247 if (!devs_present[i]) 248 continue; 249 250 ideapad_register_rfkill(adevice, i); 251 } 252 ideapad_sync_rfk_state(adevice); 253 return 0; 254} 255 256static int ideapad_acpi_remove(struct acpi_device *adevice, int type) 257{ 258 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 259 int i; 260 261 device_remove_file(&adevice->dev, &dev_attr_camera_power); 262 263 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) 264 ideapad_unregister_rfkill(adevice, i); 265 266 dev_set_drvdata(&adevice->dev, NULL); 267 kfree(priv); 268 return 0; 269} 270 271static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) 272{ 273 ideapad_sync_rfk_state(adevice); 274} 275 276static struct acpi_driver ideapad_acpi_driver = { 277 .name = "ideapad_acpi", 278 .class = "IdeaPad", 279 .ids = ideapad_device_ids, 280 .ops.add = ideapad_acpi_add, 281 .ops.remove = ideapad_acpi_remove, 282 .ops.notify = ideapad_acpi_notify, 283 .owner = THIS_MODULE, 284}; 285 286 287static int __init ideapad_acpi_module_init(void) 288{ 289 acpi_bus_register_driver(&ideapad_acpi_driver); 290 291 return 0; 292} 293 294 295static void __exit ideapad_acpi_module_exit(void) 296{ 297 acpi_bus_unregister_driver(&ideapad_acpi_driver); 298 299} 300 301MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 302MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 303MODULE_LICENSE("GPL"); 304 305module_init(ideapad_acpi_module_init); 306module_exit(ideapad_acpi_module_exit); 307