diff --git a/firmware/SOURCES b/firmware/SOURCES index d28c189213..0a6d4d30c3 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -1928,13 +1928,18 @@ target/hosted/sdl/filesystem-sdl.c drivers/touchpad.c #endif +/* Hardware drivers */ +#ifndef SIMULATOR #ifdef HAVE_I2C_ASYNC drivers/i2c-async.c #endif - #ifdef HAVE_AXP_PMU drivers/axp-pmu.c #endif +#ifdef HAVE_FT6x06 +drivers/ft6x06.c +#endif +#endif /* firmware/kernel section */ #ifdef HAVE_CORELOCK_OBJECT diff --git a/firmware/drivers/ft6x06.c b/firmware/drivers/ft6x06.c new file mode 100644 index 0000000000..538ca10480 --- /dev/null +++ b/firmware/drivers/ft6x06.c @@ -0,0 +1,115 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "ft6x06.h" +#include "kernel.h" +#include "i2c-async.h" +#include + +struct ft6x06_driver { + /* i2c bus data */ + int i2c_cookie; + i2c_descriptor i2c_desc; + + /* callback for touch events */ + ft6x06_event_cb event_cb; + + /* buffer for I2C transfers */ + uint8_t raw_data[6]; +}; + +static struct ft6x06_driver ft_drv; +struct ft6x06_state ft6x06_state; + +static void ft6x06_i2c_callback(int status, i2c_descriptor* desc) +{ + (void)desc; + if(status != I2C_STATUS_OK) + return; + + int evt = ft_drv.raw_data[1] >> 6; + int tx = ft_drv.raw_data[2] | ((ft_drv.raw_data[1] & 0xf) << 8); + int ty = ft_drv.raw_data[4] | ((ft_drv.raw_data[3] & 0xf) << 8); + + ft6x06_state.event = evt; +#ifdef FT6x06_SWAP_AXES + ft6x06_state.pos_x = ty; + ft6x06_state.pos_y = tx; +#else + ft6x06_state.pos_x = tx; + ft6x06_state.pos_y = ty; +#endif + + ft_drv.event_cb(evt, ft6x06_state.pos_x, ft6x06_state.pos_y); +} + +static void ft6x06_dummy_event_cb(int evt, int tx, int ty) +{ + (void)evt; + (void)tx; + (void)ty; +} + +void ft6x06_init(void) +{ + /* Initialize stuff */ + memset(&ft_drv, 0, sizeof(ft_drv)); + ft_drv.event_cb = ft6x06_dummy_event_cb; + + ft6x06_state.event = FT6x06_EVT_NONE; + ft6x06_state.pos_x = 0; + ft6x06_state.pos_y = 0; + + /* Reserve bus management cookie */ + ft_drv.i2c_cookie = i2c_async_reserve_cookies(FT6x06_BUS, 1); + + /* Prep an I2C descriptor to read touch data */ + ft_drv.i2c_desc.slave_addr = FT6x06_ADDR; + ft_drv.i2c_desc.bus_cond = I2C_START | I2C_STOP; + ft_drv.i2c_desc.tran_mode = I2C_READ; + ft_drv.i2c_desc.buffer[0] = &ft_drv.raw_data[5]; + ft_drv.i2c_desc.count[0] = 1; + ft_drv.i2c_desc.buffer[1] = &ft_drv.raw_data[0]; + ft_drv.i2c_desc.count[1] = 5; + ft_drv.i2c_desc.callback = ft6x06_i2c_callback; + ft_drv.i2c_desc.arg = 0; + ft_drv.i2c_desc.next = NULL; + + /* Set I2C register address */ + ft_drv.raw_data[5] = 0x02; +} + +void ft6x06_set_event_cb(ft6x06_event_cb cb) +{ + ft_drv.event_cb = cb ? cb : ft6x06_dummy_event_cb; +} + +void ft6x06_enable(bool en) +{ + i2c_reg_write1(FT6x06_BUS, FT6x06_ADDR, 0xa5, en ? 0 : 3); +} + +void ft6x06_irq_handler(void) +{ + /* We don't care if this fails, there's not much we can do about it */ + i2c_async_queue(FT6x06_BUS, TIMEOUT_NOBLOCK, I2C_Q_ONCE, + ft_drv.i2c_cookie, &ft_drv.i2c_desc); +} diff --git a/firmware/export/config/fiiom3k.h b/firmware/export/config/fiiom3k.h index a28efd43a5..849aa9c0a6 100644 --- a/firmware/export/config/fiiom3k.h +++ b/firmware/export/config/fiiom3k.h @@ -22,6 +22,8 @@ /* Drivers */ #define HAVE_I2C_ASYNC +#define HAVE_FT6x06 +#define FT6x06_SWAP_AXES /* Buffer for plugins and codecs. */ #define PLUGIN_BUFFER_SIZE 0x200000 /* 2 MiB */ diff --git a/firmware/export/ft6x06.h b/firmware/export/ft6x06.h new file mode 100644 index 0000000000..de1fdd0979 --- /dev/null +++ b/firmware/export/ft6x06.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __FT6x06_H__ +#define __FT6x06_H__ + +#include "config.h" +#include + +typedef void(*ft6x06_event_cb)(int, int, int); + +struct ft6x06_state { + int event; + int pos_x; + int pos_y; +}; + +enum ft6x06_event { + FT6x06_EVT_NONE = -1, + FT6x06_EVT_PRESS = 0, + FT6x06_EVT_RELEASE = 1, + FT6x06_EVT_CONTACT = 2, +}; + +extern struct ft6x06_state ft6x06_state; + +void ft6x06_init(void); +void ft6x06_set_event_cb(ft6x06_event_cb fn); +void ft6x06_enable(bool en); +void ft6x06_irq_handler(void); + +#endif /* __FT6x06_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c index 4a853cd88f..4354257f7b 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c @@ -25,7 +25,9 @@ #include "powermgmt.h" #include "panic.h" #include "axp-pmu.h" +#include "ft6x06.h" #include "gpio-x1000.h" +#include "irq-x1000.h" #include "i2c-x1000.h" #include #include @@ -35,12 +37,6 @@ # include "font.h" #endif -/* Touch event types */ -#define EVENT_NONE (-1) -#define EVENT_PRESS 0 -#define EVENT_RELEASE 1 -#define EVENT_CONTACT 2 - /* FSM states */ #define STATE_IDLE 0 #define STATE_PRESS 1 @@ -66,17 +62,6 @@ /* Number of touch samples to smooth before reading */ #define TOUCH_SAMPLES 3 -static struct ft_driver { - int i2c_cookie; - i2c_descriptor i2c_desc; - uint8_t raw_data[6]; - bool active; - - /* Number of pixels squared which must be moved before - * a scrollbar pulse is generated */ - int scroll_thresh_sqr; -} ftd; - static struct ft_state_machine { /* Current button state, used by button_read_device() */ int buttons; @@ -105,6 +90,12 @@ static struct ft_state_machine { /* Current touch position */ int cur_x, cur_y; + + /* Motion threshold squared, in 'pixels', required to trigger scrolling */ + int scroll_thresh_sqr; + + /* Touchpad enabled state */ + bool active; } fsm; /* coordinates below this are the left hand buttons, @@ -210,9 +201,9 @@ static void ft_start_report_or_scroll(void) static void ft_step_state(uint32_t t, int evt, int tx, int ty) { /* Generate a release event automatically in case we missed it */ - if(evt == EVENT_NONE) { + if(evt == FT6x06_EVT_NONE) { if(TICKS_SINCE(t, fsm.last_event_t) >= AUTORELEASE_TIME) { - evt = EVENT_RELEASE; + evt = FT6x06_EVT_RELEASE; tx = fsm.cur_x; ty = fsm.cur_y; } @@ -220,7 +211,7 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) switch(fsm.state) { case STATE_IDLE: { - if(evt == EVENT_PRESS || evt == EVENT_CONTACT) { + if(evt == FT6x06_EVT_PRESS || evt == FT6x06_EVT_CONTACT) { /* Move to REPORT or PRESS state */ if(ft_accum_touch(t, tx, ty)) ft_start_report_or_scroll(); @@ -230,10 +221,10 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) } break; case STATE_PRESS: { - if(evt == EVENT_RELEASE) { + if(evt == FT6x06_EVT_RELEASE) { /* Ignore if the number of samples is too low */ ft_go_idle(); - } else if(evt == EVENT_PRESS || evt == EVENT_CONTACT) { + } else if(evt == FT6x06_EVT_PRESS || evt == FT6x06_EVT_CONTACT) { /* Accumulate the touch position in the filter */ if(ft_accum_touch(t, tx, ty)) ft_start_report_or_scroll(); @@ -241,14 +232,14 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) } break; case STATE_REPORT: { - if(evt == EVENT_RELEASE) + if(evt == FT6x06_EVT_RELEASE) ft_go_idle(); - else if(evt == EVENT_PRESS || evt == EVENT_CONTACT) + else if(evt == FT6x06_EVT_PRESS || evt == FT6x06_EVT_CONTACT) ft_accum_touch(t, tx, ty); } break; case STATE_SCROLL_PRESS: { - if(evt == EVENT_RELEASE) { + if(evt == FT6x06_EVT_RELEASE) { /* This _should_ synthesize a button press. * * - ft_start_report() will set the button bit based on the @@ -257,10 +248,10 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) * * - The next button_read_device() will see the button bit * and report it back to Rockbox, then step the FSM with - * EVENT_NONE. + * FT6x06_EVT_NONE. * - * - The EVENT_NONE stepping will eventually autogenerate a - * RELEASE event and restore the button state back to 0 + * - The FT6x06_EVT_NONE stepping will eventually autogenerate + * a RELEASE event and restore the button state back to 0 * * - There's a small logic hole in the REPORT state which * could cause it to miss an immediately repeated PRESS @@ -271,7 +262,7 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) break; } - if(evt == EVENT_PRESS || evt == EVENT_CONTACT) + if(evt == FT6x06_EVT_PRESS || evt == FT6x06_EVT_CONTACT) ft_accum_touch(t, tx, ty); int dx = fsm.cur_x - fsm.orig_x; @@ -289,21 +280,21 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) } break; case STATE_SCROLLING: { - if(evt == EVENT_RELEASE) { + if(evt == FT6x06_EVT_RELEASE) { ft_go_idle(); break; } - if(evt == EVENT_PRESS || evt == EVENT_CONTACT) + if(evt == FT6x06_EVT_PRESS || evt == FT6x06_EVT_CONTACT) ft_accum_touch(t, tx, ty); int dx = fsm.cur_x - fsm.orig_x; int dy = fsm.cur_y - fsm.orig_y; int dp = (dx*dx) + (dy*dy); - if(dp >= ftd.scroll_thresh_sqr) { + if(dp >= fsm.scroll_thresh_sqr) { /* avoid generating events if we're supposed to be inactive... * should not be necessary but better to be safe. */ - if(ftd.active) { + if(fsm.active) { if(dy < 0) { queue_post(&button_queue, BUTTON_SCROLL_BACK, 0); } else { @@ -327,18 +318,8 @@ static void ft_step_state(uint32_t t, int evt, int tx, int ty) } } -static void ft_i2c_callback(int status, i2c_descriptor* desc) +static void ft_event_cb(int evt, int tx, int ty) { - (void)desc; - if(status != I2C_STATUS_OK) - return; - - /* The panel is oriented such that its X axis is vertical, - * so swap the axes for reporting */ - int evt = ftd.raw_data[1] >> 6; - int ty = ftd.raw_data[2] | ((ftd.raw_data[1] & 0xf) << 8); - int tx = ftd.raw_data[4] | ((ftd.raw_data[3] & 0xf) << 8); - /* TODO: convert the touch positions to linear positions. * * Points reported by the touch controller are distorted and non-linear, @@ -346,36 +327,11 @@ static void ft_i2c_callback(int status, i2c_descriptor* desc) * the middle of the touchpad than on the edges, so scrolling feels slow * in the middle and faster near the edge. */ - ft_step_state(__ost_read32(), evt, tx, ty); } -/* ft6x06 interrupt pin */ -void GPIOB12(void) -{ - /* We don't care if this fails */ - i2c_async_queue(FT6x06_BUS, TIMEOUT_NOBLOCK, I2C_Q_ONCE, - ftd.i2c_cookie, &ftd.i2c_desc); -} - static void ft_init(void) { - /* Initialize the driver state */ - ftd.i2c_cookie = i2c_async_reserve_cookies(FT6x06_BUS, 1); - ftd.i2c_desc.slave_addr = FT6x06_ADDR; - ftd.i2c_desc.bus_cond = I2C_START | I2C_STOP; - ftd.i2c_desc.tran_mode = I2C_READ; - ftd.i2c_desc.buffer[0] = &ftd.raw_data[5]; - ftd.i2c_desc.count[0] = 1; - ftd.i2c_desc.buffer[1] = &ftd.raw_data[0]; - ftd.i2c_desc.count[1] = 5; - ftd.i2c_desc.callback = ft_i2c_callback; - ftd.i2c_desc.arg = 0; - ftd.i2c_desc.next = NULL; - ftd.raw_data[5] = 0x02; - ftd.active = true; - touchpad_set_sensitivity(DEFAULT_TOUCHPAD_SENSITIVITY_SETTING); - /* Initialize the state machine */ fsm.buttons = 0; fsm.state = STATE_IDLE; @@ -385,16 +341,24 @@ static void ft_init(void) fsm.sum_x = fsm.sum_y = 0; fsm.orig_x = fsm.orig_y = 0; fsm.cur_x = fsm.cur_y = 0; + fsm.active = true; + touchpad_set_sensitivity(DEFAULT_TOUCHPAD_SENSITIVITY_SETTING); /* Bring up I2C bus */ i2c_x1000_set_freq(FT6x06_BUS, I2C_FREQ_400K); + /* Driver init */ + ft6x06_init(); + ft6x06_set_event_cb(ft_event_cb); + /* Reset chip */ gpio_set_level(GPIO_FT6x06_RESET, 0); mdelay(5); gpio_set_level(GPIO_FT6x06_RESET, 1); /* Configure the interrupt pin */ + system_set_irq_handler(GPIO_TO_IRQ(GPIO_FT6x06_INTERRUPT), + ft6x06_irq_handler); gpio_set_function(GPIO_FT6x06_INTERRUPT, GPIOF_IRQ_EDGE(0)); gpio_enable_irq(GPIO_FT6x06_INTERRUPT); } @@ -403,13 +367,13 @@ void touchpad_set_sensitivity(int level) { int pixels = 40; pixels -= level; - ftd.scroll_thresh_sqr = pixels * pixels; + fsm.scroll_thresh_sqr = pixels * pixels; } void touchpad_enable_device(bool en) { - i2c_reg_write1(FT6x06_BUS, FT6x06_ADDR, 0xa5, en ? 0 : 3); - ftd.active = en; + ft6x06_enable(en); + fsm.active = en; } /* Value of headphone detect register */ @@ -467,7 +431,7 @@ void button_init_device(void) int button_read_device(void) { int r = fsm.buttons; - ft_step_state(__ost_read32(), EVENT_NONE, 0, 0); + ft_step_state(__ost_read32(), FT6x06_EVT_NONE, 0, 0); /* Read GPIOs for physical buttons */ uint32_t a = REG_GPIO_PIN(GPIO_A);