pinephone-keyboard/i2c-inputd.c
Ondrej Jirman 693cf5ae86 Fix issue with ppkb inputd not working after using the kernel driver
This was caused by kernel driver disabling the matrix scan in the KB
firmware and userspace tools not re-enabling it on startup. :)
2022-07-27 23:02:15 +02:00

403 lines
8.8 KiB
C

/*
* Pinephone keyboard userspace input device daemon.
*
* Copyright (C) 2021 Ondřej Jirman <megi@xff.cz>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// {{{ includes
#include "common.c"
#include "firmware/registers.h"
#include <linux/input.h>
#include <linux/uinput.h>
int read_kb(int fd, uint8_t data[16])
{
int ret;
uint8_t b = REG_KEYMATRIX_STATE_CRC8;
struct i2c_msg msgs[] = {
{ KB_ADDR, 0, 1, &b },
{ KB_ADDR, I2C_M_RD, REG_KEYMATRIX_STATE_END - REG_KEYMATRIX_STATE_CRC8 + 1, data },
};
struct i2c_rdwr_ioctl_data msg = {
.msgs = msgs,
.nmsgs = sizeof(msgs) / sizeof(msgs[0])
};
ret = ioctl(fd, I2C_RDWR, &msg);
if (ret < 0) {
printf("WARNING: I2C_RDWR failed (%d)\n", errno);
return -1;
}
uint8_t crc = crc8(data + 1, REG_KEYMATRIX_STATE_END - REG_KEYMATRIX_STATE_CRC8);
if (crc != data[0]) {
printf("WARNING: Key data CRC8 mismatch: ");
for (int i = 0; i < REG_KEYMATRIX_STATE_END - REG_KEYMATRIX_STATE_CRC8 + 1; i++)
printf("%02hhx", data[i]);
printf("\n");
return -2;
}
return ret == 2 ? 0 : -1;
}
int read_kb_reg(int fd, uint8_t reg, uint8_t* data)
{
int ret;
struct i2c_msg msgs[] = {
{ KB_ADDR, 0, 1, &reg },
{ KB_ADDR, I2C_M_RD, 1, data },
};
struct i2c_rdwr_ioctl_data msg = {
.msgs = msgs,
.nmsgs = sizeof(msgs) / sizeof(msgs[0])
};
ret = ioctl(fd, I2C_RDWR, &msg);
if (ret < 0) {
printf("WARNING: I2C_RDWR failed (%d)\n", errno);
return -1;
}
return ret == 2 ? 0 : -1;
}
int write_kb_reg(int fd, uint8_t reg, uint8_t data)
{
int ret;
uint8_t buf[2] = {reg, data};
struct i2c_msg msgs[] = {
{ KB_ADDR, 0, 2, buf },
};
struct i2c_rdwr_ioctl_data msg = {
.msgs = msgs,
.nmsgs = sizeof(msgs) / sizeof(msgs[0])
};
ret = ioctl(fd, I2C_RDWR, &msg);
syscall_error(ret < 0, "I2C_RDWR failed");
return ret == 1 ? 0 : -1;
}
#include "kmap.h"
int open_uinput_dev(void)
{
int fd, ret;
fd = open("/dev/uinput", O_WRONLY/* | O_NONBLOCK*/);
syscall_error(fd < 0, "open(/dev/uinput) failed");
struct uinput_setup setup = {
.name = "ppkbd",
.id = {
.bustype = BUS_USB,
.vendor = 0x1234,
.product = 0x5678,
},
};
ret = ioctl(fd, UI_SET_EVBIT, EV_KEY);
syscall_error(ret < 0, "UI_SET_EVBIT failed");
for (int i = 0; i < sizeof(used_keys) / sizeof(used_keys[0]); i++) {
ret = ioctl(fd, UI_SET_KEYBIT, used_keys[i]);
syscall_error(ret < 0, "UI_SET_KEYBIT failed");
}
ret = ioctl(fd, UI_DEV_SETUP, &setup);
syscall_error(ret < 0, "UI_DEV_SETUP failed");
ret = ioctl(fd, UI_DEV_CREATE);
syscall_error(ret < 0, "UI_DEV_CREATE failed");
//ioctl(fd, UI_DEV_DESTROY);
//close(fd);
return fd;
}
void emit_ev(int fd, int type, int code, int val)
{
struct input_event ev = {
.type = type,
.code = code,
.value = val,
};
ssize_t ret = write(fd, &ev, sizeof ev);
syscall_error(ret < 0, "write event failed");
}
void print_bitmap(uint8_t* map)
{
// printf("\033[H");
for (int r = 0; r < 6; r++) {
if (r == 0) {
printf(" C");
for (int c = 0; c < 12; c++)
printf("%-3d", c + 1);
printf("\n");
}
printf("R%d", r + 1);
for (int c = 0; c < 12; c++)
printf(" %s", map[c] & (1u << r) ? "X" : ".");
printf("\n");
}
}
int get_index(int* keys, int len, int key)
{
for (int i = 0; i < len; i++) {
if (keys[i] == key)
return i;
}
return -1;
}
int compact(int* keys, int len)
{
int ckeys[len];
int ci = 0;
memset(ckeys, 0, len * sizeof(int));
for (int i = 0; i < len; i++)
if (keys[i])
ckeys[ci++] = keys[i];
memcpy(keys, ckeys, sizeof ckeys);
return ci;
}
static int uinput_fd = -1;
static int pressed_keys[128]; // contains currently pressed phys_idxs in press order
static int pressed_count;
static int fn_mode = 0;
static int pine_mode = 0;
void on_press(uint8_t phys_idx)
{
int key = keymap_base[phys_idx][0];
printf("press %02hhx %s\n", phys_idx, key ? key_names[key] : "");
if (key == KEY_FN || key == KEY_LEFTMETA) {
return;
}
//XXX: make sure fn/pine is the last presssed key prior to this one?
int fn_idx = get_index(pressed_keys, pressed_count, 0x52);
int pine_idx = get_index(pressed_keys, pressed_count, 0x31);
if (key == KEY_ESC && (fn_mode || pine_mode)) {
fn_mode = pine_mode = 0;
return;
}
const int* keys = keymap_base[phys_idx];
if (fn_idx >= 0 || fn_mode) {
if (key == KEY_ESC) {
fn_mode = 1;
return;
}
keys = keymap_fn[phys_idx];
} else if (pine_idx >= 0 || pine_mode) {
if (key == KEY_ESC) {
pine_mode = 1;
return;
}
keys = keymap_pine[phys_idx];
}
if (!keys[0])
keys = keymap_base[phys_idx];
if (keys[0]) {
emit_ev(uinput_fd, EV_KEY, keys[0], 1);
emit_ev(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
if (keys[1]) {
emit_ev(uinput_fd, EV_KEY, keys[1], 1);
emit_ev(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
}
void on_release(uint8_t phys_idx)
{
int key = keymap_base[phys_idx][0];
printf("release %02hhx %s\n", phys_idx, key ? key_names[key] : "");
if (key == KEY_FN || key == KEY_LEFTMETA) {
return;
}
int fn_idx = get_index(pressed_keys, pressed_count, 0x52);
int pine_idx = get_index(pressed_keys, pressed_count, 0x31);
const int* keys = keymap_base[phys_idx];
if (fn_idx >= 0 || fn_mode) {
keys = keymap_fn[phys_idx];
} else if (pine_idx >= 0 || pine_mode) {
keys = keymap_pine[phys_idx];
}
if (!keys[0])
keys = keymap_base[phys_idx];
if (keys[0]) {
emit_ev(uinput_fd, EV_KEY, keys[0], 0);
emit_ev(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
if (keys[1]) {
emit_ev(uinput_fd, EV_KEY, keys[1], 0);
emit_ev(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
}
void update_keys(uint8_t* map)
{
// physical indices of pressed keys reported over I2C
int keys[128];
int n_keys = 0;
for (int c = 0; c < 12; c++) {
for (int r = 0; r < 6; r++) {
if (map[c] != 0xff && map[c] & (1u << r)) {
uint8_t el_idx = (r << 4) | c;
uint8_t phys_idx = el_phys_map[el_idx];
if (phys_idx != 0xff && n_keys < 128)
keys[n_keys++] = phys_idx;
}
}
}
// which pressed keys are no longer pressed?
for (int j = 0; j < pressed_count; j++) {
int key = pressed_keys[j];
int idx = get_index(keys, n_keys, key);
if (idx < 0) {
pressed_keys[j] = 0;
pressed_count = compact(pressed_keys, 128);
on_release(key);
}
}
// which new keys are pressed?
for (int i = 0; i < n_keys; i++) {
int key = keys[i];
// if the key was not pressed, handle a new press event
int pressed_idx = get_index(pressed_keys, pressed_count, key);
if (pressed_idx < 0 && pressed_count < 128) {
pressed_keys[pressed_count++] = key;
on_press(key);
}
}
}
int main(int ac, char* av[])
{
int fd, ret;
fd = pogo_i2c_open();
uinput_fd = open_uinput_dev();
int lfd = gpio_setup_pogo_int(GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP | /*GPIO_V2_LINE_FLAG_ACTIVE_HIGH |*/ GPIO_V2_LINE_FLAG_EDGE_FALLING);
struct pollfd fds[2] = {
{ .fd = lfd, .events = POLLIN, },
};
debug("\033[2J");
uint8_t val;
ret = read_kb_reg(fd, REG_SYS_CONFIG, &val);
syscall_error(ret < 0, "read_kb_reg(PPKB_SYS_CONFIG) failed");
val &= ~REG_SYS_CONFIG_SCAN_BLOCK;
ret = write_kb_reg(fd, REG_SYS_CONFIG, val);
syscall_error(ret < 0, "write_kb_reg(PPKB_SYS_CONFIG) failed");
// - we rely on POGO interrupt to get the key updates
// - if any key is pressed, we will in addition poll
// for the current key status every 200ms of no interrupt
// activity
while (1) {
ret = poll(fds, 1, pressed_count > 0 ? 200 : 10000);
syscall_error(ret < 0, "poll failed");
bool is_poll = false;
if (fds[0].revents & POLLIN) {
struct gpio_v2_line_event ev;
ssize_t len = read(lfd, &ev, sizeof ev);
syscall_error(len != sizeof ev, "Invalid event size");
printf("%"PRIu64": Interrupt received\n", time_abs());
} else if (ret == 0 && pressed_count > 0) {
printf("%"PRIu64": Poll\n", time_abs());
is_poll = true;
} else {
continue;
}
// read keyboard data
int retries_left = 3;
uint8_t buf[16];
while (retries_left--) {
ret = read_kb(fd, buf);
if (ret)
continue;
}
if (retries_left == 0 && ret) {
printf("%"PRIu64": WARNING: Failed to read keyboard data after 3 retries\n", time_abs());
}
if (ret == 0) {
if (is_poll && pressed_count == 0) {
printf("%"PRIu64": WARNING: Missed interrupt for key release\n", time_abs());
}
print_bitmap(buf + 1);
update_keys(buf + 1);
}
}
return 0;
}