system76-dkms/input.c

173 lines
4.3 KiB
C

/*
* input.c
*
* Copyright (C) 2017 Jeremy Soller <jeremy@system76.com>
* Copyright (C) 2014-2016 Arnoud Willemsen <mail@lynthium.com>
* Copyright (C) 2013-2015 TUXEDO Computers GmbH <tux@tuxedocomputers.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#define AIRPLANE_KEY KEY_WLAN
static struct input_dev *s76_input_device;
static DEFINE_MUTEX(s76_input_report_mutex);
#define POLL_FREQ_MIN 1
#define POLL_FREQ_MAX 20
#define POLL_FREQ_DEFAULT 5
static int param_set_poll_freq(const char *val, const struct kernel_param *kp)
{
int ret;
ret = param_set_byte(val, kp);
if (!ret)
*((unsigned char *) kp->arg) = clamp_t(unsigned char,
*((unsigned char *) kp->arg),
POLL_FREQ_MIN, POLL_FREQ_MAX);
return ret;
}
static const struct kernel_param_ops param_ops_poll_freq = {
.set = param_set_poll_freq,
.get = param_get_byte,
};
static unsigned char param_poll_freq = POLL_FREQ_DEFAULT;
#define param_check_poll_freq param_check_byte
module_param_named(poll_freq, param_poll_freq, poll_freq, S_IRUSR);
MODULE_PARM_DESC(poll_freq, "Set polling frequency");
static struct task_struct *s76_input_polling_task;
static void s76_input_airplane_key(void) {
mutex_lock(&s76_input_report_mutex);
input_report_key(s76_input_device, AIRPLANE_KEY, 1);
input_sync(s76_input_device);
input_report_key(s76_input_device, AIRPLANE_KEY, 0);
input_sync(s76_input_device);
mutex_unlock(&s76_input_report_mutex);
}
static int s76_input_polling_thread(void *data) {
S76_INFO("Polling thread started (PID: %i), polling at %i Hz\n",
current->pid, param_poll_freq);
while (!kthread_should_stop()) {
u8 byte;
ec_read(0xDB, &byte);
if (byte & 0x40) {
ec_write(0xDB, byte & ~0x40);
S76_INFO("Airplane-Mode Hotkey pressed (EC)\n");
s76_input_airplane_key();
}
msleep_interruptible(1000 / param_poll_freq);
}
S76_INFO("Polling thread exiting\n");
return 0;
}
static void s76_input_airplane_wmi(void) {
S76_INFO("Airplane-Mode Hotkey pressed (WMI)\n");
if (s76_input_polling_task) {
S76_INFO("Stopping polling thread\n");
kthread_stop(s76_input_polling_task);
s76_input_polling_task = NULL;
}
s76_input_airplane_key();
}
static int s76_input_open(struct input_dev *dev) {
s76_input_polling_task = kthread_run(
s76_input_polling_thread,
NULL, "system76-polld");
if (unlikely(IS_ERR(s76_input_polling_task))) {
s76_input_polling_task = NULL;
S76_ERROR("Could not create polling thread\n");
return PTR_ERR(s76_input_polling_task);
}
return 0;
}
static void s76_input_close(struct input_dev *dev) {
if (unlikely(IS_ERR_OR_NULL(s76_input_polling_task))) {
return;
}
kthread_stop(s76_input_polling_task);
s76_input_polling_task = NULL;
}
static int __init s76_input_init(void) {
int err;
u8 byte;
s76_input_device = input_allocate_device();
if (unlikely(!s76_input_device)) {
S76_ERROR("Error allocating input device\n");
return -ENOMEM;
}
s76_input_device->name = "System76 Airplane-Mode Hotkey";
s76_input_device->phys = "system76/input0";
s76_input_device->id.bustype = BUS_HOST;
s76_input_device->dev.parent = &s76_platform_device->dev;
set_bit(EV_KEY, s76_input_device->evbit);
set_bit(AIRPLANE_KEY, s76_input_device->keybit);
s76_input_device->open = s76_input_open;
s76_input_device->close = s76_input_close;
ec_read(0xDB, &byte);
ec_write(0xDB, byte & ~0x40);
err = input_register_device(s76_input_device);
if (unlikely(err)) {
S76_ERROR("Error registering input device\n");
goto err_free_input_device;
}
return 0;
err_free_input_device:
input_free_device(s76_input_device);
return err;
}
static void __exit s76_input_exit(void) {
if (unlikely(!s76_input_device)) {
return;
}
input_unregister_device(s76_input_device);
s76_input_device = NULL;
}