pmaports/device/testing/linux-xiaomi-pipa/0013-Input-keyboard-add-Xiaomi-Nanosic-803-keyboard.patch

1002 lines
29 KiB
Diff

diff -Naur a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-pipa-common.dtsi b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-pipa-common.dtsi
--- a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-pipa-common.dtsi 2026-07-09 10:02:23.823845708 +0200
+++ b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-pipa-common.dtsi 2026-07-09 10:02:23.825845760 +0200
@@ -495,7 +513,14 @@
regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
};
- /* L2 and L4 are unused. */
+ /* L2 is unused. */
+
+ vreg_l4c_1p8: ldo4 {
+ regulator-name = "vreg_l4c_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1980000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
vreg_l3c_1p2: ldo3 {
regulator-name = "vreg_l3c_1p2";
@@ -671,6 +678,27 @@
};
};
+&i2c2 {
+ status = "okay";
+
+ nanosic_803: nanosic803@4c {
+ compatible = "nanosic,803";
+ status = "okay";
+ reg = <0x4c>;
+ reset-gpios = <&tlmm 141 0x00>;
+ sleep-gpios = <&tlmm 155 0x00>;
+ vdd-gpios = <&tlmm 127 0x00>;
+ irq-gpios = <&tlmm 100 0x6001>;
+ touchpad-resolution-x = <2879>;
+ touchpad-resolution-y = <1799>;
+ tablet-mode-gpios = <&tlmm 121 GPIO_ACTIVE_LOW>;
+ pinctrl-0 = <&hall_sensor_fold_default>;
+ pinctrl-names = "default";
+ vdd_1v8-supply = <&vreg_l4c_1p8>;
+ vdd_3v3-supply = <&vreg_l10c_3p3>;
+ };
+};
+
&i2c3 {
clock-frequency = <400000>;
status = "okay";
diff -Naur a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
--- a/drivers/input/keyboard/Kconfig 2026-07-09 10:02:23.811845393 +0200
+++ b/drivers/input/keyboard/Kconfig 2026-07-09 10:02:23.817826227 +0200
@@ -797,4 +797,12 @@
To compile this driver as a module, choose M here: the
module will be called cypress-sf.
+config KEYBOARD_XIAOMI_NANOSIC803
+ tristate "Xiaomi nanosic 803 keyboard/touchpad support"
+ depends on I2C
+ help
+ Say Y here if you want to enable support for Xiaomi nanosic 803 keyboard/touchpad.
+
+ To compile this driver as a module, choose M here: the
+ module will be called nanosic_803.
endif
diff -Naur a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
--- a/drivers/input/keyboard/Makefile 2026-07-09 10:02:23.817845550 +0200
+++ b/drivers/input/keyboard/Makefile 2026-07-09 10:02:23.820813491 +0200
@@ -70,3 +70,4 @@
obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o
obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o
+obj-$(CONFIG_KEYBOARD_XIAOMI_NANOSIC803)+= nanosic_803.o
diff -Naur a/drivers/input/keyboard/nanosic_803.c b/drivers/input/keyboard/nanosic_803.c
--- a/drivers/input/keyboard/nanosic_803.c 1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/input/keyboard/nanosic_803.c 2026-07-09 10:02:23.822108712 +0200
@@ -0,0 +1,911 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nanosic 803 keyboard controller driver
+ *
+ * Copyright (C) 2024 Luka Panio <lukapanio@gmail.com>
+ *
+ * Based on nano_driver by:
+ * Bin yuan <bin.yuan@nanosic.com>
+ * Copyright (C) 2010, Nanosic, Inc
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/mutex.h>
+
+#define I2C_DATA_LENGTH_READ (68)
+#define I2C_DATA_LENGTH_WRITE (66)
+
+#define TOUCH_TIMEOUT_MS 75
+#define INT_ADDR_MAX_BYTES 4
+
+#define NANOSIC_CLICK_BYTE_OFFSET 4
+
+static const unsigned int hid_to_linux_keycode[] = {
+ [0x04] = KEY_A,
+ [0x05] = KEY_B,
+ [0x06] = KEY_C,
+ [0x07] = KEY_D,
+ [0x08] = KEY_E,
+ [0x09] = KEY_F,
+ [0x0A] = KEY_G,
+ [0x0B] = KEY_H,
+ [0x0C] = KEY_I,
+ [0x0D] = KEY_J,
+ [0x0E] = KEY_K,
+ [0x0F] = KEY_L,
+ [0x10] = KEY_M,
+ [0x11] = KEY_N,
+ [0x12] = KEY_O,
+ [0x13] = KEY_P,
+ [0x14] = KEY_Q,
+ [0x15] = KEY_R,
+ [0x16] = KEY_S,
+ [0x17] = KEY_T,
+ [0x18] = KEY_U,
+ [0x19] = KEY_V,
+ [0x1A] = KEY_W,
+ [0x1B] = KEY_X,
+ [0x1C] = KEY_Y,
+ [0x1D] = KEY_Z,
+ [0x1E] = KEY_1,
+ [0x1F] = KEY_2,
+ [0x20] = KEY_3,
+ [0x21] = KEY_4,
+ [0x22] = KEY_5,
+ [0x23] = KEY_6,
+ [0x24] = KEY_7,
+ [0x25] = KEY_8,
+ [0x26] = KEY_9,
+ [0x27] = KEY_0,
+ [0x28] = KEY_ENTER,
+ [0x29] = KEY_ESC,
+ [0x2A] = KEY_BACKSPACE,
+ [0x2B] = KEY_TAB,
+ [0x2C] = KEY_SPACE,
+ [0x2D] = KEY_MINUS,
+ [0x2E] = KEY_EQUAL,
+ [0x2F] = KEY_LEFTBRACE,
+ [0x30] = KEY_RIGHTBRACE,
+ [0x31] = KEY_BACKSLASH,
+ [0x32] = KEY_GRAVE,
+ [0x33] = KEY_SEMICOLON,
+ [0x34] = KEY_APOSTROPHE,
+ [0x35] = KEY_GRAVE,
+ [0x36] = KEY_COMMA,
+ [0x37] = KEY_DOT,
+ [0x38] = KEY_SLASH,
+ [0x39] = KEY_CAPSLOCK,
+ [0x4f] = KEY_RIGHT,
+ [0x50] = KEY_LEFT,
+ [0x51] = KEY_DOWN,
+ [0x52] = KEY_UP,
+ [0x6f] = KEY_BRIGHTNESSUP,
+ [0x70] = KEY_BRIGHTNESSDOWN,
+ [0xb5] = KEY_NEXTSONG,
+ [0xb6] = KEY_PREVIOUSSONG,
+ [0xcd] = KEY_PLAYPAUSE,
+ [0xe2] = KEY_MUTE,
+ [0xe9] = KEY_VOLUMEUP,
+ [0xea] = KEY_VOLUMEDOWN,
+};
+
+static const uint16_t hid_modifier_to_linux_keycode[8] = {
+ KEY_LEFTCTRL,
+ KEY_LEFTSHIFT,
+ KEY_LEFTALT,
+ KEY_LEFTMETA,
+ KEY_RIGHTCTRL,
+ KEY_RIGHTSHIFT,
+ KEY_RIGHTALT,
+ KEY_RIGHTMETA
+};
+
+static const struct regmap_config nanosic_803_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+struct nanosic_803_priv {
+ struct device *dev;
+ struct i2c_client *client;
+ struct input_dev *keyboard_input_dev;
+ struct input_dev *touchpad_input_dev;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *sleep_gpio;
+ struct gpio_desc *vdd_gpio;
+ struct gpio_desc *irq_gpio;
+ struct gpio_desc *tablet_mode_gpio;
+ struct regulator *vdd_1v8;
+ struct regulator *vdd_3v3;
+ struct workqueue_struct *wq;
+ struct work_struct led_work;
+ struct timer_list finger_timer;
+ struct mutex read_mutex;
+ unsigned int irq_number;
+ int tablet_mode_irq;
+ char last_pressed_key[5];
+ char last_modifier_state;
+ char last_fn_key;
+ int slot_mapping[3];
+ bool finger_down;
+ bool is_keyboard_connected;
+ bool is_touchpad_connected;
+ struct input_dev *tablet_mode_input_dev;
+ bool caps_led_on;
+ unsigned long last_touch_time;
+ int last_x, last_y;
+};
+
+static void nanosic_803_wakeup(struct nanosic_803_priv *nanosic_dev)
+{
+ int level = 1;
+ int retry = 3;
+ level = gpiod_get_value(nanosic_dev->irq_gpio);
+
+ if (level <= 0) {
+ mdelay(25);
+ while (retry--) {
+ // Try three times
+ if (gpiod_get_value(nanosic_dev->irq_gpio)) {
+ break;
+ }
+ // Reset wn8030
+ if (retry == 0) {
+ gpiod_set_value(nanosic_dev->reset_gpio, 0);
+ mdelay(100);
+ gpiod_set_value(nanosic_dev->reset_gpio, 1);
+
+ // Delay 500ms for iic ready
+ mdelay(500);
+ }
+ // IRQ low level duration is 1ms
+ mdelay(1);
+ }
+ }
+}
+
+static int nanosic_803_read_version(struct nanosic_803_priv *nanosic_dev)
+{
+ char rsp[I2C_DATA_LENGTH_READ] = {0};
+ char cmd[I2C_DATA_LENGTH_WRITE] = {
+ 0x32, 0x00, 0x4F, 0x30, 0x80,
+ 0x18, 0x01, 0x00, 0x18
+ };
+ char hex_dump[3 * I2C_DATA_LENGTH_READ + 1] = {0};
+ u8 retry = 0;
+ int ret = -1;
+
+ while (retry++ < 30) {
+ ret = regmap_raw_write(nanosic_dev->regmap, 0, cmd, sizeof(cmd));
+ if (ret < 0) {
+ dev_err(nanosic_dev->dev, "regmap write cmd failed time %d\n", retry);
+ msleep(100);
+ continue;
+ }
+ msleep(2);
+
+ ret = regmap_raw_read(nanosic_dev->regmap, 0, rsp, sizeof(rsp));
+ if (ret == 0) {
+ for (int i = 0; i < sizeof(rsp); i++) {
+ snprintf(&hex_dump[i * 3], 4, "%02x ", rsp[i]);
+ }
+ dev_err(nanosic_dev->dev, "nanosic chip version: %s\n", hex_dump);
+ dev_err(nanosic_dev->dev, "Version read OK\n");
+ break;
+ }
+ msleep(2);
+ }
+
+ return ret;
+}
+
+static int nanosic_i2c_read(struct nanosic_803_priv *nanosic_dev, void *buf, size_t len)
+{
+ struct i2c_adapter *adap;
+ unsigned char addr[INT_ADDR_MAX_BYTES];
+ struct i2c_msg msg[2];
+ int ret;
+
+ addr[0] = nanosic_dev->client->addr;
+
+ adap = i2c_get_adapter(nanosic_dev->client->adapter->nr);
+
+ msg[0].len = 1;
+ msg[0].addr = nanosic_dev->client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = addr;
+
+ msg[1].addr = nanosic_dev->client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = len;
+ msg[1].buf = buf;
+
+ ret = i2c_transfer(adap, msg, 2);
+ if (ret < 0) {
+ dev_err(nanosic_dev->dev, "i2c_transfer read error\n");
+ len = -1;
+ }
+
+ //mutex_unlock(&i2c_client->read_mutex);
+
+ return len;
+}
+
+static int nanosic_i2c_write(struct nanosic_803_priv *nanosic_dev, void *buf, size_t len)
+{
+ struct i2c_msg msg;
+ struct i2c_adapter *adap;
+ unsigned char tmp_buf[128] = { 0 };
+ int ret;
+
+ adap = i2c_get_adapter(nanosic_dev->client->adapter->nr);
+
+ tmp_buf[0] = nanosic_dev->client->addr;
+ memcpy(tmp_buf + 1, buf, len);
+
+ msg.addr = nanosic_dev->client->addr;
+ msg.flags = 0;
+ msg.len = len + 1;
+ msg.buf = tmp_buf;
+
+ ret = i2c_transfer(adap, &msg, 1);
+ if (ret < 0) {
+ printk("i2c_transfer write error\n");
+ len = -1;
+ }
+
+ return len;
+}
+
+static void nanosic_sync_caps_led(struct work_struct *work)
+{
+ struct nanosic_803_priv *nanosic_dev = container_of(work, struct nanosic_803_priv, led_work);
+ int i = 0, ret = 0;
+
+ dev_dbg(nanosic_dev->dev, "setting caps led: %d\n", nanosic_dev->caps_led_on);
+ char cmd[I2C_DATA_LENGTH_WRITE] = {
+ 0x32, 0x00, 0x4E, 0x31,
+ 0x80, 0x38, 0x26, 0x01, nanosic_dev->caps_led_on
+ };
+
+ for (i = 2; i < 9; i++) {
+ cmd[9] += cmd[i];
+ }
+
+ /* cal sum */
+ ret = nanosic_i2c_write(nanosic_dev, cmd, sizeof(cmd));
+ if (ret < 0) {
+ dev_err(nanosic_dev->dev, "could not set caps led");
+ }
+}
+
+static int nanosic_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+ struct nanosic_803_priv *nanosic_dev = input_get_drvdata(dev);
+ bool prev_value = nanosic_dev->caps_led_on;
+
+ dev_dbg(nanosic_dev->dev, "nanosic event type: %d, code: %u, value: %d\n", type, code, value);
+ if (!nanosic_dev)
+ return -EINVAL;
+
+ if (type == EV_LED) {
+ switch (code) {
+ case LED_CAPSL:
+ nanosic_dev->caps_led_on = value;
+ if (value != prev_value)
+ queue_work(nanosic_dev->wq, &nanosic_dev->led_work);
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int nanosic_register_keyboard(struct nanosic_803_priv *nanosic_dev)
+{
+ struct input_dev *keyboard_input_dev;
+ int ret;
+
+ // Allocating keyboard device
+ keyboard_input_dev = devm_input_allocate_device(nanosic_dev->dev);
+ if (!keyboard_input_dev) {
+ ret = -ENOMEM;
+ dev_err(nanosic_dev->dev, "could not allocate keyboard input device: %d\n", ret);
+ return ret;
+ }
+ keyboard_input_dev->name = "Nanosic 803 keyboard";
+ keyboard_input_dev->phys = "input/keyboard";
+ keyboard_input_dev->id.bustype = BUS_I2C;
+ keyboard_input_dev->id.vendor = 0x1234;
+ keyboard_input_dev->id.product = 0x5678;
+ keyboard_input_dev->id.version = 0x0100;
+
+ set_bit(EV_KEY, keyboard_input_dev->evbit);
+ set_bit(EV_REP, keyboard_input_dev->evbit);
+
+ keyboard_input_dev->evbit[0] |= BIT_MASK(EV_LED) | BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+ keyboard_input_dev->ledbit[0] = BIT_MASK(LED_CAPSL);
+ keyboard_input_dev->event = nanosic_event;
+
+ input_set_drvdata(keyboard_input_dev, nanosic_dev);
+
+ for (int i = 0; i < KEY_MAX; i++)
+ set_bit(i, keyboard_input_dev->keybit);
+
+ // Registering keyboard device
+ nanosic_dev->keyboard_input_dev = keyboard_input_dev;
+ ret = input_register_device(nanosic_dev->keyboard_input_dev);
+ if (ret)
+ dev_err(nanosic_dev->dev, "failed to register input device: %d\n", ret);
+ return ret;
+}
+
+static int nanosic_register_touchpad(struct nanosic_803_priv *nanosic_dev)
+{
+ struct input_dev *touchpad_input_dev;
+ int ret;
+ unsigned int touchpad_resolution_x, touchpad_resolution_y;
+
+ // Get touchpad resolution
+ if (of_property_read_u32(nanosic_dev->dev->of_node, "touchpad-resolution-x", &touchpad_resolution_x)) {
+ dev_err(nanosic_dev->dev, "Failed to read touchpad-resolution-x from DT\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(nanosic_dev->dev->of_node, "touchpad-resolution-y", &touchpad_resolution_y)) {
+ dev_err(nanosic_dev->dev, "Failed to read touchpad-resolution-y from DT\n");
+ return -EINVAL;
+ }
+
+ // Allocating touchpad device
+ touchpad_input_dev = devm_input_allocate_device(nanosic_dev->dev);
+ if (!touchpad_input_dev) {
+ pr_err("Failed to allocate touchpad input device\n");
+ return -ENOMEM;
+ }
+
+ touchpad_input_dev->name = "Nanosic 803 touchpad";
+ touchpad_input_dev->phys = "input/touchpad";
+ touchpad_input_dev->id.bustype = BUS_I2C;
+ touchpad_input_dev->id.vendor = 0x1234;
+ touchpad_input_dev->id.product = 0x5678;
+ touchpad_input_dev->id.version = 0x0001;
+
+ ret = input_mt_init_slots(touchpad_input_dev, 3, INPUT_MT_POINTER);
+ if (ret) {
+ dev_err(nanosic_dev->dev, "Failed to initialize MT slots: %d\n", ret);
+ return ret;
+ }
+
+ set_bit(INPUT_PROP_POINTER, touchpad_input_dev->propbit);
+ set_bit(EV_ABS, touchpad_input_dev->evbit);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_POSITION_X, 0, touchpad_resolution_x, 0, 0);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_POSITION_Y, 0, touchpad_resolution_y, 0, 0);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_TRACKING_ID, 0, 3 - 1, 0, 0);
+
+ set_bit(EV_KEY, touchpad_input_dev->evbit);
+ set_bit(BTN_LEFT, touchpad_input_dev->keybit);
+ set_bit(BTN_RIGHT, touchpad_input_dev->keybit);
+
+ input_set_drvdata(touchpad_input_dev, nanosic_dev);
+
+ // Registering touchpad device
+ nanosic_dev->touchpad_input_dev = touchpad_input_dev;
+ ret = input_register_device(nanosic_dev->touchpad_input_dev);
+ if (ret)
+ dev_err(nanosic_dev->dev, "failed to register input device: %d\n", ret);
+ return ret;
+}
+
+static void nanosic_update_tablet_mode(struct nanosic_803_priv *nanosic_dev)
+{
+ bool tablet_mode;
+
+ tablet_mode = !nanosic_dev->is_keyboard_connected ||
+ gpiod_get_value_cansleep(nanosic_dev->tablet_mode_gpio);
+ input_report_switch(nanosic_dev->tablet_mode_input_dev,
+ SW_TABLET_MODE, tablet_mode);
+ input_sync(nanosic_dev->tablet_mode_input_dev);
+}
+
+static irqreturn_t nanosic_tablet_mode_irq_thread(int irq, void *dev_id)
+{
+ struct nanosic_803_priv *nanosic_dev = dev_id;
+
+ nanosic_update_tablet_mode(nanosic_dev);
+ return IRQ_HANDLED;
+}
+
+static int nanosic_register_tablet_mode(struct nanosic_803_priv *nanosic_dev)
+{
+ struct input_dev *tablet_mode_input_dev;
+ int ret;
+
+ tablet_mode_input_dev = devm_input_allocate_device(nanosic_dev->dev);
+ if (!tablet_mode_input_dev)
+ return -ENOMEM;
+
+ tablet_mode_input_dev->name = "Nanosic 803 tablet mode";
+ tablet_mode_input_dev->phys = "input/tablet-mode";
+ tablet_mode_input_dev->id.bustype = BUS_I2C;
+ input_set_capability(tablet_mode_input_dev, EV_SW, SW_TABLET_MODE);
+
+ nanosic_dev->tablet_mode_input_dev = tablet_mode_input_dev;
+ ret = input_register_device(tablet_mode_input_dev);
+ if (ret)
+ return ret;
+
+ nanosic_update_tablet_mode(nanosic_dev);
+ return 0;
+}
+
+
+static void nanosic_handle_hall(struct nanosic_803_priv *nanosic_dev, char *buf)
+{
+ if (buf[5] == 0x38 && buf[6] == 0x80 && buf[7] == 0xa2) {
+ bool was_keyboard_connected = nanosic_dev->is_keyboard_connected;
+ bool was_touchpad_connected = nanosic_dev->is_touchpad_connected;
+
+ if (buf[12] == 0x23) {
+ dev_dbg(nanosic_dev->dev, "registering input devices\n");
+ nanosic_dev->is_keyboard_connected = true;
+ nanosic_dev->is_touchpad_connected = true;
+ if (nanosic_dev->is_keyboard_connected != was_keyboard_connected)
+ nanosic_register_keyboard(nanosic_dev);
+ if (nanosic_dev->is_touchpad_connected != was_touchpad_connected)
+ nanosic_register_touchpad(nanosic_dev);
+ nanosic_update_tablet_mode(nanosic_dev);
+ } else if (buf[12] == 0x0) {
+ dev_dbg(nanosic_dev->dev, "unregistering input devices\n");
+ nanosic_dev->is_keyboard_connected = false;
+ nanosic_dev->is_touchpad_connected = false;
+ if (nanosic_dev->is_keyboard_connected != was_keyboard_connected)
+ input_unregister_device(nanosic_dev->keyboard_input_dev);
+ if (nanosic_dev->is_touchpad_connected != was_touchpad_connected)
+ input_unregister_device(nanosic_dev->touchpad_input_dev);
+ nanosic_update_tablet_mode(nanosic_dev);
+ }
+ }
+}
+
+static void handle_modifiers(struct nanosic_803_priv *nanosic_dev, char modifiers)
+{
+ char last_modifiers = nanosic_dev->last_modifier_state;
+
+ for (int i = 0; i < 8; i++) {
+ uint8_t current_bit = (modifiers >> i) & 1;
+ uint8_t last_bit = (last_modifiers >> i) & 1;
+
+ if (current_bit && !last_bit) {
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_modifier_to_linux_keycode[i], 1);
+ dev_dbg(nanosic_dev->dev, "Modifier pressed: %d\n", hid_modifier_to_linux_keycode[i]);
+ } else if (!current_bit && last_bit) {
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_modifier_to_linux_keycode[i], 0);
+ dev_dbg(nanosic_dev->dev, "Modifier released: %d\n", hid_modifier_to_linux_keycode[i]);
+ }
+ }
+
+ input_sync(nanosic_dev->keyboard_input_dev);
+
+ nanosic_dev->last_modifier_state = modifiers;
+}
+
+static void nanosic_handle_keyboard(struct nanosic_803_priv *nanosic_dev, char *buf)
+{
+ int i, j;
+ int found;
+
+ handle_modifiers(nanosic_dev, buf[4]);
+
+ for (i = 0; i < 5 && buf[6+i] != 0x00; ++i) {
+ found = 0;
+ for (j = 0; j < 5 && nanosic_dev->last_pressed_key[j] != 0x00; ++j) {
+ if (buf[6+i] == nanosic_dev->last_pressed_key[j]) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ dev_dbg(nanosic_dev->dev, "Key pressed: 0x%02X\n", buf[6+i]);
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_to_linux_keycode[(unsigned char)buf[6+i]], 1);
+ input_sync(nanosic_dev->keyboard_input_dev);
+ }
+ }
+
+ for (i = 0; i < 5 && nanosic_dev->last_pressed_key[i] != 0x00; ++i) {
+ found = 0;
+ for (j = 0; j < 5 && buf[6+j] != 0x00; ++j) {
+ if (nanosic_dev->last_pressed_key[i] == buf[6+j]) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ dev_dbg(nanosic_dev->dev, "Key released: 0x%02X\n", nanosic_dev->last_pressed_key[i]);
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_to_linux_keycode[(unsigned char)nanosic_dev->last_pressed_key[i]], 0);
+ input_sync(nanosic_dev->keyboard_input_dev);
+ }
+ }
+ memcpy(nanosic_dev->last_pressed_key, &buf[6], sizeof(nanosic_dev->last_pressed_key));
+}
+
+static void nanosic_handle_fn_key(struct nanosic_803_priv *nanosic_dev, char *buf)
+{
+ if (buf[4] != nanosic_dev->last_fn_key) {
+ if (nanosic_dev->last_fn_key != 0x00) {
+ dev_dbg(nanosic_dev->dev, "Key released: 0x%02X\n", nanosic_dev->last_fn_key);
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_to_linux_keycode[(unsigned char)nanosic_dev->last_fn_key], 0);
+ input_sync(nanosic_dev->keyboard_input_dev);
+ }
+ if (buf[4] != 0x00) {
+ dev_dbg(nanosic_dev->dev, "Key pressed: 0x%02X\n", buf[4]);
+ input_report_key(nanosic_dev->keyboard_input_dev, hid_to_linux_keycode[(unsigned char)buf[4]], 1);
+ input_sync(nanosic_dev->keyboard_input_dev);
+ }
+ nanosic_dev->last_fn_key = buf[4];
+ }
+}
+
+static void nanosic_touch_timer_callback(struct timer_list *t)
+{
+ struct nanosic_803_priv *nanosic_dev = timer_container_of(nanosic_dev, t, finger_timer);
+
+ if (nanosic_dev->finger_down) {
+ for (int i = 0; i<3; i++) {
+ input_mt_slot(nanosic_dev->touchpad_input_dev, nanosic_dev->slot_mapping[i]);
+ input_mt_report_slot_state(nanosic_dev->touchpad_input_dev, MT_TOOL_FINGER, 0);
+ input_report_abs(nanosic_dev->touchpad_input_dev, ABS_MT_TRACKING_ID, -1);
+ input_sync(nanosic_dev->touchpad_input_dev);
+ }
+ nanosic_dev->finger_down = false;
+ }
+}
+
+static void nanosic_handle_touchpad_mt(struct nanosic_803_priv *nanosic_dev, char *buf)
+{
+ int finger_id, x, y;
+
+ for (int i = 0; i < 3; i++) {
+ int offset = 6 + (i * 6);
+ finger_id = buf[offset];
+ x = (uint16_t)(buf[offset + 1] | (buf[offset + 2] << 8));
+ y = (uint16_t)(buf[offset + 3] | (buf[offset + 4] << 8));
+
+ if (finger_id == 0 && x == 0 && y == 0) {
+ if (nanosic_dev->slot_mapping[i] != -1) {
+ input_mt_slot(nanosic_dev->touchpad_input_dev, nanosic_dev->slot_mapping[i]);
+ input_mt_report_slot_state(nanosic_dev->touchpad_input_dev, MT_TOOL_FINGER, 0);
+ nanosic_dev->slot_mapping[i] = -1;
+ }
+ continue;
+ }
+
+ int slot = nanosic_dev->slot_mapping[i];
+ if (slot == -1) {
+ for (int j = 0; j < 3; j++) {
+ if (nanosic_dev->slot_mapping[j] == -1) {
+ slot = j;
+ nanosic_dev->slot_mapping[i] = slot;
+ break;
+ }
+ }
+ if (slot == -1) {
+ dev_err(nanosic_dev->dev, "No free slots available for finger %d\n", finger_id);
+ continue;
+ }
+ }
+
+ input_mt_slot(nanosic_dev->touchpad_input_dev, slot);
+ input_mt_report_slot_state(nanosic_dev->touchpad_input_dev, MT_TOOL_FINGER, 1);
+ input_report_abs(nanosic_dev->touchpad_input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(nanosic_dev->touchpad_input_dev, ABS_MT_POSITION_Y, y);
+ }
+
+ input_report_key(nanosic_dev->touchpad_input_dev, BTN_LEFT, (uint16_t)buf[NANOSIC_CLICK_BYTE_OFFSET]);
+
+ input_mt_sync_frame(nanosic_dev->touchpad_input_dev);
+ input_sync(nanosic_dev->touchpad_input_dev);
+
+ nanosic_dev->finger_down = true;
+ nanosic_dev->last_touch_time = jiffies;
+ mod_timer(&nanosic_dev->finger_timer, jiffies + msecs_to_jiffies(TOUCH_TIMEOUT_MS));
+}
+
+static irqreturn_t nanosic_irq_handler(int irq, void *dev_id)
+{
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t nanosic_interrupt_thread_fn(int irq, void *dev_id)
+{
+ struct nanosic_803_priv *nanosic_dev = dev_id;
+ int ret;
+ char buf[I2C_DATA_LENGTH_READ] = {0};
+ char hex_dump[3 * I2C_DATA_LENGTH_READ + 1] = {0};
+
+ usleep_range(1000, 5000);
+
+ mutex_lock(&nanosic_dev->read_mutex);
+ ret = nanosic_i2c_read(nanosic_dev, buf, sizeof(buf));
+ mutex_unlock(&nanosic_dev->read_mutex);
+ if (ret == 0) {
+ dev_err(nanosic_dev->dev, "Failed to read data on interrupt: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (int i = 0; i < ret; i++) {
+ snprintf(&hex_dump[i * 3], 4, "%02x ", buf[i]);
+ }
+ dev_dbg(nanosic_dev->dev, "nanosic message: %s\n", hex_dump);
+
+ if (buf[0] != 0x57 || buf[2] == 0) {
+ dev_dbg(nanosic_dev->dev, "Malformed message\n");
+ /*
+ * Our hardware might randomly return no message/random data,
+ * so assume we handled IRQ correctly
+ */
+ return IRQ_HANDLED;
+ }
+
+ switch (buf[3]) {
+ case 0x05:
+ // Handle keyboard event
+ nanosic_handle_keyboard(nanosic_dev, buf);
+ break;
+ case 0x06:
+ // Handle function keys
+ nanosic_handle_fn_key(nanosic_dev, buf);
+ break;
+ case 0x19:
+ // Handle touchpad event
+ nanosic_handle_touchpad_mt(nanosic_dev, buf);
+ break;
+ case 0x23:
+ // Handle hall event
+ nanosic_handle_hall(nanosic_dev, buf);
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+static void nanosic_803_reset(struct nanosic_803_priv *nanosic_dev)
+{
+ gpiod_set_value(nanosic_dev->reset_gpio, 0);
+ gpiod_set_value(nanosic_dev->sleep_gpio, 0);
+ gpiod_set_value(nanosic_dev->vdd_gpio, 0);
+ msleep(100);
+ gpiod_set_value(nanosic_dev->vdd_gpio, 1);
+ msleep(2);
+ gpiod_set_value(nanosic_dev->reset_gpio, 1);
+ gpiod_set_value(nanosic_dev->sleep_gpio, 1);
+}
+
+static int nanosic_803_probe(struct i2c_client *client)
+{
+ struct nanosic_803_priv *nanosic_dev;
+ struct device *dev = &client->dev;
+ struct regmap *map;
+ unsigned int ret;
+
+ nanosic_dev = devm_kzalloc(dev, sizeof(*nanosic_dev), GFP_KERNEL);
+ if (!nanosic_dev)
+ return -ENOMEM;
+
+ nanosic_dev->dev = dev;
+ dev_set_drvdata(dev, nanosic_dev);
+
+ nanosic_dev->wq = create_singlethread_workqueue("nanosic_wq");
+ INIT_WORK(&nanosic_dev->led_work, nanosic_sync_caps_led);
+
+ // Get GPIOs
+ nanosic_dev->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(nanosic_dev->reset_gpio)) {
+ dev_err(dev, "Failed to get reset GPIO\n");
+ return PTR_ERR(nanosic_dev->reset_gpio);
+ }
+
+ nanosic_dev->sleep_gpio = devm_gpiod_get(dev, "sleep", GPIOD_OUT_HIGH);
+ if (IS_ERR(nanosic_dev->sleep_gpio)) {
+ dev_err(dev, "Failed to get sleep GPIO\n");
+ return PTR_ERR(nanosic_dev->sleep_gpio);
+ }
+
+ nanosic_dev->vdd_gpio = devm_gpiod_get(dev, "vdd", GPIOD_OUT_HIGH);
+ if (IS_ERR(nanosic_dev->vdd_gpio)) {
+ dev_err(dev, "Failed to get vdd GPIO\n");
+ return PTR_ERR(nanosic_dev->vdd_gpio);
+ }
+
+ nanosic_dev->irq_gpio = devm_gpiod_get(dev, "irq", GPIOD_IN);
+ if (IS_ERR(nanosic_dev->irq_gpio)) {
+ dev_err(dev, "Failed to get irq GPIO\n");
+ return PTR_ERR(nanosic_dev->vdd_gpio);
+ }
+
+ nanosic_dev->tablet_mode_gpio = devm_gpiod_get(dev, "tablet-mode",
+ GPIOD_IN);
+ if (IS_ERR(nanosic_dev->tablet_mode_gpio)) {
+ dev_err(dev, "Failed to get tablet mode GPIO\n");
+ return PTR_ERR(nanosic_dev->tablet_mode_gpio);
+ }
+
+ // Get regulators
+ nanosic_dev->vdd_1v8 = devm_regulator_get(dev, "vdd_1v8");
+ if (IS_ERR(nanosic_dev->vdd_1v8)) {
+ dev_err(dev, "Failed to get 1.8V regulator\n");
+ return PTR_ERR(nanosic_dev->vdd_1v8);
+ }
+
+ nanosic_dev->vdd_3v3 = devm_regulator_get(dev, "vdd_3v3");
+ if (IS_ERR(nanosic_dev->vdd_3v3)) {
+ dev_err(dev, "Failed to get 3.3V regulator\n");
+ return PTR_ERR(nanosic_dev->vdd_3v3);
+ }
+
+ // Enable regulators
+ ret = regulator_enable(nanosic_dev->vdd_1v8);
+ if (ret) {
+ dev_err(dev, "Failed to enable 1.8V regulator\n");
+ return ret;
+ }
+
+ ret = regulator_enable(nanosic_dev->vdd_3v3);
+ if (ret) {
+ dev_err(dev, "Failed to enable 3.3V regulator\n");
+ return ret;
+ }
+
+ // Reset the chip
+ nanosic_803_reset(nanosic_dev);
+
+ // Wake up the chip
+ nanosic_803_wakeup(nanosic_dev);
+
+ // Set up regmap
+ map = devm_regmap_init_i2c(client, &nanosic_803_regmap_config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ nanosic_dev->client = client;
+ nanosic_dev->regmap = map;
+
+ mutex_init(&nanosic_dev->read_mutex);
+
+ i2c_set_clientdata(client, nanosic_dev);
+
+ // Try to identify the chip
+ if(nanosic_803_read_version(nanosic_dev)) {
+ dev_err(dev, "Nanosic 803 not found\n");
+ return -ENODEV;
+ }
+
+ ret = nanosic_register_tablet_mode(nanosic_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register tablet mode switch\n");
+
+ nanosic_dev->tablet_mode_irq = gpiod_to_irq(nanosic_dev->tablet_mode_gpio);
+ if (nanosic_dev->tablet_mode_irq < 0)
+ return nanosic_dev->tablet_mode_irq;
+
+ ret = devm_request_threaded_irq(dev, nanosic_dev->tablet_mode_irq, NULL,
+ nanosic_tablet_mode_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "nanosic-tablet-mode", nanosic_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request tablet mode IRQ\n");
+
+ // Set up IRQ
+ nanosic_dev->irq_number = gpiod_to_irq(nanosic_dev->irq_gpio);
+ if (nanosic_dev->irq_number < 0) {
+ dev_err(nanosic_dev->dev, "Failed to get IRQ for GPIO %d\n", desc_to_gpio(nanosic_dev->irq_gpio));
+ return nanosic_dev->irq_number;
+ }
+
+ ret = request_threaded_irq(nanosic_dev->irq_number,
+ nanosic_irq_handler, nanosic_interrupt_thread_fn,
+ 0x6001, "nanosic_irq", nanosic_dev);
+
+ if (ret) {
+ dev_err(nanosic_dev->dev, "Failed to request IRQ %d: %d\n", nanosic_dev->irq_number, ret);
+ return ret;
+ }
+
+ // Set up touchpad in-activity tracking
+ for (int i = 0; i < 3; i++) {
+ nanosic_dev->slot_mapping[i] = -1;
+ }
+
+ timer_setup(&nanosic_dev->finger_timer, nanosic_touch_timer_callback, 0);
+ nanosic_dev->finger_down = false;
+ nanosic_dev->is_keyboard_connected = false;
+ nanosic_dev->is_touchpad_connected = false;
+ return 0;
+}
+
+static int nanosic_803_suspend(struct device *dev)
+{
+ struct nanosic_803_priv *nanosic_dev = dev_get_drvdata(dev);
+ int ret;
+
+ // Actually de-init device
+ gpiod_set_value(nanosic_dev->reset_gpio, 0);
+ gpiod_set_value(nanosic_dev->sleep_gpio, 0);
+ gpiod_set_value(nanosic_dev->vdd_gpio, 0);
+
+ // Turn the regulators off
+ ret = regulator_disable(nanosic_dev->vdd_1v8);
+ if (ret) {
+ dev_err(dev, "Failed to disable 1.8V regulator\n");
+ return ret;
+ }
+
+ ret = regulator_disable(nanosic_dev->vdd_3v3);
+ if (ret) {
+ dev_err(dev, "Failed to disable 3.3V regulator\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int nanosic_803_resume(struct device *dev)
+{
+ struct nanosic_803_priv *nanosic_dev = dev_get_drvdata(dev);
+ int ret;
+
+ // Turn the regulators on
+ ret = regulator_enable(nanosic_dev->vdd_1v8);
+ if (ret) {
+ dev_err(dev, "Failed to disable 1.8V regulator\n");
+ return ret;
+ }
+
+ ret = regulator_enable(nanosic_dev->vdd_3v3);
+ if (ret) {
+ dev_err(dev, "Failed to disable 3.3V regulator\n");
+ return ret;
+ }
+
+ // Reset the chip
+ nanosic_803_reset(nanosic_dev);
+
+ // Wake up the chip
+ nanosic_803_wakeup(nanosic_dev);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(nanosic_803_pm_ops, nanosic_803_suspend, nanosic_803_resume);
+
+static const struct of_device_id __maybe_unused nanosic_803_of_match[] = {
+ { .compatible = "nanosic,803", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, nanosic_803_of_match);
+
+static struct i2c_driver nanosic_803_driver = {
+ .driver = {
+ .name = "nanosic_803",
+ .of_match_table = of_match_ptr(nanosic_803_of_match),
+ .pm = pm_sleep_ptr(&nanosic_803_pm_ops),
+ },
+ .probe = nanosic_803_probe,
+};
+
+module_i2c_driver(nanosic_803_driver);
+
+MODULE_AUTHOR("Luka Panio <lukapanio@gmail.com>");
+MODULE_DESCRIPTION("Driver for nanosic 803 keyboard controller");
+MODULE_LICENSE("GPL v2");
--- a/drivers/input/keyboard/nanosic_803.c
+++ b/drivers/input/keyboard/nanosic_803.c
@@ -342,2 +342,11 @@
- for (int i = 0; i < KEY_MAX; i++)
- set_bit(i, keyboard_input_dev->keybit);
+ /* Only advertise the keycodes this driver can actually emit.
+ * Setting every KEY_ and BTN_ bit makes udev's input_id builtin tag the
+ * device as ID_INPUT_MOUSE and ID_INPUT_POINTINGSTICK in addition to
+ * ID_INPUT_KEYBOARD, which confuses userspace, e.g. unl0kr in the
+ * initramfs classifies the keyboard as a mouse and discards all
+ * printable key presses. */
+ for (int i = 0; i < ARRAY_SIZE(hid_to_linux_keycode); i++)
+ if (hid_to_linux_keycode[i])
+ set_bit(hid_to_linux_keycode[i], keyboard_input_dev->keybit);
+ for (int i = 0; i < ARRAY_SIZE(hid_modifier_to_linux_keycode); i++)
+ set_bit(hid_modifier_to_linux_keycode[i], keyboard_input_dev->keybit);