summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2014-02-22 20:28:51 +0100
committerAmaury Pouly <amaury.pouly@gmail.com>2014-02-22 20:28:51 +0100
commit82b86d4316d0e9d74c5ea086797750b0975e9023 (patch)
tree5033684acb7a78f79e1cf9907e836a719bcf4020
parentc02bc1afd2a2400a41a863918bfe4eaf0255d60d (diff)
downloadrockbox-82b86d4.tar.gz
rockbox-82b86d4.zip
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
-rw-r--r--firmware/SOURCES1
-rw-r--r--firmware/target/arm/imx233/button-imx233.c261
-rw-r--r--firmware/target/arm/imx233/button-imx233.h174
3 files changed, 436 insertions, 0 deletions
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 <stdlib.h>
+
+#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