diff --git a/Makefile b/Makefile
index 7723023..8d031d9 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with this software. If not, see .
#
-obj-m := ./src/tuxedo_keyboard.o ./src/clevo_wmi.o ./src/clevo_acpi.o
+obj-m := ./src/tuxedo_keyboard.o \
+ ./src/clevo_wmi.o \
+ ./src/clevo_acpi.o \
+ ./src/tuxedo_io/tuxedo_io.o
PWD := $(shell pwd)
KDIR := /lib/modules/$(shell uname -r)/build
diff --git a/dkms.conf b/dkms.conf
index f3b5925..7e8238e 100644
--- a/dkms.conf
+++ b/dkms.conf
@@ -13,6 +13,10 @@ DEST_MODULE_LOCATION[2]="/kernel/lib/"
BUILT_MODULE_NAME[2]="clevo_acpi"
BUILT_MODULE_LOCATION[2]="src/"
+DEST_MODULE_LOCATION[3]="/kernel/lib/"
+BUILT_MODULE_NAME[3]="tuxedo_io"
+BUILT_MODULE_LOCATION[3]="src/tuxedo_io"
+
MAKE[0]="make KDIR=/lib/modules/${kernelver}/build"
CLEAN="make clean"
AUTOINSTALL="yes"
diff --git a/src/tuxedo_io/tongfang_wmi.h b/src/tuxedo_io/tongfang_wmi.h
new file mode 100644
index 0000000..cfabbb3
--- /dev/null
+++ b/src/tuxedo_io/tongfang_wmi.h
@@ -0,0 +1,291 @@
+/*!
+ * Copyright (c) 2020 TUXEDO Computers GmbH
+ *
+ * This file is part of tuxedo-cc-wmi.
+ *
+ * tuxedo-cc-wmi 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software. If not, see .
+ */
+#include
+#include
+#include
+#include
+
+#define UNIWILL_WMI_MGMT_GUID_BA "ABBC0F6D-8EA1-11D1-00A0-C90629100000"
+#define UNIWILL_WMI_MGMT_GUID_BB "ABBC0F6E-8EA1-11D1-00A0-C90629100000"
+#define UNIWILL_WMI_MGMT_GUID_BC "ABBC0F6F-8EA1-11D1-00A0-C90629100000"
+
+#define UNIWILL_WMI_EVENT_GUID_0 "ABBC0F70-8EA1-11D1-00A0-C90629100000"
+#define UNIWILL_WMI_EVENT_GUID_1 "ABBC0F71-8EA1-11D1-00A0-C90629100000"
+#define UNIWILL_WMI_EVENT_GUID_2 "ABBC0F72-8EA1-11D1-00A0-C90629100000"
+
+#define UNIWILL_EC_REG_LDAT 0x8a
+#define UNIWILL_EC_REG_HDAT 0x8b
+#define UNIWILL_EC_REG_FLAGS 0x8c
+#define UNIWILL_EC_REG_CMDL 0x8d
+#define UNIWILL_EC_REG_CMDH 0x8e
+
+#define UNIWILL_EC_BIT_RFLG 0
+#define UNIWILL_EC_BIT_WFLG 1
+#define UNIWILL_EC_BIT_BFLG 2
+#define UNIWILL_EC_BIT_CFLG 3
+#define UNIWILL_EC_BIT_DRDY 7
+
+#define UW_EC_WAIT_CYCLES 0x50
+
+union uw_ec_read_return {
+ u32 dword;
+ struct {
+ u8 data_low;
+ u8 data_high;
+ } bytes;
+};
+
+union uw_ec_write_return {
+ u32 dword;
+ struct {
+ u8 addr_low;
+ u8 addr_high;
+ u8 data_low;
+ u8 data_high;
+ } bytes;
+};
+
+static bool uniwill_ec_direct = true;
+
+DEFINE_MUTEX(uniwill_ec_lock);
+
+static u32 uw_wmi_ec_evaluate(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, u8 read_flag, u32 *return_buffer)
+{
+ acpi_status status;
+ union acpi_object *out_acpi;
+ u32 e_result = 0;
+
+ // Kernel buffer for input argument
+ u32 *wmi_arg = (u32 *) kmalloc(sizeof(u32)*10, GFP_KERNEL);
+ // Byte reference to the input buffer
+ u8 *wmi_arg_bytes = (u8 *) wmi_arg;
+
+ u8 wmi_instance = 0x00;
+ u32 wmi_method_id = 0x04;
+ struct acpi_buffer wmi_in = { (acpi_size) sizeof(wmi_arg), wmi_arg};
+ struct acpi_buffer wmi_out = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ mutex_lock(&uniwill_ec_lock);
+
+ // Zero input buffer
+ memset(wmi_arg, 0x00, 10 * sizeof(u32));
+
+ // Configure the input buffer
+ wmi_arg_bytes[0] = addr_low;
+ wmi_arg_bytes[1] = addr_high;
+ wmi_arg_bytes[2] = data_low;
+ wmi_arg_bytes[3] = data_high;
+
+ if (read_flag != 0) {
+ wmi_arg_bytes[5] = 0x01;
+ }
+
+ status = wmi_evaluate_method(UNIWILL_WMI_MGMT_GUID_BC, wmi_instance, wmi_method_id, &wmi_in, &wmi_out);
+ out_acpi = (union acpi_object *) wmi_out.pointer;
+
+ if (out_acpi && out_acpi->type == ACPI_TYPE_BUFFER) {
+ memcpy(return_buffer, out_acpi->buffer.pointer, out_acpi->buffer.length);
+ } /* else if (out_acpi && out_acpi->type == ACPI_TYPE_INTEGER) {
+ e_result = (u32) out_acpi->integer.value;
+ }*/
+ if (ACPI_FAILURE(status)) {
+ pr_err("uniwill_wmi.h: Error evaluating method\n");
+ e_result = -EIO;
+ }
+
+ kfree(out_acpi);
+ kfree(wmi_arg);
+
+ mutex_unlock(&uniwill_ec_lock);
+
+ return e_result;
+}
+
+/**
+ * EC address read through WMI
+ */
+static u32 uw_ec_read_addr_wmi(u8 addr_low, u8 addr_high, union uw_ec_read_return *output)
+{
+ u32 uw_data[10];
+ u32 ret = uw_wmi_ec_evaluate(addr_low, addr_high, 0x00, 0x00, 1, uw_data);
+ output->dword = uw_data[0];
+ pr_debug("addr: 0x%02x%02x value: %0#4x (high: %0#4x) result: %d\n", addr_high, addr_low, output->bytes.data_low, output->bytes.data_high, ret);
+ return ret;
+}
+
+/**
+ * EC address write through WMI
+ */
+static u32 uw_ec_write_addr_wmi(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output)
+{
+ u32 uw_data[10];
+ u32 ret = uw_wmi_ec_evaluate(addr_low, addr_high, data_low, data_high, 0, uw_data);
+ output->dword = uw_data[0];
+ return ret;
+}
+
+/**
+ * Direct EC address read
+ */
+static u32 uw_ec_read_addr_direct(u8 addr_low, u8 addr_high, union uw_ec_read_return *output)
+{
+ u32 result;
+ u8 tmp, count, flags;
+
+ mutex_lock(&uniwill_ec_lock);
+
+ ec_write(UNIWILL_EC_REG_LDAT, addr_low);
+ ec_write(UNIWILL_EC_REG_HDAT, addr_high);
+
+ flags = (0 << UNIWILL_EC_BIT_DRDY) | (1 << UNIWILL_EC_BIT_RFLG);
+ ec_write(UNIWILL_EC_REG_FLAGS, flags);
+
+ // Wait for ready flag
+ count = UW_EC_WAIT_CYCLES;
+ ec_read(UNIWILL_EC_REG_FLAGS, &tmp);
+ while (((tmp & (1 << UNIWILL_EC_BIT_DRDY)) == 0) && count != 0) {
+ msleep(1);
+ ec_read(UNIWILL_EC_REG_FLAGS, &tmp);
+ count -= 1;
+ }
+
+ if (count != 0) {
+ output->dword = 0;
+ ec_read(UNIWILL_EC_REG_CMDL, &tmp);
+ output->bytes.data_low = tmp;
+ ec_read(UNIWILL_EC_REG_CMDH, &tmp);
+ output->bytes.data_high = tmp;
+ result = 0;
+ } else {
+ output->dword = 0xfefefefe;
+ result = -EIO;
+ }
+
+ ec_write(UNIWILL_EC_REG_FLAGS, 0x00);
+
+ mutex_unlock(&uniwill_ec_lock);
+
+ pr_debug("addr: 0x%02x%02x value: %0#4x result: %d\n", addr_high, addr_low, output->bytes.data_low, result);
+
+ return result;
+}
+
+static u32 uw_ec_write_addr_direct(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output)
+{
+ u32 result = 0;
+ u8 tmp, count, flags;
+
+ mutex_lock(&uniwill_ec_lock);
+
+ ec_write(UNIWILL_EC_REG_LDAT, addr_low);
+ ec_write(UNIWILL_EC_REG_HDAT, addr_high);
+ ec_write(UNIWILL_EC_REG_CMDL, data_low);
+ ec_write(UNIWILL_EC_REG_CMDH, data_high);
+
+ flags = (0 << UNIWILL_EC_BIT_DRDY) | (1 << UNIWILL_EC_BIT_WFLG);
+ ec_write(UNIWILL_EC_REG_FLAGS, flags);
+
+ // Wait for ready flag
+ count = UW_EC_WAIT_CYCLES;
+ ec_read(UNIWILL_EC_REG_FLAGS, &tmp);
+ while (((tmp & (1 << UNIWILL_EC_BIT_DRDY)) == 0) && count != 0) {
+ msleep(1);
+ ec_read(UNIWILL_EC_REG_FLAGS, &tmp);
+ count -= 1;
+ }
+
+ // Replicate wmi output depending on success
+ if (count != 0) {
+ output->bytes.addr_low = addr_low;
+ output->bytes.addr_high = addr_high;
+ output->bytes.data_low = data_low;
+ output->bytes.data_high = data_high;
+ result = 0;
+ } else {
+ output->dword = 0xfefefefe;
+ result = -EIO;
+ }
+
+ ec_write(UNIWILL_EC_REG_FLAGS, 0x00);
+
+ mutex_unlock(&uniwill_ec_lock);
+
+ return result;
+}
+
+u32 uw_ec_read_addr(u8 addr_low, u8 addr_high, union uw_ec_read_return *output)
+{
+ if (uniwill_ec_direct) {
+ return uw_ec_read_addr_direct(addr_low, addr_high, output);
+ } else {
+ return uw_ec_read_addr_wmi(addr_low, addr_high, output);
+ }
+}
+EXPORT_SYMBOL(uw_ec_read_addr);
+
+u32 uw_ec_write_addr(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output)
+{
+ if (uniwill_ec_direct) {
+ return uw_ec_write_addr_direct(addr_low, addr_high, data_low, data_high, output);
+ } else {
+ return uw_ec_write_addr_wmi(addr_low, addr_high, data_low, data_high, output);
+ }
+}
+EXPORT_SYMBOL(uw_ec_write_addr);
+
+static u32 uniwill_identify(void)
+{
+ int status;
+
+ // Look for for GUIDs used on uniwill devices
+ status =
+ wmi_has_guid(UNIWILL_WMI_EVENT_GUID_0) &&
+ wmi_has_guid(UNIWILL_WMI_EVENT_GUID_1) &&
+ wmi_has_guid(UNIWILL_WMI_EVENT_GUID_2) &&
+ wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BA) &&
+ wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BB) &&
+ wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BC);
+
+ if (!status)
+ {
+ pr_debug("probe: At least one Uniwill GUID missing\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void uniwill_init(void)
+{
+ union uw_ec_write_return reg_write_return;
+
+ // Enable manual mode
+ uw_ec_write_addr(0x41, 0x07, 0x01, 0x00, ®_write_return);
+
+ // Zero second fan temp for detection
+ uw_ec_write_addr(0x4f, 0x04, 0x00, 0x00, ®_write_return);
+}
+
+static void uniwill_exit(void)
+{
+ union uw_ec_write_return reg_write_return;
+
+ // Disable manual mode
+ uw_ec_write_addr(0x41, 0x07, 0x00, 0x00, ®_write_return);
+}
diff --git a/src/tuxedo_io/tuxedo_io.c b/src/tuxedo_io/tuxedo_io.c
new file mode 100644
index 0000000..c83cba6
--- /dev/null
+++ b/src/tuxedo_io/tuxedo_io.c
@@ -0,0 +1,351 @@
+/*!
+ * Copyright (c) 2019-2020 TUXEDO Computers GmbH
+ *
+ * This file is part of tuxedo-cc-wmi.
+ *
+ * tuxedo-cc-wmi 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software. If not, see .
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "../clevo_interfaces.h"
+#include "tongfang_wmi.h"
+#include "tuxedo_io_ioctl.h"
+
+MODULE_DESCRIPTION("Hardware interface for TUXEDO laptops");
+MODULE_AUTHOR("TUXEDO Computers GmbH ");
+MODULE_VERSION("0.1.7");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_CLEVO_INTERFACES();
+MODULE_ALIAS("wmi:" CLEVO_WMI_METHOD_GUID);
+MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BA);
+MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BB);
+MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC);
+
+// Initialized in module init, global for ioctl interface
+static u32 id_check_clevo;
+static u32 id_check_uniwill;
+
+static u32 clevo_identify(void)
+{
+ return clevo_get_active_interface_id(&dummy) == 0 ? 1 : 0;
+}
+
+/*static int fop_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int fop_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}*/
+
+static long clevo_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ u32 result = 0, status;
+ u32 copy_result;
+ u32 argument = (u32) arg;
+
+ switch (cmd) {
+ case R_FANINFO1:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO1, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ case R_FANINFO2:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO2, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ case R_FANINFO3:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO3, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ /*case R_FANINFO4:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO4, 0);
+ copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;*/
+ case R_WEBCAM_SW:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ case R_FLIGHTMODE_SW:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_FLIGHTMODE_SW, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ case R_TOUCHPAD_SW:
+ status = clevo_evaluate_method(CLEVO_CMD_GET_TOUCHPAD_SW, 0, &result);
+ copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result));
+ break;
+ }
+
+ switch (cmd) {
+ case W_FANSPEED:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_VALUE, argument, &result);
+ // Note: Delay needed to let hardware catch up with the written value.
+ // No known ready flag. If the value is read too soon, the old value
+ // will still be read out.
+ // (Theoretically needed for other methods as well.)
+ // Can it be lower? 50ms is too low
+ msleep(100);
+ break;
+ case W_FANAUTO:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_AUTO, argument, &result);
+ break;
+ case W_WEBCAM_SW:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result);
+ // Only set status if it isn't already the right value
+ // (workaround for old and/or buggy WMI interfaces that toggle on write)
+ if ((argument & 0x01) != (result & 0x01)) {
+ clevo_evaluate_method(CLEVO_CMD_SET_WEBCAM_SW, argument, &result);
+ }
+ break;
+ case W_FLIGHTMODE_SW:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ clevo_evaluate_method(CLEVO_CMD_SET_FLIGHTMODE_SW, argument, &result);
+ break;
+ case W_TOUCHPAD_SW:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ clevo_evaluate_method(CLEVO_CMD_SET_TOUCHPAD_SW, argument, &result);
+ break;
+ }
+
+ return 0;
+}
+
+static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ u32 result = 0;
+ u32 copy_result;
+ u32 argument;
+ union uw_ec_read_return reg_read_return;
+ union uw_ec_write_return reg_write_return;
+
+#ifdef DEBUG
+ u32 uw_arg[10];
+ u32 uw_result[10];
+ int i;
+ for (i = 0; i < 10; ++i) {
+ uw_result[i] = 0xdeadbeef;
+ }
+#endif
+
+ switch (cmd) {
+ case R_UW_FANSPEED:
+ uw_ec_read_addr(0x04, 0x18, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+ case R_UW_FANSPEED2:
+ uw_ec_read_addr(0x09, 0x18, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+ case R_UW_FAN_TEMP:
+ uw_ec_read_addr(0x3e, 0x04, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+ case R_UW_FAN_TEMP2:
+ uw_ec_read_addr(0x4f, 0x04, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+ case R_UW_MODE:
+ uw_ec_read_addr(0x51, 0x07, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+ case R_UW_MODE_ENABLE:
+ uw_ec_read_addr(0x41, 0x07, ®_read_return);
+ result = reg_read_return.bytes.data_low;
+ copy_result = copy_to_user((void *) arg, &result, sizeof(result));
+ break;
+#ifdef DEBUG
+ case R_TF_BC:
+ copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg));
+ pr_info("R_TF_BC args [%0#2x, %0#2x, %0#2x, %0#2x]\n", uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3]);
+ if (uniwill_ec_direct) {
+ result = uw_ec_read_addr_direct(uw_arg[0], uw_arg[1], ®_read_return);
+ copy_result = copy_to_user((void *) arg, ®_read_return.dword, sizeof(reg_read_return.dword));
+ } else {
+ result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 1, uw_result);
+ copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result));
+ }
+ break;
+#endif
+ }
+
+ switch (cmd) {
+ case W_UW_FANSPEED:
+ // Get fan speed argument
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+
+ // Check current mode
+ uw_ec_read_addr(0x51, 0x07, ®_read_return);
+ if (reg_read_return.bytes.data_low != 0x40) {
+ // If not "full fan mode" (ie. 0x40) switch to it (required for fancontrol)
+ uw_ec_write_addr(0x51, 0x07, 0x40, 0x00, ®_write_return);
+ }
+ // Set speed
+ uw_ec_write_addr(0x04, 0x18, argument & 0xff, 0x00, ®_write_return);
+ break;
+ case W_UW_FANSPEED2:
+ // Get fan speed argument
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+
+ // Check current mode
+ uw_ec_read_addr(0x51, 0x07, ®_read_return);
+ if (reg_read_return.bytes.data_low != 0x40) {
+ // If not "full fan mode" (ie. 0x40) switch to it (required for fancontrol)
+ uw_ec_write_addr(0x51, 0x07, 0x40, 0x00, ®_write_return);
+ }
+ // Set speed
+ uw_ec_write_addr(0x09, 0x18, argument & 0xff, 0x00, ®_write_return);
+ break;
+ case W_UW_MODE:
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ uw_ec_write_addr(0x51, 0x07, argument & 0xff, 0x00, ®_write_return);
+ break;
+ case W_UW_MODE_ENABLE:
+ // Note: Is for the moment set and cleared on init/exit of module (uniwill mode)
+ /*
+ copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument));
+ uw_ec_write_addr(0x41, 0x07, argument & 0x01, 0x00, ®_write_return);
+ */
+ break;
+#ifdef DEBUG
+ case W_TF_BC:
+ copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg));
+ if (uniwill_ec_direct) {
+ result = uw_ec_write_addr_direct(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], ®_write_return);
+ copy_result = copy_to_user((void *) arg, ®_write_return.dword, sizeof(reg_write_return.dword));
+ } else {
+ result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 0, uw_result);
+ copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result));
+ reg_write_return.dword = uw_result[0];
+ }
+ /*pr_info("data_high %0#2x\n", reg_write_return.bytes.data_high);
+ pr_info("data_low %0#2x\n", reg_write_return.bytes.data_low);
+ pr_info("addr_high %0#2x\n", reg_write_return.bytes.addr_high);
+ pr_info("addr_low %0#2x\n", reg_write_return.bytes.addr_low);*/
+ break;
+#endif
+ }
+
+ return 0;
+}
+
+static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ u32 status;
+ // u32 result = 0;
+ u32 copy_result;
+
+ const char *module_version = THIS_MODULE->version;
+ switch (cmd) {
+ case R_MOD_VERSION:
+ copy_result = copy_to_user((char *) arg, module_version, strlen(module_version) + 1);
+ break;
+ // Hardware id checks, 1 = positive, 0 = negative
+ case R_HWCHECK_CL:
+ id_check_clevo = clevo_identify();
+ copy_result = copy_to_user((void *) arg, (void *) &id_check_clevo, sizeof(id_check_clevo));
+ break;
+ case R_HWCHECK_UW:
+ copy_result = copy_to_user((void *) arg, (void *) &id_check_uniwill, sizeof(id_check_uniwill));
+ break;
+ }
+
+ status = clevo_ioctl_interface(file, cmd, arg);
+ if (status != 0) return status;
+ status = uniwill_ioctl_interface(file, cmd, arg);
+ if (status != 0) return status;
+
+ return 0;
+}
+
+static struct file_operations fops_dev = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fop_ioctl
+// .open = fop_open,
+// .release = fop_release
+};
+
+struct class *tuxedo_io_device_class;
+dev_t tuxedo_io_device_handle;
+
+static struct cdev tuxedo_io_cdev;
+
+static int __init tuxedo_io_init(void)
+{
+ int err;
+
+ // Hardware identification
+ id_check_clevo = clevo_identify();
+ id_check_uniwill = uniwill_identify() == 0 ? 1 : 0;
+
+ if (id_check_uniwill == 1) {
+ uniwill_init();
+ }
+
+#ifdef DEBUG
+ if (id_check_clevo == 0 && id_check_uniwill == 0) {
+ pr_debug("No matching hardware found\n");
+ }
+#endif
+
+ err = alloc_chrdev_region(&tuxedo_io_device_handle, 0, 1, "tuxedo_io_cdev");
+ if (err != 0) {
+ pr_err("Failed to allocate chrdev region\n");
+ return err;
+ }
+ cdev_init(&tuxedo_io_cdev, &fops_dev);
+ err = (cdev_add(&tuxedo_io_cdev, tuxedo_io_device_handle, 1));
+ if (err < 0) {
+ pr_err("Failed to add cdev\n");
+ unregister_chrdev_region(tuxedo_io_device_handle, 1);
+ }
+ tuxedo_io_device_class = class_create(THIS_MODULE, "tuxedo_io");
+ device_create(tuxedo_io_device_class, NULL, tuxedo_io_device_handle, NULL, "tuxedo_io");
+ pr_debug("Module init successful\n");
+
+ return 0;
+}
+
+static void __exit tuxedo_io_exit(void)
+{
+ if (id_check_uniwill == 1) {
+ uniwill_exit();
+ }
+
+ device_destroy(tuxedo_io_device_class, tuxedo_io_device_handle);
+ class_destroy(tuxedo_io_device_class);
+ cdev_del(&tuxedo_io_cdev);
+ unregister_chrdev_region(tuxedo_io_device_handle, 1);
+ pr_debug("Module exit\n");
+}
+
+module_init(tuxedo_io_init);
+module_exit(tuxedo_io_exit);
diff --git a/src/tuxedo_io/tuxedo_io_ioctl.h b/src/tuxedo_io/tuxedo_io_ioctl.h
new file mode 100644
index 0000000..c97b228
--- /dev/null
+++ b/src/tuxedo_io/tuxedo_io_ioctl.h
@@ -0,0 +1,86 @@
+/*!
+ * Copyright (c) 2019-2020 TUXEDO Computers GmbH
+ *
+ * This file is part of tuxedo-cc-wmi.
+ *
+ * tuxedo-cc-wmi 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software. If not, see .
+ */
+#ifndef TUXEDO_IO_IOCTL_H
+#define TUXEDO_IO_IOCTL_H
+
+#define IOCTL_MAGIC 0xEC
+
+#define MAGIC_READ_CL IOCTL_MAGIC
+#define MAGIC_WRITE_CL IOCTL_MAGIC + 1
+
+#define MAGIC_READ_UW IOCTL_MAGIC + 2
+#define MAGIC_WRITE_UW IOCTL_MAGIC + 3
+
+
+// General
+#define R_MOD_VERSION _IOR(IOCTL_MAGIC, 0x00, char*)
+
+#define R_HWCHECK_CL _IOR(IOCTL_MAGIC, 0x05, char*)
+#define R_HWCHECK_UW _IOR(IOCTL_MAGIC, 0x06, char*)
+
+/**
+ * Clevo interface
+ */
+
+// Read
+#define R_FANINFO1 _IOR(MAGIC_READ_CL, 0x10, int32_t*)
+#define R_FANINFO2 _IOR(MAGIC_READ_CL, 0x11, int32_t*)
+#define R_FANINFO3 _IOR(MAGIC_READ_CL, 0x12, int32_t*)
+// #define R_FANINFO4 _IOR(MAGIC_READ_CL, 0x04, int32_t*)
+
+#define R_WEBCAM_SW _IOR(MAGIC_READ_CL, 0x13, int32_t*)
+#define R_FLIGHTMODE_SW _IOR(MAGIC_READ_CL, 0x14, int32_t*)
+#define R_TOUCHPAD_SW _IOR(MAGIC_READ_CL, 0x15, int32_t*)
+
+#ifdef DEBUG
+#define R_TF_BC _IOW(MAGIC_READ_CL, 0x91, uint32_t*)
+#endif
+
+// Write
+#define W_FANSPEED _IOW(MAGIC_WRITE_CL, 0x10, int32_t*)
+#define W_FANAUTO _IOW(MAGIC_WRITE_CL, 0x11, int32_t*)
+
+#define W_WEBCAM_SW _IOW(MAGIC_WRITE_CL, 0x12, int32_t*)
+#define W_FLIGHTMODE_SW _IOW(MAGIC_WRITE_CL, 0x13, int32_t*)
+#define W_TOUCHPAD_SW _IOW(MAGIC_WRITE_CL, 0x14, int32_t*)
+
+#ifdef DEBUG
+#define W_TF_BC _IOW(MAGIC_WRITE_CL, 0x91, uint32_t*)
+#endif
+
+/**
+ * Uniwill interface
+ */
+
+// Read
+#define R_UW_FANSPEED _IOR(MAGIC_READ_UW, 0x10, int32_t*)
+#define R_UW_FANSPEED2 _IOR(MAGIC_READ_UW, 0x11, int32_t*)
+#define R_UW_FAN_TEMP _IOR(MAGIC_READ_UW, 0x12, int32_t*)
+#define R_UW_FAN_TEMP2 _IOR(MAGIC_READ_UW, 0x13, int32_t*)
+
+#define R_UW_MODE _IOR(MAGIC_READ_UW, 0x14, int32_t*)
+#define R_UW_MODE_ENABLE _IOR(MAGIC_READ_UW, 0x15, int32_t*)
+
+// Write
+#define W_UW_FANSPEED _IOW(MAGIC_WRITE_UW, 0x10, int32_t*)
+#define W_UW_FANSPEED2 _IOW(MAGIC_WRITE_UW, 0x11, int32_t*)
+#define W_UW_MODE _IOW(MAGIC_WRITE_UW, 0x12, int32_t*)
+#define W_UW_MODE_ENABLE _IOW(MAGIC_WRITE_UW, 0x13, int32_t*)
+
+#endif