topstar-laptop.c 4.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * ACPI driver for Topstar notebooks (hotkeys support only)
 *
 * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
 *
 * Implementation inspired by existing x86 platform drivers, in special
 * asus/eepc/fujitsu-laptop, thanks to their authors
 *
 * 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.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
19
#include <linux/slab.h>
20 21
#include <linux/acpi.h>
#include <linux/input.h>
22
#include <linux/input/sparse-keymap.h>
23 24 25 26 27 28 29

#define ACPI_TOPSTAR_CLASS "topstar"

struct topstar_hkey {
	struct input_dev *inputdev;
};

30 31 32 33 34 35 36 37 38 39 40 41
static const struct key_entry topstar_keymap[] = {
	{ KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
	{ KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
	{ KE_KEY, 0x83, { KEY_VOLUMEUP } },
	{ KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
	{ KE_KEY, 0x85, { KEY_MUTE } },
	{ KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
	{ KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */
	{ KE_KEY, 0x88, { KEY_WLAN } },
	{ KE_KEY, 0x8a, { KEY_WWW } },
	{ KE_KEY, 0x8b, { KEY_MAIL } },
	{ KE_KEY, 0x8c, { KEY_MEDIA } },
42

43
	/* Known non hotkey events don't handled or that we don't care yet */
M
Manuel Lauss 已提交
44
	{ KE_IGNORE, 0x82, }, /* backlight event */
45 46 47
	{ KE_IGNORE, 0x8e, },
	{ KE_IGNORE, 0x8f, },
	{ KE_IGNORE, 0x90, },
48

49 50 51 52 53 54 55
	/*
	 * 'G key' generate two event codes, convert to only
	 * one event/key code for now, consider replacing by
	 * a switch (3G switch - SW_3G?)
	 */
	{ KE_KEY, 0x96, { KEY_F14 } },
	{ KE_KEY, 0x97, { KEY_F14 } },
56

57 58
	{ KE_END, 0 }
};
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

static void acpi_topstar_notify(struct acpi_device *device, u32 event)
{
	static bool dup_evnt[2];
	bool *dup;
	struct topstar_hkey *hkey = acpi_driver_data(device);

	/* 0x83 and 0x84 key events comes duplicated... */
	if (event == 0x83 || event == 0x84) {
		dup = &dup_evnt[event - 0x83];
		if (*dup) {
			*dup = false;
			return;
		}
		*dup = true;
	}

76 77
	if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true))
		pr_info("unknown event = 0x%02x\n", event);
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
}

static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state)
{
	acpi_status status;
	union acpi_object fncx_params[1] = {
		{ .type = ACPI_TYPE_INTEGER }
	};
	struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] };

	fncx_params[0].integer.value = state ? 0x86 : 0x87;
	status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL);
	if (ACPI_FAILURE(status)) {
		pr_err("Unable to switch FNCX notifications\n");
		return -ENODEV;
	}

	return 0;
}

static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)
{
100 101
	struct input_dev *input;
	int error;
102

103 104
	input = input_allocate_device();
	if (!input) {
105
		pr_err("Unable to allocate input device\n");
106
		return -ENOMEM;
107
	}
108 109 110 111 112 113 114 115 116

	input->name = "Topstar Laptop extra buttons";
	input->phys = "topstar/input0";
	input->id.bustype = BUS_HOST;

	error = sparse_keymap_setup(input, topstar_keymap, NULL);
	if (error) {
		pr_err("Unable to setup input device keymap\n");
		goto err_free_dev;
117
	}
118 119 120

	error = input_register_device(input);
	if (error) {
121
		pr_err("Unable to register input device\n");
122
		goto err_free_keymap;
123 124
	}

125
	hkey->inputdev = input;
126
	return 0;
127 128 129 130 131 132

 err_free_keymap:
	sparse_keymap_free(input);
 err_free_dev:
	input_free_device(input);
	return error;
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
}

static int acpi_topstar_add(struct acpi_device *device)
{
	struct topstar_hkey *tps_hkey;

	tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL);
	if (!tps_hkey)
		return -ENOMEM;

	strcpy(acpi_device_name(device), "Topstar TPSACPI");
	strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS);

	if (acpi_topstar_fncx_switch(device, true))
		goto add_err;

	if (acpi_topstar_init_hkey(tps_hkey))
		goto add_err;

	device->driver_data = tps_hkey;
	return 0;

add_err:
	kfree(tps_hkey);
	return -ENODEV;
}

static int acpi_topstar_remove(struct acpi_device *device, int type)
{
	struct topstar_hkey *tps_hkey = acpi_driver_data(device);

	acpi_topstar_fncx_switch(device, false);

166
	sparse_keymap_free(tps_hkey->inputdev);
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	input_unregister_device(tps_hkey->inputdev);
	kfree(tps_hkey);

	return 0;
}

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

static struct acpi_driver acpi_topstar_driver = {
	.name = "Topstar laptop ACPI driver",
	.class = ACPI_TOPSTAR_CLASS,
	.ids = topstar_device_ids,
	.ops = {
		.add = acpi_topstar_add,
		.remove = acpi_topstar_remove,
		.notify = acpi_topstar_notify,
	},
};
189
module_acpi_driver(acpi_topstar_driver);
190 191 192 193

MODULE_AUTHOR("Herton Ronaldo Krzesinski");
MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
MODULE_LICENSE("GPL");