blob: 2a8d42e7620e9ea884912bfbb52ac0e3aaedc311 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/common/espressif/esp_pcnt.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/timers/capture.h>
#include <nuttx/irq.h>
#include <nuttx/mutex.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <arch/irq.h>
#include <nuttx/spinlock.h>
#include <nuttx/sensors/sensor.h>
#include "esp_pcnt.h"
#include "chip.h"
#if defined(CONFIG_ARCH_CHIP_ESP32S3)
#include "esp32s3_irq.h"
#include "esp32s3_gpio.h"
#elif defined(CONFIG_ARCH_CHIP_ESP32S2)
#include "esp32s2_irq.h"
#include "esp32s2_gpio.h"
#elif defined(CONFIG_ARCH_CHIP_ESP32)
#include "esp32_irq.h"
#include "esp32_gpio.h"
#endif
#include "hal/pcnt_hal.h"
#include "hal/pcnt_ll.h"
#include "periph_ctrl.h"
#include "soc/pcnt_periph.h"
#include "soc/pcnt_reg.h"
#include "soc/pcnt_struct.h"
#include "soc/gpio_pins.h"
#include "esp_clk.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define PCNT_UNIT_COUNT SOC_PCNT_GROUPS * SOC_PCNT_UNITS_PER_GROUP
#define GET_UNIT_ID_FROM_RET_CHAN(chan_id) (int)(chan_id/SOC_PCNT_CHANNELS_PER_UNIT)
#define GET_CHAN_ID_FROM_RET_CHAN(unit_id, chan_id) (chan_id - (SOC_PCNT_CHANNELS_PER_UNIT * unit_id))
#define CREATE_RET_CHAN_ID(unit_id, chan_id) ((SOC_PCNT_CHANNELS_PER_UNIT * unit_id) + chan_id)
#if defined(CONFIG_ARCH_CHIP_ESP32S3)
#define esp_setup_irq esp32s3_setup_irq
#define esp_teardown_irq esp32s3_teardown_irq
#define esp_configgpio esp32s3_configgpio
#define esp_gpio_matrix_in esp32s3_gpio_matrix_in
#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_IRQ_PRIORITY_DEFAULT ESP32S2_INT_PRIO_DEF
#define ESP_IRQ_TRIGGER_LEVEL ESP32S2_CPUINT_LEVEL
#elif defined(CONFIG_ARCH_CHIP_ESP32)
#define esp_setup_irq esp32_setup_irq
#define esp_teardown_irq esp32_teardown_irq
#define esp_configgpio esp32_configgpio
#define esp_gpio_matrix_in esp32_gpio_matrix_in
#define ESP_IRQ_PRIORITY_DEFAULT 1
#define ESP_IRQ_TRIGGER_LEVEL ESP32_CPUINT_LEVEL
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* PCNT unit states */
enum esp_pcntstate_e
{
PCNT_UNIT_INIT,
PCNT_UNIT_ENABLE,
};
struct esp_pcnt_glitch_filter_config_s
{
uint32_t max_glitch_ns; /* Pulse width threshold, in ns */
};
/* PCNT Unit Watch Point Private Data */
struct esp_pcnt_watch_point_priv_s
{
pcnt_ll_watch_event_id_t event_id; /* Event type */
int watch_point_value; /* Value to be watched */
};
/* PCNT Unit Private Data */
struct esp_pcnt_priv_s
{
const struct cap_ops_s *ops;
int unit_id; /* PCNT unit id */
volatile enum esp_pcntstate_e state; /* PCNT unit work state (see enum esp_pcntstate_e) */
struct esp_pcnt_unit_config_s config; /* Configuration struct */
bool unit_used; /* PCNT unit usage flag */
bool intr; /* PCNT unit interrupt enable flag */
spinlock_t lock; /* Device specific lock. */
int (*cb)(int, void *, void *); /* User defined callback */
uint32_t accum_value; /* Accumulator value of overflowed PCNT unit */
bool channels[SOC_PCNT_CHANNELS_PER_UNIT]; /* Channel information of PCNT unit */
struct esp_pcnt_watch_point_priv_s watchers[PCNT_LL_WATCH_EVENT_MAX]; /* array of PCNT watchers */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int esp_pcnt_open(struct cap_lowerhalf_s *dev);
static int esp_pcnt_close(struct cap_lowerhalf_s *dev);
static int IRAM_ATTR esp_pcnt_isr_default(int irq, void *context,
void *arg);
static int esp_pcnt_isr_register(int (*fn)(int, void *, void *),
int intr_alloc_flags);
static int esp_pcnt_ioctl(struct cap_lowerhalf_s *dev, int cmd,
unsigned long arg);
static int esp_pcnt_unit_enable(struct cap_lowerhalf_s *dev);
static int esp_pcnt_unit_disable(struct cap_lowerhalf_s *dev);
static int esp_pcnt_unit_start(struct cap_lowerhalf_s *dev);
static int esp_pcnt_unit_stop(struct cap_lowerhalf_s *dev);
static int esp_pcnt_unit_get_count(struct cap_lowerhalf_s *dev, int *ret);
static int esp_pcnt_unit_clear_count(struct cap_lowerhalf_s *dev);
static int esp_pcnt_unit_set_glitch_filter(struct cap_lowerhalf_s *dev,
const struct esp_pcnt_glitch_filter_config_s *config);
static int esp_pcnt_unit_register_event_callback(struct cap_lowerhalf_s *dev,
int (*fn)(int, void *, void *));
/****************************************************************************
* Private Data
****************************************************************************/
/* Standard file operations */
static const struct cap_ops_s g_esp_pcnt_fops =
{
.start = esp_pcnt_open,
.stop = esp_pcnt_close,
.getduty = NULL,
.getfreq = NULL,
.ioctl = esp_pcnt_ioctl,
};
static struct esp_pcnt_priv_s pcnt_units[PCNT_UNIT_COUNT] =
{
0
};
static pcnt_hal_context_t ctx; /* Struct of the common layer */
static mutex_t g_pcnt_lock = NXMUTEX_INITIALIZER; /* Mutual exclusion mutex */
static int g_pcnt_refs = 0; /* Reference count */
static bool g_pcnt_intr = false; /* ISR register flag for peripheral */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: esp_pcnt_close
*
* Description:
* Stop PCNT unit from counting.
*
* Input Parameters:
* filep - The pointer of file, represents each user using the sensor
*
* Returned Value:
* Returns OK on success; a negated errno value on failure
*
****************************************************************************/
static int esp_pcnt_close(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
int ret = OK;
ret = esp_pcnt_unit_stop(dev);
if (ret != OK)
{
cperr("Failed to stop pcnt-%d", priv->unit_id);
return ret;
}
if (priv->state != PCNT_UNIT_INIT)
{
ret = esp_pcnt_unit_disable(dev);
if (ret != OK)
{
cperr("Failed to disable pcnt-%d", priv->unit_id);
}
}
return ret;
}
/****************************************************************************
* Name: esp_pcnt_open
*
* Description:
* Start PCNT unit for counting.
*
* Input Parameters:
* filep - The pointer of file, represents each user using the sensor
*
* Returned Value:
* Returns OK on success; a negated errno value on failure
*
****************************************************************************/
static int esp_pcnt_open(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
int ret = OK;
if (priv->state == PCNT_UNIT_INIT)
{
ret = esp_pcnt_unit_enable(dev);
if (ret != OK)
{
cperr("Failed to enable pcnt-%d", priv->unit_id);
return ret;
}
}
ret = esp_pcnt_unit_start(dev);
if (ret != OK)
{
cperr("Failed to start pcnt-%d", priv->unit_id);
}
return ret;
}
/****************************************************************************
* Name: esp_pcnt_ioctl
*
* Description:
* Lower-half logic may support platform-specific ioctl commands
*
* Input Parameters:
* filep - The pointer of file, represents each user using the sensor
* cmd - The ioctl command
* arg - The argument accompanying the ioctl command
*
* Returned Value:
* Zero on success; a negated errno value on failure
*
****************************************************************************/
static int esp_pcnt_ioctl(struct cap_lowerhalf_s *dev, int cmd,
unsigned long arg)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
struct esp_pcnt_glitch_filter_config_s glitch_config;
int ret = 0;
xcpt_t handler;
/* Decode and dispatch the driver-specific IOCTL command */
switch (cmd)
{
case CAPIOC_PULSES:
/* Get PCNT unit count value */
ret = esp_pcnt_unit_get_count(dev, (int *)arg);
if (ret != OK)
{
cperr("Could not get count from pcnt-%d!\n", priv->unit_id);
}
break;
case CAPIOC_CLR_CNT:
/* Clear PCNT unit count value */
ret = esp_pcnt_unit_clear_count(dev);
if (ret != OK)
{
cperr("Could not clear pcnt-%d!\n", priv->unit_id);
}
break;
case CAPIOC_FILTER:
/* Configure glitch filter */
if (arg != 0)
{
glitch_config.max_glitch_ns = arg;
ret = esp_pcnt_unit_set_glitch_filter(dev, &glitch_config);
}
else
{
ret = esp_pcnt_unit_set_glitch_filter(dev, NULL);
}
if (ret != OK)
{
cperr("Could not to set glitch filter to pcnt-%d!\n",
priv->unit_id);
}
break;
case CAPIOC_HANDLER:
/* Set event callback */
handler = (xcpt_t)arg;
ret = esp_pcnt_unit_register_event_callback(dev, handler);
if (ret != OK)
{
cperr("Could not register callback-%" PRIx32 " to pcnt-%d!\n",
(uint32_t)handler, priv->unit_id);
}
break;
case CAPIOC_ADD_WP:
/* Add watch point */
ret = esp_pcnt_unit_add_watch_point(dev, (int)arg);
if (ret != OK)
{
cperr("Could not to add watch point-%d to pcnt-%d!\n",
(int)arg, priv->unit_id);
}
break;
default:
cperr("Unrecognized IOCTL command: %d\n", cmd);
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Name: esp_pcnt_isr_default
*
* Description:
* Handler for the PCNT 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_pcnt_isr_default(int irq, void *context,
void *arg)
{
struct esp_pcnt_priv_s *unit;
int unit_id = 0;
uint32_t event_status = 0;
uint32_t intr_status = pcnt_ll_get_intr_status(ctx.dev);
struct esp_pcnt_watch_event_data_s data;
irqstate_t flags;
for (unit_id = 0; unit_id < SOC_PCNT_UNITS_PER_GROUP; unit_id++)
{
if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(unit_id))
break;
}
if (unit_id < SOC_PCNT_UNITS_PER_GROUP)
{
unit = &pcnt_units[unit_id];
pcnt_ll_clear_intr_status(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
event_status = pcnt_ll_get_event_status(ctx.dev, unit_id);
flags = spin_lock_irqsave(&unit->lock);
while (event_status)
{
int event_id = __builtin_ffs(event_status) - 1;
event_status &= (event_status - 1);
if (unit->config.accum_count)
{
if (event_id == PCNT_LL_WATCH_EVENT_LOW_LIMIT)
{
unit->accum_value += unit->config.low_limit;
}
else if (event_id == PCNT_LL_WATCH_EVENT_HIGH_LIMIT)
{
unit->accum_value += unit->config.high_limit;
}
}
if (unit->cb)
{
data.unit_id = unit_id;
data.watch_point_value =
unit->watchers[event_id].watch_point_value;
data.zero_cross_mode =
pcnt_ll_get_zero_cross_mode(ctx.dev, unit_id);
unit->cb(irq, context, &data);
}
}
spin_unlock_irqrestore(&unit->lock, flags);
}
return 0;
}
/****************************************************************************
* Name: esp_pcnt_isr_register
*
* Description:
* This function registers an interrupt service routine (ISR) for the PCNT
* peripheral. It allocates a CPU interrupt, attaches the ISR to the
* interrupt, and returns the status of the operation.
*
* Input Parameters:
* fn - Pointer to the ISR function.
* intr_alloc_flags - Flags for the interrupt allocation.
*
* Returned Value:
* Returns OK on successful registration of the ISR; a negated errno value
* is returned on any failure.
*
****************************************************************************/
static int esp_pcnt_isr_register(int (*fn)(int, void *, void *),
int intr_alloc_flags)
{
int cpuint;
int ret;
int cpu = this_cpu();
DEBUGASSERT(fn);
cpuint = esp_setup_irq(
#ifndef CONFIG_ARCH_CHIP_ESP32S2
cpu,
#endif
pcnt_periph_signals.groups[0].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((pcnt_periph_signals.groups[0].irq +
XTENSA_IRQ_FIRSTPERIPH),
fn,
0);
if (ret < 0)
{
cperr("Couldn't attach IRQ to handler.\n");
esp_teardown_irq(
#ifndef CONFIG_ARCH_CHIP_ESP32S2
cpu,
#endif
pcnt_periph_signals.groups[0].irq, cpuint);
return ERROR;
}
up_enable_irq(pcnt_periph_signals.groups[0].irq + XTENSA_IRQ_FIRSTPERIPH);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_enable
*
* Description:
* Enable PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_enable(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
if (!priv->unit_used)
{
cperr("Invalid unit ID!\n");
return ERROR;
}
if (priv->state != PCNT_UNIT_INIT)
{
cperr("Unit is already enabled!\n");
return ERROR;
}
if (priv->intr)
{
pcnt_ll_enable_intr(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(priv->unit_id),
true);
}
priv->state = PCNT_UNIT_ENABLE;
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_disable
*
* Description:
* Disable PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_disable(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
if (!priv->unit_used)
{
cperr("Invalid unit ID!\n");
return ERROR;
}
if (priv->state != PCNT_UNIT_ENABLE)
{
cperr("Unit is already disabled!\n");
return ERROR;
}
if (priv->intr)
{
pcnt_ll_enable_intr(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(priv->unit_id),
false);
}
priv->state = PCNT_UNIT_INIT;
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_start
*
* Description:
* Start PCNT unit for counting.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_start(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
irqstate_t flags;
if (priv->state != PCNT_UNIT_ENABLE)
{
cperr("Unit not enabled yet!\n");
return ERROR;
}
flags = spin_lock_irqsave(&priv->lock);
pcnt_ll_start_count(ctx.dev, priv->unit_id);
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_stop
*
* Description:
* Stop PCNT unit from counting.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_stop(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
irqstate_t flags;
if (priv->state != PCNT_UNIT_ENABLE)
{
cperr("Unit not enabled yet!\n");
return ERROR;
}
flags = spin_lock_irqsave(&priv->lock);
pcnt_ll_stop_count(ctx.dev, priv->unit_id);
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_get_count
*
* Description:
* Get count value given of PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* ret - Returned count value
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_get_count(struct cap_lowerhalf_s *dev, int *ret)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
irqstate_t flags;
int32_t tmp_count;
uint32_t event_status;
uint32_t intr_status;
if (!priv->unit_used)
{
cperr("Invalid unit ID!\n");
return ERROR;
}
flags = spin_lock_irqsave(&priv->lock);
tmp_count = pcnt_ll_get_count(ctx.dev, priv->unit_id);
if (priv->config.accum_count)
{
intr_status = pcnt_ll_get_intr_status(ctx.dev);
if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(priv->unit_id))
{
event_status = pcnt_ll_get_event_status(ctx.dev, priv->unit_id);
if ((event_status & (1 << PCNT_LL_WATCH_EVENT_LOW_LIMIT)) && \
(tmp_count >= (priv->config.low_limit / 2)))
{
tmp_count += priv->config.low_limit;
}
else if ((event_status & \
(1 << PCNT_LL_WATCH_EVENT_HIGH_LIMIT)) && \
(tmp_count <= (priv->config.high_limit / 2)))
{
tmp_count += priv->config.high_limit;
}
}
}
*ret = tmp_count + priv->accum_value;
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_clear_count
*
* Description:
* Clear count value given of PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_clear_count(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
irqstate_t flags;
if (!priv->unit_used)
{
cperr("Invalid unit ID!\n");
return ERROR;
}
flags = spin_lock_irqsave(&priv->lock);
pcnt_ll_clear_count(ctx.dev, priv->unit_id);
priv->accum_value = 0;
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_set_glitch_filter
*
* Description:
* Configure glitch filter of given unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* config - Glitch filter configuration
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
static int esp_pcnt_unit_set_glitch_filter(struct cap_lowerhalf_s *dev,
const struct esp_pcnt_glitch_filter_config_s *config)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
uint32_t glitch_filter_thres = 0;
irqstate_t flags;
if (config != NULL)
{
glitch_filter_thres = ((esp_clk_apb_freq() / 1000000) *
(config->max_glitch_ns / 1000));
if (glitch_filter_thres > PCNT_LL_MAX_GLITCH_WIDTH)
{
cperr("Glitch width %" PRId32 " out of range!\n",
config->max_glitch_ns);
return ERROR;
}
}
flags = spin_lock_irqsave(&priv->lock);
if (config != NULL)
{
pcnt_ll_set_glitch_filter_thres(ctx.dev, priv->unit_id,
glitch_filter_thres);
pcnt_ll_enable_glitch_filter(ctx.dev, priv->unit_id, true);
}
else
{
pcnt_ll_enable_glitch_filter(ctx.dev, priv->unit_id, false);
}
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_register_event_callback
*
* Description:
* Set event callbacks for PCNT unit
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* fn - Pointer to the ISR function.
*
* Returned Value:
* Returns OK on successful registration of the ISR; a negated errno value
* is returned on any failure.
*
****************************************************************************/
static int esp_pcnt_unit_register_event_callback(struct cap_lowerhalf_s *dev,
int (*fn)(int, void *, void *))
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
int ret = OK;
int intr_flag = (fn != NULL) ? 1 : 0;
irqstate_t flags;
priv->intr = intr_flag;
flags = spin_lock_irqsave(&priv->lock);
pcnt_ll_enable_intr(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(priv->unit_id),
intr_flag);
spin_unlock_irqrestore(&priv->lock, flags);
priv->cb = fn;
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: esp_pcnt_new_unit
*
* Description:
* Request PCNT unit and config it with given parameters.
*
* Input Parameters:
* config - PCNT unit configuration
*
* Returned Value:
* PCNT unit number (>=0) if success or -1 if fail.
*
****************************************************************************/
struct cap_lowerhalf_s *esp_pcnt_new_unit(
struct esp_pcnt_unit_config_s *config)
{
int unit_id;
int cpuint;
int cpu = this_cpu();
int ret;
int i;
irqstate_t flags;
if (config == NULL)
{
cperr("Configuration struct is NULL!\n");
return NULL;
}
if (config->low_limit >= 0 && config->low_limit < PCNT_LL_MIN_LIM)
{
cperr("Configuration low limit is out of range!\n");
return NULL;
}
if (config->high_limit < 0 && config->high_limit >= PCNT_LL_MAX_LIM)
{
cperr("Configuration high limit is out of range!\n");
return NULL;
}
flags = spin_lock_irqsave(&pcnt_units[unit_id].lock);
if (g_pcnt_refs++ == 0)
{
periph_module_enable(PERIPH_PCNT_MODULE);
periph_module_reset(PERIPH_PCNT_MODULE);
pcnt_hal_init(&ctx, 0);
}
for (unit_id = 0; unit_id < PCNT_UNIT_COUNT; unit_id++)
{
if (!pcnt_units[unit_id].unit_used)
{
pcnt_units[unit_id].unit_used = true;
break;
}
}
if (unit_id == PCNT_UNIT_COUNT)
{
cperr("No available PCNT unit for allocation\n");
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
return NULL;
}
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
cpinfo("Allocated pcnt unit: %" PRId16 "\n", unit_id);
if (!g_pcnt_intr)
{
nxmutex_lock(&g_pcnt_lock);
ret = esp_pcnt_isr_register(esp_pcnt_isr_default, 0);
if (ret < 0)
{
pcnt_units[unit_id].unit_used = false;
nxmutex_unlock(&g_pcnt_lock);
return NULL;
}
g_pcnt_intr = true;
nxmutex_unlock(&g_pcnt_lock);
}
pcnt_ll_disable_all_events(ctx.dev, unit_id);
pcnt_ll_enable_high_limit_event(ctx.dev, unit_id, config->accum_count);
pcnt_ll_enable_low_limit_event(ctx.dev, unit_id, config->accum_count);
pcnt_ll_enable_glitch_filter(ctx.dev, unit_id, false);
pcnt_ll_set_high_limit_value(ctx.dev, unit_id, config->high_limit);
pcnt_ll_set_low_limit_value(ctx.dev, unit_id, config->low_limit);
pcnt_units[unit_id].config.high_limit = config->high_limit;
pcnt_units[unit_id].config.low_limit = config->low_limit;
pcnt_units[unit_id].config.accum_count = config->accum_count;
pcnt_units[unit_id].intr = config->accum_count;
pcnt_units[unit_id].accum_value = 0;
pcnt_units[unit_id].unit_id = unit_id;
pcnt_units[unit_id].ops = &g_esp_pcnt_fops;
flags = spin_lock_irqsave(&pcnt_units[unit_id].lock);
pcnt_ll_stop_count(ctx.dev, unit_id);
pcnt_ll_clear_count(ctx.dev, unit_id);
pcnt_ll_enable_intr(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id),
config->accum_count);
pcnt_ll_clear_intr_status(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
pcnt_units[unit_id].state = PCNT_UNIT_INIT;
for (i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++)
{
pcnt_units[unit_id].watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID;
}
cpinfo("Allocated pcnt unit: %" PRId16 " count range:[%d %d]\n", unit_id,
pcnt_units[unit_id].config.low_limit,
pcnt_units[unit_id].config.high_limit);
return (struct cap_lowerhalf_s *)&pcnt_units[unit_id];
}
/****************************************************************************
* Name: esp_pcnt_del_unit
*
* Description:
* Delete PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
int esp_pcnt_del_unit(struct cap_lowerhalf_s *dev)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
int i;
irqstate_t flags;
int cpu = this_cpu();
if (priv->state != PCNT_UNIT_INIT)
{
cperr("Unit is running!\n");
return ERROR;
}
for (i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++)
{
if (!priv->channels[i])
{
cperr("Channel %" PRId8
" still in working!\n", i);
return ERROR;
}
}
cpinfo("Delete pcnt unit: %" PRId8 "\n", priv->unit_id);
if (priv->intr)
{
pcnt_ll_enable_intr(ctx.dev, PCNT_LL_UNIT_WATCH_EVENT(priv->unit_id),
false);
priv->intr = false;
}
flags = spin_lock_irqsave(&priv->lock);
priv->unit_used = false;
g_pcnt_refs--;
if (g_pcnt_refs == 0)
{
periph_module_disable(PERIPH_PCNT_MODULE);
esp_teardown_irq(
#ifndef CONFIG_ARCH_CHIP_ESP32S2
cpu,
#endif
pcnt_periph_signals.groups[0].irq, -ENOMEM);
}
spin_unlock_irqrestore(&priv->lock, flags);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_unit_add_watch_point
*
* Description:
* Add watch point to given PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* ret - Value to watch
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
int esp_pcnt_unit_add_watch_point(struct cap_lowerhalf_s *dev,
int watch_point)
{
struct esp_pcnt_priv_s *unit = (struct esp_pcnt_priv_s *)dev;
int ret = OK;
struct esp_pcnt_watch_point_priv_s *wp;
irqstate_t flags;
if (watch_point > unit->config.high_limit ||
watch_point < unit->config.low_limit)
{
cperr("Watch point is out of limit!\n");
return ERROR;
}
flags = spin_lock_irqsave(&unit->lock);
/* zero cross watch point */
if (watch_point == 0)
{
if (unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id !=
PCNT_LL_WATCH_EVENT_INVALID)
{
cperr("Zero cross event watcher has been installed already!\n");
ret = ERROR;
}
else
{
unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id =
PCNT_LL_WATCH_EVENT_ZERO_CROSS;
unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].watch_point_value =
0;
pcnt_ll_enable_zero_cross_event(ctx.dev,
unit->unit_id,
true);
}
}
/* high limit watch point */
else if (watch_point == unit->config.high_limit)
{
if (unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id !=
PCNT_LL_WATCH_EVENT_INVALID)
{
cperr("High limit event watcher has been installed already!\n");
ret = ERROR;
}
else
{
unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id =
PCNT_LL_WATCH_EVENT_HIGH_LIMIT;
unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].watch_point_value =
unit->config.high_limit;
pcnt_ll_enable_high_limit_event(ctx.dev,
unit->unit_id,
true);
}
}
/* low limit watch point */
else if (watch_point == unit->config.low_limit)
{
if (unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id !=
PCNT_LL_WATCH_EVENT_INVALID)
{
cperr("Low limit event watcher has been installed already!\n");
ret = ERROR;
}
else
{
unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id =
PCNT_LL_WATCH_EVENT_LOW_LIMIT;
unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].watch_point_value =
unit->config.low_limit;
pcnt_ll_enable_low_limit_event(ctx.dev,
unit->unit_id,
true);
}
}
/* other threshold watch point */
else
{
int thres_num = SOC_PCNT_THRES_POINT_PER_UNIT - 1;
switch (thres_num)
{
case 1:
wp = &unit->watchers[PCNT_LL_WATCH_EVENT_THRES1];
if (wp->event_id == PCNT_LL_WATCH_EVENT_INVALID)
{
wp->event_id = PCNT_LL_WATCH_EVENT_THRES1;
wp->watch_point_value = watch_point;
pcnt_ll_set_thres_value(ctx.dev,
unit->unit_id,
1,
watch_point);
pcnt_ll_enable_thres_event(ctx.dev,
unit->unit_id,
1,
true);
break;
}
else if (wp->watch_point_value == watch_point)
{
cperr("Watcher has been installed already!\n");
ret = ERROR;
break;
}
case 0:
wp = &unit->watchers[PCNT_LL_WATCH_EVENT_THRES0];
if (wp->event_id == PCNT_LL_WATCH_EVENT_INVALID)
{
wp->event_id = PCNT_LL_WATCH_EVENT_THRES0;
wp->watch_point_value = watch_point;
pcnt_ll_set_thres_value(ctx.dev,
unit->unit_id,
0,
watch_point);
pcnt_ll_enable_thres_event(ctx.dev,
unit->unit_id,
0,
true);
break;
}
else if (wp->watch_point_value == watch_point)
{
cperr("Watcher has been installed already!\n");
ret = ERROR;
break;
}
default:
cperr("No free threshold watch point available!\n");
ret = ERROR;
break;
}
}
spin_unlock_irqrestore(&unit->lock, flags);
if (ret != ERROR)
{
cpinfo("Watchpoint %d for pcnt unit-%d added successfully\n",
watch_point,
unit->unit_id);
}
return ret;
}
/****************************************************************************
* Name: esp_pcnt_unit_remove_watch_point
*
* Description:
* Remove watch point from given PCNT unit.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* ret - Watch point value to remove
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
int esp_pcnt_unit_remove_watch_point(struct cap_lowerhalf_s *dev,
int watch_point)
{
struct esp_pcnt_priv_s *unit = (struct esp_pcnt_priv_s *)dev;
int ret = OK;
int i;
pcnt_ll_watch_event_id_t event_id = PCNT_LL_WATCH_EVENT_INVALID;
irqstate_t flags;
flags = spin_lock_irqsave(&unit->lock);
for (i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++)
{
if (unit->watchers[i].event_id != PCNT_LL_WATCH_EVENT_INVALID &&
unit->watchers[i].watch_point_value == watch_point)
{
event_id = unit->watchers[i].event_id;
unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID;
break;
}
}
switch (event_id)
{
case PCNT_LL_WATCH_EVENT_ZERO_CROSS:
pcnt_ll_enable_zero_cross_event(ctx.dev, unit->unit_id, false);
break;
case PCNT_LL_WATCH_EVENT_LOW_LIMIT:
pcnt_ll_enable_low_limit_event(ctx.dev, unit->unit_id, false);
break;
case PCNT_LL_WATCH_EVENT_HIGH_LIMIT:
pcnt_ll_enable_high_limit_event(ctx.dev, unit->unit_id, false);
break;
case PCNT_LL_WATCH_EVENT_THRES0:
pcnt_ll_enable_thres_event(ctx.dev, unit->unit_id, 0, false);
break;
case PCNT_LL_WATCH_EVENT_THRES1:
pcnt_ll_enable_thres_event(ctx.dev, unit->unit_id, 1, false);
break;
default:
break;
}
spin_unlock_irqrestore(&unit->lock, flags);
return ret;
}
/****************************************************************************
* Name: esp_pcnt_new_channel
*
* Description:
* Request channel on given PCNT unit and config it with given parameters.
*
* Input Parameters:
* dev - Pointer to the pcnt driver struct
* config - PCNT unit channel configuration
*
* Returned Value:
* PCNT unit channel number (>=0) if success or -1 if fail.
*
****************************************************************************/
int esp_pcnt_new_channel(struct cap_lowerhalf_s *dev,
struct esp_pcnt_chan_config_s *config)
{
struct esp_pcnt_priv_s *priv = (struct esp_pcnt_priv_s *)dev;
int channel_id = -1;
int unit_id = priv->unit_id;
int gpio_mode;
int virt_gpio;
int ret_id = 0;
const pcnt_signal_conn_t *chan;
if (!config)
{
cperr("Channel config is NULL!\n");
return ERROR;
}
if (!priv->unit_used)
{
cperr("Unit did not initialize yet!\n");
return ERROR;
}
if (priv->state != PCNT_UNIT_INIT)
{
cperr("Unit already running!\n");
return ERROR;
}
for (int i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++)
{
if (!priv->channels[i])
{
channel_id = i;
break;
}
}
if (channel_id == -1)
{
cperr("no free channel in unit %" PRId8 "!\n", priv->unit_id);
return ERROR;
}
gpio_mode = INPUT_FUNCTION | PULLUP |
(config->flags && ESP_PCNT_CHAN_IO_LOOPBACK ? OUTPUT_FUNCTION : 0);
virt_gpio = (config->flags && ESP_PCNT_CHAN_VIRT_LVL_IO_LVL) ?
GPIO_MATRIX_CONST_ONE_INPUT : GPIO_MATRIX_CONST_ZERO_INPUT;
chan = &pcnt_periph_signals;
if (config->edge_gpio_num >= 0)
{
esp_configgpio(config->edge_gpio_num, gpio_mode);
esp_gpio_matrix_in(config->edge_gpio_num,
chan->groups[0].units[unit_id].channels[channel_id].pulse_sig,
(config->flags && ESP_PCNT_CHAN_INVERT_EDGE_IN));
}
else
{
/* using virtual IO */
esp_gpio_matrix_in(virt_gpio,
chan->groups[0].units[unit_id].channels[channel_id].pulse_sig,
(config->flags && ESP_PCNT_CHAN_INVERT_EDGE_IN));
}
if (config->level_gpio_num >= 0)
{
esp_configgpio(config->level_gpio_num, gpio_mode);
esp_gpio_matrix_in(config->level_gpio_num,
chan->groups[0].units[unit_id].channels[channel_id].control_sig,
(config->flags && ESP_PCNT_CHAN_INVERT_LVL_IN));
}
else
{
/* using virtual IO */
esp_gpio_matrix_in(virt_gpio,
chan->groups[0].units[unit_id].channels[channel_id].control_sig,
(config->flags && ESP_PCNT_CHAN_INVERT_LVL_IN));
}
priv->channels[channel_id] = true;
cpinfo("Added pcnt unit: %" PRId8 " channel: %" PRId8 "\n",
unit_id, channel_id);
ret_id = CREATE_RET_CHAN_ID(unit_id, channel_id);
return ret_id;
}
/****************************************************************************
* Name: esp_pcnt_del_channel
*
* Description:
* Delete PCNT unit channel.
*
* Input Parameters:
* channel - Channel number to delete
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
int esp_pcnt_del_channel(int channel)
{
int unit_id = GET_UNIT_ID_FROM_RET_CHAN(channel);
int chan_id = GET_CHAN_ID_FROM_RET_CHAN(unit_id, channel);
irqstate_t flags;
flags = spin_lock_irqsave(&pcnt_units[unit_id].lock);
pcnt_units[unit_id].channels[chan_id] = false;
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
cpinfo("Deleted pcnt unit: %" PRId8 " channel: %" PRId8 "\n",
unit_id, chan_id);
return OK;
}
/****************************************************************************
* Name: esp_pcnt_channel_set_edge_action
*
* Description:
* Set channel actions when edge signal changes.
*
* Input Parameters:
* channel - Channel number to set actions
* post act - Action on posedge signal
* neg_act - Action on negedge signal
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
void esp_pcnt_channel_set_edge_action(int channel,
enum esp_pcnt_chan_edge_action_e pos_act,
enum esp_pcnt_chan_edge_action_e neg_act)
{
int unit_id = GET_UNIT_ID_FROM_RET_CHAN(channel);
int chan_id = GET_CHAN_ID_FROM_RET_CHAN(unit_id, channel);
irqstate_t flags;
flags = spin_lock_irqsave(&pcnt_units[unit_id].lock);
pcnt_ll_set_edge_action(ctx.dev, unit_id, chan_id,
pos_act, neg_act);
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
}
/****************************************************************************
* Name: esp_pcnt_channel_set_level_action
*
* Description:
* Set channel actions when level signal changes.
*
* Input Parameters:
* channel - Channel number to set actions
* post act - Action on posedge signal
* neg_act - Action on negedge signal
*
* Returned Value:
* OK on success; ERROR on failure
*
****************************************************************************/
void esp_pcnt_channel_set_level_action(int channel,
enum esp_pcnt_chan_level_action_e pos_act,
enum esp_pcnt_chan_level_action_e neg_act)
{
int unit_id = GET_UNIT_ID_FROM_RET_CHAN(channel);
int chan_id = GET_CHAN_ID_FROM_RET_CHAN(unit_id, channel);
irqstate_t flags;
flags = spin_lock_irqsave(&pcnt_units[unit_id].lock);
pcnt_ll_set_level_action(ctx.dev, unit_id, chan_id,
pos_act, neg_act);
spin_unlock_irqrestore(&pcnt_units[unit_id].lock, flags);
}