| /**************************************************************************** |
| * 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 <nuttx/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_FREQUENCY - Get the pulse frequency from the capture. |
| * Argument: int32_t pointer to the location to return the frequency. |
| */ |
| |
| case CAPIOC_FREQUENCY: |
| { |
| 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, 0600, 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 */ |