summaryrefslogblamecommitdiffstats
path: root/drivers/platform/x86/ideapad_acpi.c
blob: ae744caf47262ede90545bccfcdeaf7fd203b810 (plain) (tree)

























































































































































































































































































                                                                                                           
/*
 *  ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
 *
 *  Copyright © 2010 Intel Corporation
 *  Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *  02110-1301, USA.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
#include <linux/rfkill.h>

#define IDEAPAD_DEV_CAMERA	0
#define IDEAPAD_DEV_WLAN	1
#define IDEAPAD_DEV_BLUETOOTH	2
#define IDEAPAD_DEV_3G		3
#define IDEAPAD_DEV_KILLSW	4

static struct rfkill *ideapad_rfkill[5];

static const char *ideapad_rfk_names[] = {
	"ideapad_camera", "ideapad_wlan", "ideapad_bluetooth", "ideapad_3g", "ideapad_rfkill"
};
static const int ideapad_rfk_types[] = {
	0, RFKILL_TYPE_WLAN, RFKILL_TYPE_BLUETOOTH, RFKILL_TYPE_WWAN, RFKILL_TYPE_WLAN
};

static int ideapad_dev_exists(int device)
{
	acpi_status status;
	union acpi_object in_param;
	struct acpi_object_list input = { 1, &in_param };
	struct acpi_buffer output;
	union acpi_object out_obj;

	output.length = sizeof(out_obj);
	output.pointer = &out_obj;

	in_param.type = ACPI_TYPE_INTEGER;
	in_param.integer.value = device + 1;

	status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output);
	if (ACPI_FAILURE(status)) {
		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status);
		return -ENODEV;
	}
	if (out_obj.type != ACPI_TYPE_INTEGER) {
		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n");
		return -ENODEV;
	}
	return out_obj.integer.value;
}

static int ideapad_dev_get_state(int device)
{
	acpi_status status;
	union acpi_object in_param;
	struct acpi_object_list input = { 1, &in_param };
	struct acpi_buffer output;
	union acpi_object out_obj;

	output.length = sizeof(out_obj);
	output.pointer = &out_obj;

	in_param.type = ACPI_TYPE_INTEGER;
	in_param.integer.value = device + 1;

	status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
	if (ACPI_FAILURE(status)) {
		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
		return -ENODEV;
	}
	if (out_obj.type != ACPI_TYPE_INTEGER) {
		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
		return -ENODEV;
	}
	return out_obj.integer.value;
}

static int ideapad_dev_set_state(int device, int state)
{
	acpi_status status;
	union acpi_object in_params[2];
	struct acpi_object_list input = { 2, in_params };

	in_params[0].type = ACPI_TYPE_INTEGER;
	in_params[0].integer.value = device + 1;
	in_params[1].type = ACPI_TYPE_INTEGER;
	in_params[1].integer.value = state;

	status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
	if (ACPI_FAILURE(status)) {
		printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
		return -ENODEV;
	}
	return 0;
}
static ssize_t show_ideapad_cam(struct device *dev,
				struct device_attribute *attr,
				char *buf)
{
	int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
	if (state < 0)
		return state;

	return sprintf(buf, "%d\n", state);
}

static ssize_t store_ideapad_cam(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t count)
{
	int ret, state;

	if (!count)
		return 0;
	if (sscanf(buf, "%i", &state) != 1)
		return -EINVAL;
	ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, state);
	if (ret < 0)
		return ret;
	return count;
}

static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);

static int ideapad_rfk_set(void *data, bool blocked)
{
	int device = (unsigned long)data;

	if (device == IDEAPAD_DEV_KILLSW)
		return -EINVAL;
	return ideapad_dev_set_state(device, !blocked);
}

static struct rfkill_ops ideapad_rfk_ops = {
	.set_block = ideapad_rfk_set,
};

static void ideapad_sync_rfk_state(void)
{
	int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
	int i;

	rfkill_set_hw_state(ideapad_rfkill[IDEAPAD_DEV_KILLSW], hw_blocked);
	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
		if (ideapad_rfkill[i])
			rfkill_set_hw_state(ideapad_rfkill[i], hw_blocked);
	if (hw_blocked)
		return;

	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
		if (ideapad_rfkill[i])
			rfkill_set_sw_state(ideapad_rfkill[i], !ideapad_dev_get_state(i));
}

static int ideapad_register_rfkill(struct acpi_device *device, int dev)
{
	int ret;

	ideapad_rfkill[dev] = rfkill_alloc(ideapad_rfk_names[dev], &device->dev,
					   ideapad_rfk_types[dev], &ideapad_rfk_ops,
					   (void *)(long)dev);
	if (!ideapad_rfkill[dev])
		return -ENOMEM;

	ret = rfkill_register(ideapad_rfkill[dev]);
	if (ret) {
		rfkill_destroy(ideapad_rfkill[dev]);
		return ret;
	}
	return 0;
}

static void ideapad_unregister_rfkill(int dev)
{
	if (!ideapad_rfkill[dev])
		return;

	rfkill_unregister(ideapad_rfkill[dev]);
	rfkill_destroy(ideapad_rfkill[dev]);
}

static const struct acpi_device_id ideapad_device_ids[] = {
	{ "VPC2004", 0},
	{ "", 0},
};
MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);

static int ideapad_acpi_add(struct acpi_device *device)
{
	int i;
	int devs_present[5];

	for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
		devs_present[i] = ideapad_dev_exists(i);
		if (devs_present[i] < 0)
			return devs_present[i];
	}

	/* The hardware switch is always present */
	devs_present[IDEAPAD_DEV_KILLSW] = 1;

	if (devs_present[IDEAPAD_DEV_CAMERA]) {
		int ret = device_create_file(&device->dev, &dev_attr_camera_power);
		if (ret)
			return ret;
	}

	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
		if (!devs_present[i])
			continue;

		ideapad_register_rfkill(device, i);
	}
	ideapad_sync_rfk_state();
	return 0;
}

static int ideapad_acpi_remove(struct acpi_device *device, int type)
{
	int i;
	device_remove_file(&device->dev, &dev_attr_camera_power);
	for (i = 0; i < 5; i++)
		ideapad_unregister_rfkill(i);
	return 0;
}

static void ideapad_acpi_notify(struct acpi_device *device, u32 event)
{
	ideapad_sync_rfk_state();
}

static struct acpi_driver ideapad_acpi_driver = {
	.name = "ideapad_acpi",
	.class = "IdeaPad",
	.ids = ideapad_device_ids,
	.ops.add = ideapad_acpi_add,
	.ops.remove = ideapad_acpi_remove,
	.ops.notify = ideapad_acpi_notify,
	.owner = THIS_MODULE,
};


static int __init ideapad_acpi_module_init(void)
{
	acpi_bus_register_driver(&ideapad_acpi_driver);

	return 0;
}


static void __exit ideapad_acpi_module_exit(void)
{
	acpi_bus_unregister_driver(&ideapad_acpi_driver);

}

MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_DESCRIPTION("IdeaPad ACPI Extras");
MODULE_LICENSE("GPL");

module_init(ideapad_acpi_module_init);
module_exit(ideapad_acpi_module_exit);