162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * This is a driver for the keyboard, touchpad and USB port of the 462306a36Sopenharmony_ci * keyboard dock for the Asus TF103C 2-in-1 tablet. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This keyboard dock has its own I2C attached embedded controller 762306a36Sopenharmony_ci * and the keyboard and touchpad are also connected over I2C, 862306a36Sopenharmony_ci * instead of using the usual USB connection. This means that the 962306a36Sopenharmony_ci * keyboard dock requires this special driver to function. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com> 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/acpi.h> 1562306a36Sopenharmony_ci#include <linux/delay.h> 1662306a36Sopenharmony_ci#include <linux/dmi.h> 1762306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1862306a36Sopenharmony_ci#include <linux/gpio/machine.h> 1962306a36Sopenharmony_ci#include <linux/hid.h> 2062306a36Sopenharmony_ci#include <linux/i2c.h> 2162306a36Sopenharmony_ci#include <linux/input.h> 2262306a36Sopenharmony_ci#include <linux/irq.h> 2362306a36Sopenharmony_ci#include <linux/irqdomain.h> 2462306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 2562306a36Sopenharmony_ci#include <linux/moduleparam.h> 2662306a36Sopenharmony_ci#include <linux/module.h> 2762306a36Sopenharmony_ci#include <linux/pm.h> 2862306a36Sopenharmony_ci#include <linux/workqueue.h> 2962306a36Sopenharmony_ci#include <asm/unaligned.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic bool fnlock; 3262306a36Sopenharmony_cimodule_param(fnlock, bool, 0644); 3362306a36Sopenharmony_ciMODULE_PARM_DESC(fnlock, 3462306a36Sopenharmony_ci "By default the kbd toprow sends multimedia key presses. AltGr " 3562306a36Sopenharmony_ci "can be pressed to change this to F1-F12. Set this to 1 to " 3662306a36Sopenharmony_ci "change the default. Press AltGr + Esc to toggle at runtime."); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define TF103C_DOCK_DEV_NAME "NPCE69A:00" 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define TF103C_DOCK_HPD_DEBOUNCE msecs_to_jiffies(20) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/*** Touchpad I2C device defines ***/ 4362306a36Sopenharmony_ci#define TF103C_DOCK_TP_ADDR 0x15 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/*** Keyboard I2C device defines **A*/ 4662306a36Sopenharmony_ci#define TF103C_DOCK_KBD_ADDR 0x16 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#define TF103C_DOCK_KBD_DATA_REG 0x73 4962306a36Sopenharmony_ci#define TF103C_DOCK_KBD_DATA_MIN_LENGTH 4 5062306a36Sopenharmony_ci#define TF103C_DOCK_KBD_DATA_MAX_LENGTH 11 5162306a36Sopenharmony_ci#define TF103C_DOCK_KBD_DATA_MODIFIERS 3 5262306a36Sopenharmony_ci#define TF103C_DOCK_KBD_DATA_KEYS 5 5362306a36Sopenharmony_ci#define TF103C_DOCK_KBD_CMD_REG 0x75 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define TF103C_DOCK_KBD_CMD_ENABLE 0x0800 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/*** EC innterrupt data I2C device defines ***/ 5862306a36Sopenharmony_ci#define TF103C_DOCK_INTR_ADDR 0x19 5962306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA_REG 0x6a 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_OBF_MASK 0x01 6262306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_KEY_MASK 0x04 6362306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_KBC_MASK 0x08 6462306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_AUX_MASK 0x20 6562306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_SCI_MASK 0x40 6662306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_SMI_MASK 0x80 6762306a36Sopenharmony_ci/* Special values for the OOB data on kbd_client / tp_client */ 6862306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA1_OOB_VALUE 0xc1 6962306a36Sopenharmony_ci#define TF103C_DOCK_INTR_DATA2_OOB_VALUE 0x04 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci#define TF103C_DOCK_SMI_AC_EVENT 0x31 7262306a36Sopenharmony_ci#define TF103C_DOCK_SMI_HANDSHAKING 0x50 7362306a36Sopenharmony_ci#define TF103C_DOCK_SMI_EC_WAKEUP 0x53 7462306a36Sopenharmony_ci#define TF103C_DOCK_SMI_BOOTBLOCK_RESET 0x5e 7562306a36Sopenharmony_ci#define TF103C_DOCK_SMI_WATCHDOG_RESET 0x5f 7662306a36Sopenharmony_ci#define TF103C_DOCK_SMI_ADAPTER_CHANGE 0x60 7762306a36Sopenharmony_ci#define TF103C_DOCK_SMI_DOCK_INSERT 0x61 7862306a36Sopenharmony_ci#define TF103C_DOCK_SMI_DOCK_REMOVE 0x62 7962306a36Sopenharmony_ci#define TF103C_DOCK_SMI_PAD_BL_CHANGE 0x63 8062306a36Sopenharmony_ci#define TF103C_DOCK_SMI_HID_STATUS_CHANGED 0x64 8162306a36Sopenharmony_ci#define TF103C_DOCK_SMI_HID_WAKEUP 0x65 8262306a36Sopenharmony_ci#define TF103C_DOCK_SMI_S3 0x83 8362306a36Sopenharmony_ci#define TF103C_DOCK_SMI_S5 0x85 8462306a36Sopenharmony_ci#define TF103C_DOCK_SMI_NOTIFY_SHUTDOWN 0x90 8562306a36Sopenharmony_ci#define TF103C_DOCK_SMI_RESUME 0x91 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/*** EC (dockram) I2C device defines ***/ 8862306a36Sopenharmony_ci#define TF103C_DOCK_EC_ADDR 0x1b 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci#define TF103C_DOCK_EC_CMD_REG 0x0a 9162306a36Sopenharmony_ci#define TF103C_DOCK_EC_CMD_LEN 9 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cienum { 9462306a36Sopenharmony_ci TF103C_DOCK_FLAG_HID_OPEN, 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistruct tf103c_dock_data { 9862306a36Sopenharmony_ci struct delayed_work hpd_work; 9962306a36Sopenharmony_ci struct irq_chip tp_irqchip; 10062306a36Sopenharmony_ci struct irq_domain *tp_irq_domain; 10162306a36Sopenharmony_ci struct i2c_client *ec_client; 10262306a36Sopenharmony_ci struct i2c_client *intr_client; 10362306a36Sopenharmony_ci struct i2c_client *kbd_client; 10462306a36Sopenharmony_ci struct i2c_client *tp_client; 10562306a36Sopenharmony_ci struct gpio_desc *pwr_en; 10662306a36Sopenharmony_ci struct gpio_desc *irq_gpio; 10762306a36Sopenharmony_ci struct gpio_desc *hpd_gpio; 10862306a36Sopenharmony_ci struct input_dev *input; 10962306a36Sopenharmony_ci struct hid_device *hid; 11062306a36Sopenharmony_ci unsigned long flags; 11162306a36Sopenharmony_ci int board_rev; 11262306a36Sopenharmony_ci int irq; 11362306a36Sopenharmony_ci int hpd_irq; 11462306a36Sopenharmony_ci int tp_irq; 11562306a36Sopenharmony_ci int last_press_0x13; 11662306a36Sopenharmony_ci int last_press_0x14; 11762306a36Sopenharmony_ci bool enabled; 11862306a36Sopenharmony_ci bool tp_enabled; 11962306a36Sopenharmony_ci bool altgr_pressed; 12062306a36Sopenharmony_ci bool esc_pressed; 12162306a36Sopenharmony_ci bool filter_esc; 12262306a36Sopenharmony_ci u8 kbd_buf[TF103C_DOCK_KBD_DATA_MAX_LENGTH]; 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic struct gpiod_lookup_table tf103c_dock_gpios = { 12662306a36Sopenharmony_ci .dev_id = "i2c-" TF103C_DOCK_DEV_NAME, 12762306a36Sopenharmony_ci .table = { 12862306a36Sopenharmony_ci GPIO_LOOKUP("INT33FC:00", 55, "dock_pwr_en", GPIO_ACTIVE_HIGH), 12962306a36Sopenharmony_ci GPIO_LOOKUP("INT33FC:02", 1, "dock_irq", GPIO_ACTIVE_HIGH), 13062306a36Sopenharmony_ci GPIO_LOOKUP("INT33FC:02", 29, "dock_hpd", GPIO_ACTIVE_HIGH), 13162306a36Sopenharmony_ci GPIO_LOOKUP("gpio_crystalcove", 2, "board_rev", GPIO_ACTIVE_HIGH), 13262306a36Sopenharmony_ci {} 13362306a36Sopenharmony_ci }, 13462306a36Sopenharmony_ci}; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci/* Byte 0 is the length of the rest of the packet */ 13762306a36Sopenharmony_cistatic const u8 tf103c_dock_enable_cmd[9] = { 8, 0x20, 0, 0, 0, 0, 0x20, 0, 0 }; 13862306a36Sopenharmony_cistatic const u8 tf103c_dock_usb_enable_cmd[9] = { 8, 0, 0, 0, 0, 0, 0, 0x40, 0 }; 13962306a36Sopenharmony_cistatic const u8 tf103c_dock_suspend_cmd[9] = { 8, 0, 0x20, 0, 0, 0x22, 0, 0, 0 }; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci/*** keyboard related code ***/ 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic u8 tf103c_dock_kbd_hid_desc[] = { 14462306a36Sopenharmony_ci 0x05, 0x01, /* Usage Page (Desktop), */ 14562306a36Sopenharmony_ci 0x09, 0x06, /* Usage (Keyboard), */ 14662306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 14762306a36Sopenharmony_ci 0x85, 0x11, /* Report ID (17), */ 14862306a36Sopenharmony_ci 0x95, 0x08, /* Report Count (8), */ 14962306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 15062306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 15162306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 15262306a36Sopenharmony_ci 0x05, 0x07, /* Usage Page (Keyboard), */ 15362306a36Sopenharmony_ci 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ 15462306a36Sopenharmony_ci 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ 15562306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 15662306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1), */ 15762306a36Sopenharmony_ci 0x75, 0x08, /* Report Size (8), */ 15862306a36Sopenharmony_ci 0x81, 0x01, /* Input (Constant), */ 15962306a36Sopenharmony_ci 0x95, 0x06, /* Report Count (6), */ 16062306a36Sopenharmony_ci 0x75, 0x08, /* Report Size (8), */ 16162306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 16262306a36Sopenharmony_ci 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 16362306a36Sopenharmony_ci 0x05, 0x07, /* Usage Page (Keyboard), */ 16462306a36Sopenharmony_ci 0x19, 0x00, /* Usage Minimum (None), */ 16562306a36Sopenharmony_ci 0x2A, 0xFF, 0x00, /* Usage Maximum (FFh), */ 16662306a36Sopenharmony_ci 0x81, 0x00, /* Input, */ 16762306a36Sopenharmony_ci 0xC0 /* End Collection */ 16862306a36Sopenharmony_ci}; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic int tf103c_dock_kbd_read(struct tf103c_dock_data *dock) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci struct i2c_client *client = dock->kbd_client; 17362306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 17462306a36Sopenharmony_ci struct i2c_msg msgs[2]; 17562306a36Sopenharmony_ci u8 reg[2]; 17662306a36Sopenharmony_ci int ret; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci reg[0] = TF103C_DOCK_KBD_DATA_REG & 0xff; 17962306a36Sopenharmony_ci reg[1] = TF103C_DOCK_KBD_DATA_REG >> 8; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci msgs[0].addr = client->addr; 18262306a36Sopenharmony_ci msgs[0].flags = 0; 18362306a36Sopenharmony_ci msgs[0].len = sizeof(reg); 18462306a36Sopenharmony_ci msgs[0].buf = reg; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci msgs[1].addr = client->addr; 18762306a36Sopenharmony_ci msgs[1].flags = I2C_M_RD; 18862306a36Sopenharmony_ci msgs[1].len = TF103C_DOCK_KBD_DATA_MAX_LENGTH; 18962306a36Sopenharmony_ci msgs[1].buf = dock->kbd_buf; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); 19262306a36Sopenharmony_ci if (ret != ARRAY_SIZE(msgs)) { 19362306a36Sopenharmony_ci dev_err(dev, "error %d reading kbd data\n", ret); 19462306a36Sopenharmony_ci return -EIO; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void tf103c_dock_kbd_write(struct tf103c_dock_data *dock, u16 cmd) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 20362306a36Sopenharmony_ci u8 buf[4]; 20462306a36Sopenharmony_ci int ret; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci put_unaligned_le16(TF103C_DOCK_KBD_CMD_REG, &buf[0]); 20762306a36Sopenharmony_ci put_unaligned_le16(cmd, &buf[2]); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci ret = i2c_master_send(dock->kbd_client, buf, sizeof(buf)); 21062306a36Sopenharmony_ci if (ret != sizeof(buf)) 21162306a36Sopenharmony_ci dev_err(dev, "error %d writing kbd cmd\n", ret); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci/* HID ll_driver functions for forwarding input-reports from the kbd_client */ 21562306a36Sopenharmony_cistatic int tf103c_dock_hid_parse(struct hid_device *hid) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci return hid_parse_report(hid, tf103c_dock_kbd_hid_desc, 21862306a36Sopenharmony_ci sizeof(tf103c_dock_kbd_hid_desc)); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic int tf103c_dock_hid_start(struct hid_device *hid) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci return 0; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic void tf103c_dock_hid_stop(struct hid_device *hid) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci hid->claimed = 0; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic int tf103c_dock_hid_open(struct hid_device *hid) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct tf103c_dock_data *dock = hid->driver_data; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci set_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags); 23662306a36Sopenharmony_ci return 0; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic void tf103c_dock_hid_close(struct hid_device *hid) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct tf103c_dock_data *dock = hid->driver_data; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci clear_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci/* Mandatory, but not used */ 24762306a36Sopenharmony_cistatic int tf103c_dock_hid_raw_request(struct hid_device *hid, u8 reportnum, 24862306a36Sopenharmony_ci u8 *buf, size_t len, u8 rtype, int reqtype) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic const struct hid_ll_driver tf103c_dock_hid_ll_driver = { 25462306a36Sopenharmony_ci .parse = tf103c_dock_hid_parse, 25562306a36Sopenharmony_ci .start = tf103c_dock_hid_start, 25662306a36Sopenharmony_ci .stop = tf103c_dock_hid_stop, 25762306a36Sopenharmony_ci .open = tf103c_dock_hid_open, 25862306a36Sopenharmony_ci .close = tf103c_dock_hid_close, 25962306a36Sopenharmony_ci .raw_request = tf103c_dock_hid_raw_request, 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic const int tf103c_dock_toprow_codes[13][2] = { 26362306a36Sopenharmony_ci /* Normal, AltGr pressed */ 26462306a36Sopenharmony_ci { KEY_POWER, KEY_F1 }, 26562306a36Sopenharmony_ci { KEY_RFKILL, KEY_F2 }, 26662306a36Sopenharmony_ci { KEY_F21, KEY_F3 }, /* Touchpad toggle, userspace expects F21 */ 26762306a36Sopenharmony_ci { KEY_BRIGHTNESSDOWN, KEY_F4 }, 26862306a36Sopenharmony_ci { KEY_BRIGHTNESSUP, KEY_F5 }, 26962306a36Sopenharmony_ci { KEY_CAMERA, KEY_F6 }, 27062306a36Sopenharmony_ci { KEY_CONFIG, KEY_F7 }, 27162306a36Sopenharmony_ci { KEY_PREVIOUSSONG, KEY_F8 }, 27262306a36Sopenharmony_ci { KEY_PLAYPAUSE, KEY_F9 }, 27362306a36Sopenharmony_ci { KEY_NEXTSONG, KEY_F10 }, 27462306a36Sopenharmony_ci { KEY_MUTE, KEY_F11 }, 27562306a36Sopenharmony_ci { KEY_VOLUMEDOWN, KEY_F12 }, 27662306a36Sopenharmony_ci { KEY_VOLUMEUP, KEY_SYSRQ }, 27762306a36Sopenharmony_ci}; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic void tf103c_dock_report_toprow_kbd_hook(struct tf103c_dock_data *dock) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci u8 *esc, *buf = dock->kbd_buf; 28262306a36Sopenharmony_ci int size; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* 28562306a36Sopenharmony_ci * Stop AltGr reports from getting reported on the "Asus TF103C Dock 28662306a36Sopenharmony_ci * Keyboard" input_dev, since this gets used as "Fn" key for the toprow 28762306a36Sopenharmony_ci * keys. Instead we report this on the "Asus TF103C Dock Top Row Keys" 28862306a36Sopenharmony_ci * input_dev, when not used to modify the toprow keys. 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci dock->altgr_pressed = buf[TF103C_DOCK_KBD_DATA_MODIFIERS] & 0x40; 29162306a36Sopenharmony_ci buf[TF103C_DOCK_KBD_DATA_MODIFIERS] &= ~0x40; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci input_report_key(dock->input, KEY_RIGHTALT, dock->altgr_pressed); 29462306a36Sopenharmony_ci input_sync(dock->input); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* Toggle fnlock on AltGr + Esc press */ 29762306a36Sopenharmony_ci buf = buf + TF103C_DOCK_KBD_DATA_KEYS; 29862306a36Sopenharmony_ci size = TF103C_DOCK_KBD_DATA_MAX_LENGTH - TF103C_DOCK_KBD_DATA_KEYS; 29962306a36Sopenharmony_ci esc = memchr(buf, 0x29, size); 30062306a36Sopenharmony_ci if (!dock->esc_pressed && esc) { 30162306a36Sopenharmony_ci if (dock->altgr_pressed) { 30262306a36Sopenharmony_ci fnlock = !fnlock; 30362306a36Sopenharmony_ci dock->filter_esc = true; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci if (esc && dock->filter_esc) 30762306a36Sopenharmony_ci *esc = 0; 30862306a36Sopenharmony_ci else 30962306a36Sopenharmony_ci dock->filter_esc = false; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci dock->esc_pressed = esc != NULL; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic void tf103c_dock_toprow_press(struct tf103c_dock_data *dock, int key_code) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci /* 31762306a36Sopenharmony_ci * Release AltGr before reporting the toprow key, so that userspace 31862306a36Sopenharmony_ci * sees e.g. just KEY_SUSPEND and not AltGr + KEY_SUSPEND. 31962306a36Sopenharmony_ci */ 32062306a36Sopenharmony_ci if (dock->altgr_pressed) { 32162306a36Sopenharmony_ci input_report_key(dock->input, KEY_RIGHTALT, false); 32262306a36Sopenharmony_ci input_sync(dock->input); 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci input_report_key(dock->input, key_code, true); 32662306a36Sopenharmony_ci input_sync(dock->input); 32762306a36Sopenharmony_ci} 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_cistatic void tf103c_dock_toprow_release(struct tf103c_dock_data *dock, int key_code) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci input_report_key(dock->input, key_code, false); 33262306a36Sopenharmony_ci input_sync(dock->input); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (dock->altgr_pressed) { 33562306a36Sopenharmony_ci input_report_key(dock->input, KEY_RIGHTALT, true); 33662306a36Sopenharmony_ci input_sync(dock->input); 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic void tf103c_dock_toprow_event(struct tf103c_dock_data *dock, 34162306a36Sopenharmony_ci int toprow_index, int *last_press) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci int key_code, fn = dock->altgr_pressed ^ fnlock; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci if (last_press && *last_press) { 34662306a36Sopenharmony_ci tf103c_dock_toprow_release(dock, *last_press); 34762306a36Sopenharmony_ci *last_press = 0; 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci if (toprow_index < 0) 35162306a36Sopenharmony_ci return; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci key_code = tf103c_dock_toprow_codes[toprow_index][fn]; 35462306a36Sopenharmony_ci tf103c_dock_toprow_press(dock, key_code); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (last_press) 35762306a36Sopenharmony_ci *last_press = key_code; 35862306a36Sopenharmony_ci else 35962306a36Sopenharmony_ci tf103c_dock_toprow_release(dock, key_code); 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci/* 36362306a36Sopenharmony_ci * The keyboard sends what appears to be standard I2C-HID input-reports, 36462306a36Sopenharmony_ci * except that a 16 bit register address of where the I2C-HID format 36562306a36Sopenharmony_ci * input-reports are stored must be send before reading it in a single 36662306a36Sopenharmony_ci * (I2C repeated-start) I2C transaction. 36762306a36Sopenharmony_ci * 36862306a36Sopenharmony_ci * Its unknown how to get the HID descriptors but they are easy to reconstruct: 36962306a36Sopenharmony_ci * 37062306a36Sopenharmony_ci * Input report id 0x11 is 8 bytes long and contain standard USB HID intf-class, 37162306a36Sopenharmony_ci * Boot Interface Subclass reports. 37262306a36Sopenharmony_ci * Input report id 0x13 is 2 bytes long and sends Consumer Control events 37362306a36Sopenharmony_ci * Input report id 0x14 is 1 byte long and sends System Control events 37462306a36Sopenharmony_ci * 37562306a36Sopenharmony_ci * However the top row keys (where a normal keyboard has F1-F12 + Print-Screen) 37662306a36Sopenharmony_ci * are a mess, using a mix of the 0x13 and 0x14 input reports as well as EC SCI 37762306a36Sopenharmony_ci * events; and these need special handling to allow actually sending F1-F12, 37862306a36Sopenharmony_ci * since the Fn key on the keyboard only works on the cursor keys and the top 37962306a36Sopenharmony_ci * row keys always send their special "Multimedia hotkey" codes. 38062306a36Sopenharmony_ci * 38162306a36Sopenharmony_ci * So only forward the 0x11 reports to HID and handle the top-row keys here. 38262306a36Sopenharmony_ci */ 38362306a36Sopenharmony_cistatic void tf103c_dock_kbd_interrupt(struct tf103c_dock_data *dock) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 38662306a36Sopenharmony_ci u8 *buf = dock->kbd_buf; 38762306a36Sopenharmony_ci int size; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (tf103c_dock_kbd_read(dock)) 39062306a36Sopenharmony_ci return; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci size = buf[0] | buf[1] << 8; 39362306a36Sopenharmony_ci if (size < TF103C_DOCK_KBD_DATA_MIN_LENGTH || 39462306a36Sopenharmony_ci size > TF103C_DOCK_KBD_DATA_MAX_LENGTH) { 39562306a36Sopenharmony_ci dev_err(dev, "error reported kbd pkt size %d is out of range %d-%d\n", size, 39662306a36Sopenharmony_ci TF103C_DOCK_KBD_DATA_MIN_LENGTH, 39762306a36Sopenharmony_ci TF103C_DOCK_KBD_DATA_MAX_LENGTH); 39862306a36Sopenharmony_ci return; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci switch (buf[2]) { 40262306a36Sopenharmony_ci case 0x11: 40362306a36Sopenharmony_ci if (size != 11) 40462306a36Sopenharmony_ci break; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci tf103c_dock_report_toprow_kbd_hook(dock); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci if (test_bit(TF103C_DOCK_FLAG_HID_OPEN, &dock->flags)) 40962306a36Sopenharmony_ci hid_input_report(dock->hid, HID_INPUT_REPORT, buf + 2, size - 2, 1); 41062306a36Sopenharmony_ci return; 41162306a36Sopenharmony_ci case 0x13: 41262306a36Sopenharmony_ci if (size != 5) 41362306a36Sopenharmony_ci break; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci switch (buf[3] | buf[4] << 8) { 41662306a36Sopenharmony_ci case 0: 41762306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, -1, &dock->last_press_0x13); 41862306a36Sopenharmony_ci return; 41962306a36Sopenharmony_ci case 0x70: 42062306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 3, &dock->last_press_0x13); 42162306a36Sopenharmony_ci return; 42262306a36Sopenharmony_ci case 0x6f: 42362306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 4, &dock->last_press_0x13); 42462306a36Sopenharmony_ci return; 42562306a36Sopenharmony_ci case 0xb6: 42662306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 7, &dock->last_press_0x13); 42762306a36Sopenharmony_ci return; 42862306a36Sopenharmony_ci case 0xcd: 42962306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 8, &dock->last_press_0x13); 43062306a36Sopenharmony_ci return; 43162306a36Sopenharmony_ci case 0xb5: 43262306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 9, &dock->last_press_0x13); 43362306a36Sopenharmony_ci return; 43462306a36Sopenharmony_ci case 0xe2: 43562306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 10, &dock->last_press_0x13); 43662306a36Sopenharmony_ci return; 43762306a36Sopenharmony_ci case 0xea: 43862306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 11, &dock->last_press_0x13); 43962306a36Sopenharmony_ci return; 44062306a36Sopenharmony_ci case 0xe9: 44162306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 12, &dock->last_press_0x13); 44262306a36Sopenharmony_ci return; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci break; 44562306a36Sopenharmony_ci case 0x14: 44662306a36Sopenharmony_ci if (size != 4) 44762306a36Sopenharmony_ci break; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci switch (buf[3]) { 45062306a36Sopenharmony_ci case 0: 45162306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, -1, &dock->last_press_0x14); 45262306a36Sopenharmony_ci return; 45362306a36Sopenharmony_ci case 1: 45462306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 0, &dock->last_press_0x14); 45562306a36Sopenharmony_ci return; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci break; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci dev_warn(dev, "warning unknown kbd data: %*ph\n", size, buf); 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci/*** touchpad related code ***/ 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic const struct property_entry tf103c_dock_touchpad_props[] = { 46662306a36Sopenharmony_ci PROPERTY_ENTRY_BOOL("elan,clickpad"), 46762306a36Sopenharmony_ci { } 46862306a36Sopenharmony_ci}; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cistatic const struct software_node tf103c_dock_touchpad_sw_node = { 47162306a36Sopenharmony_ci .properties = tf103c_dock_touchpad_props, 47262306a36Sopenharmony_ci}; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci/* 47562306a36Sopenharmony_ci * tf103c_enable_touchpad() is only called from the threaded interrupt handler 47662306a36Sopenharmony_ci * and tf103c_disable_touchpad() is only called after the irq is disabled, 47762306a36Sopenharmony_ci * so no locking is necessary. 47862306a36Sopenharmony_ci */ 47962306a36Sopenharmony_cistatic void tf103c_dock_enable_touchpad(struct tf103c_dock_data *dock) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct i2c_board_info board_info = { }; 48262306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 48362306a36Sopenharmony_ci int ret; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (dock->tp_enabled) { 48662306a36Sopenharmony_ci /* Happens after resume, the tp needs to be reinitialized */ 48762306a36Sopenharmony_ci ret = device_reprobe(&dock->tp_client->dev); 48862306a36Sopenharmony_ci if (ret) 48962306a36Sopenharmony_ci dev_err_probe(dev, ret, "reprobing tp-client\n"); 49062306a36Sopenharmony_ci return; 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci strscpy(board_info.type, "elan_i2c", I2C_NAME_SIZE); 49462306a36Sopenharmony_ci board_info.addr = TF103C_DOCK_TP_ADDR; 49562306a36Sopenharmony_ci board_info.dev_name = TF103C_DOCK_DEV_NAME "-tp"; 49662306a36Sopenharmony_ci board_info.irq = dock->tp_irq; 49762306a36Sopenharmony_ci board_info.swnode = &tf103c_dock_touchpad_sw_node; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci dock->tp_client = i2c_new_client_device(dock->ec_client->adapter, &board_info); 50062306a36Sopenharmony_ci if (IS_ERR(dock->tp_client)) { 50162306a36Sopenharmony_ci dev_err(dev, "error %ld creating tp client\n", PTR_ERR(dock->tp_client)); 50262306a36Sopenharmony_ci return; 50362306a36Sopenharmony_ci } 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci dock->tp_enabled = true; 50662306a36Sopenharmony_ci} 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_cistatic void tf103c_dock_disable_touchpad(struct tf103c_dock_data *dock) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci if (!dock->tp_enabled) 51162306a36Sopenharmony_ci return; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci i2c_unregister_device(dock->tp_client); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci dock->tp_enabled = false; 51662306a36Sopenharmony_ci} 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci/*** interrupt handling code ***/ 51962306a36Sopenharmony_cistatic void tf103c_dock_ec_cmd(struct tf103c_dock_data *dock, const u8 *cmd) 52062306a36Sopenharmony_ci{ 52162306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 52262306a36Sopenharmony_ci int ret; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci ret = i2c_smbus_write_i2c_block_data(dock->ec_client, TF103C_DOCK_EC_CMD_REG, 52562306a36Sopenharmony_ci TF103C_DOCK_EC_CMD_LEN, cmd); 52662306a36Sopenharmony_ci if (ret) 52762306a36Sopenharmony_ci dev_err(dev, "error %d sending %*ph cmd\n", ret, 52862306a36Sopenharmony_ci TF103C_DOCK_EC_CMD_LEN, cmd); 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_cistatic void tf103c_dock_sci(struct tf103c_dock_data *dock, u8 val) 53262306a36Sopenharmony_ci{ 53362306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci switch (val) { 53662306a36Sopenharmony_ci case 2: 53762306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 1, NULL); 53862306a36Sopenharmony_ci return; 53962306a36Sopenharmony_ci case 4: 54062306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 2, NULL); 54162306a36Sopenharmony_ci return; 54262306a36Sopenharmony_ci case 8: 54362306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 5, NULL); 54462306a36Sopenharmony_ci return; 54562306a36Sopenharmony_ci case 17: 54662306a36Sopenharmony_ci tf103c_dock_toprow_event(dock, 6, NULL); 54762306a36Sopenharmony_ci return; 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci dev_warn(dev, "warning unknown SCI value: 0x%02x\n", val); 55162306a36Sopenharmony_ci} 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_cistatic void tf103c_dock_smi(struct tf103c_dock_data *dock, u8 val) 55462306a36Sopenharmony_ci{ 55562306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci switch (val) { 55862306a36Sopenharmony_ci case TF103C_DOCK_SMI_EC_WAKEUP: 55962306a36Sopenharmony_ci tf103c_dock_ec_cmd(dock, tf103c_dock_enable_cmd); 56062306a36Sopenharmony_ci tf103c_dock_ec_cmd(dock, tf103c_dock_usb_enable_cmd); 56162306a36Sopenharmony_ci tf103c_dock_kbd_write(dock, TF103C_DOCK_KBD_CMD_ENABLE); 56262306a36Sopenharmony_ci break; 56362306a36Sopenharmony_ci case TF103C_DOCK_SMI_PAD_BL_CHANGE: 56462306a36Sopenharmony_ci /* There is no backlight, but the EC still sends this */ 56562306a36Sopenharmony_ci break; 56662306a36Sopenharmony_ci case TF103C_DOCK_SMI_HID_STATUS_CHANGED: 56762306a36Sopenharmony_ci tf103c_dock_enable_touchpad(dock); 56862306a36Sopenharmony_ci break; 56962306a36Sopenharmony_ci default: 57062306a36Sopenharmony_ci dev_warn(dev, "warning unknown SMI value: 0x%02x\n", val); 57162306a36Sopenharmony_ci break; 57262306a36Sopenharmony_ci } 57362306a36Sopenharmony_ci} 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_cistatic irqreturn_t tf103c_dock_irq(int irq, void *data) 57662306a36Sopenharmony_ci{ 57762306a36Sopenharmony_ci struct tf103c_dock_data *dock = data; 57862306a36Sopenharmony_ci struct device *dev = &dock->ec_client->dev; 57962306a36Sopenharmony_ci u8 intr_data[8]; 58062306a36Sopenharmony_ci int ret; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci ret = i2c_smbus_read_i2c_block_data(dock->intr_client, TF103C_DOCK_INTR_DATA_REG, 58362306a36Sopenharmony_ci sizeof(intr_data), intr_data); 58462306a36Sopenharmony_ci if (ret != sizeof(intr_data)) { 58562306a36Sopenharmony_ci dev_err(dev, "error %d reading intr data\n", ret); 58662306a36Sopenharmony_ci return IRQ_NONE; 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci if (!(intr_data[1] & TF103C_DOCK_INTR_DATA1_OBF_MASK)) 59062306a36Sopenharmony_ci return IRQ_NONE; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci /* intr_data[0] is the length of the rest of the packet */ 59362306a36Sopenharmony_ci if (intr_data[0] == 3 && intr_data[1] == TF103C_DOCK_INTR_DATA1_OOB_VALUE && 59462306a36Sopenharmony_ci intr_data[2] == TF103C_DOCK_INTR_DATA2_OOB_VALUE) { 59562306a36Sopenharmony_ci /* intr_data[3] seems to contain a HID input report id */ 59662306a36Sopenharmony_ci switch (intr_data[3]) { 59762306a36Sopenharmony_ci case 0x01: 59862306a36Sopenharmony_ci handle_nested_irq(dock->tp_irq); 59962306a36Sopenharmony_ci break; 60062306a36Sopenharmony_ci case 0x11: 60162306a36Sopenharmony_ci case 0x13: 60262306a36Sopenharmony_ci case 0x14: 60362306a36Sopenharmony_ci tf103c_dock_kbd_interrupt(dock); 60462306a36Sopenharmony_ci break; 60562306a36Sopenharmony_ci default: 60662306a36Sopenharmony_ci dev_warn(dev, "warning unknown intr_data[3]: 0x%02x\n", intr_data[3]); 60762306a36Sopenharmony_ci break; 60862306a36Sopenharmony_ci } 60962306a36Sopenharmony_ci return IRQ_HANDLED; 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (intr_data[1] & TF103C_DOCK_INTR_DATA1_SCI_MASK) { 61362306a36Sopenharmony_ci tf103c_dock_sci(dock, intr_data[2]); 61462306a36Sopenharmony_ci return IRQ_HANDLED; 61562306a36Sopenharmony_ci } 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci if (intr_data[1] & TF103C_DOCK_INTR_DATA1_SMI_MASK) { 61862306a36Sopenharmony_ci tf103c_dock_smi(dock, intr_data[2]); 61962306a36Sopenharmony_ci return IRQ_HANDLED; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci dev_warn(dev, "warning unknown intr data: %*ph\n", 8, intr_data); 62362306a36Sopenharmony_ci return IRQ_NONE; 62462306a36Sopenharmony_ci} 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci/* 62762306a36Sopenharmony_ci * tf103c_dock_[dis|en]able only run from hpd_work or at times when 62862306a36Sopenharmony_ci * hpd_work cannot run (hpd_irq disabled), so no locking is necessary. 62962306a36Sopenharmony_ci */ 63062306a36Sopenharmony_cistatic void tf103c_dock_enable(struct tf103c_dock_data *dock) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci if (dock->enabled) 63362306a36Sopenharmony_ci return; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci if (dock->board_rev != 2) 63662306a36Sopenharmony_ci gpiod_set_value(dock->pwr_en, 1); 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci msleep(500); 63962306a36Sopenharmony_ci enable_irq(dock->irq); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci dock->enabled = true; 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic void tf103c_dock_disable(struct tf103c_dock_data *dock) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci if (!dock->enabled) 64762306a36Sopenharmony_ci return; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci disable_irq(dock->irq); 65062306a36Sopenharmony_ci tf103c_dock_disable_touchpad(dock); 65162306a36Sopenharmony_ci if (dock->board_rev != 2) 65262306a36Sopenharmony_ci gpiod_set_value(dock->pwr_en, 0); 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci dock->enabled = false; 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistatic void tf103c_dock_hpd_work(struct work_struct *work) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci struct tf103c_dock_data *dock = 66062306a36Sopenharmony_ci container_of(work, struct tf103c_dock_data, hpd_work.work); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci if (gpiod_get_value(dock->hpd_gpio)) 66362306a36Sopenharmony_ci tf103c_dock_enable(dock); 66462306a36Sopenharmony_ci else 66562306a36Sopenharmony_ci tf103c_dock_disable(dock); 66662306a36Sopenharmony_ci} 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_cistatic irqreturn_t tf103c_dock_hpd_irq(int irq, void *data) 66962306a36Sopenharmony_ci{ 67062306a36Sopenharmony_ci struct tf103c_dock_data *dock = data; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci mod_delayed_work(system_long_wq, &dock->hpd_work, TF103C_DOCK_HPD_DEBOUNCE); 67362306a36Sopenharmony_ci return IRQ_HANDLED; 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic void tf103c_dock_start_hpd(struct tf103c_dock_data *dock) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci enable_irq(dock->hpd_irq); 67962306a36Sopenharmony_ci /* Sync current HPD status */ 68062306a36Sopenharmony_ci queue_delayed_work(system_long_wq, &dock->hpd_work, TF103C_DOCK_HPD_DEBOUNCE); 68162306a36Sopenharmony_ci} 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_cistatic void tf103c_dock_stop_hpd(struct tf103c_dock_data *dock) 68462306a36Sopenharmony_ci{ 68562306a36Sopenharmony_ci disable_irq(dock->hpd_irq); 68662306a36Sopenharmony_ci cancel_delayed_work_sync(&dock->hpd_work); 68762306a36Sopenharmony_ci} 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci/*** probe ***/ 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_cistatic const struct dmi_system_id tf103c_dock_dmi_ids[] = { 69262306a36Sopenharmony_ci { 69362306a36Sopenharmony_ci .matches = { 69462306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 69562306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"), 69662306a36Sopenharmony_ci }, 69762306a36Sopenharmony_ci }, 69862306a36Sopenharmony_ci { } 69962306a36Sopenharmony_ci}; 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_cistatic void tf103c_dock_non_devm_cleanup(void *data) 70262306a36Sopenharmony_ci{ 70362306a36Sopenharmony_ci struct tf103c_dock_data *dock = data; 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci if (dock->tp_irq_domain) 70662306a36Sopenharmony_ci irq_domain_remove(dock->tp_irq_domain); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(dock->hid)) 70962306a36Sopenharmony_ci hid_destroy_device(dock->hid); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci i2c_unregister_device(dock->kbd_client); 71262306a36Sopenharmony_ci i2c_unregister_device(dock->intr_client); 71362306a36Sopenharmony_ci gpiod_remove_lookup_table(&tf103c_dock_gpios); 71462306a36Sopenharmony_ci} 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_cistatic int tf103c_dock_probe(struct i2c_client *client) 71762306a36Sopenharmony_ci{ 71862306a36Sopenharmony_ci struct i2c_board_info board_info = { }; 71962306a36Sopenharmony_ci struct device *dev = &client->dev; 72062306a36Sopenharmony_ci struct gpio_desc *board_rev_gpio; 72162306a36Sopenharmony_ci struct tf103c_dock_data *dock; 72262306a36Sopenharmony_ci enum gpiod_flags flags; 72362306a36Sopenharmony_ci int i, ret; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci /* GPIOs are hardcoded for the Asus TF103C, don't bind on other devs */ 72662306a36Sopenharmony_ci if (!dmi_check_system(tf103c_dock_dmi_ids)) 72762306a36Sopenharmony_ci return -ENODEV; 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci dock = devm_kzalloc(dev, sizeof(*dock), GFP_KERNEL); 73062306a36Sopenharmony_ci if (!dock) 73162306a36Sopenharmony_ci return -ENOMEM; 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci INIT_DELAYED_WORK(&dock->hpd_work, tf103c_dock_hpd_work); 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci /* 1. Get GPIOs and their IRQs */ 73662306a36Sopenharmony_ci gpiod_add_lookup_table(&tf103c_dock_gpios); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci ret = devm_add_action_or_reset(dev, tf103c_dock_non_devm_cleanup, dock); 73962306a36Sopenharmony_ci if (ret) 74062306a36Sopenharmony_ci return ret; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci /* 74362306a36Sopenharmony_ci * The pin is configured as input by default, use ASIS because otherwise 74462306a36Sopenharmony_ci * the gpio-crystalcove.c switches off the internal pull-down replacing 74562306a36Sopenharmony_ci * it with a pull-up. 74662306a36Sopenharmony_ci */ 74762306a36Sopenharmony_ci board_rev_gpio = gpiod_get(dev, "board_rev", GPIOD_ASIS); 74862306a36Sopenharmony_ci if (IS_ERR(board_rev_gpio)) 74962306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(board_rev_gpio), "requesting board_rev GPIO\n"); 75062306a36Sopenharmony_ci dock->board_rev = gpiod_get_value_cansleep(board_rev_gpio) + 1; 75162306a36Sopenharmony_ci gpiod_put(board_rev_gpio); 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci /* 75462306a36Sopenharmony_ci * The Android driver drives the dock-pwr-en pin high at probe for 75562306a36Sopenharmony_ci * revision 2 boards and then never touches it again? 75662306a36Sopenharmony_ci * This code has only been tested on a revision 1 board, so for now 75762306a36Sopenharmony_ci * just mimick what Android does on revision 2 boards. 75862306a36Sopenharmony_ci */ 75962306a36Sopenharmony_ci flags = (dock->board_rev == 2) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW; 76062306a36Sopenharmony_ci dock->pwr_en = devm_gpiod_get(dev, "dock_pwr_en", flags); 76162306a36Sopenharmony_ci if (IS_ERR(dock->pwr_en)) 76262306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->pwr_en), "requesting pwr_en GPIO\n"); 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci dock->irq_gpio = devm_gpiod_get(dev, "dock_irq", GPIOD_IN); 76562306a36Sopenharmony_ci if (IS_ERR(dock->irq_gpio)) 76662306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->irq_gpio), "requesting IRQ GPIO\n"); 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci dock->irq = gpiod_to_irq(dock->irq_gpio); 76962306a36Sopenharmony_ci if (dock->irq < 0) 77062306a36Sopenharmony_ci return dev_err_probe(dev, dock->irq, "getting dock IRQ"); 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, dock->irq, NULL, tf103c_dock_irq, 77362306a36Sopenharmony_ci IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN, 77462306a36Sopenharmony_ci "dock_irq", dock); 77562306a36Sopenharmony_ci if (ret) 77662306a36Sopenharmony_ci return dev_err_probe(dev, ret, "requesting dock IRQ"); 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci dock->hpd_gpio = devm_gpiod_get(dev, "dock_hpd", GPIOD_IN); 77962306a36Sopenharmony_ci if (IS_ERR(dock->hpd_gpio)) 78062306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->hpd_gpio), "requesting HPD GPIO\n"); 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci dock->hpd_irq = gpiod_to_irq(dock->hpd_gpio); 78362306a36Sopenharmony_ci if (dock->hpd_irq < 0) 78462306a36Sopenharmony_ci return dev_err_probe(dev, dock->hpd_irq, "getting HPD IRQ"); 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci ret = devm_request_irq(dev, dock->hpd_irq, tf103c_dock_hpd_irq, 78762306a36Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, 78862306a36Sopenharmony_ci "dock_hpd", dock); 78962306a36Sopenharmony_ci if (ret) 79062306a36Sopenharmony_ci return ret; 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci /* 79362306a36Sopenharmony_ci * 2. Create I2C clients. The dock uses 4 different i2c addresses, 79462306a36Sopenharmony_ci * the ACPI NPCE69A node being probed points to the EC address. 79562306a36Sopenharmony_ci */ 79662306a36Sopenharmony_ci dock->ec_client = client; 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci strscpy(board_info.type, "tf103c-dock-intr", I2C_NAME_SIZE); 79962306a36Sopenharmony_ci board_info.addr = TF103C_DOCK_INTR_ADDR; 80062306a36Sopenharmony_ci board_info.dev_name = TF103C_DOCK_DEV_NAME "-intr"; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci dock->intr_client = i2c_new_client_device(client->adapter, &board_info); 80362306a36Sopenharmony_ci if (IS_ERR(dock->intr_client)) 80462306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->intr_client), "creating intr client\n"); 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci strscpy(board_info.type, "tf103c-dock-kbd", I2C_NAME_SIZE); 80762306a36Sopenharmony_ci board_info.addr = TF103C_DOCK_KBD_ADDR; 80862306a36Sopenharmony_ci board_info.dev_name = TF103C_DOCK_DEV_NAME "-kbd"; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci dock->kbd_client = i2c_new_client_device(client->adapter, &board_info); 81162306a36Sopenharmony_ci if (IS_ERR(dock->kbd_client)) 81262306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->kbd_client), "creating kbd client\n"); 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci /* 3. Create input_dev for the top row of the keyboard */ 81562306a36Sopenharmony_ci dock->input = devm_input_allocate_device(dev); 81662306a36Sopenharmony_ci if (!dock->input) 81762306a36Sopenharmony_ci return -ENOMEM; 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci dock->input->name = "Asus TF103C Dock Top Row Keys"; 82062306a36Sopenharmony_ci dock->input->phys = dev_name(dev); 82162306a36Sopenharmony_ci dock->input->dev.parent = dev; 82262306a36Sopenharmony_ci dock->input->id.bustype = BUS_I2C; 82362306a36Sopenharmony_ci dock->input->id.vendor = /* USB_VENDOR_ID_ASUSTEK */ 82462306a36Sopenharmony_ci dock->input->id.product = /* From TF-103-C */ 82562306a36Sopenharmony_ci dock->input->id.version = 0x0100; /* 1.0 */ 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(tf103c_dock_toprow_codes); i++) { 82862306a36Sopenharmony_ci input_set_capability(dock->input, EV_KEY, tf103c_dock_toprow_codes[i][0]); 82962306a36Sopenharmony_ci input_set_capability(dock->input, EV_KEY, tf103c_dock_toprow_codes[i][1]); 83062306a36Sopenharmony_ci } 83162306a36Sopenharmony_ci input_set_capability(dock->input, EV_KEY, KEY_RIGHTALT); 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci ret = input_register_device(dock->input); 83462306a36Sopenharmony_ci if (ret) 83562306a36Sopenharmony_ci return ret; 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci /* 4. Create HID device for the keyboard */ 83862306a36Sopenharmony_ci dock->hid = hid_allocate_device(); 83962306a36Sopenharmony_ci if (IS_ERR(dock->hid)) 84062306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(dock->hid), "allocating hid dev\n"); 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci dock->hid->driver_data = dock; 84362306a36Sopenharmony_ci dock->hid->ll_driver = &tf103c_dock_hid_ll_driver; 84462306a36Sopenharmony_ci dock->hid->dev.parent = &client->dev; 84562306a36Sopenharmony_ci dock->hid->bus = BUS_I2C; 84662306a36Sopenharmony_ci dock->hid->vendor = 0x0b05; /* USB_VENDOR_ID_ASUSTEK */ 84762306a36Sopenharmony_ci dock->hid->product = 0x0103; /* From TF-103-C */ 84862306a36Sopenharmony_ci dock->hid->version = 0x0100; /* 1.0 */ 84962306a36Sopenharmony_ci strscpy(dock->hid->name, "Asus TF103C Dock Keyboard", sizeof(dock->hid->name)); 85062306a36Sopenharmony_ci strscpy(dock->hid->phys, dev_name(dev), sizeof(dock->hid->phys)); 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci ret = hid_add_device(dock->hid); 85362306a36Sopenharmony_ci if (ret) 85462306a36Sopenharmony_ci return dev_err_probe(dev, ret, "adding hid dev\n"); 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci /* 5. Setup irqchip for touchpad IRQ pass-through */ 85762306a36Sopenharmony_ci dock->tp_irqchip.name = KBUILD_MODNAME; 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci dock->tp_irq_domain = irq_domain_add_linear(NULL, 1, &irq_domain_simple_ops, NULL); 86062306a36Sopenharmony_ci if (!dock->tp_irq_domain) 86162306a36Sopenharmony_ci return -ENOMEM; 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci dock->tp_irq = irq_create_mapping(dock->tp_irq_domain, 0); 86462306a36Sopenharmony_ci if (!dock->tp_irq) 86562306a36Sopenharmony_ci return -ENOMEM; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci irq_set_chip_data(dock->tp_irq, dock); 86862306a36Sopenharmony_ci irq_set_chip_and_handler(dock->tp_irq, &dock->tp_irqchip, handle_simple_irq); 86962306a36Sopenharmony_ci irq_set_nested_thread(dock->tp_irq, true); 87062306a36Sopenharmony_ci irq_set_noprobe(dock->tp_irq); 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci dev_info(dev, "Asus TF103C board-revision: %d\n", dock->board_rev); 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci tf103c_dock_start_hpd(dock); 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci device_init_wakeup(dev, true); 87762306a36Sopenharmony_ci i2c_set_clientdata(client, dock); 87862306a36Sopenharmony_ci return 0; 87962306a36Sopenharmony_ci} 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_cistatic void tf103c_dock_remove(struct i2c_client *client) 88262306a36Sopenharmony_ci{ 88362306a36Sopenharmony_ci struct tf103c_dock_data *dock = i2c_get_clientdata(client); 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci tf103c_dock_stop_hpd(dock); 88662306a36Sopenharmony_ci tf103c_dock_disable(dock); 88762306a36Sopenharmony_ci} 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_cistatic int __maybe_unused tf103c_dock_suspend(struct device *dev) 89062306a36Sopenharmony_ci{ 89162306a36Sopenharmony_ci struct tf103c_dock_data *dock = dev_get_drvdata(dev); 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci tf103c_dock_stop_hpd(dock); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci if (dock->enabled) { 89662306a36Sopenharmony_ci tf103c_dock_ec_cmd(dock, tf103c_dock_suspend_cmd); 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci if (device_may_wakeup(dev)) 89962306a36Sopenharmony_ci enable_irq_wake(dock->irq); 90062306a36Sopenharmony_ci } 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_ci return 0; 90362306a36Sopenharmony_ci} 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_cistatic int __maybe_unused tf103c_dock_resume(struct device *dev) 90662306a36Sopenharmony_ci{ 90762306a36Sopenharmony_ci struct tf103c_dock_data *dock = dev_get_drvdata(dev); 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci if (dock->enabled) { 91062306a36Sopenharmony_ci if (device_may_wakeup(dev)) 91162306a36Sopenharmony_ci disable_irq_wake(dock->irq); 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci /* Don't try to resume if the dock was unplugged during suspend */ 91462306a36Sopenharmony_ci if (gpiod_get_value(dock->hpd_gpio)) 91562306a36Sopenharmony_ci tf103c_dock_ec_cmd(dock, tf103c_dock_enable_cmd); 91662306a36Sopenharmony_ci } 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci tf103c_dock_start_hpd(dock); 91962306a36Sopenharmony_ci return 0; 92062306a36Sopenharmony_ci} 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(tf103c_dock_pm_ops, tf103c_dock_suspend, tf103c_dock_resume); 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_cistatic const struct acpi_device_id tf103c_dock_acpi_match[] = { 92562306a36Sopenharmony_ci {"NPCE69A"}, 92662306a36Sopenharmony_ci { } 92762306a36Sopenharmony_ci}; 92862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, tf103c_dock_acpi_match); 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_cistatic struct i2c_driver tf103c_dock_driver = { 93162306a36Sopenharmony_ci .driver = { 93262306a36Sopenharmony_ci .name = "asus-tf103c-dock", 93362306a36Sopenharmony_ci .pm = &tf103c_dock_pm_ops, 93462306a36Sopenharmony_ci .acpi_match_table = tf103c_dock_acpi_match, 93562306a36Sopenharmony_ci }, 93662306a36Sopenharmony_ci .probe = tf103c_dock_probe, 93762306a36Sopenharmony_ci .remove = tf103c_dock_remove, 93862306a36Sopenharmony_ci}; 93962306a36Sopenharmony_cimodule_i2c_driver(tf103c_dock_driver); 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); 94262306a36Sopenharmony_ciMODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver"); 94362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 944