blob: a1780a54d26a4c4003d953fd797b023bb7bcf0ec [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/common/espressif/esp_dedic_gpio.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <syslog.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/timers/capture.h>
#include <nuttx/mutex.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <arch/irq.h>
#include <nuttx/spinlock.h>
#include <nuttx/ioexpander/gpio.h>
#include "chip.h"
#include "esp_dedic_gpio.h"
#if defined(CONFIG_ARCH_CHIP_ESP32S3)
#include "esp32s3_gpio.h"
#elif defined(CONFIG_ARCH_CHIP_ESP32S2)
#include "esp32s2_irq.h"
#include "esp32s2_gpio.h"
#endif
#if defined(CONFIG_ARCH_CHIP_ESP32S2)
#include "soc/dedic_gpio_struct.h"
#endif
#include "hal/dedic_gpio_ll.h"
#include "hal/dedic_gpio_cpu_ll.h"
#include "soc/dedic_gpio_periph.h"
#include "soc/gpio_sig_map.h"
#include "periph_ctrl.h"
#include "soc/soc_caps.h"
#include "soc/gpio_pins.h"
#include "esp_clk.h"
#include "esp_attr.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_ARCH_CHIP_ESP32S3)
#define esp_configgpio esp32s3_configgpio
#define esp_gpio_matrix_in esp32s3_gpio_matrix_in
#define esp_gpio_matrix_out esp32s3_gpio_matrix_out
#define ESP_IRQ_PRIORITY_DEFAULT ESP32S3_INT_PRIO_DEF
#define ESP_IRQ_TRIGGER_LEVEL ESP32S3_CPUINT_LEVEL
#elif defined(CONFIG_ARCH_CHIP_ESP32S2)
#define esp_setup_irq esp32s2_setup_irq
#define esp_teardown_irq esp32s2_teardown_irq
#define esp_configgpio esp32s2_configgpio
#define esp_gpio_matrix_in esp32s2_gpio_matrix_in
#define esp_gpio_matrix_out esp32s2_gpio_matrix_out
#define ESP_IRQ_PRIORITY_DEFAULT ESP32S2_INT_PRIO_DEF
#define ESP_IRQ_TRIGGER_LEVEL ESP32S2_CPUINT_LEVEL
#endif
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
enum esp_dedic_gpio_intr_type_t
{
DEDIC_GPIO_INTR_NONE, /* No interrupt */
DEDIC_GPIO_INTR_LOW_LEVEL = 2, /* Interrupt on low level */
DEDIC_GPIO_INTR_HIGH_LEVEL, /* Interrupt on high level */
DEDIC_GPIO_INTR_NEG_EDGE, /* Interrupt on negedge */
DEDIC_GPIO_INTR_POS_EDGE, /* Interrupt on posedge */
DEDIC_GPIO_INTR_BOTH_EDGE /* Interrupt on both negedge and posedge */
};
#endif
struct esp_dedic_gpio_bundle_priv_s
{
struct file_operations *ops; /* Externally visible part of the GPIO interface */
uint32_t out_offset; /* Offset of output channels */
uint32_t in_offset; /* Offset of input channels */
uint32_t out_mask; /* Mask of output channels */
uint32_t in_mask; /* Mask of input channels */
};
struct esp_dedic_gpio_common_priv_s
{
int refs; /* Reference count */
spinlock_t spinlock; /* Held while chip is selected for mutual exclusion */
uint32_t out_occupied_mask; /* Mask of output channels */
uint32_t in_occupied_mask; /* Mask of input channels */
#ifdef CONFIG_ARCH_CHIP_ESP32S2
dedic_dev_t *dev;
#endif
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
bool isr_init; /* ISR register flag for peripheral */
int (*cbs[SOC_DEDIC_GPIO_IN_CHANNELS_NUM])(struct file *filep, int pin);
/* Demonstrate that which bundle belongs to for input channel */
struct esp_dedic_gpio_bundle_priv_s *
in_bundles[SOC_DEDIC_GPIO_IN_CHANNELS_NUM];
#endif
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int esp_dedic_gpio_bundle_read(struct file *filep,
char *buffer,
size_t buflen);
static int esp_dedic_gpio_bundle_write(struct file *filep,
const char *buffer,
size_t buflen);
/****************************************************************************
* Private Data
****************************************************************************/
static struct file_operations dedic_gpio_ops =
{
NULL, /* open */
NULL, /* close */
esp_dedic_gpio_bundle_read, /* read */
esp_dedic_gpio_bundle_write, /* write */
};
static struct esp_dedic_gpio_common_priv_s
dedic_gpio_common[SOC_CPU_CORES_NUM] =
{
0
};
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
/****************************************************************************
* Name: esp_dedic_gpio_pin_get_pin_attr
*
* Description:
* Convert pin type to dedicated GPIO pin attr.
*
* Input Parameters:
* pintype - The pin type. See nuttx/ioexpander/gpio.h.
*
* Returned Value:
* Dedicated gpio driver pin attr if available, 0 otherwise.
*
****************************************************************************/
static int esp_dedic_gpio_pin_get_pin_attr(enum gpio_pintype_e pintype)
{
switch (pintype)
{
case GPIO_INTERRUPT_LOW_PIN:
return DEDIC_GPIO_INTR_LOW_LEVEL;
break;
case GPIO_INTERRUPT_HIGH_PIN:
return DEDIC_GPIO_INTR_HIGH_LEVEL;
break;
case GPIO_INTERRUPT_FALLING_PIN:
return DEDIC_GPIO_INTR_NEG_EDGE;
break;
case GPIO_INTERRUPT_RISING_PIN:
return DEDIC_GPIO_INTR_POS_EDGE;
break;
case GPIO_INTERRUPT_BOTH_PIN:
return DEDIC_GPIO_INTR_BOTH_EDGE;
break;
default:
return DEDIC_GPIO_INTR_NONE;
break;
}
return DEDIC_GPIO_INTR_NONE;
}
/****************************************************************************
* Name: esp_pcnt_isr_default
*
* Description:
* Handler for the dedicated GPIO controller interrupt.
*
* Input Parameters:
* irq - Number of the IRQ that generated the interrupt
* context - Interrupt register state save info
* arg - PCNT controller private data
*
* Returned Value:
* Standard interrupt return value.
*
****************************************************************************/
static int IRAM_ATTR esp_dedic_gpio_isr_default(int irq, void *context,
void *arg)
{
struct esp_dedic_gpio_common_priv_s *platform =
(struct esp_dedic_gpio_common_priv_s *)arg;
uint32_t flags = spin_lock_irqsave(dedic_gpio_common->spinlock);
uint32_t status = dedic_gpio_ll_get_interrupt_status(platform->dev);
dedic_gpio_ll_clear_interrupt_status(platform->dev, status);
spin_unlock_irqrestore(dedic_gpio_common->spinlock, flags);
while (status)
{
uint32_t channel = __builtin_ffs(status) - 1;
if (platform->cbs[channel] != NULL)
{
platform->cbs[channel]((struct file *)
platform->in_bundles[channel],
channel -
platform->in_bundles[channel]->in_offset);
}
status = status & (status - 1);
}
return 0;
}
/****************************************************************************
* Name: esp_pcnt_isr_register
*
* Description:
* This function registers an interrupt service routine (ISR) for the
* peripheral. It allocates a CPU interrupt, attaches the ISR to the
* interrupt, and returns the status of the operation.
*
* Input Parameters:
* None.
*
* Returned Value:
* Returns OK on successful registration of the ISR; a negated errno value
* is returned on any failure.
*
****************************************************************************/
static int esp_dedic_gpio_isr_register(void)
{
int cpuint;
int ret;
int cpu = this_cpu();
uint32_t status;
cpuint = esp_setup_irq(dedic_gpio_periph_signals.irq,
ESP_IRQ_PRIORITY_DEFAULT,
ESP_IRQ_TRIGGER_LEVEL);
if (cpuint < 0)
{
cperr("Failed to allocate a CPU interrupt.\n");
return ERROR;
}
ret = irq_attach(dedic_gpio_periph_signals.irq + XTENSA_IRQ_FIRSTPERIPH,
esp_dedic_gpio_isr_default,
&dedic_gpio_common[cpu]);
if (ret < 0)
{
cperr("Couldn't attach IRQ to handler.\n");
esp_teardown_irq(dedic_gpio_periph_signals.irq, cpuint);
return ERROR;
}
status = dedic_gpio_ll_get_interrupt_status(dedic_gpio_common[cpu].dev);
dedic_gpio_ll_clear_interrupt_status(dedic_gpio_common[cpu].dev, status);
up_enable_irq(dedic_gpio_periph_signals.irq + XTENSA_IRQ_FIRSTPERIPH);
return OK;
}
#endif /* CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ */
/****************************************************************************
* Name: esp_dedic_gpio_bundle_read
*
* Description:
* Read dedicated gpio bundle data.
*
* Input Parameters:
* filep - The pointer of file, represents each user using the device.
* buffer - Buffer to read.
* buflen - Not in use, have it for compatibility reasons.
*
* Returned Value:
* OK
*
****************************************************************************/
static int esp_dedic_gpio_bundle_read(struct file *filep,
char *buffer,
size_t buflen)
{
struct inode *inode = filep->f_inode;
struct esp_dedic_gpio_bundle_priv_s *priv = inode->i_private;
uint32_t dedic_value = 0;
UNUSED(buflen);
DEBUGASSERT(priv != NULL && buffer != NULL);
gpioinfo("Reading pin...\n");
dedic_value = dedic_gpio_cpu_ll_read_in();
*buffer = (dedic_value & priv->in_mask) >> (priv->in_offset);
return OK;
}
/****************************************************************************
* Name: esp_dedic_gpio_bundle_write
*
* Description:
* Write data to the dedicated gpio bundle.
*
* Input Parameters:
* filep - The pointer of file, represents each user using the device.
* buffer - Buffer to write value to GPIO bundle.
* buflen - Mask of the value.
*
* Returned Value:
* OK
*
****************************************************************************/
static int esp_dedic_gpio_bundle_write(struct file *filep,
const char *buffer,
size_t buflen)
{
struct inode *inode = filep->f_inode;
struct esp_dedic_gpio_bundle_priv_s *priv = inode->i_private;
uint32_t mask_value = 0;
uint32_t write_value = 0;
DEBUGASSERT(priv != NULL);
gpioinfo("Writing %d with mask: %d\n", (int)*buffer, buflen);
mask_value = priv->out_mask & (buflen << priv->out_offset);
write_value = *(buffer) << priv->out_offset;
dedic_gpio_cpu_ll_write_mask(mask_value, write_value);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
/****************************************************************************
* Name: esp_dedic_gpio_set_cb
*
* Description:
* Set event callbacks
*
* Input Parameters:
* dev - Pointer to the dedicated gpio driver struct
* mask - Mask of the GPIOs to assign callback
* pintype - Type of interrupt to trigger
* callback - Pointer to the ISR function.
*
* Returned Value:
* None
*
****************************************************************************/
void esp_dedic_gpio_set_cb(struct file *dev,
uint32_t mask,
enum gpio_pintype_e pintype,
int (*callback)(struct file *dev, int pin))
{
struct esp_dedic_gpio_bundle_priv_s *priv =
(struct esp_dedic_gpio_bundle_priv_s *)dev;
uint32_t channel_mask = priv->in_mask & (mask << priv->in_offset);
uint32_t channel = 0;
irqstate_t flags;
int cpu = this_cpu();
int pin_attr = esp_dedic_gpio_pin_get_pin_attr(pintype);
while (channel_mask)
{
channel = __builtin_ffs(channel_mask) - 1;
flags = spin_lock_irqsave(&dedic_gpio_common[cpu].spinlock);
dedic_gpio_ll_set_interrupt_type(dedic_gpio_common[cpu].dev,
channel,
pin_attr);
if (pin_attr != DEDIC_GPIO_INTR_NONE)
{
dedic_gpio_ll_enable_interrupt(dedic_gpio_common[cpu].dev,
1 << channel,
true);
}
else
{
dedic_gpio_ll_enable_interrupt(dedic_gpio_common[cpu].dev,
1 << channel,
false);
}
spin_unlock_irqrestore(&dedic_gpio_common[cpu].spinlock, flags);
dedic_gpio_common[cpu].cbs[channel] = callback;
dedic_gpio_common[cpu].in_bundles[channel] = priv;
channel_mask = channel_mask & (channel_mask - 1);
}
}
#endif
/****************************************************************************
* Name: esp_dedic_gpio_write
*
* Description:
* Write data to the dedicated gpio bundle with given mask value.
*
* Input Parameters:
* dev - Pointer to the dedicated gpio driver struct
* mask - Mask of the GPIOs to be written
* value - Value to write
*
* Returned Value:
* None
*
****************************************************************************/
void esp_dedic_gpio_write(struct file *dev,
uint32_t mask,
uint32_t value)
{
struct esp_dedic_gpio_bundle_priv_s *priv =
(struct esp_dedic_gpio_bundle_priv_s *)dev;
uint32_t mask_value = priv->out_mask & (mask << priv->out_offset);
uint32_t write_value = value << priv->out_offset;
dedic_gpio_cpu_ll_write_mask(mask_value, write_value);
}
/****************************************************************************
* Name: esp_dedic_gpio_read
*
* Description:
* Read dedicated gpio bundle data.
*
* Input Parameters:
* dev - Pointer to the dedicated gpio driver struct
* value - Pointer to the read data will be saved
*
* Returned Value:
* None
*
****************************************************************************/
void esp_dedic_gpio_read(struct file *dev, int *value)
{
struct esp_dedic_gpio_bundle_priv_s *priv =
(struct esp_dedic_gpio_bundle_priv_s *)dev;
*value = dedic_gpio_cpu_ll_read_in();
*value = ((*value) & priv->in_mask) >> (priv->in_offset);
}
/****************************************************************************
* Name: esp_dedic_gpio_new_bundle
*
* Description:
* Request dedicated GPIO bundle and config it with given parameters.
*
* Input Parameters:
* config - Dedicated GPIO bundle configuration
*
* Returned Value:
* Valid GPIO device structure reference on success; NULL on failure.
*
****************************************************************************/
struct file *esp_dedic_gpio_new_bundle(
struct esp_dedic_gpio_config_s *config)
{
struct esp_dedic_gpio_bundle_priv_s *priv = NULL;
irqstate_t flags = 0;
uint32_t out_mask = 0;
uint32_t in_mask = 0;
uint32_t pattern = 0;
uint32_t out_offset = 0;
uint32_t in_offset = 0;
gpio_pinattr_t attr = 0;
int cpu = this_cpu();
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
int ret;
#endif
DEBUGASSERT(config != NULL);
DEBUGASSERT(config->gpio_array != NULL && config->array_size > 0);
DEBUGASSERT(config->path != NULL);
DEBUGASSERT(config->flags->input_enable ||
config->flags->output_enable > 0);
if (dedic_gpio_common[cpu].refs == 0)
{
flags = spin_lock_irqsave(&dedic_gpio_common[cpu].spinlock);
#ifdef CONFIG_ARCH_CHIP_ESP32S2
dedic_gpio_common[cpu].dev = &DEDIC_GPIO;
#endif
PERIPH_RCC_ATOMIC()
{
dedic_gpio_ll_enable_bus_clock(true);
dedic_gpio_ll_reset_register();
}
dedic_gpio_common[cpu].out_occupied_mask =
UINT32_MAX & ~((1 << SOC_DEDIC_GPIO_OUT_CHANNELS_NUM) - 1);
dedic_gpio_common[cpu].in_occupied_mask =
UINT32_MAX & ~((1 << SOC_DEDIC_GPIO_IN_CHANNELS_NUM) - 1);
spin_unlock_irqrestore(&dedic_gpio_common[cpu].spinlock, flags);
}
priv = kmm_zalloc(sizeof(struct esp_dedic_gpio_bundle_priv_s));
if (priv)
{
pattern = (1 << config->array_size) - 1;
/* configure outwards channels */
priv->ops = &dedic_gpio_ops;
out_offset = 0;
if (config->flags->output_enable)
{
if (config->array_size > SOC_DEDIC_GPIO_OUT_CHANNELS_NUM)
{
gpioerr("ERROR: array size(%d) exceeds maximum supported out\
channels(%d)\n",
config->array_size, SOC_DEDIC_GPIO_OUT_CHANNELS_NUM);
free(priv);
return NULL;
}
flags = spin_lock_irqsave(&dedic_gpio_common[cpu].spinlock);
for (int i = 0;
i <= SOC_DEDIC_GPIO_OUT_CHANNELS_NUM - config->array_size;
i++)
{
if ((dedic_gpio_common[cpu].out_occupied_mask & (pattern << i))
== 0)
{
out_mask = pattern << i;
out_offset = i;
break;
}
}
if (out_mask)
{
dedic_gpio_common[cpu].out_occupied_mask |= out_mask;
}
spin_unlock_irqrestore(&dedic_gpio_common[cpu].spinlock, flags);
if (out_mask == 0)
{
gpioerr("ERROR: no free outward channels\n");
free(priv);
return NULL;
}
attr |= OUTPUT;
#ifdef CONFIG_ARCH_CHIP_ESP32S2
dedic_gpio_ll_enable_instruction_access_out(
dedic_gpio_common[cpu].dev,
out_mask,
true);
#endif
gpioinfo("New out bundle(%p), offset=%"PRIu32", mask(%"PRIx32")",
priv, priv->out_offset, priv->out_mask);
}
if (config->flags->input_enable)
{
if (config->array_size > SOC_DEDIC_GPIO_IN_CHANNELS_NUM)
{
gpioerr("ERROR: array size(%d) exceeds maximum supported in\
channels(%d)\n",
config->array_size, SOC_DEDIC_GPIO_IN_CHANNELS_NUM);
free(priv);
return NULL;
}
flags = spin_lock_irqsave(&dedic_gpio_common[cpu].spinlock);
for (int i = 0;
i <= SOC_DEDIC_GPIO_IN_CHANNELS_NUM - config->array_size;
i++)
{
if ((dedic_gpio_common[cpu].in_occupied_mask &
(pattern << i)) == 0)
{
in_mask = pattern << i;
in_offset = i;
break;
}
}
if (in_mask)
{
dedic_gpio_common[cpu].in_occupied_mask |= in_mask;
}
spin_unlock_irqrestore(&dedic_gpio_common[cpu].spinlock, flags);
if (in_mask == 0)
{
gpioerr("ERROR: no free inward channels\n");
free(priv);
return NULL;
}
attr |= INPUT;
gpioinfo("New in bundle(%p), offset=%"PRIu32", mask(%"PRIx32")",
priv, priv->in_offset, priv->in_mask);
}
/* Route dedicated GPIO channel signals to GPIO matrix */
for (int i = 0; i < config->array_size; i++)
{
esp_configgpio(config->gpio_array[i], attr);
if (config->flags->input_enable)
{
esp_gpio_matrix_in(config->gpio_array[i],
dedic_gpio_periph_signals.cores[cpu].in_sig_per_channel[in_offset + i],
config->flags->invert_input_enable);
}
if (config->flags->output_enable)
{
esp_gpio_matrix_out(config->gpio_array[i],
dedic_gpio_periph_signals.cores[cpu].out_sig_per_channel[in_offset + i],
config->flags->invert_output_enable, 0);
}
}
priv->out_mask = out_mask;
priv->in_mask = in_mask;
priv->out_offset = out_offset;
priv->in_offset = in_offset;
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
if (config->flags->input_enable &&
config->flags->intr_enable &&
dedic_gpio_common[cpu].isr_init == false)
{
ret = esp_dedic_gpio_isr_register();
if (ret != OK)
{
gpioerr("Failed to initialize interrupt\n");
free(priv);
return NULL;
}
}
#endif
}
else
{
gpioerr("ERROR: memory allocation failed\n");
return NULL;
}
dedic_gpio_common[cpu].refs++;
register_driver(config->path, &dedic_gpio_ops,
0666, priv);
return (struct file *)priv;
}
/****************************************************************************
* Name: esp_dedic_gpio_del_bundle
*
* Description:
* Delete dedicated gpio bundle.
*
* Input Parameters:
* dev - Pointer to the dedicated gpio driver struct
*
* Returned Value:
* OK
*
****************************************************************************/
int esp_dedic_gpio_del_bundle(struct file *dev)
{
struct esp_dedic_gpio_bundle_priv_s *priv =
(struct esp_dedic_gpio_bundle_priv_s *)dev;
int cpu = this_cpu();
irqstate_t flags = spin_lock_irqsave(&dedic_gpio_common[cpu].spinlock);
dedic_gpio_common[cpu].refs--;
dedic_gpio_common[cpu].out_occupied_mask &= ~(priv->out_mask);
dedic_gpio_common[cpu].in_occupied_mask &= ~(priv->in_mask);
if (dedic_gpio_common[cpu].refs == 0)
{
PERIPH_RCC_ATOMIC()
{
dedic_gpio_ll_enable_bus_clock(false);
}
#ifdef CONFIG_ESPRESSIF_DEDICATED_GPIO_IRQ
dedic_gpio_ll_enable_interrupt(dedic_gpio_common[cpu].dev,
~0UL,
false);
up_disable_irq(dedic_gpio_periph_signals.irq + XTENSA_IRQ_FIRSTPERIPH);
dedic_gpio_common[cpu].isr_init = false;
#endif
}
free(priv);
priv = NULL;
spin_unlock_irqrestore(&dedic_gpio_common[cpu].spinlock, flags);
return OK;
}