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 struct ideapad_private { 38 acpi_handle handle; 39 struct rfkill *rfk[5]; 40 } *ideapad_priv; 41 42 static struct { 43 char *name; 44 int cfgbit; 45 int opcode; 46 int type; 47 } ideapad_rfk_data[] = { 48 { "ideapad_camera", 19, 0x1E, NUM_RFKILL_TYPES }, 49 { "ideapad_wlan", 18, 0x15, RFKILL_TYPE_WLAN }, 50 { "ideapad_bluetooth", 16, 0x17, RFKILL_TYPE_BLUETOOTH }, 51 { "ideapad_3g", 17, 0x20, RFKILL_TYPE_WWAN }, 52 { "ideapad_killsw", 0, 0, RFKILL_TYPE_WLAN } 53 }; 54 55 static bool no_bt_rfkill; 56 module_param(no_bt_rfkill, bool, 0444); 57 MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 58 59 /* 60 * ACPI Helpers 61 */ 62 #define IDEAPAD_EC_TIMEOUT (100) /* in ms */ 63 64 static int read_method_int(acpi_handle handle, const char *method, int *val) 65 { 66 acpi_status status; 67 unsigned long long result; 68 69 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 70 if (ACPI_FAILURE(status)) { 71 *val = -1; 72 return -1; 73 } else { 74 *val = result; 75 return 0; 76 } 77 } 78 79 static int method_vpcr(acpi_handle handle, int cmd, int *ret) 80 { 81 acpi_status status; 82 unsigned long long result; 83 struct acpi_object_list params; 84 union acpi_object in_obj; 85 86 params.count = 1; 87 params.pointer = &in_obj; 88 in_obj.type = ACPI_TYPE_INTEGER; 89 in_obj.integer.value = cmd; 90 91 status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); 92 93 if (ACPI_FAILURE(status)) { 94 *ret = -1; 95 return -1; 96 } else { 97 *ret = result; 98 return 0; 99 } 100 } 101 102 static int method_vpcw(acpi_handle handle, int cmd, int data) 103 { 104 struct acpi_object_list params; 105 union acpi_object in_obj[2]; 106 acpi_status status; 107 108 params.count = 2; 109 params.pointer = in_obj; 110 in_obj[0].type = ACPI_TYPE_INTEGER; 111 in_obj[0].integer.value = cmd; 112 in_obj[1].type = ACPI_TYPE_INTEGER; 113 in_obj[1].integer.value = data; 114 115 status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 116 if (status != AE_OK) 117 return -1; 118 return 0; 119 } 120 121 static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 122 { 123 int val; 124 unsigned long int end_jiffies; 125 126 if (method_vpcw(handle, 1, cmd)) 127 return -1; 128 129 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 130 time_before(jiffies, end_jiffies);) { 131 schedule(); 132 if (method_vpcr(handle, 1, &val)) 133 return -1; 134 if (val == 0) { 135 if (method_vpcr(handle, 0, &val)) 136 return -1; 137 *data = val; 138 return 0; 139 } 140 } 141 pr_err("timeout in read_ec_cmd\n"); 142 return -1; 143 } 144 145 static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 146 { 147 int val; 148 unsigned long int end_jiffies; 149 150 if (method_vpcw(handle, 0, data)) 151 return -1; 152 if (method_vpcw(handle, 1, cmd)) 153 return -1; 154 155 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 156 time_before(jiffies, end_jiffies);) { 157 schedule(); 158 if (method_vpcr(handle, 1, &val)) 159 return -1; 160 if (val == 0) 161 return 0; 162 } 163 pr_err("timeout in write_ec_cmd\n"); 164 return -1; 165 } 166 /* the above is ACPI helpers */ 167 168 static ssize_t show_ideapad_cam(struct device *dev, 169 struct device_attribute *attr, 170 char *buf) 171 { 172 struct ideapad_private *priv = dev_get_drvdata(dev); 173 acpi_handle handle = priv->handle; 174 unsigned long result; 175 176 if (read_ec_data(handle, 0x1D, &result)) 177 return sprintf(buf, "-1\n"); 178 return sprintf(buf, "%lu\n", result); 179 } 180 181 static ssize_t store_ideapad_cam(struct device *dev, 182 struct device_attribute *attr, 183 const char *buf, size_t count) 184 { 185 struct ideapad_private *priv = dev_get_drvdata(dev); 186 acpi_handle handle = priv->handle; 187 int ret, state; 188 189 if (!count) 190 return 0; 191 if (sscanf(buf, "%i", &state) != 1) 192 return -EINVAL; 193 ret = write_ec_cmd(handle, 0x1E, state); 194 if (ret < 0) 195 return ret; 196 return count; 197 } 198 199 static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 200 201 static int ideapad_rfk_set(void *data, bool blocked) 202 { 203 int device = (unsigned long)data; 204 205 if (device == IDEAPAD_DEV_KILLSW) 206 return -EINVAL; 207 208 return write_ec_cmd(ideapad_priv->handle, 209 ideapad_rfk_data[device].opcode, 210 !blocked); 211 } 212 213 static struct rfkill_ops ideapad_rfk_ops = { 214 .set_block = ideapad_rfk_set, 215 }; 216 217 static void ideapad_sync_rfk_state(struct acpi_device *adevice) 218 { 219 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 220 acpi_handle handle = priv->handle; 221 unsigned long hw_blocked; 222 int i; 223 224 if (read_ec_data(handle, 0x23, &hw_blocked)) 225 return; 226 hw_blocked = !hw_blocked; 227 228 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) 229 if (priv->rfk[i]) 230 rfkill_set_hw_state(priv->rfk[i], hw_blocked); 231 } 232 233 static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) 234 { 235 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 236 int ret; 237 unsigned long sw_blocked; 238 239 if (no_bt_rfkill && 240 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 241 /* Force to enable bluetooth when no_bt_rfkill=1 */ 242 write_ec_cmd(ideapad_priv->handle, 243 ideapad_rfk_data[dev].opcode, 1); 244 return 0; 245 } 246 247 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev, 248 ideapad_rfk_data[dev].type, &ideapad_rfk_ops, 249 (void *)(long)dev); 250 if (!priv->rfk[dev]) 251 return -ENOMEM; 252 253 if (read_ec_data(ideapad_priv->handle, ideapad_rfk_data[dev].opcode-1, 254 &sw_blocked)) { 255 rfkill_init_sw_state(priv->rfk[dev], 0); 256 } else { 257 sw_blocked = !sw_blocked; 258 rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 259 } 260 261 ret = rfkill_register(priv->rfk[dev]); 262 if (ret) { 263 rfkill_destroy(priv->rfk[dev]); 264 return ret; 265 } 266 return 0; 267 } 268 269 static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) 270 { 271 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 272 273 if (!priv->rfk[dev]) 274 return; 275 276 rfkill_unregister(priv->rfk[dev]); 277 rfkill_destroy(priv->rfk[dev]); 278 } 279 280 static const struct acpi_device_id ideapad_device_ids[] = { 281 { "VPC2004", 0}, 282 { "", 0}, 283 }; 284 MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 285 286 static int ideapad_acpi_add(struct acpi_device *adevice) 287 { 288 int i, cfg; 289 int devs_present[5]; 290 struct ideapad_private *priv; 291 292 if (read_method_int(adevice->handle, "_CFG", &cfg)) 293 return -ENODEV; 294 295 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) { 296 if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg)) 297 devs_present[i] = 1; 298 else 299 devs_present[i] = 0; 300 } 301 302 /* The hardware switch is always present */ 303 devs_present[IDEAPAD_DEV_KILLSW] = 1; 304 305 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 306 if (!priv) 307 return -ENOMEM; 308 309 if (devs_present[IDEAPAD_DEV_CAMERA]) { 310 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power); 311 if (ret) { 312 kfree(priv); 313 return ret; 314 } 315 } 316 317 priv->handle = adevice->handle; 318 dev_set_drvdata(&adevice->dev, priv); 319 ideapad_priv = priv; 320 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) { 321 if (!devs_present[i]) 322 continue; 323 324 ideapad_register_rfkill(adevice, i); 325 } 326 ideapad_sync_rfk_state(adevice); 327 return 0; 328 } 329 330 static int ideapad_acpi_remove(struct acpi_device *adevice, int type) 331 { 332 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 333 int i; 334 335 device_remove_file(&adevice->dev, &dev_attr_camera_power); 336 337 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) 338 ideapad_unregister_rfkill(adevice, i); 339 340 dev_set_drvdata(&adevice->dev, NULL); 341 kfree(priv); 342 return 0; 343 } 344 345 static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) 346 { 347 acpi_handle handle = adevice->handle; 348 unsigned long vpc1, vpc2, vpc_bit; 349 350 if (read_ec_data(handle, 0x10, &vpc1)) 351 return; 352 if (read_ec_data(handle, 0x1A, &vpc2)) 353 return; 354 355 vpc1 = (vpc2 << 8) | vpc1; 356 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 357 if (test_bit(vpc_bit, &vpc1)) { 358 if (vpc_bit == 9) 359 ideapad_sync_rfk_state(adevice); 360 } 361 } 362 } 363 364 static struct acpi_driver ideapad_acpi_driver = { 365 .name = "ideapad_acpi", 366 .class = "IdeaPad", 367 .ids = ideapad_device_ids, 368 .ops.add = ideapad_acpi_add, 369 .ops.remove = ideapad_acpi_remove, 370 .ops.notify = ideapad_acpi_notify, 371 .owner = THIS_MODULE, 372 }; 373 374 375 static int __init ideapad_acpi_module_init(void) 376 { 377 acpi_bus_register_driver(&ideapad_acpi_driver); 378 379 return 0; 380 } 381 382 383 static void __exit ideapad_acpi_module_exit(void) 384 { 385 acpi_bus_unregister_driver(&ideapad_acpi_driver); 386 387 } 388 389 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 390 MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 391 MODULE_LICENSE("GPL"); 392 393 module_init(ideapad_acpi_module_init); 394 module_exit(ideapad_acpi_module_exit); 395