From c9791af550ff150d337d5e76c7753b691ee2bed4 Mon Sep 17 00:00:00 2001 From: arturo182 Date: Wed, 3 Nov 2021 19:26:43 +0100 Subject: [PATCH] Initial import --- .gitignore | 2 + .gitmodules | 6 + 3rdparty/pico-sdk | 1 + CMakeLists.txt | 20 ++ README.md | 7 + app/CMakeLists.txt | 30 +++ app/app_config.h | 6 + app/backlight.c | 22 +++ app/backlight.h | 6 + app/debug.c | 118 ++++++++++++ app/debug.h | 3 + app/fifo.c | 60 ++++++ app/fifo.h | 15 ++ app/gpioexp.c | 279 +++++++++++++++++++++++++++ app/gpioexp.h | 20 ++ app/interrupt.c | 106 ++++++++++ app/interrupt.h | 3 + app/keyboard.c | 385 +++++++++++++++++++++++++++++++++++++ app/keyboard.h | 53 +++++ app/main.c | 62 ++++++ app/puppet_i2c.c | 210 ++++++++++++++++++++ app/puppet_i2c.h | 5 + app/reg.c | 73 +++++++ app/reg.h | 76 ++++++++ app/touchpad.c | 128 ++++++++++++ app/touchpad.h | 16 ++ app/tusb_config.h | 31 +++ app/usb.c | 147 ++++++++++++++ app/usb.h | 7 + app/usb_descriptors.c | 117 +++++++++++ boards/bbq20kbd_breakout.h | 54 ++++++ 31 files changed, 2068 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 160000 3rdparty/pico-sdk create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 app/CMakeLists.txt create mode 100644 app/app_config.h create mode 100644 app/backlight.c create mode 100644 app/backlight.h create mode 100644 app/debug.c create mode 100644 app/debug.h create mode 100644 app/fifo.c create mode 100644 app/fifo.h create mode 100644 app/gpioexp.c create mode 100644 app/gpioexp.h create mode 100644 app/interrupt.c create mode 100644 app/interrupt.h create mode 100644 app/keyboard.c create mode 100644 app/keyboard.h create mode 100644 app/main.c create mode 100644 app/puppet_i2c.c create mode 100644 app/puppet_i2c.h create mode 100644 app/reg.c create mode 100644 app/reg.h create mode 100644 app/touchpad.c create mode 100644 app/touchpad.h create mode 100644 app/tusb_config.h create mode 100644 app/usb.c create mode 100644 app/usb.h create mode 100644 app/usb_descriptors.c create mode 100644 boards/bbq20kbd_breakout.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83bacb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*.txt.user diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7d01eca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "3rdparty/pico-sdk"] + path = 3rdparty/pico-sdk + url = https://github.com/raspberrypi/pico-sdk.git +[submodule "pico-sdk"] + path = pico-sdk + url = https://github.com/raspberrypi/pico-sdk.git diff --git a/3rdparty/pico-sdk b/3rdparty/pico-sdk new file mode 160000 index 0000000..bfcbefa --- /dev/null +++ b/3rdparty/pico-sdk @@ -0,0 +1 @@ +Subproject commit bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5c3452d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.13) + +set(PICO_PLATFORM "rp2040") +set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR}/boards) + +include(3rdparty/pico-sdk/pico_sdk_init.cmake) + +project(i2c_puppet) + +pico_sdk_init() + +add_subdirectory(app) + +# binary info in flash +pico_set_program_name(i2c_puppet "I2C Puppet") +pico_set_program_version(i2c_puppet "0.1") + +# printf targets +#pico_enable_stdio_usb(i2c_puppet 1) +pico_enable_stdio_uart(i2c_puppet 1) diff --git a/README.md b/README.md new file mode 100644 index 0000000..21502c8 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# I2C Puppet + + git clone https://github.com/solderparty/i2c_puppet + cd i2c_puppet + git submodule update --init + cd 3rdparty/pico-sdk + git submodule update --init diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..cbc7f53 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,30 @@ +add_executable(i2c_puppet + backlight.c + debug.c + fifo.c + gpioexp.c + puppet_i2c.c + interrupt.c + keyboard.c + main.c + reg.c + touchpad.c + usb.c + usb_descriptors.c +) + +add_compile_options(-Wall -Wextra -Wpedantic) + +target_include_directories(i2c_puppet PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(i2c_puppet + cmsis_core + hardware_i2c + hardware_pwm + pico_bootsel_via_double_reset + pico_stdlib + tinyusb_device +) + +# create map/bin/hex/uf2 file in addition to elf +pico_add_extra_outputs(i2c_puppet) diff --git a/app/app_config.h b/app/app_config.h new file mode 100644 index 0000000..bdd0f79 --- /dev/null +++ b/app/app_config.h @@ -0,0 +1,6 @@ +#pragma once + +#define VERSION_MAJOR 1 +#define VERSION_MINOR 0 + +#define KEY_FIFO_SIZE 31 // number of keys in the public FIFO diff --git a/app/backlight.c b/app/backlight.c new file mode 100644 index 0000000..f56e1d0 --- /dev/null +++ b/app/backlight.c @@ -0,0 +1,22 @@ +#include "backlight.h" +#include "reg.h" + +#include +#include + +void backlight_sync(void) +{ + pwm_set_gpio_level(PIN_BKL, reg_get_value(REG_ID_BKL) * 0x80); +} + +void backlight_init(void) +{ + gpio_set_function(PIN_BKL, GPIO_FUNC_PWM); + + const uint slice_num = pwm_gpio_to_slice_num(PIN_BKL); + + pwm_config config = pwm_get_default_config(); + pwm_init(slice_num, &config, true); + + backlight_sync(); +} diff --git a/app/backlight.h b/app/backlight.h new file mode 100644 index 0000000..035f426 --- /dev/null +++ b/app/backlight.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void backlight_sync(void); +void backlight_init(void); diff --git a/app/debug.c b/app/debug.c new file mode 100644 index 0000000..3019210 --- /dev/null +++ b/app/debug.c @@ -0,0 +1,118 @@ +#include "debug.h" + +#include "app_config.h" +#include "gpioexp.h" +#include "keyboard.h" +#include "reg.h" +#include "touchpad.h" +#include "usb.h" + +#include +#include +#include +#include +#include + +#define PICO_STDIO_USB_STDOUT_TIMEOUT_US 500000 + +static void key_cb(char key, enum key_state state) +{ + printf("key: 0x%02X/%d/%c, state: %d, bkl: %d\r\n", key, key, key, state, reg_get_value(REG_ID_BKL)); +} +static struct key_callback key_callback = +{ + .func = key_cb +}; + +static void key_lock_cb(bool caps_changed, bool num_changed) +{ + printf("lock, caps_c: %d, caps: %d, num_c: %d, num: %d\r\n", + caps_changed, keyboard_get_capslock(), + num_changed, keyboard_get_numlock()); +} +static struct key_lock_callback key_lock_callback = +{ + .func = key_lock_cb +}; + +static void touch_cb(int8_t x, int8_t y) +{ + printf("%s: x: %d, y: %d !\r\n", __func__, x, y); +} +static struct touch_callback touch_callback = +{ + .func = touch_cb +}; + +static void gpioexp_cb(uint8_t gpio, uint8_t gpio_idx) +{ + printf("gpioexp, pin: %d, idx: %d\r\n", gpio, gpio_idx); +} +static struct gpioexp_callback gpioexp_callback = +{ + .func = gpioexp_cb +}; + +// copied from pico_stdio_usb in the SDK +static void usb_out_chars(const char *buf, int length) +{ + static uint64_t last_avail_time; + uint32_t owner; + + if (!mutex_try_enter(usb_get_mutex(), &owner)) { + if (owner == get_core_num()) + return; + + mutex_enter_blocking(usb_get_mutex()); + } + + if (tud_cdc_connected()) { + for (int i = 0; i < length;) { + int n = length - i; + int avail = tud_cdc_write_available(); + if (n > avail) n = avail; + if (n) { + int n2 = tud_cdc_write(buf + i, n); + tud_task(); + tud_cdc_write_flush(); + i += n2; + last_avail_time = time_us_64(); + } else { + tud_task(); + tud_cdc_write_flush(); + if (!tud_cdc_connected() || + (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) { + break; + } + } + } + } else { + // reset our timeout + last_avail_time = 0; + } + + mutex_exit(usb_get_mutex()); +} +static struct stdio_driver stdio_usb = +{ + .out_chars = usb_out_chars, +#if PICO_STDIO_ENABLE_CRLF_SUPPORT + .crlf_enabled = PICO_STDIO_DEFAULT_CRLF +#endif +}; + +void debug_init(void) +{ + stdio_init_all(); + + stdio_set_driver_enabled(&stdio_usb, true); + + printf("I2C Puppet SW v%d.%d\r\n", VERSION_MAJOR, VERSION_MINOR); + + keyboard_add_key_callback(&key_callback); + keyboard_add_lock_callback(&key_lock_callback); + + touchpad_add_touch_callback(&touch_callback); + + gpioexp_add_int_callback(&gpioexp_callback); +} diff --git a/app/debug.h b/app/debug.h new file mode 100644 index 0000000..c657628 --- /dev/null +++ b/app/debug.h @@ -0,0 +1,3 @@ +#pragma once + +void debug_init(void); diff --git a/app/fifo.c b/app/fifo.c new file mode 100644 index 0000000..77749d5 --- /dev/null +++ b/app/fifo.c @@ -0,0 +1,60 @@ +#include "app_config.h" +#include "fifo.h" + +static struct +{ + struct fifo_item fifo[KEY_FIFO_SIZE]; + uint8_t count; + uint8_t read_idx; + uint8_t write_idx; +} self; + +uint8_t fifo_count(void) +{ + return self.count; +} + +void fifo_flush(void) +{ + self.write_idx = 0; + self.read_idx = 0; + self.count = 0; +} + +bool fifo_enqueue(const struct fifo_item item) +{ + if (self.count >= KEY_FIFO_SIZE) + return false; + + self.fifo[self.write_idx++] = item; + + self.write_idx %= KEY_FIFO_SIZE; + ++self.count; + + return true; +} + +void fifo_enqueue_force(const struct fifo_item item) +{ + if (fifo_enqueue(item)) + return; + + self.fifo[self.write_idx++] = item; + self.write_idx %= KEY_FIFO_SIZE; + + self.read_idx++; + self.read_idx %= KEY_FIFO_SIZE; +} + +struct fifo_item fifo_dequeue(void) +{ + struct fifo_item item = { 0 }; + if (self.count == 0) + return item; + + item = self.fifo[self.read_idx++]; + self.read_idx %= KEY_FIFO_SIZE; + --self.count; + + return item; +} diff --git a/app/fifo.h b/app/fifo.h new file mode 100644 index 0000000..585892a --- /dev/null +++ b/app/fifo.h @@ -0,0 +1,15 @@ +#pragma once + +#include "keyboard.h" + +struct fifo_item +{ + char key; + enum key_state state; +}; + +uint8_t fifo_count(void); +void fifo_flush(void); +bool fifo_enqueue(const struct fifo_item item); +void fifo_enqueue_force(const struct fifo_item item); +struct fifo_item fifo_dequeue(void); diff --git a/app/gpioexp.c b/app/gpioexp.c new file mode 100644 index 0000000..7e52dc5 --- /dev/null +++ b/app/gpioexp.c @@ -0,0 +1,279 @@ +#include "gpioexp.h" +#include "reg.h" + +#include +#include + +static struct +{ + struct gpioexp_callback *callbacks; +} self; + +static void set_dir(uint8_t gpio, uint8_t gpio_idx, uint8_t dir) +{ +#ifndef NDEBUG + printf("%s: gpio: %d, gpio_idx: %d, dir: %d\r\n", __func__, gpio, gpio_idx, dir); +#endif + + gpio_init(gpio); + + if (dir == DIR_INPUT) { + if (reg_is_bit_set(REG_ID_PUE, (1 << gpio_idx))) { + if (reg_is_bit_set(REG_ID_PUD, (1 << gpio_idx)) == PUD_UP) { + gpio_is_pulled_up(gpio); + } else { + gpio_is_pulled_down(gpio); + } + } else { + gpio_disable_pulls(gpio); + } + + gpio_set_dir(gpio, GPIO_IN); + + gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true); + + reg_set_bit(REG_ID_DIR, (1 << gpio_idx)); + } else { + gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, false); + + gpio_set_dir(gpio, GPIO_OUT); + + reg_clear_bit(REG_ID_DIR, (1 << gpio_idx)); + } +} + +void gpioexp_gpio_irq(uint gpio, uint32_t events) +{ + (void)gpio; + (void)events; + +#define CALLBACK(bit) \ + if (gpio == PIN_GPIOEXP ## bit) { \ + struct gpioexp_callback *cb = self.callbacks; \ + while (cb) { \ + cb->func(PIN_GPIOEXP ## bit, bit); \ + cb = cb->next; \ + } \ + return; \ + } + +#ifdef PIN_GPIOEXP0 + CALLBACK(0) +#endif + +#ifdef PIN_GPIOEXP1 + CALLBACK(1) +#endif + +#ifdef PIN_GPIOEXP2 + CALLBACK(2) +#endif + +#ifdef PIN_GPIOEXP3 + CALLBACK(3) +#endif + +#ifdef PIN_GPIOEXP4 + CALLBACK(4) +#endif + +#ifdef PIN_GPIOEXP5 + CALLBACK(5) +#endif + +#ifdef PIN_GPIOEXP6 + CALLBACK(6) +#endif + +#ifdef PIN_GPIOEXP7 + CALLBACK(7) +#endif +} + +void gpioexp_update_dir(uint8_t new_dir) +{ +#ifndef NDEBUG + printf("%s: dir: 0x%02X\r\n", __func__, new_dir); +#endif + + const uint8_t old_dir = reg_get_value(REG_ID_DIR); + + (void)old_dir; // Shut up warning in case no GPIOs configured + +#define UPDATE_DIR(bit) \ + if ((old_dir & (1 << bit)) != (new_dir & (1 << bit))) \ + set_dir(PIN_GPIOEXP ## bit, bit, (new_dir & (1 << bit)) != 0); + +#ifdef PIN_GPIOEXP0 + UPDATE_DIR(0) +#endif +#ifdef PIN_GPIOEXP1 + UPDATE_DIR(1) +#endif +#ifdef PIN_GPIOEXP2 + UPDATE_DIR(2) +#endif +#ifdef PIN_GPIOEXP3 + UPDATE_DIR(3) +#endif +#ifdef PIN_GPIOEXP4 + UPDATE_DIR(4) +#endif +#ifdef PIN_GPIOEXP5 + UPDATE_DIR(5) +#endif +#ifdef PIN_GPIOEXP6 + UPDATE_DIR(6) +#endif +#ifdef PIN_GPIOEXP7 + UPDATE_DIR(7) +#endif +} + +void gpioexp_update_pue_pud(uint8_t new_pue, uint8_t new_pud) +{ +#ifndef NDEBUG + printf("%s: pue: 0x%02X, pud: 0x%02X\r\n", __func__, new_pue, new_pud); +#endif + + const uint8_t old_pue = reg_get_value(REG_ID_PUE); + const uint8_t old_pud = reg_get_value(REG_ID_PUD); + + // Shut up warnings in case no GPIOs configured + (void)old_pue; + (void)old_pud; + + reg_set_value(REG_ID_PUE, new_pue); + reg_set_value(REG_ID_PUD, new_pud); + +#define UPDATE_PULL(bit) \ + if (((old_pue & (1 << bit)) != (new_pue & (1 << bit))) || \ + ((old_pud & (1 << bit)) != (new_pud & (1 << bit)))) { \ + set_dir(PIN_GPIOEXP ## bit, bit, reg_is_bit_set(REG_ID_DIR, (1 << bit))); \ + } + +#ifdef PIN_GPIOEXP0 + UPDATE_PULL(0) +#endif +#ifdef PIN_GPIOEXP1 + UPDATE_PULL(1) +#endif +#ifdef PIN_GPIOEXP2 + UPDATE_PULL(2) +#endif +#ifdef PIN_GPIOEXP3 + UPDATE_PULL(3) +#endif +#ifdef PIN_GPIOEXP4 + UPDATE_PULL(4) +#endif +#ifdef PIN_GPIOEXP5 + UPDATE_PULL(5) +#endif +#ifdef PIN_GPIOEXP6 + UPDATE_PULL(6) +#endif +#ifdef PIN_GPIOEXP7 + UPDATE_PULL(7) +#endif +} + +void gpioexp_set_value(uint8_t value) +{ +#ifndef NDEBUG + printf("%s: value: 0x%02X\r\n", __func__, value); +#endif + +#define SET_VALUE(bit) \ + if (reg_is_bit_set(REG_ID_DIR, (1 << bit)) == DIR_OUTPUT) { \ + gpio_put(PIN_GPIOEXP ## bit, (value & (1 << bit))); \ + } + +#ifdef PIN_GPIOEXP0 + SET_VALUE(0) +#endif +#ifdef PIN_GPIOEXP1 + SET_VALUE(1) +#endif +#ifdef PIN_GPIOEXP2 + SET_VALUE(2) +#endif +#ifdef PIN_GPIOEXP3 + SET_VALUE(3) +#endif +#ifdef PIN_GPIOEXP4 + SET_VALUE(4) +#endif +#ifdef PIN_GPIOEXP5 + SET_VALUE(5) +#endif +#ifdef PIN_GPIOEXP6 + SET_VALUE(6) +#endif +#ifdef PIN_GPIOEXP7 + SET_VALUE(7) +#endif +} + +uint8_t gpioexp_get_value(void) +{ + uint8_t value = 0; + +#define GET_VALUE(bit) \ + value |= (gpio_get(PIN_GPIOEXP ## bit) << bit); + +// if (reg_is_bit_set(REG_ID_DIR, (1 << bit)) == DIR_INPUT) { \ +// value |= (port_pin_get_input_level(PIN_GPIOEXP ## bit) << bit); \ +// } else { \ +// value |= (port_pin_get_output_level(PIN_GPIOEXP ## bit) << bit); \ +// } + +#ifdef PIN_GPIOEXP0 + GET_VALUE(0) +#endif +#ifdef PIN_GPIOEXP1 + GET_VALUE(1) +#endif +#ifdef PIN_GPIOEXP2 + GET_VALUE(2) +#endif +#ifdef PIN_GPIOEXP3 + GET_VALUE(3) +#endif +#ifdef PIN_GPIOEXP4 + GET_VALUE(4) +#endif +#ifdef PIN_GPIOEXP5 + GET_VALUE(5) +#endif +#ifdef PIN_GPIOEXP6 + GET_VALUE(6) +#endif +#ifdef PIN_GPIOEXP7 + GET_VALUE(7) +#endif + + return value; +} + +void gpioexp_add_int_callback(struct gpioexp_callback *callback) +{ + // first callback + if (!self.callbacks) { + self.callbacks = callback; + return; + } + + // find last and insert after + struct gpioexp_callback *cb = self.callbacks; + while (cb->next) + cb = cb->next; + + cb->next = callback; +} + +void gpioexp_init(void) +{ + // Configure all to inputs + gpioexp_update_dir(0xFF); +} diff --git a/app/gpioexp.h b/app/gpioexp.h new file mode 100644 index 0000000..9016485 --- /dev/null +++ b/app/gpioexp.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +struct gpioexp_callback +{ + void (*func)(uint8_t gpio, uint8_t gpio_idx); + struct gpioexp_callback *next; +}; + +void gpioexp_gpio_irq(uint gpio, uint32_t events); + +void gpioexp_update_dir(uint8_t dir); +void gpioexp_update_pue_pud(uint8_t pue, uint8_t pud); + +void gpioexp_set_value(uint8_t value); +uint8_t gpioexp_get_value(void); + +void gpioexp_add_int_callback(struct gpioexp_callback *callback); +void gpioexp_init(void); diff --git a/app/interrupt.c b/app/interrupt.c new file mode 100644 index 0000000..07da68d --- /dev/null +++ b/app/interrupt.c @@ -0,0 +1,106 @@ +#include "interrupt.h" + +#include "app_config.h" +#include "gpioexp.h" +#include "keyboard.h" +#include "reg.h" +#include "touchpad.h" + +#include + +static void key_cb(char key, enum key_state state) +{ + (void)key; + (void)state; + + if (!reg_is_bit_set(REG_ID_CFG, CFG_KEY_INT)) + return; + + reg_set_bit(REG_ID_INT, INT_KEY); + + gpio_put(PIN_INT, 0); + busy_wait_ms(reg_get_value(REG_ID_IND)); + gpio_put(PIN_INT, 1); +} +static struct key_callback key_callback = +{ + .func = key_cb +}; + +static void key_lock_cb(bool caps_changed, bool num_changed) +{ + bool do_int = false; + + if (caps_changed && reg_is_bit_set(REG_ID_CFG, CFG_CAPSLOCK_INT)) { + reg_set_bit(REG_ID_INT, INT_CAPSLOCK); + do_int = true; + } + + if (num_changed && reg_is_bit_set(REG_ID_CFG, CFG_NUMLOCK_INT)) { + reg_set_bit(REG_ID_INT, INT_NUMLOCK); + do_int = true; + } + + if (do_int) { + gpio_put(PIN_INT, 0); + busy_wait_ms(reg_get_value(REG_ID_IND)); + gpio_put(PIN_INT, 1); + } +} +static struct key_lock_callback key_lock_callback = +{ + .func = key_lock_cb +}; + +static void touch_cb(int8_t x, int8_t y) +{ + (void)x; + (void)y; + + if (!reg_is_bit_set(REG_ID_CF2, CF2_TOUCH_INT)) + return; + + reg_set_bit(REG_ID_INT, INT_TOUCH); + + gpio_put(PIN_INT, 0); + busy_wait_ms(reg_get_value(REG_ID_IND)); + gpio_put(PIN_INT, 1); +} +static struct touch_callback touch_callback = +{ + .func = touch_cb +}; + +static void gpioexp_cb(uint8_t gpio, uint8_t gpio_idx) +{ + (void)gpio; + + if (!reg_is_bit_set(REG_ID_GIC, (1 << gpio_idx))) + return; + + reg_set_bit(REG_ID_INT, INT_GPIO); + reg_set_bit(REG_ID_GIN, (1 << gpio_idx)); + + gpio_put(PIN_INT, 0); + busy_wait_ms(reg_get_value(REG_ID_IND)); + gpio_put(PIN_INT, 1); +} +static struct gpioexp_callback gpioexp_callback = +{ + .func = gpioexp_cb +}; + +void interrupt_init(void) +{ + gpio_init(PIN_INT); + gpio_set_dir(PIN_INT, GPIO_OUT); + gpio_pull_up(PIN_INT); + gpio_put(PIN_INT, true); + + keyboard_add_key_callback(&key_callback); + keyboard_add_lock_callback(&key_lock_callback); + + touchpad_add_touch_callback(&touch_callback); + + gpioexp_add_int_callback(&gpioexp_callback); +} diff --git a/app/interrupt.h b/app/interrupt.h new file mode 100644 index 0000000..d2afb81 --- /dev/null +++ b/app/interrupt.h @@ -0,0 +1,3 @@ +#pragma once + +void interrupt_init(void); diff --git a/app/keyboard.c b/app/keyboard.c new file mode 100644 index 0000000..6f2c8ee --- /dev/null +++ b/app/keyboard.c @@ -0,0 +1,385 @@ +#include "app_config.h" +#include "fifo.h" +#include "keyboard.h" +#include "reg.h" + +#include + +#define LIST_SIZE 10 // size of the list keeping track of all the pressed keys + +enum mod +{ + MOD_NONE = 0, + MOD_SYM, + MOD_ALT, + MOD_SHL, + MOD_SHR, + + MOD_LAST, +}; + +struct entry +{ + char chr; + char symb; + enum mod mod; +}; + +struct list_item +{ + const struct entry *p_entry; + uint32_t hold_start_time; + enum key_state state; + bool mods[MOD_LAST]; +}; + +static const uint8_t row_pins[NUM_OF_ROWS] = +{ + PINS_ROWS +}; + +static const uint8_t col_pins[NUM_OF_COLS] = +{ + PINS_COLS +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + +static const struct entry kbd_entries[][NUM_OF_COLS] = +{ + { { KEY_JOY_CENTER }, { 'W', '1' }, { 'G', '/' }, { 'S', '4' }, { 'L', '"' }, { 'H' , ':' } }, + { { }, { 'Q', '#' }, { 'R', '3' }, { 'E', '2' }, { 'O', '+' }, { 'U', '_' } }, + { { KEY_BTN_LEFT1 }, { '~', '0' }, { 'F', '6' }, { .mod = MOD_SHL }, { 'K', '\'' }, { 'J', ';' } }, + { { }, { ' ', '\t' }, { 'C', '9' }, { 'Z', '7' }, { 'M', '.' }, { 'N', ',' } }, + { { KEY_BTN_LEFT2 }, { .mod = MOD_SYM }, { 'T', '(' }, { 'D', '5' }, { 'I', '-' }, { 'Y', ')' } }, + { { KEY_BTN_RIGHT1 }, { .mod = MOD_ALT }, { 'V', '?' }, { 'X', '8' }, { '$', '`' }, { 'B', '!' } }, + { { }, { 'A', '*' }, { .mod = MOD_SHR }, { 'P', '@' }, { '\b' }, { '\n', '|' } }, +}; + +#if NUM_OF_BTNS > 0 +static const struct entry btn_entries[NUM_OF_BTNS] = +{ + BTN_KEYS +}; + +static const uint8_t btn_pins[NUM_OF_BTNS] = +{ + PINS_BTNS +}; +#endif + +#pragma GCC diagnostic pop + +static struct +{ + struct key_lock_callback *lock_callbacks; + struct key_callback *key_callbacks; + + struct list_item list[LIST_SIZE]; + + uint32_t last_process_time; + + bool mods[MOD_LAST]; + + bool capslock_changed; + bool capslock; + + bool numlock_changed; + bool numlock; +} self; + +static void transition_to(struct list_item * const p_item, const enum key_state next_state) +{ + const struct entry * const p_entry = p_item->p_entry; + + p_item->state = next_state; + + if (!self.key_callbacks || !p_entry) + return; + + char chr = p_entry->chr; + + switch (p_entry->mod) { + case MOD_ALT: + if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) + chr = KEY_MOD_ALT; + break; + + case MOD_SHL: + if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) + chr = KEY_MOD_SHL; + break; + + case MOD_SHR: + if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) + chr = KEY_MOD_SHR; + break; + + case MOD_SYM: + if (reg_is_bit_set(REG_ID_CFG, CFG_REPORT_MODS)) + chr = KEY_MOD_SYM; + break; + + default: + { + if (reg_is_bit_set(REG_ID_CFG, CFG_USE_MODS)) { + const bool shift = (self.mods[MOD_SHL] || self.mods[MOD_SHR]) | self.capslock; + const bool alt = self.mods[MOD_ALT] | self.numlock; + + if (alt) { + chr = p_entry->symb; + } else if (!shift && (chr >= 'A' && chr <= 'Z')) { + chr = (chr + ' '); + } + } + + break; + } + } + + if (chr != 0) { + const struct fifo_item item = { chr, next_state }; + if (!fifo_enqueue(item)) { + if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_INT)) { + reg_set_bit(REG_ID_INT, INT_OVERFLOW); + } + + if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_ON)) + fifo_enqueue_force(item); + } + + struct key_callback *cb = self.key_callbacks; + while (cb) { + cb->func(chr, next_state); + cb = cb->next; + } + } +} + +static void next_item_state(struct list_item * const p_item, const bool pressed) +{ + switch (p_item->state) { + case KEY_STATE_IDLE: + if (pressed) { + if (p_item->p_entry->mod != MOD_NONE) + self.mods[p_item->p_entry->mod] = true; + + if (!self.capslock_changed && self.mods[MOD_SHR] && self.mods[MOD_ALT]) { + self.capslock = true; + self.capslock_changed = true; + } + + if (!self.numlock_changed && self.mods[MOD_SHL] && self.mods[MOD_ALT]) { + self.numlock = true; + self.numlock_changed = true; + } + + if (!self.capslock_changed && (self.mods[MOD_SHL] || self.mods[MOD_SHR])) { + self.capslock = false; + self.capslock_changed = true; + } + + if (!self.numlock_changed && (self.mods[MOD_SHL] || self.mods[MOD_SHR])) { + self.numlock = false; + self.numlock_changed = true; + } + + if (!self.mods[MOD_ALT]) { + self.capslock_changed = false; + self.numlock_changed = false; + } + + if (self.lock_callbacks && (self.capslock_changed || self.numlock_changed)) { + struct key_lock_callback *cb = self.lock_callbacks; + while (cb) { + cb->func(self.capslock_changed, self.numlock_changed); + + cb = cb->next; + } + } + + transition_to(p_item, KEY_STATE_PRESSED); + + p_item->hold_start_time = to_ms_since_boot(get_absolute_time()); + } + break; + + case KEY_STATE_PRESSED: + if ((to_ms_since_boot(get_absolute_time()) - p_item->hold_start_time) > (reg_get_value(REG_ID_HLD) * 10)) { + transition_to(p_item, KEY_STATE_HOLD); + } else if(!pressed) { + transition_to(p_item, KEY_STATE_RELEASED); + } + break; + + case KEY_STATE_HOLD: + if (!pressed) + transition_to(p_item, KEY_STATE_RELEASED); + break; + case KEY_STATE_RELEASED: + { + if (p_item->p_entry->mod != MOD_NONE) + self.mods[p_item->p_entry->mod] = false; + + p_item->p_entry = NULL; + transition_to(p_item, KEY_STATE_IDLE); + break; + } + } +} + +void keyboard_task(void) +{ + if ((to_ms_since_boot(get_absolute_time()) - self.last_process_time) <= reg_get_value(REG_ID_FRQ)) + return; + + for (uint32_t c = 0; c < NUM_OF_COLS; ++c) { + gpio_pull_up(col_pins[c]); + gpio_put(col_pins[c], 0); + gpio_set_dir(col_pins[c], GPIO_OUT); + + for (uint32_t r = 0; r < NUM_OF_ROWS; ++r) { + const bool pressed = (gpio_get(row_pins[r]) == 0); + const int32_t key_idx = (int32_t)((r * NUM_OF_COLS) + c); + + int32_t list_idx = -1; + for (int32_t i = 0; i < LIST_SIZE; ++i) { + if (self.list[i].p_entry != &((const struct entry*)kbd_entries)[key_idx]) + continue; + + list_idx = i; + break; + } + + if (list_idx > -1) { + next_item_state(&self.list[list_idx], pressed); + continue; + } + + if (!pressed) + continue; + + for (uint32_t i = 0 ; i < LIST_SIZE; ++i) { + if (self.list[i].p_entry != NULL) + continue; + + self.list[i].p_entry = &((const struct entry*)kbd_entries)[key_idx]; + self.list[i].state = KEY_STATE_IDLE; + next_item_state(&self.list[i], pressed); + + break; + } + } + + gpio_put(col_pins[c], 1); + gpio_disable_pulls(col_pins[c]); + gpio_set_dir(col_pins[c], GPIO_IN); + } + +#if NUM_OF_BTNS > 0 + for (uint32_t b = 0; b < NUM_OF_BTNS; ++b) { + const bool pressed = (gpio_get(btn_pins[b]) == 0); + + int32_t list_idx = -1; + for (int32_t i = 0; i < LIST_SIZE; ++i) { + if (self.list[i].p_entry != &((const struct entry*)btn_entries)[b]) + continue; + + list_idx = i; + break; + } + + if (list_idx > -1) { + next_item_state(&self.list[list_idx], pressed); + continue; + } + + if (!pressed) + continue; + + for (uint32_t i = 0 ; i < LIST_SIZE; ++i) { + if (self.list[i].p_entry != NULL) + continue; + + self.list[i].p_entry = &((const struct entry*)btn_entries)[b]; + self.list[i].state = KEY_STATE_IDLE; + next_item_state(&self.list[i], pressed); + + break; + } + } +#endif + + self.last_process_time = to_ms_since_boot(get_absolute_time()); +} + +void keyboard_add_key_callback(struct key_callback *callback) +{ + // first callback + if (!self.key_callbacks) { + self.key_callbacks = callback; + return; + } + + // find last and insert after + struct key_callback *cb = self.key_callbacks; + while (cb->next) + cb = cb->next; + + cb->next = callback; +} + +void keyboard_add_lock_callback(struct key_lock_callback *callback) +{ + // first callback + if (!self.lock_callbacks) { + self.lock_callbacks = callback; + return; + } + + // find last and insert after + struct key_lock_callback *cb = self.lock_callbacks; + while (cb->next) + cb = cb->next; + + cb->next = callback; +} + +bool keyboard_get_capslock(void) +{ + return self.capslock; +} + +bool keyboard_get_numlock(void) +{ + return self.numlock; +} + +void keyboard_init(void) +{ + for (int i = 0; i < MOD_LAST; ++i) + self.mods[i] = false; + + // Rows + for (uint32_t i = 0; i < NUM_OF_ROWS; ++i) { + gpio_init(row_pins[i]); + gpio_pull_up(row_pins[i]); + gpio_set_dir(row_pins[i], GPIO_IN); + } + + // Cols + for(uint32_t i = 0; i < NUM_OF_COLS; ++i) { + gpio_init(col_pins[i]); + gpio_set_dir(col_pins[i], GPIO_IN); + } + + // Btns +#if NUM_OF_BTNS > 0 + for(uint32_t i = 0; i < NUM_OF_BTNS; ++i) { + gpio_init(btn_pins[i]); + gpio_set_dir(btn_pins[i], GPIO_IN); + gpio_pull_up(btn_pins[i]); + } +#endif +} diff --git a/app/keyboard.h b/app/keyboard.h new file mode 100644 index 0000000..26aa621 --- /dev/null +++ b/app/keyboard.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +enum key_state +{ + KEY_STATE_IDLE = 0, + KEY_STATE_PRESSED, + KEY_STATE_HOLD, + KEY_STATE_RELEASED, +}; + +#define KEY_JOY_UP 0x01 +#define KEY_JOY_DOWN 0x02 +#define KEY_JOY_LEFT 0x03 +#define KEY_JOY_RIGHT 0x04 +#define KEY_JOY_CENTER 0x05 +#define KEY_BTN_LEFT1 0x06 +#define KEY_BTN_RIGHT1 0x07 +// 0x08 - BACKSPACE +// 0x09 - TAB +// 0x0A - NEW LINE +// 0x0D - CARRIAGE RETURN +#define KEY_BTN_LEFT2 0x11 +#define KEY_BTN_RIGHT2 0x12 + +#define KEY_MOD_ALT 0x1A +#define KEY_MOD_SHL 0x1B +#define KEY_MOD_SHR 0x1C +#define KEY_MOD_SYM 0x1D + +struct key_callback +{ + void (*func)(char, enum key_state); + struct key_callback *next; +}; + +struct key_lock_callback +{ + void (*func)(bool, bool); + struct key_lock_callback *next; +}; + +void keyboard_task(void); + +void keyboard_add_key_callback(struct key_callback *callback); +void keyboard_add_lock_callback(struct key_lock_callback *callback); + +bool keyboard_get_capslock(void); +bool keyboard_get_numlock(void); + +void keyboard_init(void); diff --git a/app/main.c b/app/main.c new file mode 100644 index 0000000..55b115a --- /dev/null +++ b/app/main.c @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "backlight.h" +#include "debug.h" +#include "gpioexp.h" +#include "interrupt.h" +#include "keyboard.h" +#include "puppet_i2c.h" +#include "reg.h" +#include "touchpad.h" +#include "usb.h" + +// since the SDK doesn't support per-GPIO irq, we use this global irq and forward it +static void gpio_irq(uint gpio, uint32_t events) +{ +// printf("%s: gpio %d, events 0x%02X\r\n", __func__, gpio, events); + touchpad_gpio_irq(gpio, events); + gpioexp_gpio_irq(gpio, events); +} + +// TODO: Microphone +int main(void) +{ + // The here order is important because it determines callback call order + + usb_init(); + +#ifndef NDEBUG + debug_init(); +#endif + + reg_init(); + + backlight_init(); + + gpioexp_init(); + + keyboard_init(); + + touchpad_init(); + + interrupt_init(); + + puppet_i2c_init(); + + // For now, the `gpio` param is ignored and all enabled GPIOs generate the irq + gpio_set_irq_enabled_with_callback(0xFF, 0, true, &gpio_irq); + +#ifndef NDEBUG + printf("Starting main loop\r\n"); +#endif + + while (true) { + keyboard_task(); + +// tud_task(); + } + + return 0; +} diff --git a/app/puppet_i2c.c b/app/puppet_i2c.c new file mode 100644 index 0000000..0b96f58 --- /dev/null +++ b/app/puppet_i2c.c @@ -0,0 +1,210 @@ +#include "puppet_i2c.h" + +#include "app_config.h" +#include "backlight.h" +#include "fifo.h" +#include "gpioexp.h" +#include "keyboard.h" +#include "reg.h" + +#include +#include +#include +#include +#include + +#define WRITE_MASK (1 << 7) +#define REG_ID_INVALID 0x00 + +static i2c_inst_t *i2c_instances[2] = { i2c0, i2c1 }; + +static struct +{ + i2c_inst_t *i2c; + + struct + { + uint8_t reg; + uint8_t data; + } read_buffer; + + uint8_t write_buffer[2]; + uint8_t write_len; +} self; + +static void process_read(void) +{ + const bool is_write = (self.read_buffer.reg & WRITE_MASK); + const uint8_t reg = (self.read_buffer.reg & ~WRITE_MASK); + +// printf("read complete, is_write: %d, reg: 0x%02X\r\n", is_write, reg); + + switch (reg) { + + // common R/W registers + case REG_ID_CFG: + case REG_ID_INT: + case REG_ID_DEB: + case REG_ID_FRQ: + case REG_ID_BKL: + case REG_ID_BK2: + case REG_ID_GIC: + case REG_ID_GIN: + case REG_ID_HLD: + case REG_ID_ADR: + case REG_ID_IND: + case REG_ID_CF2: + { + if (is_write) { + reg_set_value(reg, self.read_buffer.data); + + switch (reg) { + case REG_ID_BKL: + case REG_ID_BK2: + backlight_sync(); + break; + + case REG_ID_ADR: + puppet_i2c_sync_address(); + break; + + default: + break; + } + } else { + self.write_buffer[0] = reg_get_value(reg); + self.write_len = sizeof(uint8_t); + } + break; + } + + // special R/W registers + case REG_ID_DIR: // gpio direction + case REG_ID_PUE: // gpio input pull enable + case REG_ID_PUD: // gpio input pull direction + { + if (is_write) { + switch (reg) { + case REG_ID_DIR: + gpioexp_update_dir(self.read_buffer.data); + break; + case REG_ID_PUE: + gpioexp_update_pue_pud(self.read_buffer.data, reg_get_value(REG_ID_PUD)); + break; + case REG_ID_PUD: + gpioexp_update_pue_pud(reg_get_value(REG_ID_PUE), self.read_buffer.data); + break; + } + } else { + self.write_buffer[0] = reg_get_value(reg); + self.write_len = sizeof(uint8_t); + } + break; + } + + case REG_ID_GIO: // gpio value + { + if (is_write) { + gpioexp_set_value(self.read_buffer.data); + } else { + self.write_buffer[0] = gpioexp_get_value(); + self.write_len = sizeof(uint8_t); + } + break; + } + + // read-only registers + case REG_ID_TOX: + case REG_ID_TOY: + self.write_buffer[0] = reg_get_value(reg); + self.write_len = sizeof(uint8_t); + + reg_set_value(reg, 0); + break; + + case REG_ID_VER: + self.write_buffer[0] = VER_VAL; + self.write_len = sizeof(uint8_t); + break; + + case REG_ID_KEY: + self.write_buffer[0] = fifo_count(); + self.write_buffer[0] |= keyboard_get_numlock() ? KEY_NUMLOCK : 0x00; + self.write_buffer[0] |= keyboard_get_capslock() ? KEY_CAPSLOCK : 0x00; + self.write_len = sizeof(uint8_t); + break; + + case REG_ID_FIF: + { + const struct fifo_item item = fifo_dequeue(); + + self.write_buffer[0] = (uint8_t)item.state; + self.write_buffer[1] = (uint8_t)item.key; + self.write_len = sizeof(uint8_t) * 2; + break; + } + + case REG_ID_RST: + NVIC_SystemReset(); + break; + } +} + +static void irq_handler(void) +{ + // the controller sent data + if (self.i2c->hw->intr_stat & I2C_IC_INTR_MASK_M_RX_FULL_BITS) { + if (self.read_buffer.reg == REG_ID_INVALID) { + self.read_buffer.reg = self.i2c->hw->data_cmd & 0xff; + + if (self.read_buffer.reg & WRITE_MASK) { + // it'sq a reg write, we need to wait for the second byte before we process + return; + } + } else { + self.read_buffer.data = self.i2c->hw->data_cmd & 0xff; + } + + process_read(); + + // ready for the next operation + self.read_buffer.reg = REG_ID_INVALID; + + return; + } + + // the controller requested a read + if (self.i2c->hw->intr_stat & I2C_IC_INTR_MASK_M_RD_REQ_BITS) { + i2c_write_raw_blocking(self.i2c, self.write_buffer, self.write_len); + + self.i2c->hw->clr_rd_req; + return; + } +} + +void puppet_i2c_sync_address(void) +{ + i2c_set_slave_mode(self.i2c, true, reg_get_value(REG_ID_ADR)); +} + +void puppet_i2c_init(void) +{ + // determine the instance based on SCL pin, hope you didn't screw up the SDA pin! + self.i2c = i2c_instances[(PIN_PUPPET_SCL / 2) % 2]; + + i2c_init(self.i2c, 100 * 1000); + puppet_i2c_sync_address(); + + gpio_set_function(PIN_PUPPET_SDA, GPIO_FUNC_I2C); + gpio_pull_up(PIN_PUPPET_SDA); + + gpio_set_function(PIN_PUPPET_SCL, GPIO_FUNC_I2C); + gpio_pull_up(PIN_PUPPET_SCL); + + // irq when the controller sends data, and when it requests a read + self.i2c->hw->intr_mask = I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_INTR_MASK_M_RX_FULL_BITS; + + const int irq = I2C0_IRQ + i2c_hw_index(self.i2c); + irq_set_exclusive_handler(irq, irq_handler); + irq_set_enabled(irq, true); +} diff --git a/app/puppet_i2c.h b/app/puppet_i2c.h new file mode 100644 index 0000000..a6a66ad --- /dev/null +++ b/app/puppet_i2c.h @@ -0,0 +1,5 @@ +#pragma once + +void puppet_i2c_sync_address(void); + +void puppet_i2c_init(void); diff --git a/app/reg.c b/app/reg.c new file mode 100644 index 0000000..1f4d1a0 --- /dev/null +++ b/app/reg.c @@ -0,0 +1,73 @@ +#include "reg.h" + +#include "touchpad.h" + +#include + +static struct +{ + uint8_t regs[REG_ID_LAST]; +} self; + +static void touch_cb(int8_t x, int8_t y) +{ + self.regs[REG_ID_TOX] = x; + self.regs[REG_ID_TOY] = y; +} +static struct touch_callback touch_callback = +{ + .func = touch_cb +}; + +uint8_t reg_get_value(enum reg_id reg) +{ + return self.regs[reg]; +} + +void reg_set_value(enum reg_id reg, uint8_t value) +{ +#ifndef NDEBUG + printf("%s: reg: 0x%02X, val: 0x%02X\r\n", __func__, reg, value); +#endif + + self.regs[reg] = value; +} + +bool reg_is_bit_set(enum reg_id reg, uint8_t bit) +{ + return self.regs[reg] & bit; +} + +void reg_set_bit(enum reg_id reg, uint8_t bit) +{ +#ifndef NDEBUG + printf("%s: reg: 0x%02X, bit: %d\r\n", __func__, reg, bit); +#endif + + self.regs[reg] |= bit; +} + +void reg_clear_bit(enum reg_id reg, uint8_t bit) +{ +#ifndef NDEBUG + printf("%s: reg: 0x%02X, bit: %d\r\n", __func__, reg, bit); +#endif + + self.regs[reg] &= ~bit; +} + +void reg_init(void) +{ + self.regs[REG_ID_CFG] = CFG_OVERFLOW_INT | CFG_KEY_INT | CFG_USE_MODS; + self.regs[REG_ID_BKL] = 255; + self.regs[REG_ID_DEB] = 10; + self.regs[REG_ID_FRQ] = 10; // ms + self.regs[REG_ID_BK2] = 255; + self.regs[REG_ID_PUD] = 0xFF; + self.regs[REG_ID_HLD] = 30; // 10ms units + self.regs[REG_ID_ADR] = 0x1F; + self.regs[REG_ID_IND] = 1; // ms + self.regs[REG_ID_CF2] = CF2_TOUCH_INT | CF2_USB_KEYB_ON | CF2_USB_MOUSE_ON; + + touchpad_add_touch_callback(&touch_callback); +} diff --git a/app/reg.h b/app/reg.h new file mode 100644 index 0000000..884a283 --- /dev/null +++ b/app/reg.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +enum reg_id +{ + REG_ID_VER = 0x01, // fw version + REG_ID_CFG = 0x02, // config + REG_ID_INT = 0x03, // interrupt status + REG_ID_KEY = 0x04, // key status + REG_ID_BKL = 0x05, // backlight + REG_ID_DEB = 0x06, // key debounce cfg (not implemented) + REG_ID_FRQ = 0x07, // key poll freq cfg + REG_ID_RST = 0x08, // trigger a reset + REG_ID_FIF = 0x09, // key fifo + REG_ID_BK2 = 0x0A, // backlight 2 + REG_ID_DIR = 0x0B, // gpio direction + REG_ID_PUE = 0x0C, // gpio input pull enable + REG_ID_PUD = 0x0D, // gpio input pull direction + REG_ID_GIO = 0x0E, // gpio value + REG_ID_GIC = 0x0F, // gpio interrupt config + REG_ID_GIN = 0x10, // gpio interrupt status + REG_ID_HLD = 0x11, // key hold time cfg (in 10ms units) + REG_ID_ADR = 0x12, // i2c puppet address + REG_ID_IND = 0x13, // interrupt pin assert duration + REG_ID_CF2 = 0x14, // config 2 + REG_ID_TOX = 0x15, // touch delta x + REG_ID_TOY = 0x16, // touch delta y + + REG_ID_LAST, +}; + +#define CFG_OVERFLOW_ON (1 << 0) // Should new FIFO entries overwrite oldest ones if FIFO is full +#define CFG_OVERFLOW_INT (1 << 1) // Should FIFO overflow generate an interrupt +#define CFG_CAPSLOCK_INT (1 << 2) // Should toggling caps lock generate interrupts +#define CFG_NUMLOCK_INT (1 << 3) // Should toggling num lock generate interrupts +#define CFG_KEY_INT (1 << 4) // Should key events generate interrupts +#define CFG_PANIC_INT (1 << 5) // Not implemented +#define CFG_REPORT_MODS (1 << 6) // Should Alt, Sym and Shifts be reported as well +#define CFG_USE_MODS (1 << 7) // Should Alt, Sym and Shifts modify the keys reported + +#define CF2_TOUCH_INT (1 << 0) // Should touch events generate interrupts +#define CF2_USB_KEYB_ON (1 << 1) // Should key events be sent over USB HID +#define CF2_USB_MOUSE_ON (1 << 2) // Should touch events be sent over USB HID +// TODO? CF2_STICKY_MODS // Pressing and releasing a mod affects next key pressed + +#define INT_OVERFLOW (1 << 0) +#define INT_CAPSLOCK (1 << 1) +#define INT_NUMLOCK (1 << 2) +#define INT_KEY (1 << 3) +#define INT_PANIC (1 << 4) +#define INT_GPIO (1 << 5) +#define INT_TOUCH (1 << 6) +// Future me: If we need more INT_*, add a INT2 and use (1 << 7) here as indicator that the info is in INT2 + +#define KEY_CAPSLOCK (1 << 5) // Caps lock status +#define KEY_NUMLOCK (1 << 6) // Num lock status +#define KEY_COUNT_MASK 0x1F + +#define DIR_OUTPUT 0 +#define DIR_INPUT 1 + +#define PUD_DOWN 0 +#define PUD_UP 1 + +#define VER_VAL ((VERSION_MAJOR << 4) | (VERSION_MINOR << 0)) + +uint8_t reg_get_value(enum reg_id reg); +void reg_set_value(enum reg_id reg, uint8_t value); + +bool reg_is_bit_set(enum reg_id reg, uint8_t bit); +void reg_set_bit(enum reg_id reg, uint8_t bit); +void reg_clear_bit(enum reg_id reg, uint8_t bit); + +void reg_init(void); diff --git a/app/touchpad.c b/app/touchpad.c new file mode 100644 index 0000000..83feb56 --- /dev/null +++ b/app/touchpad.c @@ -0,0 +1,128 @@ +#include "touchpad.h" + +#include +#include +#include +#include + +#define DEV_ADDR 0x3B + +#define REG_PID 0x00 +#define REG_REV 0x01 +#define REG_MOTION 0x02 +#define REG_DELTA_X 0x03 +#define REG_DELTA_Y 0x04 +#define REG_DELTA_XY_H 0x05 +#define REG_CONFIG 0x11 +#define REG_OBSERV 0x2E +#define REG_MBURST 0x42 + +#define BIT_MOTION_MOT (1 << 7) +#define BIT_MOTION_OVF (1 << 4) + +#define BIT_CONFIG_HIRES (1 << 7) + +#define BIT_OBSERV_RUN (0 << 6) +#define BIT_OBSERV_REST1 (1 << 6) +#define BIT_OBSERV_REST2 (2 << 6) +#define BIT_OBSERV_REST3 (3 << 6) + +static i2c_inst_t *i2c_instances[2] = { i2c0, i2c1 }; + +static struct +{ + struct touch_callback *callbacks; + i2c_inst_t *i2c; +} self; + +static uint8_t read_register8(uint8_t reg) +{ + uint8_t val; + + i2c_write_blocking(self.i2c, DEV_ADDR, ®, sizeof(reg), true); + i2c_read_blocking(self.i2c, DEV_ADDR, &val, sizeof(val), false); + + return val; +} + +//static void write_register8(uint8_t reg, uint8_t val) +//{ +// uint8_t buffer[2] = { reg, val }; +// i2c_write_blocking(self.i2c, DEV_ADDR, buffer, sizeof(buffer), false); +//} + +void touchpad_gpio_irq(uint gpio, uint32_t events) +{ + if (gpio != PIN_TP_MOTION) + return; + + if (!(events & GPIO_IRQ_EDGE_FALL)) + return; + + const uint8_t motion = read_register8(REG_MOTION); + if (motion & BIT_MOTION_MOT) { + int8_t x = read_register8(REG_DELTA_X); + int8_t y = read_register8(REG_DELTA_Y); + + x = ((x < 127) ? x : (x - 256)) * -1; + y = ((y < 127) ? y : (y - 256)); + + if (self.callbacks) { + struct touch_callback *cb = self.callbacks; + + while (cb) { + cb->func(x, y); + + cb = cb->next; + } + } + } +} + +void touchpad_add_touch_callback(struct touch_callback *callback) +{ + // first callback + if (!self.callbacks) { + self.callbacks = callback; + return; + } + + // find last and insert after + struct touch_callback *cb = self.callbacks; + while (cb->next) + cb = cb->next; + + cb->next = callback; +} + +void touchpad_init(void) +{ + // determine the instance based on SCL pin, hope you didn't screw up the SDA pin! + self.i2c = i2c_instances[(PIN_SCL / 2) % 2]; + + i2c_init(self.i2c, 100 * 1000); + + gpio_set_function(PIN_SDA, GPIO_FUNC_I2C); + gpio_pull_up(PIN_SDA); + + gpio_set_function(PIN_SCL, GPIO_FUNC_I2C); + gpio_pull_up(PIN_SCL); + + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PIN_SDA, PIN_SCL, GPIO_FUNC_I2C)); + + gpio_init(PIN_TP_SHUTDOWN); + gpio_set_dir(PIN_TP_SHUTDOWN, GPIO_OUT); + gpio_put(PIN_TP_SHUTDOWN, 0); + + gpio_init(PIN_TP_MOTION); + gpio_set_dir(PIN_TP_MOTION, GPIO_IN); + gpio_set_irq_enabled(PIN_TP_MOTION, GPIO_IRQ_EDGE_FALL, true); + + gpio_init(PIN_TP_RESET); + gpio_set_dir(PIN_TP_RESET, GPIO_OUT); + + gpio_put(PIN_TP_RESET, 0); + sleep_ms(100); + gpio_put(PIN_TP_RESET, 1); +} diff --git a/app/touchpad.h b/app/touchpad.h new file mode 100644 index 0000000..48cc68c --- /dev/null +++ b/app/touchpad.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +struct touch_callback +{ + void (*func)(int8_t, int8_t); + struct touch_callback *next; +}; + +void touchpad_gpio_irq(uint gpio, uint32_t events); + +void touchpad_add_touch_callback(struct touch_callback *callback); + +void touchpad_init(void); diff --git a/app/tusb_config.h b/app/tusb_config.h new file mode 100644 index 0000000..ef4ee2f --- /dev/null +++ b/app/tusb_config.h @@ -0,0 +1,31 @@ +#pragma once + +enum +{ + USB_ITF_KEYBOARD = 0, + USB_ITF_MOUSE, + USB_ITF_CDC, + USB_ITF_CDC2, + USB_ITF_MAX, +}; + +#define BOARD_DEVICE_RHPORT_NUM 0 +#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +#define CFG_TUD_HID 2 +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +#define CFG_TUD_HID_EP_BUFSIZE 8 + +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 diff --git a/app/usb.c b/app/usb.c new file mode 100644 index 0000000..c0981b0 --- /dev/null +++ b/app/usb.c @@ -0,0 +1,147 @@ +#include "usb.h" + +#include "keyboard.h" +#include "touchpad.h" +#include "reg.h" + +#include +#include +#include + +#define USB_LOW_PRIORITY_IRQ 31 +#define USB_TASK_INTERVAL_US 1000 + +static struct +{ + mutex_t mutex; + bool mouse_moved; + uint8_t mouse_btn; +} self; + +// TODO: Should mods always be sent? +// TODO: What about Ctrl? +// TODO: What should L1, L2, R1, R2 do +// TODO: Should touch send arrow keys as an option? + +static void low_priority_worker_irq(void) +{ + if (mutex_try_enter(&self.mutex, NULL)) { + tud_task(); + + mutex_exit(&self.mutex); + } +} + +static int64_t timer_task(alarm_id_t id, void *user_data) +{ + (void)id; + (void)user_data; + + irq_set_pending(USB_LOW_PRIORITY_IRQ); + + return USB_TASK_INTERVAL_US; +} + +static void key_cb(char key, enum key_state state) +{ + if (tud_hid_n_ready(USB_ITF_KEYBOARD) && reg_is_bit_set(REG_ID_CF2, CF2_USB_KEYB_ON)) { + uint8_t const conv_table[128][2] = { HID_ASCII_TO_KEYCODE }; + + uint8_t keycode[6] = { 0 }; + uint8_t modifier = 0; + + if (state == KEY_STATE_PRESSED) { + if (conv_table[(int)key][0]) + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + + keycode[0] = conv_table[(int)key][1]; + + // Fixup: Enter instead of Return + if (key == '\n') + keycode[0] = HID_KEY_ENTER; + } + + tud_hid_n_keyboard_report(USB_ITF_KEYBOARD, 0, modifier, keycode); + } + + if (tud_hid_n_ready(USB_ITF_MOUSE) && reg_is_bit_set(REG_ID_CF2, CF2_USB_MOUSE_ON)) { + if (key == KEY_JOY_CENTER) { + if (state == KEY_STATE_PRESSED) { + self.mouse_btn = MOUSE_BUTTON_LEFT; + self.mouse_moved = false; + tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, MOUSE_BUTTON_LEFT, 0, 0, 0, 0); + } else if ((state == KEY_STATE_HOLD) && !self.mouse_moved) { + self.mouse_btn = MOUSE_BUTTON_RIGHT; + tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, MOUSE_BUTTON_RIGHT, 0, 0, 0, 0); + } else if (state == KEY_STATE_RELEASED) { + self.mouse_btn = 0x00; + tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, 0x00, 0, 0, 0, 0); + } + } + } +} +static struct key_callback key_callback = +{ + .func = key_cb +}; + +static void touch_cb(int8_t x, int8_t y) +{ + if (!tud_hid_n_ready(USB_ITF_MOUSE) || !reg_is_bit_set(REG_ID_CF2, CF2_USB_MOUSE_ON)) + return; + + self.mouse_moved = true; + + tud_hid_n_mouse_report(USB_ITF_MOUSE, 0, self.mouse_btn, x, y, 0, 0); +} +static struct touch_callback touch_callback = +{ + .func = touch_cb +}; + +uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) +{ + // TODO not Implemented + (void)itf; + (void)report_id; + (void)report_type; + (void)buffer; + (void)reqlen; + + printf("%s: itf: %d, report id: %d, type: %d, len: %d\r\n", __func__, itf, report_id, report_type, reqlen); + + return 0; +} + +void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) +{ + // TODO set LED based on CAPLOCK, NUMLOCK etc... + (void)itf; + (void)report_id; + (void)report_type; + (void)buffer; + (void)bufsize; + + printf("%s: itf: %d, report id: %d, type: %d, size: %d\r\n", __func__, itf, report_id, report_type, bufsize); +} + +void usb_init(void) +{ + tusb_init(); + + keyboard_add_key_callback(&key_callback); + + touchpad_add_touch_callback(&touch_callback); + + // create a new interrupt to call tud_task, and trigger that irq from a timer + irq_set_exclusive_handler(USB_LOW_PRIORITY_IRQ, low_priority_worker_irq); + irq_set_enabled(USB_LOW_PRIORITY_IRQ, true); + + mutex_init(&self.mutex); + add_alarm_in_us(USB_TASK_INTERVAL_US, timer_task, NULL, true); +} + +mutex_t *usb_get_mutex(void) +{ + return &self.mutex; +} diff --git a/app/usb.h b/app/usb.h new file mode 100644 index 0000000..d3cf559 --- /dev/null +++ b/app/usb.h @@ -0,0 +1,7 @@ +#pragma once + +typedef struct mutex mutex_t; + +mutex_t *usb_get_mutex(void); + +void usb_init(void); diff --git a/app/usb_descriptors.c b/app/usb_descriptors.c new file mode 100644 index 0000000..9d9ead7 --- /dev/null +++ b/app/usb_descriptors.c @@ -0,0 +1,117 @@ +#include + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) + +#define EPNUM_HID_KEYBOARD 0x81 +#define EPNUM_HID_MOUSE 0x82 + +#define EPNUM_CDC_CMD 0x83 +#define EPNUM_CDC_IN 0x84 +#define EPNUM_CDC_OUT 0x02 + +#define CDC_CMD_MAX_SIZE 8 +#define CDC_IN_OUT_MAX_SIZE 64 + +static uint16_t temp_string[32]; + +char const *string_descriptors[] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "Solder Party", // 1: Manufacturer + USB_PRODUCT, // 2: Product + "123456", // 3: Serials, should use chip ID + "Keyboard Interface", // 4: Interface 1 String + "Mouse Interface", // 5: Interface 2 String + "Board CDC", // 6: Interface 3 String +}; + +tusb_desc_device_t const device_descriptor = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x1000, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +uint8_t const hid_keyboard_descriptor[] = +{ + TUD_HID_REPORT_DESC_KEYBOARD() +}; + +uint8_t const hid_mouse_descriptor[] = +{ + TUD_HID_REPORT_DESC_MOUSE() +}; + +uint8_t const config_descriptor[] = +{ + TUD_CONFIG_DESCRIPTOR(1, USB_ITF_MAX, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + TUD_HID_DESCRIPTOR(USB_ITF_KEYBOARD, 4, HID_ITF_PROTOCOL_NONE, sizeof(hid_keyboard_descriptor), EPNUM_HID_KEYBOARD, CFG_TUD_HID_EP_BUFSIZE, 10), + TUD_HID_DESCRIPTOR(USB_ITF_MOUSE, 5, HID_ITF_PROTOCOL_NONE, sizeof(hid_mouse_descriptor), EPNUM_HID_MOUSE, CFG_TUD_HID_EP_BUFSIZE, 10), + + TUD_CDC_DESCRIPTOR(USB_ITF_CDC, 6, EPNUM_CDC_CMD, CDC_CMD_MAX_SIZE, EPNUM_CDC_OUT, EPNUM_CDC_IN, CDC_IN_OUT_MAX_SIZE), +}; + +uint8_t const *tud_descriptor_device_cb(void) +{ + return (uint8_t const*)&device_descriptor; +} + +uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) +{ + if (itf == USB_ITF_KEYBOARD) + return hid_keyboard_descriptor; + + if (itf == USB_ITF_MOUSE) + return hid_mouse_descriptor; + + return NULL; +} + +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; + + return config_descriptor; +} + +uint16_t const *tud_descriptor_string_cb(uint8_t idx, uint16_t langid) +{ + (void) langid; + + if (idx == 0) { + temp_string[0] = (TUSB_DESC_STRING << 8 ) | (2 * sizeof(uint16_t)); + memcpy(&temp_string[1], string_descriptors[0], 2); + return temp_string; + } + + if (!(idx < sizeof(string_descriptors) / sizeof(string_descriptors[0]))) + return NULL; + + const char *str = string_descriptors[idx]; + uint8_t size = strlen(str); + if (size > 31) + size = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i = 0; i < size; ++i) + temp_string[1 + i] = str[i]; + + temp_string[0] = (TUSB_DESC_STRING << 8 ) | ((size + 1) * sizeof(uint16_t)); + + return temp_string; +} diff --git a/boards/bbq20kbd_breakout.h b/boards/bbq20kbd_breakout.h new file mode 100644 index 0000000..31cd12c --- /dev/null +++ b/boards/bbq20kbd_breakout.h @@ -0,0 +1,54 @@ +#pragma once + +#define USB_PID 0x4009 +#define USB_VID 0xABBA +#define USB_PRODUCT "BBQ20KBD" + +#define PIN_INT 0 +#define PIN_BKL 25 + +#define PIN_SDA 18 +#define PIN_SCL 23 + +#define PIN_TP_RESET 16 +#define PIN_TP_MOTION 22 +#define PIN_TP_SHUTDOWN 24 + +#define PIN_PUPPET_SDA 28 +#define PIN_PUPPET_SCL 29 + +#define NUM_OF_ROWS 7 +#define PINS_ROWS \ + 1, \ + 2, \ + 3, \ + 4, \ + 5, \ + 6, \ + 7 + +#define NUM_OF_COLS 6 +#define PINS_COLS \ + 8, \ + 9, \ + 14, \ + 13, \ + 12, \ + 11 + +#define NUM_OF_BTNS 1 +#define PINS_BTNS \ + 10, +#define BTN_KEYS \ + { KEY_BTN_RIGHT2 }, + +#define PIN_GPIOEXP0 15 +#define PIN_GPIOEXP1 17 +#define PIN_GPIOEXP2 19 +#define PIN_GPIOEXP3 21 +#define PIN_GPIOEXP4 26 + +#define PICO_DEFAULT_UART 1 +#define PICO_DEFAULT_UART_TX_PIN 20 + +//{ MP_ROM_QSTR(MP_QSTR_MIC), MP_ROM_PTR(&pin_GPIO27) },