From 82b86d4316d0e9d74c5ea086797750b0975e9023 Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Sat, 22 Feb 2014 20:28:51 +0100 Subject: imx233: introduce new generic button driver This driver will subsume the old button-lradc driver and support far more options. It can sense LRADC channels, PSWITCH, GPIOs and it handles special "buttons" like headphone insertion and hold detection. It also provides a more natural description of the buttons using a target-defined table with some macros to make it easy to read and write. It uniformely handles debouncing on LRADC channels and PSWITCH. Change-Id: Ie61d1f593fdcf3bd456ba1d53a1fd784286834ce --- firmware/SOURCES | 1 + firmware/target/arm/imx233/button-imx233.c | 261 +++++++++++++++++++++++++++++ firmware/target/arm/imx233/button-imx233.h | 174 +++++++++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 firmware/target/arm/imx233/button-imx233.c create mode 100644 firmware/target/arm/imx233/button-imx233.h (limited to 'firmware') diff --git a/firmware/SOURCES b/firmware/SOURCES index 38e4bef0bb..aec0884c0c 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -561,6 +561,7 @@ target/arm/imx233/adc-imx233.c target/arm/imx233/lradc-imx233.c target/arm/imx233/pwm-imx233.c target/arm/imx233/rtc-imx233.c +target/arm/imx233/button-imx233.c #if IMX233_SUBTARGET >= 3700 target/arm/imx233/dcp-imx233.c #endif diff --git a/firmware/target/arm/imx233/button-imx233.c b/firmware/target/arm/imx233/button-imx233.c new file mode 100644 index 0000000000..4ebba41ad3 --- /dev/null +++ b/firmware/target/arm/imx233/button-imx233.c @@ -0,0 +1,261 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2013 by Amaury Pouly + * + * 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 + +#include "cpu.h" +#include "kernel.h" +#include "button-imx233.h" +#include "lradc-imx233.h" +#include "pinctrl-imx233.h" +#include "power-imx233.h" +#include "backlight.h" + +static int delay_chan = -1; /* delay channel used to trigger */ +static int raw_val[LRADC_NUM_CHANNELS]; /* channel values sampled (last two) */ +static int chan_mask; /* trigger channel mask */ +static int irq_chan_mask; /* triggered channel mask */ +static int src_map[LRADC_NUM_SOURCES]; /* physical -> virtual channel map */ +static int src_mask; /* sampled source mask */ +#ifdef HAS_BUTTON_HOLD +static int hold_idx = -1; /* index of hold button in map */ +#endif +#ifdef HAVE_HEADPHONE_DETECTION +static int jack_idx = -1; /* index of jack detect in map */ +#endif + +/* shortcut of button map */ +#define MAP imx233_button_map + +/* sample rate for LRADC */ +#define RATE HZ +/* number of samples per irq */ +#define SAMPLES 10 +/* delay's delay */ +#define DELAY (LRADC_DELAY_FREQ / RATE / SAMPLES) + +/* correct value for channel with builtin dividers */ +static int correct_lradc(int src, int raw) +{ + if(src == LRADC_SRC_VDDIO) + return raw * 2; + else + return raw; +} + +/* return raw value for the button */ +int imx233_button_read_raw(int idx) +{ + if(MAP[idx].periph == IMX233_BUTTON_GPIO) + return imx233_pinctrl_get_gpio(MAP[idx].u.gpio.bank, MAP[idx].u.gpio.pin); + else if(MAP[idx].periph == IMX233_BUTTON_LRADC) + return correct_lradc(MAP[idx].u.lradc.src, raw_val[src_map[MAP[idx].u.lradc.src]]); + else if(MAP[idx].periph == IMX233_BUTTON_PSWITCH) + return imx233_power_read_pswitch(); + else + return -1; +} + +/* return cooked (interpreted) value for the button, ignoring debouncing */ +static bool imx233_button_read_cooked(int idx) +{ + int raw = imx233_button_read_raw(idx); + bool res; + if(MAP[idx].periph == IMX233_BUTTON_GPIO) + { + res = raw; + } + else if(MAP[idx].periph == IMX233_BUTTON_LRADC) + { + /* correct value in relative mode */ + int rel = MAP[idx].u.lradc.relative; + if(rel != -1) + raw = (raw * MAP[rel].u.lradc.value) / imx233_button_read_raw(rel); + res = abs(raw - MAP[idx].u.lradc.value) <= 30; + } + else if(MAP[idx].periph == IMX233_BUTTON_PSWITCH) + { + res = raw == MAP[idx].u.pswitch.level; + } + else + res = false; + /* handle inversion */ + if(MAP[idx].flags & IMX233_BUTTON_INVERTED) + res = !res; + return res; +} + +/* finish round */ +static void do_round(void) +{ + for(int i = 0; MAP[i].btn != IMX233_BUTTON_END; i++) + { + bool cooked = imx233_button_read_cooked(i); + if(MAP[i].last_val == cooked) + MAP[i].rounds = MIN(MAP[i].rounds + 1, MAP[i].threshold); + else + MAP[i].rounds = 1; + MAP[i].last_val = cooked; + } +} + +/* process IRQ */ +static void button_lradc_irq(int chan) +{ + /* read value */ + raw_val[chan] = imx233_lradc_read_channel(chan) / SAMPLES; + imx233_lradc_clear_channel(chan); + imx233_lradc_setup_sampling(chan, true, SAMPLES - 1); + /* record irq, trigger delay if all IRQs have been fired */ + irq_chan_mask |= 1 << chan; + if(irq_chan_mask == chan_mask) + { + irq_chan_mask = 0; + do_round(); + imx233_lradc_setup_delay(delay_chan, chan_mask, 0, SAMPLES - 1, DELAY); + imx233_lradc_kick_delay(delay_chan); + } +} + +bool imx233_button_read_btn(int idx) +{ + return MAP[idx].rounds >= MAP[idx].threshold ? MAP[idx].last_val : false; +} + +int imx233_button_read(int others) +{ + int res = others; +#ifdef HAS_BUTTON_HOLD + if(imx233_button_read_hold()) + return 0; +#endif + for(int i = 0; MAP[i].btn != IMX233_BUTTON_END; i++) + { + if(MAP[i].btn >= 0 && imx233_button_read_btn(i)) + res |= MAP[i].btn; + } + return res; +} + +#ifdef HAS_BUTTON_HOLD +bool imx233_button_read_hold(void) +{ + return imx233_button_read_btn(hold_idx); +} + +bool __attribute__((weak)) button_hold(void) +{ + bool hold_button = imx233_button_read_hold(); +#ifndef BOOTLOADER + static bool hold_button_old = false; + /* light handling */ + if (hold_button != hold_button_old) + { + hold_button_old = hold_button; + backlight_hold_changed(hold_button); + } +#endif /* BOOTLOADER */ + return hold_button; +} +#endif + +#ifdef HAVE_HEADPHONE_DETECTION +bool imx233_button_read_jack(void) +{ + return imx233_button_read_btn(jack_idx); +} + +bool __attribute__((weak)) headphones_inserted(void) +{ + return imx233_button_read_jack(); +} +#endif + +/* return number of debouncing rounds by type of peripheral */ +static int threshold_by_periph(int periph) +{ + if(periph == IMX233_BUTTON_GPIO) return 1; // no debouncing + if(periph == IMX233_BUTTON_LRADC) return 2; // 2 times at HZ gives ~10 ms hold + if(periph == IMX233_BUTTON_PSWITCH) return 10; // PSWITCH is very slow to ramp + return 1; // other ? +} + +void imx233_button_init(void) +{ + /* go through the table and init stuff which needs to be */ + for(int i = 0; MAP[i].btn != IMX233_BUTTON_END; i++) + { + MAP[i].threshold = threshold_by_periph(MAP[i].periph); + if(MAP[i].periph == IMX233_BUTTON_GPIO) + { + imx233_pinctrl_acquire(MAP[i].u.gpio.bank, MAP[i].u.gpio.pin, MAP[i].name); + imx233_pinctrl_set_function(MAP[i].u.gpio.bank, MAP[i].u.gpio.pin, PINCTRL_FUNCTION_GPIO); + imx233_pinctrl_enable_gpio(MAP[i].u.gpio.bank, MAP[i].u.gpio.pin, false); + bool pullup = !!(MAP[i].flags & IMX233_BUTTON_PULLUP); + imx233_pinctrl_enable_pullup(MAP[i].u.gpio.bank, MAP[i].u.gpio.pin, pullup); + } + else if(MAP[i].periph == IMX233_BUTTON_LRADC) + { + int src = MAP[i].u.lradc.src; + /* if channel was already acquired, there is nothing to do */ + if(src_mask & (1 << src)) + continue; + src_map[src] = imx233_lradc_acquire_channel(LRADC_SRC(src), TIMEOUT_NOBLOCK); + if(src_map[src] < 0) + panicf("Cannot get channel for %s", MAP[i].name); + imx233_lradc_setup_source(src_map[src], true, src); + imx233_lradc_setup_sampling(src_map[src], true, SAMPLES - 1); + imx233_lradc_enable_channel_irq(src_map[src], true); + imx233_lradc_set_channel_irq_callback(src_map[src], button_lradc_irq); + src_mask |= 1 << src; + chan_mask |= 1 << src_map[src]; + } +#ifdef HAS_BUTTON_HOLD + if(MAP[i].btn == IMX233_BUTTON_HOLD) + hold_idx = i; +#endif +#ifdef HAVE_HEADPHONE_DETECTION + if(MAP[i].btn == IMX233_BUTTON_JACK) + jack_idx = i; +#endif + } +#ifdef HAS_BUTTON_HOLD + if(hold_idx == -1) + panicf("No hold entry found"); +#endif +#ifdef HAVE_HEADPHONE_DETECTION + if(jack_idx == -1) + panicf("No jack entry found"); +#endif + /* create delay channel if necessary + * NOTE other buttons are polled as part of the delay irq processing */ + if(src_mask != 0) + { + delay_chan = imx233_lradc_acquire_delay(TIMEOUT_NOBLOCK); + if(delay_chan < 0) + panicf("Cannot get delay channel"); + imx233_lradc_setup_delay(delay_chan, chan_mask, 0, SAMPLES - 1, DELAY); + imx233_lradc_kick_delay(delay_chan); + } + /* otherwise we need to regularly poll for other buttons */ + else + tick_add_task(do_round); +} \ No newline at end of file diff --git a/firmware/target/arm/imx233/button-imx233.h b/firmware/target/arm/imx233/button-imx233.h new file mode 100644 index 0000000000..61adff8436 --- /dev/null +++ b/firmware/target/arm/imx233/button-imx233.h @@ -0,0 +1,174 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Amaury Pouly + * + * 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 __button_imx233__ +#define __button_imx233__ + +#include "button.h" +#include "button-target.h" +#include "lradc-imx233.h" +#include "pinctrl-imx233.h" +#include "power-imx233.h" + +/* special values for .btn field */ +#define IMX233_BUTTON_END -1 /* end of the list */ +#define IMX233_BUTTON_HOLD -2 /* HOLD button */ +#define IMX233_BUTTON_JACK -3 /* JACK detect */ +#define IMX233_BUTTON_VDDIO -4 /* VDDIO value */ + +/* values for .periph field */ +#define IMX233_BUTTON_GPIO 0 /* GPIO pin */ +#define IMX233_BUTTON_LRADC 1 /* LRADC channel */ +#define IMX233_BUTTON_PSWITCH 2 /* PSWITCH */ + +/* values for the .flags field */ +#define IMX233_BUTTON_INVERTED (1 << 0) /* invert button detection */ +#define IMX233_BUTTON_PULLUP (1 << 1) /* pin needs a pullup (GPIO) */ + +/** target-defined + * NOTE for proper operation: + * - the table must end which a dummy entry of type IMX233_BUTTON_END + * - default is active: high for GPIO, around value for LRADC, exact value for PSWITCH + * - inverted flags reverses the above behaviour + * - if lradc channel is relative, the .relative field specify a entry in the + * button table to which it is relative. This entry must have <0 .btn value + * and be of type LRADC. For example, if a channel is VDDIO relative, set + * .relative to IMX233_BUTTON_VDDIO and create an entry which .btn set to + * IMX233_BUTTON_VDDIO, channel set to VDDIO source and value set to the ideal values + * for which no correction is necessary. Note that the IMX233_BUTTON_VDDIO value is + * defined for convenience */ +struct imx233_button_map_t +{ + int btn; /* target button or magic value (END, HOLD, JACK) */ + int periph; /* GPIO, LRADC, or PSWITCH, ... */ + union + { + struct + { + int bank; + int pin; + }gpio; + struct + { + int src; /* source channel */ + int value; /* expected value */ + int relative; /* button to which it is relative or -1 if none */ + }lradc; + struct + { + int level; /* value of the PSWITCH field: low, mid, high */ + }pswitch; + }u; + int flags; /* flags */ + const char *name; /* name of the button */ + /** private fields */ + bool last_val; /* last interpreted value */ + int rounds; /* how many rounds it survived (debouncing) */ + int threshold; /* round threshold for acceptance */ +}; + +/* macros for the common cases */ +#define IMX233_BUTTON_PATH_GPIO(bank_, pin_) .periph = IMX233_BUTTON_GPIO, \ + .u = {.gpio = {.bank = bank_, .pin = pin_}} +#define IMX233_BUTTON_PATH_LRADC_REL(src_, val_, rel_) .periph = IMX233_BUTTON_LRADC, \ + .u = {.lradc = {.src = src_, .value = val_, .relative = rel_}} +#define IMX233_BUTTON_PATH_LRADC(src_, val_) IMX233_BUTTON_PATH_LRADC_REL(src_, val_, -1) +#define IMX233_BUTTON_PATH_PSWITCH(lvl_) .periph = IMX233_BUTTON_PSWITCH, \ + .u = {.pswitch = {.level = lvl_}} +#define IMX233_BUTTON_PATH_END() .periph = -1 +/* special macro for VDDIO, for convenience */ +#define IMX233_BUTTON_PATH_VDDIO(val_) IMX233_BUTTON_PATH_LRADC(LRADC_SRC_VDDIO, val_) + +#define IMX233_BUTTON_NAMEFLAGS1(_,name_) .name = name_ +#define IMX233_BUTTON_NAMEFLAGS2(_,name_,f1) .name = name_, .flags = IMX233_BUTTON_##f1 +#define IMX233_BUTTON_NAMEFLAGS3(_,name_,f1,f2) .name = name_, \ + .flags = IMX233_BUTTON_##f1 | IMX233_BUTTON_##f2 +#define IMX233_BUTTON_NAMEFLAGS4(_,name_,f1,f2,f3) .name = name_, \ + .flags =IMX233_BUTTON_##f1 | IMX233_BUTTON_##f2 | IMX233_BUTTON_##f3 + +#define IMX233_BUTTON__(btn_, path_, ...) \ + {.btn = btn_, IMX233_BUTTON_PATH_##path_, \ + REG_VARIADIC(IMX233_BUTTON_NAMEFLAGS, dummy, __VA_ARGS__)} +#define IMX233_BUTTON_(btn_, path_, ...) \ + IMX233_BUTTON__(IMX233_BUTTON_##btn_, path_, __VA_ARGS__) +#define IMX233_BUTTON(btn_, path_, ...) \ + IMX233_BUTTON__(BUTTON_##btn_, path_, __VA_ARGS__) + +#define IMX233_BUTTON_END_() \ + {.btn = IMX233_BUTTON_END} + +/** + * This header provides macro to ease definition of buttons. There three types of + * buttons: + * - normal buttons: BUTTON_X -> defined using IMX233_BUTTON(X, ...) + * - special buttons IMX233_BUTTON_X -> defined using IMX233_BUTTON_(X, ...) + * - others: X -> defined using IMX233_BUTTON__(X, ...) + * A definition contains three mandatory parts and a fourth optional: + * IMX233_BUTTON(btn, path_spec, name [, flags]) + * The path_spec can be of the form: + * - GPIO(bank, pin) + * - LRADC(src, value) + * - LRADC_REL(src, value, rel_idx) + * - PSWITCH(level) + * - END() + * The optional flags are specified as a list, without the IMX233_BUTTON_ prefix. + * Examples: + * - BUTTON_POWER named "power", mapped to PSWITCH mid level, no flags: + * IMX233_BUTTON(POWER, PSWICTH(1), "power") + * - IMX233_BUTTON_JACK named "jack", mapped to GPIO B2P17 inverted and pullup: + * IMX233_BUTTON_(JACK, GPIO(2, 17), "jack", INVERTED, PULLUP) + * - BIZARRE named "bizarre", mapped to LRADC channel 0, expected value 42: + * IMX233_BUTTON__(BIZARRE, LRADC(0, 42), "bizarre") + * - BUTTON_PLAY named "play", mapped to LRADc channel 2, expected value 300 related + * to button entry IDX + * IMX233_BUTTON(PLAY, LRADC_REL(2, 300, IDX), "play") + * - VDDIO entry, expected value 4000 (for relative entries): + * IMX233_BUTTON_(VDDIO, VDDIO(4000), "vddio") + * - last entry: + * IMX233_BUTTON_(END, END(), "") + * + * The driver also provides default implementations for headphones_inserted() + * and button_hold() which can be overriden since they have weak linkage. + */ + +extern struct imx233_button_map_t imx233_button_map[]; + +/* initialise button driver */ +void imx233_button_init(void); +/* others gives the bitmask of other buttons: the function will OR the result + * with them except if hold is detected in which case 0 is always returned */ +int imx233_button_read(int others); +#ifdef HAS_BUTTON_HOLD +bool imx233_button_read_hold(void); +bool button_hold(void); +#endif +#ifdef HAVE_HEADPHONE_DETECTION +bool imx233_button_read_jack(void); +bool headphones_inserted(void); +#endif +/* return interpreted button value, after processing. + * Argument is an index into the imx233_button_map table */ +bool imx233_button_read_btn(int idx); +/* return raw value for a button, before any processing: GPIO state, LRADC + * channel value, PSWITCH value. Return -1 if there is no match. + * Argument is an index into the imx233_button_map table */ +int imx233_button_read_raw(int idx); + +#endif /* __button_imx233__ */ \ No newline at end of file -- cgit v1.2.3