esp-idf/components/driver/dedic_gpio.c
morris 16677b0d3c global: make periph enable/disable APIs private
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"
2021-11-08 10:37:47 +08:00

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