From 1aaecc7c3ee2098689f2c5a0e5833a66e4a7b8d7 Mon Sep 17 00:00:00 2001 From: Vincent-FK Date: Fri, 13 Sep 2019 03:13:58 +0800 Subject: [PATCH] bug correction: retry reading values if i2c read was unsuccessfull, do not loose interuption added files for AXP209 interrupt started mapping axp209 interrupt --- Makefile | 2 +- driver_axp209.c | 71 ++++++++++++++++++++ driver_axp209.h | 31 +++++++++ driver_axp209.o | Bin 0 -> 1936 bytes driver_pcal6416a.c | 24 ++++--- driver_pcal6416a.h | 12 ++-- driver_pcal6416a.o | Bin 2052 -> 2092 bytes funkey_gpio_management | Bin 30344 -> 32384 bytes gpio-utils.c | 29 +------- gpio_mapping.c | 146 +++++++++++++++++++++++++++++------------ gpio_mapping.h | 4 +- gpio_mapping.o | Bin 5408 -> 7592 bytes 12 files changed, 233 insertions(+), 86 deletions(-) create mode 100644 driver_axp209.c create mode 100644 driver_axp209.h create mode 100644 driver_axp209.o diff --git a/Makefile b/Makefile index 4cdcb4e..658b749 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ TOOLS_CFLAGS := -Wall -std=c99 -D _DEFAULT_SOURCE # all: funkey_gpio_management -funkey_gpio_management: funkey_gpio_management.o gpio-utils.o uinput.o gpio_mapping.o read_conf_file.o keydefs.o driver_pcal6416a.o smbus.o +funkey_gpio_management: funkey_gpio_management.o gpio-utils.o uinput.o gpio_mapping.o read_conf_file.o keydefs.o driver_pcal6416a.o driver_axp209.o smbus.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ diff --git a/driver_axp209.c b/driver_axp209.c new file mode 100644 index 0000000..a4b61ae --- /dev/null +++ b/driver_axp209.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "read_conf_file.h" +#include +#include +#include +#include "smbus.h" +#include "driver_axp209.h" + +/**************************************************************** + * Defines + ****************************************************************/ +#define DEBUG_AXP209_PRINTF (1) + +#if (DEBUG_AXP209_PRINTF) + #define DEBUG_PRINTF(...) printf(__VA_ARGS__); +#else + #define DEBUG_PRINTF(...) +#endif + +/**************************************************************** + * Static variables + ****************************************************************/ +static int fd_axp209; +static char i2c0_sysfs_filename[] = "/dev/i2c-0"; + +/**************************************************************** + * Static functions + ****************************************************************/ + +/**************************************************************** + * Public functions + ****************************************************************/ +bool axp209_init(void) { + if ((fd_axp209 = open(i2c0_sysfs_filename,O_RDWR)) < 0) { + printf("In axp209_init - Failed to open the I2C bus %s", i2c0_sysfs_filename); + // ERROR HANDLING; you can check errno to see what went wrong + return false; + } + + if (ioctl(fd_axp209, I2C_SLAVE, AXP209_I2C_ADDR) < 0) { + printf("In axp209_init - Failed to acquire bus access and/or talk to slave.\n"); + // ERROR HANDLING; you can check errno to see what went wrong + return false; + } + + return true; +} + +bool axp209_deinit(void) { + // Close I2C open interface + close(fd_axp209); + return true; +} + +int axp209_read_interrupt_bank_3(void){ + int val = i2c_smbus_read_byte_data ( fd_axp209 , AXP209_INTERRUPT_BANK_3_STATUS ); + if(val < 0){ + return val; + } + DEBUG_PRINTF("READ AXP209_INTERRUPT_BANK_3_STATUS: 0x%02X\n",val); + return 0xFF & val; +} \ No newline at end of file diff --git a/driver_axp209.h b/driver_axp209.h new file mode 100644 index 0000000..ae5cabf --- /dev/null +++ b/driver_axp209.h @@ -0,0 +1,31 @@ +#ifndef _DRIVER_AXP209_H_ +#define _DRIVER_AXP209_H_ + + + /**************************************************************** + * Includes + ****************************************************************/ +#include + + /**************************************************************** + * Defines + ****************************************************************/ +// Chip physical address +#define AXP209_I2C_ADDR 0x34 + +// Chip registers adresses +#define AXP209_INTERRUPT_BANK_3_STATUS 0x4A + +// Masks +#define AXP209_INTERRUPT_PEK_SHORT_PRESS 0x02 +#define AXP209_INTERRUPT_PEK_LONG_PRESS 0x01 + +/**************************************************************** + * Public functions + ****************************************************************/ +bool axp209_init(void); +bool axp209_deinit(void); +int axp209_read_interrupt_bank_3(void); + + +#endif //_DRIVER_AXP209_H_ diff --git a/driver_axp209.o b/driver_axp209.o new file mode 100644 index 0000000000000000000000000000000000000000..46e9c45b91178ad4bf15ec06d746cd1df73f679b GIT binary patch literal 1936 zcma)6L2nyH6n^Wq6UVJk328A2DodJ3p}`x+4N6-fsBz+w5~7eMs0XCYtapM}vDfbI zIEob{B2N4PB*cLe2W|u;q#me1{S6$dBBcI>a_NomzV%Mx8ma0_p5J`m`{wQJ%)I?_ zd9`X725B>BhE5UDQ6|o07UD2NXK92Ef2E&p{6q8yd+N>Or~e+Os*Fv>=J>WAP1I@j z%p*pzj-$bO18TZ-;qj>|&vVVY3zNv0Rhk_>nbXtKW8+f3dU&6HPo5h;XQH03B7d6u z>L^~-N%mH}viz8~ryLdmYJ?F{2IlojZ6p2;>OYFBx z#cPfq_{u6+RpB?JXQ`IeYRkY8@X=Ay(WR>r%<-}18F$&o*-@`EkUwDj4f}gZ{@Z~(d7-~2 zL#Njic8zwtWK2^Yanlr?s2lA>&JMRpAexfw)k+aR^EAcxVYO1CzJ?++5w$}TquRd&8TD;cG}8uMX>8!!IPb+$zABgdAF;i6JJGMKkmX@ zmWcnU$UiS}%)LT12A+>351u3L0`O#l-v;K3f%7eLLl_!s8DZXs;5mK@ z{yfn(__IVFc#d`O7l^(9&$>hK9KQz7i})_#zfbrd!Sg}g1J8^84LrvO;Ia4@v^Q9X zJEU(H-s8`{x!4~VdS!(D20QE_CmwRdHwN>e%d$@=Sa(a7ll*!ort8fz1HIObu}Zd@mLH9#%82@S=_8mItVfPqdE!vQSmy@XZzd`nyhMzFUYisKibnj@uQS0zc s(eHrtC@)lCkeJpX>UTg&F*srp-~Vj+DNg*%oHkwWKhp&uW&i*H literal 0 HcmV?d00001 diff --git a/driver_pcal6416a.c b/driver_pcal6416a.c index 83a5836..2e234bf 100644 --- a/driver_pcal6416a.c +++ b/driver_pcal6416a.c @@ -29,8 +29,8 @@ /**************************************************************** * Static variables ****************************************************************/ -int fd_i2c_expander; -char i2c0_sysfs_filename[] = "/dev/i2c-0"; +static int fd_i2c_expander; +static char i2c0_sysfs_filename[] = "/dev/i2c-0"; /**************************************************************** * Static functions @@ -67,15 +67,23 @@ bool pcal6416a_deinit(void) { return true; } -uint16_t pcal6416a_read_mask_interrupts(void){ - uint16_t val = i2c_smbus_read_word_data ( fd_i2c_expander , PCAL6416A_INT_STATUS ); +int pcal6416a_read_mask_interrupts(void){ + int val_int = i2c_smbus_read_word_data ( fd_i2c_expander , PCAL6416A_INT_STATUS ); + if(val_int < 0){ + return val_int; + } + uint16_t val = val_int & 0xFFFF; DEBUG_PRINTF("READ PCAL6416A_INT_STATUS : 0x%04X\n",val); - return val; + return (int) val; } -uint16_t pcal6416a_read_mask_active_GPIOs(void){ - uint16_t val = i2c_smbus_read_word_data ( fd_i2c_expander , PCAL6416A_INPUT ); +int pcal6416a_read_mask_active_GPIOs(void){ + int val_int = i2c_smbus_read_word_data ( fd_i2c_expander , PCAL6416A_INPUT ); + if(val_int < 0){ + return val_int; + } + uint16_t val = val_int & 0xFFFF; val = 0xFFFF-val; DEBUG_PRINTF("READ PCAL6416A_INPUT (active GPIOs) : 0x%04X\n",val); - return val; + return (int) val; } \ No newline at end of file diff --git a/driver_pcal6416a.h b/driver_pcal6416a.h index 872efde..b3bd2e6 100644 --- a/driver_pcal6416a.h +++ b/driver_pcal6416a.h @@ -14,16 +14,16 @@ #define PCAL6416A_I2C_ADDR 0x20 // Chip registers adresses -#define PCAL6416A_INPUT 0x00 /* Input port [RO] */ +#define PCAL6416A_INPUT 0x00 /* Input port [RO] */ #define PCAL6416A_DAT_OUT 0x02 /* GPIO DATA OUT [R/W] */ #define PCAL6416A_POLARITY 0x04 /* Polarity Inversion port [R/W] */ #define PCAL6416A_CONFIG 0x06 /* Configuration port [R/W] */ -#define PCAL6416A_DRIVE0 0x40 /* Output drive strength Port0 [R/W] */ -#define PCAL6416A_DRIVE1 0x42 /* Output drive strength Port1 [R/W] */ +#define PCAL6416A_DRIVE0 0x40 /* Output drive strength Port0 [R/W] */ +#define PCAL6416A_DRIVE1 0x42 /* Output drive strength Port1 [R/W] */ #define PCAL6416A_INPUT_LATCH 0x44 /* Port0 Input latch [R/W] */ #define PCAL6416A_EN_PULLUPDOWN 0x46 /* Port0 Pull-up/Pull-down enbl [R/W] */ #define PCAL6416A_SEL_PULLUPDOWN 0x48 /* Port0 Pull-up/Pull-down slct [R/W] */ -#define PCAL6416A_INT_MASK 0x4A /* Interrupt mask [R/W] */ +#define PCAL6416A_INT_MASK 0x4A /* Interrupt mask [R/W] */ #define PCAL6416A_INT_STATUS 0x4C /* Interrupt status [RO] */ #define PCAL6416A_OUTPUT_CONFIG 0x4F /* Output port config [R/W] */ @@ -32,8 +32,8 @@ ****************************************************************/ bool pcal6416a_init(void); bool pcal6416a_deinit(void); -uint16_t pcal6416a_read_mask_interrupts(void); -uint16_t pcal6416a_read_mask_active_GPIOs(void); +int pcal6416a_read_mask_interrupts(void); +int pcal6416a_read_mask_active_GPIOs(void); #endif //_DRIVER_PCAL6416A_H_ diff --git a/driver_pcal6416a.o b/driver_pcal6416a.o index 3be6a6b5e3d4b77831bd34b776e8b5898492fec3..b86987affb09f6f9283c720e8113000c39844043 100644 GIT binary patch delta 636 zcmZ9J&1(}u7{;H;?(9mek{D})K}cz2Awt|lDxxBRs-UD4Z{A`w35g#em>eP-sRxB3 zJ*Y2u^X5?xB8UeM9{dCB#Y4S#@Lwntq~PMvz_!(zmi_^B#}k4v@oM~pKknIVeI3Yd2E(_00urv)Y?;k=Z+vW&eAQKR$*?tOcOIhD z27TNdJsNdK&2ImGXV7}kL8Y-0>rzE&Ecg6R>j!`|zYtS1)btVH81=LTjw`1Yo!0}a zuc+reYU-(H)wBM*dg>)=ItN@;&iw1jsW+7$1#T;!1G?1pn47yAmw!(sK_`t4a{RNZjU_<}_ delta 676 zcmZ9J&r1|x7{|Zw%+8Fhzh-}AXo4#{xR+X2kv)ib5rm`;K?iTsv@;tMy2$P#2FZHp zkP-RgfiB{y=u{AR@gSc18xksS9Xoka7OLl2UxXZZ-tYVUe4l4t=ABROOLse`yq(du zWzM%Z+H_atyF0sAKjzpUJXP!J9y*VCDS99*U zs~nw70Ryb#eZY`#)|~e8N#cv*yMRgYtYz^$KP{ehmNl6M=7n?rf^gPF;ThnG@Ebst zwN>KHGYR;r*CgP^7pyYyTKG+1SMzt8f6)9;^CQi_3g=(+P4n-X{}Mh3{L$RtE0y>6 zD3~13Lpx88ZHJn6R(Z5(=hZNMwhPuG_w|r+GW6SajZo2BV}P=bYlR#<>I5^6s}AXb rQ!q|1<2MXWY2PVWX#)so$H>r6$L(#h`@&=L|NH2|xovp~;9T;5iV|vT diff --git a/funkey_gpio_management b/funkey_gpio_management index 1c7da8ba38fadfa5b32359fc654259207ada615a..971738d4df382ed925ab75907be2eff953a79937 100755 GIT binary patch delta 9957 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 ggQbhmzX;jIx|c8Q&HdB05x<*ydDhH3&fvey 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 F_+NM%Q>y>~ diff --git a/gpio-utils.c b/gpio-utils.c index 88a1559..bb9b0e1 100644 --- a/gpio-utils.c +++ b/gpio-utils.c @@ -1,33 +1,6 @@ -/* Copyright (c) 2011, RidgeRun +/* Copyright (c) 2019 FunKey * All rights reserved. * -From https://www.ridgerun.com/developer/wiki/index.php/Gpio-int-test.c - - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the RidgeRun. - * 4. Neither the name of the RidgeRun nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY RIDGERUN ''AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL RIDGERUN BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gpio-utils.h" diff --git a/gpio_mapping.c b/gpio_mapping.c index d63fe18..400d14e 100644 --- a/gpio_mapping.c +++ b/gpio_mapping.c @@ -12,6 +12,7 @@ #include #include "gpio_mapping.h" #include "driver_pcal6416a.h" +#include "driver_axp209.h" /**************************************************************** * Defines @@ -21,7 +22,7 @@ return(EXIT_FAILURE); \ } while(0) -#define DEBUG_GPIO_PRINTF (0) +#define DEBUG_GPIO_PRINTF (1) #define ERROR_GPIO_PRINTF (1) #if (DEBUG_GPIO_PRINTF) @@ -40,6 +41,9 @@ // If not declared, there will be no timeout and no periodical sanity check of GPIO expander values #define TIMEOUT_SEC_SANITY_CHECK_GPIO_EXP 2 +// This is for debug purposes on cards or eval boards that do not have the AXP209 +//#define ENABLE_AXP209_INTERRUPTS + /**************************************************************** * Static variables @@ -48,9 +52,12 @@ static int nb_mapped_gpios; static int * gpio_pins; STRUCT_MAPPED_GPIO * chained_list_mapping; static int max_fd = 0; -static int gpio_fd_interrupt_i2c; +static int gpio_fd_interrupt_expander_gpio; +static int gpio_fd_interrupt_axp209; static fd_set fds; static bool * mask_gpio_value; +static bool interrupt_i2c_expander_found = false; +static bool interrupt_axp209_found = false; /**************************************************************** @@ -178,37 +185,37 @@ static void find_and_call_mapping_function(int idx_gpio_interrupted, } /***** Init GPIO Interrupt i2c expander fd *****/ -static int init_gpio_interrupt(void) +static int init_gpio_interrupt(int pin_nb, int *fd_saved) { // Variables - int cur_pin_nb = GPIO_PIN_I2C_EXPANDER_INTERRUPT; - GPIO_PRINTF("Initializing Interrupt i2c expander GPIO pin fd: %d\n", cur_pin_nb); + GPIO_PRINTF("Initializing Interrupt on GPIO pin: %d\n", pin_nb); // Init fds fd_set FD_ZERO(&fds); //Initializing I2C interrupt GPIO - gpio_export(cur_pin_nb); + gpio_export(pin_nb); //gpio_set_edge(cur_pin_nb, "both"); // Can be rising, falling or both - gpio_set_edge(cur_pin_nb, "falling"); // Can be rising, falling or both - gpio_fd_interrupt_i2c = gpio_fd_open(cur_pin_nb, O_RDONLY); + gpio_set_edge(pin_nb, "falling"); // Can be rising, falling or both + *fd_saved = gpio_fd_open(pin_nb, O_RDONLY); + GPIO_PRINTF("fd is: %d\n", *fd_saved); // add stdin and the sock fd to fds fd_set - safe_fd_set(gpio_fd_interrupt_i2c, &fds, &max_fd); + safe_fd_set(*fd_saved, &fds, &max_fd); return 0; } /***** DeInit GPIO Interrupt i2c expander fd *****/ -static int deinit_gpio_interrupt(void) +static int deinit_gpio_interrupt(int fd_saved) { - GPIO_PRINTF("DeInitializing Interrupt i2c expander GPIO pin fd\n"); + GPIO_PRINTF("DeInitializing Interrupt on GPIO pin fd: %d\n", fd_saved); // Remove stdin and sock fd from fds fd_set - safe_fd_clr(gpio_fd_interrupt_i2c, &fds, &max_fd); + safe_fd_clr(fd_saved, &fds, &max_fd); // Unexport GPIO - gpio_fd_close(gpio_fd_interrupt_i2c); + gpio_fd_close(fd_saved); return 0; } @@ -242,12 +249,22 @@ int init_mapping_gpios(int * gpio_pins_to_declare, int nb_gpios_to_declare, current = current->next_mapped_gpio; } while(current != NULL); - // Init GPIO interrupt from I2C expander - init_gpio_interrupt(); + // Init GPIO interrupt from I2C GPIO expander + GPIO_PRINTF(" Initiating interrupt for GPIO_PIN_I2C_EXPANDER_INTERRUPT\n"); + init_gpio_interrupt(GPIO_PIN_I2C_EXPANDER_INTERRUPT, &gpio_fd_interrupt_expander_gpio); // Init I2C expander pcal6416a_init(); +#ifdef ENABLE_AXP209_INTERRUPTS + // Init GPIO interrupt from AXP209 + GPIO_PRINTF(" Initiating interrupt for GPIO_PIN_AXP209_INTERRUPT\n"); + init_gpio_interrupt(GPIO_PIN_AXP209_INTERRUPT, &gpio_fd_interrupt_axp209); + + // Init AXP209 + axp209_init(); +#endif //ENABLE_AXP209_INTERRUPTS + return 0; } @@ -255,10 +272,21 @@ int init_mapping_gpios(int * gpio_pins_to_declare, int nb_gpios_to_declare, int deinit_mapping_gpios(void) { // DeInit GPIO interrupt from I2C expander - deinit_gpio_interrupt(); + GPIO_PRINTF(" DeInitiating interrupt for GPIO_PIN_I2C_EXPANDER_INTERRUPT\n"); + deinit_gpio_interrupt(gpio_fd_interrupt_expander_gpio); // DeInit I2C expander pcal6416a_deinit(); + +#ifdef ENABLE_AXP209_INTERRUPTS + // DeInit GPIO interrupt from AXP209 + GPIO_PRINTF(" DeInitiating interrupt for GPIO_PIN_AXP209_INTERRUPT\n"); + deinit_gpio_interrupt(gpio_fd_interrupt_axp209); + + // DeInit AXP209 + axp209_deinit(); +#endif //ENABLE_AXP209_INTERRUPTS + return 0; } @@ -267,12 +295,11 @@ int deinit_mapping_gpios(void) int listen_gpios_interrupts(void) { // Variables - char buffer[2]; - int idx_gpio, value; + //char buffer[2]; + //int value; + int idx_gpio; bool previous_mask_gpio_value[nb_mapped_gpios]; bool mask_gpio_current_interrupts[nb_mapped_gpios]; - uint16_t val_i2c_mask_interrupted, val_i2c_mask_active; - bool interrupt_found = false; // Back up master fd_set dup = fds; @@ -282,29 +309,31 @@ int listen_gpios_interrupts(void) memset(mask_gpio_value, false, nb_mapped_gpios*sizeof(bool)); memset(mask_gpio_current_interrupts, false, nb_mapped_gpios*sizeof(bool)); - // Waiting for interrupt or timeout, Note the max_fd+1 + // If interrupt not already found, waiting for interrupt or timeout, Note the max_fd+1 + if(!interrupt_i2c_expander_found && !interrupt_axp209_found){ #ifdef TIMEOUT_SEC_SANITY_CHECK_GPIO_EXP - struct timeval timeout = {TIMEOUT_SEC_SANITY_CHECK_GPIO_EXP, 0}; - int nb_interrupts = select(max_fd+1, NULL, NULL, &dup, &timeout); + struct timeval timeout = {TIMEOUT_SEC_SANITY_CHECK_GPIO_EXP, 0}; + int nb_interrupts = select(max_fd+1, NULL, NULL, &dup, &timeout); #else - int nb_interrupts = select(max_fd+1, NULL, NULL, &dup, NULL); + int nb_interrupts = select(max_fd+1, NULL, NULL, &dup, NULL); #endif //TIMEOUT_SEC_SANITY_CHECK_GPIO_EXP - if(!nb_interrupts){ - // Timeout case - GPIO_PRINTF(" Timeout, forcing sanity check\n"); - // Timeout forcing a "Found interrupt" event for sanity check - interrupt_found = true; - } - else if ( nb_interrupts < 0) { - perror("select"); - return -1; + if(!nb_interrupts){ + // Timeout case + GPIO_PRINTF(" Timeout, forcing sanity check\n"); + // Timeout forcing a "Found interrupt" event for sanity check + interrupt_i2c_expander_found = true; + } + else if ( nb_interrupts < 0) { + perror("select"); + return -1; + } } - // Check if interrupt from I2C expander + // Check if interrupt from I2C expander or AXP209 // Check which cur_fd is available for read for (int cur_fd = 0; cur_fd <= max_fd; cur_fd++) { if (FD_ISSET(cur_fd, &dup)) { - // Revenir au debut du fichier (lectures successives). + /*// Rewind file lseek(cur_fd, 0, SEEK_SET); // Read current gpio value @@ -313,19 +342,54 @@ int listen_gpios_interrupts(void) break; } - // Effacer le retour-chariot. + // remove end of line char buffer[1] = '\0'; - value = 1-atoi(buffer); + value = 1-atoi(buffer);*/ // Found interrupt - interrupt_found = true; + if(cur_fd == gpio_fd_interrupt_expander_gpio){ + interrupt_i2c_expander_found = true; + } + else if(cur_fd == gpio_fd_interrupt_axp209){ + interrupt_axp209_found = true; + } } } - if(interrupt_found){ + +#ifdef ENABLE_AXP209_INTERRUPTS + if(interrupt_axp209_found){ + GPIO_PRINTF(" Found interrupt AXP209\n"); + int val_int_bank_3 = axp209_read_interrupt_bank_3(); + if(val_int_bank_3 < 0){ + GPIO_PRINTF(" Could not read AXP209 with I2C\n"); + return 0; + } + interrupt_axp209_found = false; + + if(val_int_bank_3 & AXP209_INTERRUPT_PEK_SHORT_PRESS){ + GPIO_PRINTF(" AXP209 short PEK key press detected\n"); + } + if(val_int_bank_3 & AXP209_INTERRUPT_PEK_LONG_PRESS){ + GPIO_PRINTF(" AXP209 long PEK key press detected\n"); + } + } +#endif //ENABLE_AXP209_INTERRUPTS + + if(interrupt_i2c_expander_found){ // Read I2C GPIO masks: - val_i2c_mask_interrupted = pcal6416a_read_mask_interrupts(); - val_i2c_mask_active = pcal6416a_read_mask_active_GPIOs(); + int val_i2c_mask_interrupted = pcal6416a_read_mask_interrupts(); + if(val_i2c_mask_interrupted < 0){ + GPIO_PRINTF(" Could not read pcal6416a_read_mask_interrupts\n"); + return 0; + } + int val_i2c_mask_active = pcal6416a_read_mask_active_GPIOs(); + if(val_i2c_mask_active < 0){ + GPIO_PRINTF(" Could not read pcal6416a_read_mask_active_GPIOs\n"); + return 0; + } + interrupt_i2c_expander_found = false; + // Find GPIO idx correspondance for (idx_gpio=0; idx_gpio`QlLv%6+D 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 P&Un9G2s?+Pz3#sN5ot_i