mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 19:05:07 +00:00
329 lines
13 KiB
C++
329 lines
13 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include "cl32_v4.h"
|
|
#include "cl32_v4_keyboard.h"
|
|
#include <tactility/device.h>
|
|
#include <tactility/drivers/i2c_controller.h>
|
|
#include <tactility/drivers/keyboard.h>
|
|
#include <tactility/log.h>
|
|
#include <tactility/module.h>
|
|
|
|
#include <new>
|
|
|
|
extern Module cl32_v04_module;
|
|
|
|
constexpr auto* TAG = "cl32-v4-keyboard";
|
|
|
|
#define GET_CONFIG(device) (static_cast<const Cl32V4KeyboardConfig*>((device)->config))
|
|
|
|
// v4's core chip reports keyboard events on the same I2C address as the power-supply function
|
|
// (see cl32_v4_power.h) - CL-32/CL-32's CL32_core class talks to both over one address.
|
|
// Registers below come from that project's regMap.h (CL32_INTERRUPT/EVENT_COUNT/EVENT1).
|
|
constexpr uint8_t REG_INTERRUPT = 0x02; // bit0: keyboard event pending
|
|
constexpr uint8_t REG_EVENT_COUNT = 0x03;
|
|
constexpr uint8_t REG_EVENT1 = 0x04; // reading this register pops one event off the chip's FIFO
|
|
constexpr uint8_t INTERRUPT_BIT_KEYBOARD = 0x01;
|
|
|
|
// Upper bound on events drained per read_key() call, same purpose as tca8418.cpp's equivalent:
|
|
// caps the damage if the chip ever reports a count that never reaches 0.
|
|
constexpr uint8_t MAX_EVENTS_PER_DRAIN = 16;
|
|
|
|
// HID Keyboard/Keypad usage codes (USB HID Usage Tables page 0x07) this chip's matrix produces,
|
|
// named after CL-32/CL-32's CL32_core::_matrix keymap (CL32_core.cpp).
|
|
constexpr uint8_t KEY_FN = 0x65;
|
|
constexpr uint8_t KEY_FILE = 0x76;
|
|
constexpr uint8_t KEY_MENU = 0x74;
|
|
constexpr uint8_t KEY_ENTER = 0x28;
|
|
constexpr uint8_t KEY_ESCAPE = 0x29;
|
|
constexpr uint8_t KEY_BACKSPACE = 0x2A;
|
|
constexpr uint8_t KEY_TAB = 0x2B;
|
|
constexpr uint8_t KEY_SPACE = 0x2C;
|
|
constexpr uint8_t KEY_EQUALS = 0x2E;
|
|
constexpr uint8_t KEY_PERIOD = 0x37;
|
|
constexpr uint8_t KEY_ARROW_RIGHT = 0x4F;
|
|
constexpr uint8_t KEY_ARROW_LEFT = 0x50;
|
|
constexpr uint8_t KEY_ARROW_DOWN = 0x51;
|
|
constexpr uint8_t KEY_ARROW_UP = 0x52;
|
|
constexpr uint8_t KEY_YES = 0x77;
|
|
constexpr uint8_t KEY_NO = 0x78;
|
|
constexpr uint8_t KEYPAD_SLASH = 0x54;
|
|
constexpr uint8_t KEYPAD_ASTERISK = 0x55;
|
|
constexpr uint8_t KEYPAD_MINUS = 0x56;
|
|
constexpr uint8_t KEYPAD_PLUS = 0x57;
|
|
constexpr uint8_t KEYPAD_ENTER = 0x58;
|
|
constexpr uint8_t KEYPAD_1 = 0x59;
|
|
constexpr uint8_t KEYPAD_9 = 0x61;
|
|
constexpr uint8_t KEYPAD_0 = 0x62;
|
|
constexpr uint8_t KEYPAD_PERIOD = 0x63;
|
|
constexpr uint8_t KEY_SHIFT = 0x7F;
|
|
|
|
enum class ToggleState { Unpressed, OnePress, Locked };
|
|
|
|
static ToggleState next_toggle_state(ToggleState state) {
|
|
switch (state) {
|
|
case ToggleState::Unpressed: return ToggleState::OnePress;
|
|
case ToggleState::OnePress: return ToggleState::Locked;
|
|
case ToggleState::Locked: return ToggleState::Unpressed;
|
|
}
|
|
return ToggleState::Unpressed;
|
|
}
|
|
|
|
static const char* toggle_state_name(ToggleState state) {
|
|
switch (state) {
|
|
case ToggleState::Unpressed: return "unpressed";
|
|
case ToggleState::OnePress: return "one-press";
|
|
case ToggleState::Locked: return "locked";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
constexpr uint8_t PENDING_CAPACITY = 32;
|
|
|
|
struct Cl32V4KeyEvent {
|
|
uint32_t key;
|
|
bool pressed;
|
|
uint8_t hid_keycode;
|
|
uint8_t hid_modifier;
|
|
};
|
|
|
|
struct Cl32V4KeyboardInternal {
|
|
ToggleState shift_state = ToggleState::Unpressed;
|
|
ToggleState fn_state = ToggleState::Unpressed;
|
|
Cl32V4KeyEvent pending[PENDING_CAPACITY];
|
|
uint8_t pending_head = 0;
|
|
uint8_t pending_count = 0;
|
|
};
|
|
|
|
static void push_pending(Cl32V4KeyboardInternal* internal, uint32_t key, bool pressed, uint8_t hid_keycode, uint8_t hid_modifier) {
|
|
if (internal->pending_count >= PENDING_CAPACITY) {
|
|
LOG_W(TAG, "Pending event queue full, dropping event");
|
|
return;
|
|
}
|
|
uint8_t tail = (internal->pending_head + internal->pending_count) % PENDING_CAPACITY;
|
|
internal->pending[tail] = { key, pressed, hid_keycode, hid_modifier };
|
|
internal->pending_count++;
|
|
}
|
|
|
|
static bool pop_pending(Cl32V4KeyboardInternal* internal, Cl32V4KeyEvent* out_event) {
|
|
if (internal->pending_count == 0) {
|
|
return false;
|
|
}
|
|
*out_event = internal->pending[internal->pending_head];
|
|
internal->pending_head = (internal->pending_head + 1) % PENDING_CAPACITY;
|
|
internal->pending_count--;
|
|
return true;
|
|
}
|
|
|
|
// Maps a HID code to a Unicode codepoint, mirroring CL32_core::_matrix. Fn selects the symbol
|
|
// layer (that table's third column); Shift selects uppercase. Returns false for a code with no
|
|
// Unicode/LVGL representation (Page Up/Down) - callers report KeyboardKeyData::hid_keycode
|
|
// instead, same as tab5_keyboard.cpp's F1-F12 handling.
|
|
static bool translate_key(uint8_t code, bool shift, bool fn, uint32_t* out_key) {
|
|
if (code >= 0x04 && code <= 0x1D) {
|
|
if (fn) {
|
|
static constexpr uint32_t FN_SYMBOLS[26] = {
|
|
'|', '>', '~', ',', '@', '?', '\'', ':', '&', ';', '{', '}', ']', '[',
|
|
'(', ')', '!', 0x00A3 /* GBP sign */, '#', '$', '^', '<', '"', '\\', '%', 0,
|
|
};
|
|
uint32_t symbol = FN_SYMBOLS[code - 0x04];
|
|
if (symbol != 0) {
|
|
*out_key = symbol;
|
|
return true;
|
|
}
|
|
// 'z' has no symbol layer in the reference matrix - fall through to the plain letter.
|
|
}
|
|
uint32_t c = 'a' + (code - 0x04);
|
|
*out_key = shift ? (c - 0x20) : c;
|
|
return true;
|
|
}
|
|
|
|
if (fn) {
|
|
switch (code) {
|
|
case KEY_ARROW_RIGHT: *out_key = CODEPOINT_END; return true;
|
|
case KEY_ARROW_LEFT: *out_key = CODEPOINT_HOME; return true;
|
|
case KEY_ARROW_DOWN: return false; // Page Down - hid_keycode only
|
|
case KEY_ARROW_UP: return false; // Page Up - hid_keycode only
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
switch (code) {
|
|
case KEY_ENTER: *out_key = CODEPOINT_ENTER; return true;
|
|
case KEY_TAB: *out_key = CODEPOINT_TAB; return true;
|
|
case KEY_ESCAPE: *out_key = CODEPOINT_ESCAPE; return true;
|
|
case KEY_BACKSPACE: *out_key = CODEPOINT_BACKSPACE; return true;
|
|
case KEY_SPACE: *out_key = ' '; return true;
|
|
case KEY_EQUALS: *out_key = '='; return true;
|
|
case KEY_PERIOD: *out_key = '.'; return true;
|
|
case KEY_ARROW_RIGHT: *out_key = CODEPOINT_ARROW_RIGHT; return true;
|
|
case KEY_ARROW_LEFT: *out_key = CODEPOINT_ARROW_LEFT; return true;
|
|
case KEY_ARROW_DOWN: *out_key = CODEPOINT_ARROW_DOWN; return true;
|
|
case KEY_ARROW_UP: *out_key = CODEPOINT_ARROW_UP; return true;
|
|
case KEY_YES: *out_key = CODEPOINT_YES; return true;
|
|
case KEY_NO: *out_key = CODEPOINT_NO; return true;
|
|
case KEYPAD_SLASH: *out_key = '/'; return true;
|
|
case KEYPAD_ASTERISK: *out_key = '*'; return true;
|
|
case KEYPAD_MINUS: *out_key = '-'; return true;
|
|
case KEYPAD_PLUS: *out_key = '+'; return true;
|
|
case KEYPAD_ENTER: *out_key = CODEPOINT_ENTER; return true;
|
|
case KEYPAD_0: *out_key = '0'; return true;
|
|
case KEYPAD_PERIOD: *out_key = '.'; return true;
|
|
default:
|
|
if (code >= KEYPAD_1 && code <= KEYPAD_9) {
|
|
*out_key = static_cast<uint32_t>('1' + (code - KEYPAD_1));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void handle_key_down(Cl32V4KeyboardInternal* internal, uint8_t code) {
|
|
if (code == KEY_SHIFT) {
|
|
internal->shift_state = next_toggle_state(internal->shift_state);
|
|
LOG_D(TAG, "code=0x%02X shift -> %s", code, toggle_state_name(internal->shift_state));
|
|
return;
|
|
}
|
|
if (code == KEY_FN) {
|
|
internal->fn_state = next_toggle_state(internal->fn_state);
|
|
LOG_D(TAG, "code=0x%02X fn -> %s", code, toggle_state_name(internal->fn_state));
|
|
return;
|
|
}
|
|
|
|
const bool shift = internal->shift_state != ToggleState::Unpressed;
|
|
const uint8_t hid_modifier = shift ? 0x02 : 0x00; // bit1 = LeftShift
|
|
|
|
// No app-level menu system at the driver layer - surface the raw HID code only, same as
|
|
// tab5_keyboard.cpp's F1-F12 handling.
|
|
if (code == KEY_MENU || code == KEY_FILE) {
|
|
LOG_D(TAG, "code=0x%02X (menu/file) shift=%s fn=%s hid_modifier=0x%02X",
|
|
code, toggle_state_name(internal->shift_state), toggle_state_name(internal->fn_state), hid_modifier);
|
|
push_pending(internal, 0, true, code, hid_modifier);
|
|
push_pending(internal, 0, false, code, hid_modifier);
|
|
return;
|
|
}
|
|
|
|
const bool fn = internal->fn_state != ToggleState::Unpressed;
|
|
uint32_t key = 0;
|
|
const bool has_key = translate_key(code, shift, fn, &key);
|
|
|
|
const char printable = (has_key && key >= 0x20 && key < 0x7F) ? static_cast<char>(key) : '.';
|
|
LOG_D(TAG, "code=0x%02X key='%c' key_hex=0x%04X shift=%s fn=%s hid_modifier=0x%02X",
|
|
code, printable, static_cast<unsigned int>(has_key ? key : 0),
|
|
toggle_state_name(internal->shift_state), toggle_state_name(internal->fn_state), hid_modifier);
|
|
|
|
// LVGL only registers a key on a RELEASED->PRESSED edge, and this chip doesn't report
|
|
// reliable release events for character keys - queue an immediate press+release pair, same as
|
|
// tca8418.cpp.
|
|
push_pending(internal, has_key ? key : 0, true, code, hid_modifier);
|
|
push_pending(internal, has_key ? key : 0, false, code, hid_modifier);
|
|
|
|
if (internal->shift_state == ToggleState::OnePress) {
|
|
internal->shift_state = ToggleState::Unpressed;
|
|
}
|
|
if (internal->fn_state == ToggleState::OnePress) {
|
|
internal->fn_state = ToggleState::Unpressed;
|
|
}
|
|
}
|
|
|
|
static void drain_events(Device* i2c0, uint8_t address, Cl32V4KeyboardInternal* internal) {
|
|
uint8_t interrupt_status = 0;
|
|
if (i2c_controller_register8_get(i2c0, address, REG_INTERRUPT, &interrupt_status, CL32_V4_CORE_TIMEOUT) != ERROR_NONE) {
|
|
return;
|
|
}
|
|
if ((interrupt_status & INTERRUPT_BIT_KEYBOARD) == 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t count = 0;
|
|
if (i2c_controller_register8_get(i2c0, address, REG_EVENT_COUNT, &count, CL32_V4_CORE_TIMEOUT) != ERROR_NONE) {
|
|
return;
|
|
}
|
|
|
|
for (uint8_t drained = 0; drained < MAX_EVENTS_PER_DRAIN && count > 0; drained++) {
|
|
uint8_t raw = 0;
|
|
if (i2c_controller_register8_get(i2c0, address, REG_EVENT1, &raw, CL32_V4_CORE_TIMEOUT) != ERROR_NONE) {
|
|
break;
|
|
}
|
|
|
|
LOG_D(TAG, "raw event=0x%02X (%s)", raw, (raw & 0x80) != 0 ? "down" : "up");
|
|
|
|
if ((raw & 0x80) != 0) {
|
|
handle_key_down(internal, static_cast<uint8_t>(raw & 0x7F));
|
|
}
|
|
|
|
if (i2c_controller_register8_get(i2c0, address, REG_EVENT_COUNT, &count, CL32_V4_CORE_TIMEOUT) != ERROR_NONE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only the keyboard bit is ours to clear - the power-supply function on this same chip owns
|
|
// the other status bits in this register.
|
|
i2c_controller_register8_reset_bits(i2c0, address, REG_INTERRUPT, INTERRUPT_BIT_KEYBOARD, CL32_V4_CORE_TIMEOUT);
|
|
}
|
|
|
|
static error_t v4_read_key(Device* device, KeyboardKeyData* data) {
|
|
auto* internal = static_cast<Cl32V4KeyboardInternal*>(device_get_driver_data(device));
|
|
|
|
Cl32V4KeyEvent event;
|
|
if (internal->pending_count == 0) {
|
|
drain_events(device_get_parent(device), GET_CONFIG(device)->address, internal);
|
|
}
|
|
|
|
if (pop_pending(internal, &event)) {
|
|
data->key = event.key;
|
|
data->pressed = event.pressed;
|
|
data->continue_reading = internal->pending_count > 0;
|
|
data->ctrl = false;
|
|
data->alt = false;
|
|
data->hid_keycode = event.hid_keycode;
|
|
data->hid_modifier = event.hid_modifier;
|
|
} else {
|
|
data->key = 0;
|
|
data->pressed = false;
|
|
data->continue_reading = false;
|
|
data->ctrl = false;
|
|
data->alt = false;
|
|
data->hid_keycode = 0;
|
|
data->hid_modifier = 0;
|
|
}
|
|
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
static constexpr KeyboardApi cl32_v4_keyboard_api = {
|
|
.read_key = v4_read_key,
|
|
.get_backlight = nullptr,
|
|
.is_present = nullptr,
|
|
};
|
|
|
|
static error_t start(Device* device) {
|
|
if (device_get_type(device_get_parent(device)) != &I2C_CONTROLLER_TYPE) {
|
|
LOG_E(TAG, "Parent is not an I2C controller");
|
|
return ERROR_RESOURCE;
|
|
}
|
|
|
|
auto* internal = new(std::nothrow) Cl32V4KeyboardInternal();
|
|
if (internal == nullptr) {
|
|
return ERROR_OUT_OF_MEMORY;
|
|
}
|
|
device_set_driver_data(device, internal);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
static error_t stop(Device* device) {
|
|
auto* internal = static_cast<Cl32V4KeyboardInternal*>(device_get_driver_data(device));
|
|
delete internal;
|
|
device_set_driver_data(device, nullptr);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
Driver cl32_v4_keyboard_driver = {
|
|
.name = "cl32-v4-keyboard",
|
|
.compatible = (const char*[]) { "cl32-v4-keyboard", nullptr },
|
|
.start_device = start,
|
|
.stop_device = stop,
|
|
.probe = nullptr,
|
|
.api = &cl32_v4_keyboard_api,
|
|
.device_type = &KEYBOARD_TYPE,
|
|
.internal = nullptr,
|
|
};
|