Files
nuttx/drivers/timers/capture.c
T
dongjiuzhu1 f6a9f8239d drivers/capture: add signal notification support for edge capture events
This commit adds comprehensive signal notification support to the capture
driver, enabling applications to be asynchronously notified when capture
edge events occur on any channel, rather than having to poll for events.

Background:
The capture driver is used to measure frequency and duty cycle of external
signals by detecting edges (rising/falling). Previously, applications could
only retrieve captured values through periodic polling via CAPIOC_GETDUTY
and CAPIOC_GETFREQ ioctl commands, which is inefficient for event-driven
applications.

Problem:
Without event notification, applications must:
- Continuously poll the capture device to check for new events
- Waste CPU cycles in polling loops
- Experience higher latency in responding to capture events
- Cannot efficiently handle multiple capture channels simultaneously

Solution:
This commit adds a signal-based notification mechanism that allows
applications to register callbacks for specific capture channels and
edge types (rising, falling, or both). When the hardware detects the
configured edge, a signal is sent to the registered task.

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
2026-01-02 07:46:52 -03:00

626 lines
17 KiB
C

/****************************************************************************
* drivers/timers/capture.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/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/timers/capture.h>
#include <arch/irq.h>
#ifdef CONFIG_CAPTURE
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Debug ********************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_CAPTURE_NOTIFY
struct cap_signal_s
{
pid_t pid; /* The pid of the registering task */
struct cap_notify_s notify; /* The notification callback info */
#ifdef CONFIG_SIG_EVTHREAD
struct sigwork_s work; /* The signal work structure */
#endif
};
#endif
/* This structure describes the state of the upper half driver */
struct cap_upperhalf_s
{
uint8_t crefs; /* The number of times the device has been opened */
uint8_t nchan; /* The number of channels, only invalid for multi channels */
mutex_t lock; /* Supports mutual exclusion */
FAR struct cap_lowerhalf_s **lower; /* lower-half state */
#ifdef CONFIG_CAPTURE_NOTIFY
/* The array of signal structures, the length of the array is nchan,
* save signal info for each channel.
*/
struct cap_signal_s signals[1];
#endif
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int cap_open(FAR struct file *filep);
static int cap_close(FAR struct file *filep);
static ssize_t cap_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t cap_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int cap_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_capops =
{
cap_open, /* open */
cap_close, /* close */
cap_read, /* read */
cap_write, /* write */
NULL, /* seek */
cap_ioctl, /* ioctl */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: cap_notify_cb
*
* Description:
* Capture edge interrupt notification callback
*
****************************************************************************/
#ifdef CONFIG_CAPTURE_NOTIFY
static void cap_notify_cb(FAR struct cap_lowerhalf_s *lower, FAR void *priv)
{
FAR struct cap_upperhalf_s *upper = priv;
uint8_t i;
DEBUGASSERT(upper != NULL);
for (i = 0; i < upper->nchan; i++)
{
if (lower == upper->lower[i])
{
FAR struct cap_signal_s *signal = &upper->signals[i];
# ifdef CONFIG_SIG_EVTHREAD
nxsig_notification(signal->pid, &signal->notify.event,
SI_QUEUE, &signal->work);
# else
nxsig_notification(signal->pid, &signal->notify.event,
SI_QUEUE, NULL);
# endif
break;
}
}
}
#endif
/****************************************************************************
* Name: cap_open
*
* Description:
* This function is called whenever the PWM Capture device is opened.
*
****************************************************************************/
static int cap_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct cap_upperhalf_s *upper = inode->i_private;
uint8_t tmp;
int ret;
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
goto errout;
}
/* Increment the count of references to the device. If this is the first
* time that the driver has been opened for this device, then initialize
* the device.
*/
tmp = upper->crefs + 1;
if (tmp == 0)
{
/* More than 255 opens; uint8_t overflows to zero */
ret = -EMFILE;
goto errout_with_lock;
}
/* Check if this is the first time that the driver has been opened. */
if (tmp == 1)
{
FAR struct cap_lowerhalf_s **lower = upper->lower;
uint8_t i;
for (i = 0; i < upper->nchan; i++)
{
ret = lower[i]->ops->start(lower[i]);
if (ret < 0)
{
while (i-- > 0)
{
lower[i]->ops->stop(lower[i]);
}
goto errout_with_lock;
}
}
}
/* Save the new open count on success */
upper->crefs = tmp;
ret = OK;
errout_with_lock:
nxmutex_unlock(&upper->lock);
errout:
return ret;
}
/****************************************************************************
* Name: cap_close
*
* Description:
* This function is called when the PWM Capture device is closed.
*
****************************************************************************/
static int cap_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct cap_upperhalf_s *upper = inode->i_private;
int ret;
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
goto errout;
}
/* Decrement the references to the driver. If the reference count will
* decrement to 0, then uninitialize the driver.
*/
if (upper->crefs > 1)
{
upper->crefs--;
}
else
{
FAR struct cap_lowerhalf_s **lower = upper->lower;
uint8_t i;
/* There are no more references to the port */
upper->crefs = 0;
for (i = 0; i < upper->nchan; i++)
{
/* Disable the PWM Capture device */
lower[i]->ops->stop(lower[i]);
}
}
nxmutex_unlock(&upper->lock);
ret = OK;
errout:
return ret;
}
/****************************************************************************
* Name: cap_read
*
* Description:
* A dummy read method. This is provided only to satisfy the VFS layer.
*
****************************************************************************/
static ssize_t cap_read(FAR struct file *filep,
FAR char *buffer,
size_t buflen)
{
/* Return zero -- usually meaning end-of-file */
return 0;
}
/****************************************************************************
* Name: cap_write
*
* Description:
* A dummy write method. This is provided only to satisfy the VFS layer.
*
****************************************************************************/
static ssize_t cap_write(FAR struct file *filep,
FAR const char *buffer,
size_t buflen)
{
/* Return a failure */
return -EPERM;
}
/****************************************************************************
* Name: cap_ioctl
*
* Description:
* The standard ioctl method.
* This is where ALL of the PWM Capture work is done.
*
****************************************************************************/
static int cap_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct cap_upperhalf_s *upper;
FAR struct cap_lowerhalf_s **lower;
int ret;
uint8_t i;
cpinfo("cmd: %d arg: %ld\n", cmd, arg);
upper = inode->i_private;
DEBUGASSERT(upper != NULL);
lower = upper->lower;
DEBUGASSERT(lower != NULL);
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&upper->lock);
if (ret < 0)
{
return ret;
}
/* Handle built-in ioctl commands */
switch (cmd)
{
/* CAPIOC_DUTYCYCLE - Get the pwm duty from the capture.
* Argument: int8_t pointer to the location to return the duty.
*/
case CAPIOC_DUTYCYCLE:
{
FAR uint8_t *ptr = (FAR uint8_t *)((uintptr_t)arg);
DEBUGASSERT(ptr);
for (i = 0; i < upper->nchan; i++)
{
DEBUGASSERT(lower[i]->ops->getduty != NULL);
ret = lower[i]->ops->getduty(lower[i], &ptr[i]);
if (ret < 0)
{
break;
}
}
}
break;
/* CAPIOC_FREQUENCE - Get the pulse frequency from the capture.
* Argument: int32_t pointer to the location to return the frequency.
*/
case CAPIOC_FREQUENCE:
{
FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
DEBUGASSERT(ptr);
for (i = 0; i < upper->nchan; i++)
{
DEBUGASSERT(lower[i]->ops->getfreq != NULL);
ret = lower[i]->ops->getfreq(lower[i], &ptr[i]);
if (ret < 0)
{
break;
}
}
}
break;
/* CAPIOC_EDGES - Get the pwm edges from the capture.
* Argument: int32_t pointer to the location to return the edges.
*/
case CAPIOC_EDGES:
{
FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg);
DEBUGASSERT(ptr);
for (i = 0; i < upper->nchan; i++)
{
DEBUGASSERT(lower[i]->ops->getedges != NULL);
ret = lower[i]->ops->getedges(lower[i], &ptr[i]);
if (ret < 0)
{
break;
}
}
}
break;
/* CAPIOC_ALL - Get the pwm duty, pulse frequency, pwm edges, from
* the capture.
* Argument: A reference to struct cap_all_s.
*/
case CAPIOC_ALL:
{
FAR struct cap_all_s *ptr =
(FAR struct cap_all_s *)((uintptr_t)arg);
DEBUGASSERT(ptr);
for (i = 0 ; i < upper->nchan ; i++)
{
ret = lower[i]->ops->getduty(lower[i], &ptr[i].duty);
if (ret < 0)
{
break;
}
ret = lower[i]->ops->getfreq(lower[i], &ptr[i].freq);
if (ret < 0)
{
break;
}
ret = lower[i]->ops->getedges(lower[i], &ptr[i].edges);
if (ret < 0)
{
break;
}
}
}
break;
/* CAPIOC_REGISTER - Register to receive a signal whenever there is
* an interrupt received on an input capture pin. This feature,
* of course, depends upon interrupt capture support from the platform.
* Argument: The event of signal to be generated when the interrupt
* occurs.
*
* the argument is a pointer to a struct cap_notify_s, that contains
* the channel number, edge type and signal event info.
*/
#ifdef CONFIG_CAPTURE_NOTIFY
case CAPIOC_REGISTER:
{
FAR struct cap_signal_s *tmp;
FAR struct cap_notify_s *new;
pid_t pid;
new = (FAR struct cap_notify_s *)(uintptr_t)arg;
if (!new || (new->chan >= upper->nchan && new->chan < 0))
{
ret = -EINVAL;
break;
}
pid = nxsched_getpid();
tmp = &upper->signals[new->chan];
if (tmp->pid != 0 && tmp->pid != pid)
{
ret = -EBUSY;
break;
}
tmp->pid = pid;
memcpy(&tmp->notify, new, sizeof(*new));
DEBUGASSERT(lower[new->chan]->ops->bind != NULL);
ret = lower[new->chan]->ops->bind(lower[new->chan], new->type,
cap_notify_cb, upper);
}
break;
/* CAPIOC_UNREGISTER - Stop receiving signals for capture interrupts.
* Argument: The channel number
*/
case CAPIOC_UNREGISTER:
{
# ifdef CONFIG_SIG_EVTHREAD
FAR struct sigwork_s *work;
# endif
int chan = (int)arg;
upper->signals[chan].pid = 0;
# ifdef CONFIG_SIG_EVTHREAD
work = &upper->signals[chan].work;
nxsig_cancel_notification(work);
# endif
DEBUGASSERT(lower[chan]->ops->unbind != NULL);
ret = lower[chan]->ops->unbind(lower[chan]);
}
break;
#endif
/* Any unrecognized IOCTL commands might be platform-specific ioctl
* commands
*/
default:
{
cperr("Forwarding unrecognized cmd: %d arg: %ld\n", cmd, arg);
/* Unrecognized cmd will be forwarded to lower half driver for
* specific use cases (e.g Pulse Counter (PCNT))
*/
FAR unsigned long int *ptr =
(FAR unsigned long int *)arg;
for (i = 0; i < upper->nchan; i++)
{
ret = lower[i]->ops->ioctl(lower[i], cmd,
(unsigned long)&ptr[i]);
if (ret < 0)
{
break;
}
}
}
break;
}
nxmutex_unlock(&upper->lock);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cap_register
*
* Description:
* Register the PWM Capture lower half device as 'devpath'
*
* Input Parameters:
* devpath - The full path to the driver to register. E.g., "/dev/cap0"
* lower - An instance of the lower half interface
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure. The following
* possible error values may be returned (most are returned by
* register_driver()):
*
* EINVAL - 'path' is invalid for this operation
* EEXIST - An inode already exists at 'path'
* ENOMEM - Failed to allocate in-memory resources for the operation
*
****************************************************************************/
int cap_register(FAR const char *devpath, FAR struct cap_lowerhalf_s *lower)
{
FAR struct cap_upperhalf_s *upper;
/* Allocate the upper-half data structure */
upper = (FAR struct cap_upperhalf_s *)
kmm_zalloc(sizeof(struct cap_upperhalf_s) +
sizeof(FAR struct cap_lowerhalf_s *));
if (!upper)
{
return -ENOMEM;
}
/* Initialize the PWM Capture device structure
* (it was already zeroed by kmm_zalloc())
*/
nxmutex_init(&upper->lock);
upper->lower = (FAR struct cap_lowerhalf_s **)(upper + 1);
upper->lower[0] = lower;
upper->nchan = 1;
/* Register the PWM Capture device */
return register_driver(devpath, &g_capops, 0666, upper);
}
int cap_register_multiple(FAR const char *devpath,
FAR struct cap_lowerhalf_s **lower,
int n)
{
char fullpath[32];
int ret;
if (!devpath || !lower || n < 1)
{
return -EINVAL;
}
for (int i = 0; i < n; i++)
{
int written = snprintf(fullpath, sizeof(fullpath), "%s%d", devpath, i);
if (written < 0)
{
return -EIO;
}
if ((size_t)written >= sizeof(fullpath))
{
return -ENAMETOOLONG;
}
ret = cap_register(fullpath, lower[i]);
if (ret < 0)
{
/* TODO: unwind */
return ret;
}
}
return OK;
}
#endif /* CONFIG_CAPTURE */