/** * \file * * \brief SAM External Interrupt Driver * * Copyright (c) 2014-2018 Microchip Technology Inc. and its subsidiaries. * * \asf_license_start * * \page License * * Subject to your compliance with these terms, you may use Microchip * software and any derivatives exclusively with Microchip products. * It is your responsibility to comply with third party license terms applicable * to your use of third party software (including open source software) that * may accompany Microchip software. * * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. * * \asf_license_stop * */ /* * Support and FAQ: visit Microchip Support */ #include #include #include #include #if !defined(EXTINT_CLOCK_SELECTION) || defined(__DOXYGEN__) # warning EXTINT_CLOCK_SELECTION is not defined, assuming EXTINT_CLK_GCLK. /** Configuration option, setting the EIC clock source which can be used for * EIC edge detection or filtering. This option may be overridden in the module * configuration header file \c conf_extint.h. */ # define EXTINT_CLOCK_SELECTION EXTINT_CLK_GCLK #endif #if (EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) #if !defined(EXTINT_CLOCK_SOURCE) || defined(__DOXYGEN__) # warning EXTINT_CLOCK_SOURCE is not defined, assuming GCLK_GENERATOR_0. /** Configuration option, setting the EIC clock source which can be used for * EIC edge detection or filtering. This option may be overridden in the module * configuration header file \c conf_extint.h. */ # define EXTINT_CLOCK_SOURCE GCLK_GENERATOR_0 #endif #endif /** * \internal * Internal driver device instance struct. */ struct _extint_module _extint_dev; /** * \brief Determin if the general clock is required. * * \param[in] filter_input_signal Filter the raw input signal to prevent noise * \param[in] detection_criteria Edge detection mode to use (\ref extint_detect) */ #define _extint_is_gclk_required(filter_input_signal, detection_criteria) \ ((filter_input_signal) ? true : (\ (EXTINT_DETECT_RISING == (detection_criteria)) ? true : (\ (EXTINT_DETECT_FALLING == (detection_criteria)) ? true : (\ (EXTINT_DETECT_BOTH == (detection_criteria)) ? true : false)))) static void _extint_enable(void); static void _extint_disable(void); /** * \brief Determines if the hardware module(s) are currently synchronizing to the bus. * * Checks to see if the underlying hardware peripheral module(s) are currently * synchronizing across multiple clock domains to the hardware bus, This * function can be used to delay further operations on a module until such time * that it is ready, to prevent blocking delays for synchronization in the * user application. * * \return Synchronization status of the underlying hardware module(s). * * \retval true If the module synchronization is ongoing * \retval false If the module has completed synchronization */ static inline bool extint_is_syncing(void) { Eic *const eics[EIC_INST_NUM] = EIC_INSTS; for (uint32_t i = 0; i < EIC_INST_NUM; i++) { if((eics[i]->SYNCBUSY.reg & EIC_SYNCBUSY_ENABLE) || (eics[i]->SYNCBUSY.reg & EIC_SYNCBUSY_SWRST)){ return true; } } return false; } /** * \internal * \brief Initializes and enables the External Interrupt driver. * * Enable the clocks used by External Interrupt driver. * * Resets the External Interrupt driver, resetting all hardware * module registers to their power-on defaults, then enable it for further use. * * Reset the callback list if callback mode is used. * * This function must be called before attempting to use any NMI or standard * external interrupt channel functions. * * \note When SYSTEM module is used, this function will be invoked by * \ref system_init() automatically if the module is included. */ void _system_extint_init(void); void _system_extint_init(void) { Eic *const eics[EIC_INST_NUM] = EIC_INSTS; /* Turn on the digital interface clock */ system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBA, MCLK_APBAMASK_EIC); #if (EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) /* Configure the generic clock for the module and enable it */ struct system_gclk_chan_config gclk_chan_conf; system_gclk_chan_get_config_defaults(&gclk_chan_conf); gclk_chan_conf.source_generator = EXTINT_CLOCK_SOURCE; system_gclk_chan_set_config(EIC_GCLK_ID, &gclk_chan_conf); /* Enable the clock anyway, since when needed it will be requested * by External Interrupt driver */ system_gclk_chan_enable(EIC_GCLK_ID); #endif /* Reset all EIC hardware modules. */ for (uint32_t i = 0; i < EIC_INST_NUM; i++) { eics[i]->CTRLA.reg |= EIC_CTRLA_SWRST; } while (extint_is_syncing()) { /* Wait for all hardware modules to complete synchronization */ } #if (EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) for (uint32_t i = 0; i < EIC_INST_NUM; i++) { eics[i]->CTRLA.bit.CKSEL = EXTINT_CLK_GCLK; } #else for (uint32_t i = 0; i < EIC_INST_NUM; i++) { eics[i]->CTRLA.bit.CKSEL = EXTINT_CLK_ULP32K; } #endif /* Reset the software module */ #if EXTINT_CALLBACK_MODE == true /* Clear callback registration table */ for (uint8_t j = 0; j < EIC_NUMBER_OF_INTERRUPTS; j++) { _extint_dev.callbacks[j] = NULL; } system_interrupt_enable(SYSTEM_INTERRUPT_MODULE_EIC); #endif /* Enables the driver for further use */ _extint_enable(); } /** * \internal * \brief Enables the External Interrupt driver. * * Enables EIC modules. * Registered callback list will not be affected if callback mode is used. */ void _extint_enable(void) { Eic *const eics[EIC_INST_NUM] = EIC_INSTS; /* Enable all EIC hardware modules. */ for (uint32_t i = 0; i < EIC_INST_NUM; i++) { eics[i]->CTRLA.reg |= EIC_CTRLA_ENABLE; } while (extint_is_syncing()) { /* Wait for all hardware modules to complete synchronization */ } } /** * \internal * \brief Disables the External Interrupt driver. * * Disables EIC modules that were previously started via a call to * \ref _extint_enable(). * Registered callback list will not be affected if callback mode is used. */ void _extint_disable(void) { Eic *const eics[EIC_INST_NUM] = EIC_INSTS; /* Disable all EIC hardware modules. */ for (uint32_t i = 0; i < EIC_INST_NUM; i++) { eics[i]->CTRLA.reg &= ~EIC_CTRLA_ENABLE; } while (extint_is_syncing()) { /* Wait for all hardware modules to complete synchronization */ } } /** * \brief Initializes an External Interrupt channel configuration structure to defaults. * * Initializes a given External Interrupt channel configuration structure to a * set of known default values. This function should be called on all new * instances of these configuration structures before being modified by the * user application. * * The default configuration is as follows: * \li Input filtering disabled * \li Internal pull-up enabled * \li Detect falling edges of a signal * \li Asynchronous edge detection is disabled * * \param[out] config Configuration structure to initialize to default values */ void extint_chan_get_config_defaults( struct extint_chan_conf *const config) { /* Sanity check arguments */ Assert(config); /* Default configuration values */ config->gpio_pin = 0; config->gpio_pin_mux = 0; config->gpio_pin_pull = EXTINT_PULL_UP; config->filter_input_signal = false; config->detection_criteria = EXTINT_DETECT_FALLING; config->enable_async_edge_detection = false; } /** * \brief Writes an External Interrupt channel configuration to the hardware module. * * Writes out a given configuration of an External Interrupt channel * configuration to the hardware module. If the channel is already configured, * the new configuration will replace the existing one. * * \param[in] channel External Interrupt channel to configure * \param[in] config Configuration settings for the channel */ void extint_chan_set_config( const uint8_t channel, const struct extint_chan_conf *const config) { /* Sanity check arguments */ Assert(config); _extint_disable(); #if(EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) /* Sanity check clock requirements */ Assert(!(!system_gclk_gen_is_enabled(EXTINT_CLOCK_SOURCE) && _extint_is_gclk_required(config->filter_input_signal, config->detection_criteria))); #endif struct system_pinmux_config pinmux_config; system_pinmux_get_config_defaults(&pinmux_config); pinmux_config.mux_position = config->gpio_pin_mux; pinmux_config.direction = SYSTEM_PINMUX_PIN_DIR_INPUT; pinmux_config.input_pull = (enum system_pinmux_pin_pull)config->gpio_pin_pull; system_pinmux_pin_set_config(config->gpio_pin, &pinmux_config); /* Get a pointer to the module hardware instance */ Eic *const EIC_module = _extint_get_eic_from_channel(channel); uint32_t config_pos = (4 * (channel % 8)); uint32_t new_config; /* Determine the channel's new edge detection configuration */ new_config = (config->detection_criteria << EIC_CONFIG_SENSE0_Pos); /* Enable the hardware signal filter if requested in the config */ if (config->filter_input_signal) { new_config |= EIC_CONFIG_FILTEN0; } /* Clear the existing and set the new channel configuration */ EIC_module->CONFIG[channel / 8].reg = (EIC_module->CONFIG[channel / 8].reg & ~((EIC_CONFIG_SENSE0_Msk | EIC_CONFIG_FILTEN0) << config_pos)) | (new_config << config_pos); #if (SAML22) || (SAML21XXXB) || (SAMC20) || (SAMR30) || (SAMR34) || (SAMR35) /* Config asynchronous edge detection */ if (config->enable_async_edge_detection) { EIC_module->ASYNCH.reg |= (1UL << channel); } else { EIC_module->ASYNCH.reg &= (EIC_ASYNCH_MASK & (~(1UL << channel))); } #endif #if (SAMC21) /* Config asynchronous edge detection */ if (config->enable_async_edge_detection) { EIC_module->EIC_ASYNCH.reg |= (1UL << channel); } else { EIC_module->EIC_ASYNCH.reg &= (EIC_EIC_ASYNCH_MASK & (~(1UL << channel))); } #endif _extint_enable(); } /** * \brief Writes an External Interrupt NMI channel configuration to the hardware module. * * Writes out a given configuration of an External Interrupt NMI channel * configuration to the hardware module. If the channel is already configured, * the new configuration will replace the existing one. * * \param[in] nmi_channel External Interrupt NMI channel to configure * \param[in] config Configuration settings for the channel * * \returns Status code indicating the success or failure of the request. * \retval STATUS_OK Configuration succeeded * \retval STATUS_ERR_PIN_MUX_INVALID An invalid pin mux value was supplied * \retval STATUS_ERR_BAD_FORMAT An invalid detection mode was requested */ enum status_code extint_nmi_set_config( const uint8_t nmi_channel, const struct extint_nmi_conf *const config) { /* Sanity check arguments */ Assert(config); /* Sanity check clock requirements */ Assert(!(!system_gclk_gen_is_enabled(EXTINT_CLOCK_SOURCE) && _extint_is_gclk_required(config->filter_input_signal, config->detection_criteria))); struct system_pinmux_config pinmux_config; system_pinmux_get_config_defaults(&pinmux_config); pinmux_config.mux_position = config->gpio_pin_mux; pinmux_config.direction = SYSTEM_PINMUX_PIN_DIR_INPUT; pinmux_config.input_pull = SYSTEM_PINMUX_PIN_PULL_UP; pinmux_config.input_pull = (enum system_pinmux_pin_pull)config->gpio_pin_pull; system_pinmux_pin_set_config(config->gpio_pin, &pinmux_config); /* Get a pointer to the module hardware instance */ Eic *const EIC_module = _extint_get_eic_from_channel(nmi_channel); uint32_t new_config; /* Determine the NMI's new edge detection configuration */ new_config = (config->detection_criteria << EIC_NMICTRL_NMISENSE_Pos); /* Enable the hardware signal filter if requested in the config */ if (config->filter_input_signal) { new_config |= EIC_NMICTRL_NMIFILTEN; } #if (SAML21XXXB) || (SAML22) || (SAMC21) || (SAMR30) || (SAMR34) || (SAMR35) /* Enable asynchronous edge detection if requested in the config */ if (config->enable_async_edge_detection) { new_config |= EIC_NMICTRL_NMIASYNCH; } #endif /* Disable EIC and general clock to configure NMI */ _extint_disable(); #if(EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) system_gclk_chan_disable(EIC_GCLK_ID); #else Eic *const eics[EIC_INST_NUM] = EIC_INSTS; for (uint32_t i = 0; i < EIC_INST_NUM; i++){ eics[i]->CTRLA.bit.CKSEL = EXTINT_CLK_GCLK; system_gclk_chan_disable(EIC_GCLK_ID); } #endif EIC_module->NMICTRL.reg = new_config; /* Enable the EIC clock and EIC after configure NMI */ #if(EXTINT_CLOCK_SELECTION == EXTINT_CLK_GCLK) system_gclk_chan_enable(EIC_GCLK_ID); #else for (uint32_t i = 0; i < EIC_INST_NUM; i++){ eics[i]->CTRLA.bit.CKSEL = EXTINT_CLK_ULP32K; } #endif _extint_enable(); return STATUS_OK; } /** * \brief Enables an External Interrupt event output. * * Enables one or more output events from the External Interrupt module. See * \ref extint_events "here" for a list of events this module supports. * * \note Events cannot be altered while the module is enabled. * * \param[in] events Struct containing flags of events to enable */ void extint_enable_events( struct extint_events *const events) { /* Sanity check arguments */ Assert(events); /* Array of available EICs. */ Eic *const eics[EIC_INST_NUM] = EIC_INSTS; _extint_disable(); /* Update the event control register for each physical EIC instance */ for (uint32_t i = 0; i < EIC_INST_NUM; i++) { uint32_t event_mask = 0; /* Create an enable mask for the current EIC module */ for (uint32_t j = 0; j < 32; j++) { if (events->generate_event_on_detect[(32 * i) + j]) { event_mask |= (1UL << j); } } /* Enable the masked events */ eics[i]->EVCTRL.reg |= event_mask; } _extint_enable(); } /** * \brief Disables an External Interrupt event output. * * Disables one or more output events from the External Interrupt module. See * \ref extint_events "here" for a list of events this module supports. * * \note Events cannot be altered while the module is enabled. * * \param[in] events Struct containing flags of events to disable */ void extint_disable_events( struct extint_events *const events) { /* Sanity check arguments */ Assert(events); /* Array of available EICs. */ Eic *const eics[EIC_INST_NUM] = EIC_INSTS; _extint_disable(); /* Update the event control register for each physical EIC instance */ for (uint32_t i = 0; i < EIC_INST_NUM; i++) { uint32_t event_mask = 0; /* Create a disable mask for the current EIC module */ for (uint32_t j = 0; j < 32; j++) { if (events->generate_event_on_detect[(32 * i) + j]) { event_mask |= (1UL << j); } } /* Disable the masked events */ eics[i]->EVCTRL.reg &= ~event_mask; } _extint_enable(); }