mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
16677b0d3c
peripheral enable/disable usually should be managed by driver itself, so make it as espressif private APIs, not recommended for user to use it in application code. However, if user want to re-write the driver or ports to other platform, this is still possible by including the header in this way: "esp_private/peripheral_ctrl.h"
398 lines
16 KiB
C
398 lines
16 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/lock.h>
|
|
#include "sdkconfig.h"
|
|
#include "esp_compiler.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_intr_alloc.h"
|
|
#include "esp_log.h"
|
|
#include "esp_check.h"
|
|
#include "soc/soc_caps.h"
|
|
#include "soc/gpio_periph.h"
|
|
#include "soc/io_mux_reg.h"
|
|
#include "hal/cpu_hal.h"
|
|
#include "hal/cpu_ll.h"
|
|
#include "hal/gpio_hal.h"
|
|
#include "esp_private/periph_ctrl.h"
|
|
#include "esp_rom_gpio.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "driver/dedic_gpio.h"
|
|
#include "soc/dedic_gpio_periph.h"
|
|
#if SOC_DEDIC_GPIO_ALLOW_REG_ACCESS
|
|
#include "soc/dedic_gpio_struct.h"
|
|
#include "hal/dedic_gpio_ll.h"
|
|
#endif
|
|
|
|
|
|
static const char *TAG = "dedic_gpio";
|
|
|
|
typedef struct dedic_gpio_platform_t dedic_gpio_platform_t;
|
|
typedef struct dedic_gpio_bundle_t dedic_gpio_bundle_t;
|
|
|
|
// Dedicated GPIO driver platform, GPIO bundles will be installed onto it
|
|
static dedic_gpio_platform_t *s_platform[SOC_CPU_CORES_NUM];
|
|
// platform level mutex lock
|
|
static _lock_t s_platform_mutexlock[SOC_CPU_CORES_NUM];
|
|
|
|
struct dedic_gpio_platform_t {
|
|
portMUX_TYPE spinlock; // Spinlock, stop GPIO channels from accessing common resource concurrently
|
|
uint32_t out_occupied_mask; // mask of output channels that already occupied
|
|
uint32_t in_occupied_mask; // mask of input channels that already occupied
|
|
#if SOC_DEDIC_GPIO_HAS_INTERRUPT
|
|
intr_handle_t intr_hdl; // interrupt handle
|
|
dedic_gpio_isr_callback_t cbs[SOC_DEDIC_GPIO_IN_CHANNELS_NUM]; // array of callback function for input channel
|
|
void *cb_args[SOC_DEDIC_GPIO_IN_CHANNELS_NUM]; // array of callback arguments for input channel
|
|
dedic_gpio_bundle_t *in_bundles[SOC_DEDIC_GPIO_IN_CHANNELS_NUM]; // which bundle belongs to for input channel
|
|
#endif
|
|
#if SOC_DEDIC_GPIO_ALLOW_REG_ACCESS
|
|
dedic_dev_t *dev;
|
|
#endif
|
|
};
|
|
|
|
struct dedic_gpio_bundle_t {
|
|
uint32_t core_id; // CPU core ID, a GPIO bundle must be installed to a specific CPU core
|
|
uint32_t out_mask; // mask of output channels in the bank
|
|
uint32_t in_mask; // mask of input channels in the bank
|
|
uint32_t out_offset; // offset in the bank (seen from output channel)
|
|
uint32_t in_offset; // offset in the bank (seen from input channel)
|
|
size_t nr_gpio; // number of GPIOs in the gpio_array
|
|
int gpio_array[]; // array of GPIO numbers (configured by user)
|
|
};
|
|
|
|
static esp_err_t dedic_gpio_build_platform(uint32_t core_id)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
if (!s_platform[core_id]) {
|
|
// prevent building platform concurrently
|
|
_lock_acquire(&s_platform_mutexlock[core_id]);
|
|
if (!s_platform[core_id]) {
|
|
s_platform[core_id] = calloc(1, sizeof(dedic_gpio_platform_t));
|
|
if (s_platform[core_id]) {
|
|
// initialize platfrom members
|
|
s_platform[core_id]->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
#if SOC_DEDIC_GPIO_ALLOW_REG_ACCESS
|
|
s_platform[core_id]->dev = &DEDIC_GPIO;
|
|
#endif // SOC_DEDIC_GPIO_ALLOW_REG_ACCESS
|
|
#if !SOC_DEDIC_PERIPH_AUTO_ENABLE
|
|
periph_module_enable(dedic_gpio_periph_signals.module); // enable APB clock to peripheral
|
|
#endif // !SOC_DEDIC_PERIPH_AUTO_ENABLE
|
|
}
|
|
}
|
|
_lock_release(&s_platform_mutexlock[core_id]);
|
|
|
|
ESP_GOTO_ON_FALSE(s_platform[core_id], ESP_ERR_NO_MEM, err, TAG, "no mem for s_platform[%d]", core_id);
|
|
ESP_LOGD(TAG, "build platform on core[%d] at %p", core_id, s_platform);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void dedic_gpio_break_platform(uint32_t core_id)
|
|
{
|
|
if (s_platform[core_id]) {
|
|
// prevent breaking platform concurrently
|
|
_lock_acquire(&s_platform_mutexlock[core_id]);
|
|
if (s_platform[core_id]) {
|
|
free(s_platform[core_id]);
|
|
s_platform[core_id] = NULL;
|
|
#if !SOC_DEDIC_PERIPH_AUTO_ENABLE
|
|
periph_module_disable(dedic_gpio_periph_signals.module); // disable module if no GPIO channel is being used
|
|
#endif // !SOC_DEDIC_PERIPH_AUTO_ENABLE
|
|
}
|
|
_lock_release(&s_platform_mutexlock[core_id]);
|
|
}
|
|
}
|
|
|
|
#if SOC_DEDIC_GPIO_HAS_INTERRUPT
|
|
static void dedic_gpio_default_isr(void *arg)
|
|
{
|
|
bool need_yield = false;
|
|
dedic_gpio_platform_t *platform = (dedic_gpio_platform_t *)arg;
|
|
|
|
// get and clear interrupt status
|
|
portENTER_CRITICAL_ISR(&platform->spinlock);
|
|
uint32_t status = dedic_gpio_ll_get_interrupt_status(platform->dev);
|
|
dedic_gpio_ll_clear_interrupt_status(platform->dev, status);
|
|
portEXIT_CRITICAL_ISR(&platform->spinlock);
|
|
|
|
// handle dedicated channel one by one
|
|
while (status) {
|
|
uint32_t channel = __builtin_ffs(status) - 1; // get dedicated channel number which triggered the interrupt
|
|
if (platform->cbs[channel]) {
|
|
if (platform->cbs[channel](platform->in_bundles[channel], channel - platform->in_bundles[channel]->in_offset, platform->cb_args[channel])) {
|
|
need_yield = true; // note that we need to yield at the end of isr
|
|
}
|
|
}
|
|
status = status & (status - 1); // clear the right most bit '1'
|
|
}
|
|
|
|
if (need_yield) {
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
}
|
|
|
|
static esp_err_t dedic_gpio_install_interrupt(uint32_t core_id)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
if (!s_platform[core_id]->intr_hdl) {
|
|
// prevent install interrupt concurrently
|
|
_lock_acquire(&s_platform_mutexlock[core_id]);
|
|
if (!s_platform[core_id]->intr_hdl) {
|
|
int isr_flags = 0;
|
|
ret = esp_intr_alloc(dedic_gpio_periph_signals.irq, isr_flags, dedic_gpio_default_isr, s_platform[core_id], &s_platform[core_id]->intr_hdl);
|
|
// clear pending interrupt
|
|
uint32_t status = dedic_gpio_ll_get_interrupt_status(s_platform[core_id]->dev);
|
|
dedic_gpio_ll_clear_interrupt_status(s_platform[core_id]->dev, status);
|
|
}
|
|
_lock_release(&s_platform_mutexlock[core_id]);
|
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed");
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void dedic_gpio_uninstall_interrupt(uint32_t core_id)
|
|
{
|
|
if (s_platform[core_id]->intr_hdl) {
|
|
// prevent uninstall interrupt concurrently
|
|
_lock_acquire(&s_platform_mutexlock[core_id]);
|
|
if (s_platform[core_id]->intr_hdl) {
|
|
esp_intr_free(s_platform[core_id]->intr_hdl);
|
|
s_platform[core_id]->intr_hdl = NULL;
|
|
// disable all interrupt
|
|
dedic_gpio_ll_enable_interrupt(s_platform[core_id]->dev, ~0UL, false);
|
|
}
|
|
_lock_release(&s_platform_mutexlock[core_id]);
|
|
}
|
|
}
|
|
|
|
static void dedic_gpio_set_interrupt(uint32_t core_id, uint32_t channel, dedic_gpio_intr_type_t type)
|
|
{
|
|
dedic_gpio_ll_set_interrupt_type(s_platform[core_id]->dev, channel, type);
|
|
if (type != DEDIC_GPIO_INTR_NONE) {
|
|
dedic_gpio_ll_enable_interrupt(s_platform[core_id]->dev, 1 << channel, true);
|
|
} else {
|
|
dedic_gpio_ll_enable_interrupt(s_platform[core_id]->dev, 1 << channel, false);
|
|
}
|
|
}
|
|
#endif // SOC_DEDIC_GPIO_HAS_INTERRUPT
|
|
|
|
esp_err_t dedic_gpio_new_bundle(const dedic_gpio_bundle_config_t *config, dedic_gpio_bundle_handle_t *ret_bundle)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
dedic_gpio_bundle_t *bundle = NULL;
|
|
uint32_t out_mask = 0;
|
|
uint32_t in_mask = 0;
|
|
uint32_t core_id = cpu_hal_get_core_id(); // dedicated GPIO will be binded to the CPU who invokes this API
|
|
|
|
ESP_GOTO_ON_FALSE(config && ret_bundle, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE(config->gpio_array && config->array_size > 0, ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO array or size");
|
|
ESP_GOTO_ON_FALSE(config->flags.in_en || config->flags.out_en, ESP_ERR_INVALID_ARG, err, TAG, "no input/output mode specified");
|
|
// lazy install s_platform[core_id]
|
|
ESP_GOTO_ON_ERROR(dedic_gpio_build_platform(core_id), err, TAG, "build platform %d failed", core_id);
|
|
|
|
size_t bundle_size = sizeof(dedic_gpio_bundle_t) + config->array_size * sizeof(config->gpio_array[0]);
|
|
bundle = calloc(1, bundle_size);
|
|
ESP_GOTO_ON_FALSE(bundle, ESP_ERR_NO_MEM, err, TAG, "no mem for bundle");
|
|
|
|
// for performance reasons, we only search for continuous channels
|
|
uint32_t pattern = (1 << config->array_size) - 1;
|
|
// configure outwards channels
|
|
uint32_t out_offset = 0;
|
|
if (config->flags.out_en) {
|
|
ESP_GOTO_ON_FALSE(config->array_size <= SOC_DEDIC_GPIO_OUT_CHANNELS_NUM, ESP_ERR_INVALID_ARG, err, TAG,
|
|
"array size(%d) exceeds maximum supported out channels(%d)", config->array_size, SOC_DEDIC_GPIO_OUT_CHANNELS_NUM);
|
|
// prevent install bundle concurrently
|
|
portENTER_CRITICAL(&s_platform[core_id]->spinlock);
|
|
for (size_t i = 0; i <= SOC_DEDIC_GPIO_OUT_CHANNELS_NUM - config->array_size; i++) {
|
|
if ((s_platform[core_id]->out_occupied_mask & (pattern << i)) == 0) {
|
|
out_mask = pattern << i;
|
|
out_offset = i;
|
|
break;
|
|
}
|
|
}
|
|
if (out_mask) {
|
|
s_platform[core_id]->out_occupied_mask |= out_mask;
|
|
#if SOC_DEDIC_GPIO_ALLOW_REG_ACCESS
|
|
// always enable instruction to access output GPIO, which has better performance than register access
|
|
dedic_gpio_ll_enable_instruction_access_out(s_platform[core_id]->dev, out_mask, true);
|
|
#endif
|
|
}
|
|
portEXIT_CRITICAL(&s_platform[core_id]->spinlock);
|
|
ESP_GOTO_ON_FALSE(out_mask, ESP_ERR_NOT_FOUND, err, TAG, "no free outward channels on core[%d]", core_id);
|
|
ESP_LOGD(TAG, "new outward bundle(%p) on core[%d], offset=%d, mask(%x)", bundle, core_id, out_offset, out_mask);
|
|
}
|
|
|
|
// configure inwards channels
|
|
uint32_t in_offset = 0;
|
|
if (config->flags.in_en) {
|
|
ESP_GOTO_ON_FALSE(config->array_size <= SOC_DEDIC_GPIO_IN_CHANNELS_NUM, ESP_ERR_INVALID_ARG, err, TAG,
|
|
"array size(%d) exceeds maximum supported in channels(%d)", config->array_size, SOC_DEDIC_GPIO_IN_CHANNELS_NUM);
|
|
// prevent install bundle concurrently
|
|
portENTER_CRITICAL(&s_platform[core_id]->spinlock);
|
|
for (size_t i = 0; i <= SOC_DEDIC_GPIO_IN_CHANNELS_NUM - config->array_size; i++) {
|
|
if ((s_platform[core_id]->in_occupied_mask & (pattern << i)) == 0) {
|
|
in_mask = pattern << i;
|
|
in_offset = i;
|
|
break;
|
|
}
|
|
}
|
|
if (in_mask) {
|
|
s_platform[core_id]->in_occupied_mask |= in_mask;
|
|
}
|
|
portEXIT_CRITICAL(&s_platform[core_id]->spinlock);
|
|
ESP_GOTO_ON_FALSE(in_mask, ESP_ERR_NOT_FOUND, err, TAG, "no free inward channels on core[%d]", core_id);
|
|
ESP_LOGD(TAG, "new inward bundle(%p) on core[%d], offset=%d, mask(%x)", bundle, core_id, in_offset, in_mask);
|
|
}
|
|
|
|
// route dedicated GPIO channel signals to GPIO matrix
|
|
if (config->flags.in_en) {
|
|
for (size_t i = 0; i < config->array_size; i++) {
|
|
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_array[i]], PIN_FUNC_GPIO);
|
|
esp_rom_gpio_connect_in_signal(config->gpio_array[i], dedic_gpio_periph_signals.cores[core_id].in_sig_per_channel[in_offset + i], config->flags.in_invert);
|
|
}
|
|
}
|
|
if (config->flags.out_en) {
|
|
for (size_t i = 0; i < config->array_size; i++) {
|
|
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_array[i]], PIN_FUNC_GPIO);
|
|
esp_rom_gpio_connect_out_signal(config->gpio_array[i], dedic_gpio_periph_signals.cores[core_id].out_sig_per_channel[out_offset + i], config->flags.out_invert, false);
|
|
}
|
|
#if !SOC_DEDIC_GPIO_OUT_AUTO_ENABLE
|
|
cpu_ll_enable_dedic_gpio_output(s_platform[core_id]->out_occupied_mask);
|
|
#endif // !SOC_DEDIC_GPIO_OUT_AUTO_ENABLE
|
|
}
|
|
|
|
// it's safe to initialize bundle members without locks here
|
|
bundle->core_id = core_id;
|
|
bundle->out_mask = out_mask;
|
|
bundle->in_mask = in_mask;
|
|
bundle->out_offset = out_offset;
|
|
bundle->in_offset = in_offset;
|
|
bundle->nr_gpio = config->array_size;
|
|
memcpy(bundle->gpio_array, config->gpio_array, config->array_size * sizeof(config->gpio_array[0]));
|
|
|
|
*ret_bundle = bundle; // return bundle instance
|
|
return ESP_OK;
|
|
|
|
err:
|
|
if (s_platform[core_id] && (out_mask || in_mask)) {
|
|
portENTER_CRITICAL(&s_platform[core_id]->spinlock);
|
|
s_platform[core_id]->out_occupied_mask &= ~out_mask;
|
|
s_platform[core_id]->in_occupied_mask &= ~in_mask;
|
|
portEXIT_CRITICAL(&s_platform[core_id]->spinlock);
|
|
}
|
|
if (bundle) {
|
|
free(bundle);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dedic_gpio_del_bundle(dedic_gpio_bundle_handle_t bundle)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
bool recycle_all = false;
|
|
ESP_GOTO_ON_FALSE(bundle, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
|
|
uint32_t core_id = cpu_hal_get_core_id();
|
|
ESP_GOTO_ON_FALSE(core_id == bundle->core_id, ESP_FAIL, err, TAG, "del bundle on wrong CPU");
|
|
|
|
portENTER_CRITICAL(&s_platform[core_id]->spinlock);
|
|
s_platform[core_id]->out_occupied_mask &= ~(bundle->out_mask);
|
|
s_platform[core_id]->in_occupied_mask &= ~(bundle->in_mask);
|
|
if (!s_platform[core_id]->in_occupied_mask && !s_platform[core_id]->out_occupied_mask) {
|
|
recycle_all = true;
|
|
}
|
|
portEXIT_CRITICAL(&s_platform[core_id]->spinlock);
|
|
|
|
free(bundle);
|
|
|
|
if (recycle_all) {
|
|
#if SOC_DEDIC_GPIO_HAS_INTERRUPT
|
|
dedic_gpio_uninstall_interrupt(core_id);
|
|
#endif
|
|
dedic_gpio_break_platform(core_id);
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dedic_gpio_get_out_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *mask)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE(bundle && mask, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
*mask = bundle->out_mask;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t dedic_gpio_get_in_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *mask)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE(bundle && mask, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
*mask = bundle->in_mask;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
void dedic_gpio_bundle_write(dedic_gpio_bundle_handle_t bundle, uint32_t mask, uint32_t value)
|
|
{
|
|
// For performance reasons, we don't want to check the validation of parameters here
|
|
// Even didn't check if we're working on the correct CPU core (i.e. bundle->core_id == current core_id)
|
|
cpu_ll_write_dedic_gpio_mask(bundle->out_mask & (mask << bundle->out_offset), value << bundle->out_offset);
|
|
}
|
|
|
|
uint32_t dedic_gpio_bundle_read_out(dedic_gpio_bundle_handle_t bundle)
|
|
{
|
|
// For performance reasons, we don't want to check the validation of parameters here
|
|
// Even didn't check if we're working on the correct CPU core (i.e. bundle->core_id == current core_id)
|
|
uint32_t value = cpu_ll_read_dedic_gpio_out();
|
|
return (value & bundle->out_mask) >> (bundle->out_offset);
|
|
}
|
|
|
|
uint32_t dedic_gpio_bundle_read_in(dedic_gpio_bundle_handle_t bundle)
|
|
{
|
|
// For performance reasons, we don't want to check the validation of parameters here
|
|
// Even didn't check if we're working on the correct CPU core (i.e. bundle->core_id == current core_id)
|
|
uint32_t value = cpu_ll_read_dedic_gpio_in();
|
|
return (value & bundle->in_mask) >> (bundle->in_offset);
|
|
}
|
|
|
|
#if SOC_DEDIC_GPIO_HAS_INTERRUPT
|
|
esp_err_t dedic_gpio_bundle_set_interrupt_and_callback(dedic_gpio_bundle_handle_t bundle, uint32_t mask, dedic_gpio_intr_type_t intr_type, dedic_gpio_isr_callback_t cb_isr, void *cb_args)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ESP_GOTO_ON_FALSE(bundle, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
uint32_t core_id = cpu_hal_get_core_id();
|
|
// lazy alloc interrupt
|
|
ESP_GOTO_ON_ERROR(dedic_gpio_install_interrupt(core_id), err, TAG, "allocate interrupt on core %d failed", core_id);
|
|
|
|
uint32_t channel_mask = bundle->in_mask & (mask << bundle->in_offset);
|
|
uint32_t channel = 0;
|
|
while (channel_mask) {
|
|
channel = __builtin_ffs(channel_mask) - 1;
|
|
portENTER_CRITICAL(&s_platform[core_id]->spinlock);
|
|
dedic_gpio_set_interrupt(core_id, channel, intr_type);
|
|
portEXIT_CRITICAL(&s_platform[core_id]->spinlock);
|
|
|
|
s_platform[core_id]->cbs[channel] = cb_isr;
|
|
s_platform[core_id]->cb_args[channel] = cb_args;
|
|
s_platform[core_id]->in_bundles[channel] = bundle;
|
|
channel_mask = channel_mask & (channel_mask - 1); // clear the right most bit '1'
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
#endif // SOC_DEDIC_GPIO_HAS_INTERRUPT
|