520 lines
14 KiB
C
520 lines
14 KiB
C
/****************************************************************************
|
|
* drivers/sensors/zerocross.c
|
|
*
|
|
* 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/arch.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/sensors/zerocross.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
#ifdef CONFIG_SENSORS_ZEROCROSS
|
|
|
|
/****************************************************************************
|
|
* Private Type Definitions
|
|
****************************************************************************/
|
|
|
|
/* This structure describes the state of the upper half driver */
|
|
|
|
struct zc_upperhalf_s
|
|
{
|
|
FAR struct zc_lowerhalf_s *lower; /* lower-half state */
|
|
sem_t exclsem; /* Supports mutual exclusion */
|
|
|
|
/* The following is a singly linked list of open references to the
|
|
* zero cross device.
|
|
*/
|
|
|
|
FAR struct zc_open_s *zu_open;
|
|
};
|
|
|
|
/* This structure describes the state of one open zero cross driver
|
|
* instance
|
|
*/
|
|
|
|
struct zc_open_s
|
|
{
|
|
/* Supports a singly linked list */
|
|
|
|
FAR struct zc_open_s *do_flink;
|
|
|
|
/* The following will be true if we are closing */
|
|
|
|
volatile bool do_closing;
|
|
|
|
/* Zero cross event notification information */
|
|
|
|
pid_t do_pid;
|
|
struct sigevent do_event;
|
|
struct sigwork_s do_work;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int zc_open(FAR struct file *filep);
|
|
static int zc_close(FAR struct file *filep);
|
|
static ssize_t zc_read(FAR struct file *filep, FAR char *buffer, size_t
|
|
buflen);
|
|
static ssize_t zc_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int zc_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
|
|
static void zerocross_enable(FAR struct zc_upperhalf_s *priv);
|
|
static void zerocross_interrupt(FAR const struct zc_lowerhalf_s *lower,
|
|
FAR void *arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_zcops =
|
|
{
|
|
zc_open, /* open */
|
|
zc_close, /* close */
|
|
zc_read, /* read */
|
|
zc_write, /* write */
|
|
NULL, /* seek */
|
|
zc_ioctl, /* ioctl */
|
|
NULL /* poll */
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, NULL /* unlink */
|
|
#endif
|
|
};
|
|
|
|
volatile int sample = 0;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: zerocross_enable
|
|
****************************************************************************/
|
|
|
|
static void zerocross_enable(FAR struct zc_upperhalf_s *priv)
|
|
{
|
|
FAR const struct zc_lowerhalf_s *lower;
|
|
irqstate_t flags;
|
|
|
|
DEBUGASSERT(priv && priv->lower);
|
|
lower = priv->lower;
|
|
|
|
/* This routine is called both task level and interrupt level, so
|
|
* interrupts must be disabled.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Enable interrupts */
|
|
|
|
DEBUGASSERT(lower->zc_enable);
|
|
|
|
/* Enable interrupts with the new button set */
|
|
|
|
lower->zc_enable(lower, (zc_interrupt_t)zerocross_interrupt, priv);
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zerocross_interrupt
|
|
****************************************************************************/
|
|
|
|
static void zerocross_interrupt(FAR const struct zc_lowerhalf_s *lower,
|
|
FAR void *arg)
|
|
{
|
|
FAR struct zc_upperhalf_s *priv = (FAR struct zc_upperhalf_s *)arg;
|
|
FAR struct zc_open_s *opriv;
|
|
irqstate_t flags;
|
|
|
|
/* This routine is called both task level and interrupt level, so
|
|
* interrupts must be disabled.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Update sample value */
|
|
|
|
sample++;
|
|
|
|
/* Visit each opened reference and notify a zero cross event */
|
|
|
|
for (opriv = priv->zu_open; opriv; opriv = opriv->do_flink)
|
|
{
|
|
/* Signal the waiter */
|
|
|
|
opriv->do_event.sigev_value.sival_int = sample;
|
|
nxsig_notification(opriv->do_pid, &opriv->do_event,
|
|
SI_QUEUE, &opriv->do_work);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zc_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the PWM device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int zc_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct zc_upperhalf_s *priv;
|
|
FAR struct zc_open_s *opriv;
|
|
int ret;
|
|
|
|
DEBUGASSERT(filep && filep->f_inode);
|
|
inode = filep->f_inode;
|
|
DEBUGASSERT(inode->i_private);
|
|
priv = (FAR struct zc_upperhalf_s *)inode->i_private;
|
|
|
|
/* Get exclusive access to the driver structure */
|
|
|
|
ret = nxsem_wait(&priv->exclsem);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: nxsem_wait failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Allocate a new open structure */
|
|
|
|
opriv = (FAR struct zc_open_s *)kmm_zalloc(sizeof(struct zc_open_s));
|
|
if (!opriv)
|
|
{
|
|
snerr("ERROR: Failed to allocate open structure\n");
|
|
ret = -ENOMEM;
|
|
goto errout_with_sem;
|
|
}
|
|
|
|
/* Attach the open structure to the device */
|
|
|
|
opriv->do_flink = priv->zu_open;
|
|
priv->zu_open = opriv;
|
|
|
|
/* Attach the open structure to the file structure */
|
|
|
|
filep->f_priv = (FAR void *)opriv;
|
|
ret = OK;
|
|
|
|
errout_with_sem:
|
|
nxsem_post(&priv->exclsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zc_close
|
|
*
|
|
* Description:
|
|
* This function is called when the PWM device is closed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int zc_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct zc_upperhalf_s *priv;
|
|
FAR struct zc_open_s *opriv;
|
|
FAR struct zc_open_s *curr;
|
|
FAR struct zc_open_s *prev;
|
|
irqstate_t flags;
|
|
bool closing;
|
|
int ret;
|
|
|
|
DEBUGASSERT(filep && filep->f_priv && filep->f_inode);
|
|
opriv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
DEBUGASSERT(inode->i_private);
|
|
priv = (FAR struct zc_upperhalf_s *)inode->i_private;
|
|
|
|
/* Handle an improbable race conditions with the following atomic test
|
|
* and set.
|
|
*
|
|
* This is actually a pretty feeble attempt to handle this. The
|
|
* improbable race condition occurs if two different threads try to
|
|
* close the zero cross driver at the same time. The rule: don't do
|
|
* that! It is feeble because we do not really enforce stale pointer
|
|
* detection anyway.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
closing = opriv->do_closing;
|
|
opriv->do_closing = true;
|
|
leave_critical_section(flags);
|
|
|
|
if (closing)
|
|
{
|
|
/* Another thread is doing the close */
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* Get exclusive access to the driver structure */
|
|
|
|
ret = nxsem_wait(&priv->exclsem);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: nxsem_wait failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Find the open structure in the list of open structures for the device */
|
|
|
|
for (prev = NULL, curr = priv->zu_open;
|
|
curr && curr != opriv;
|
|
prev = curr, curr = curr->do_flink);
|
|
|
|
DEBUGASSERT(curr);
|
|
if (!curr)
|
|
{
|
|
snerr("ERROR: Failed to find open entry\n");
|
|
ret = -ENOENT;
|
|
goto errout_with_exclsem;
|
|
}
|
|
|
|
/* Remove the structure from the device */
|
|
|
|
if (prev)
|
|
{
|
|
prev->do_flink = opriv->do_flink;
|
|
}
|
|
else
|
|
{
|
|
priv->zu_open = opriv->do_flink;
|
|
}
|
|
|
|
/* Cancel any pending notification */
|
|
|
|
nxsig_cancel_notification(&opriv->do_work);
|
|
|
|
/* And free the open structure */
|
|
|
|
kmm_free(opriv);
|
|
|
|
/* Enable/disable interrupt handling */
|
|
|
|
zerocross_enable(priv);
|
|
ret = OK;
|
|
|
|
errout_with_exclsem:
|
|
nxsem_post(&priv->exclsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zc_read
|
|
*
|
|
* Description:
|
|
* A dummy read method. This is provided only to satsify the VFS layer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t zc_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
/* Return zero -- usually meaning end-of-file */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zc_write
|
|
*
|
|
* Description:
|
|
* A dummy write method. This is provided only to satsify the VFS layer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t zc_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
/* Return a failure */
|
|
|
|
return -EPERM;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: zc_ioctl
|
|
*
|
|
* Description:
|
|
* The standard ioctl method. This is where ALL of the PWM work is done.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int zc_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct zc_upperhalf_s *priv;
|
|
FAR struct zc_open_s *opriv;
|
|
int ret;
|
|
|
|
sninfo("cmd: %d arg: %ld\n", cmd, arg);
|
|
DEBUGASSERT(filep && filep->f_priv && filep->f_inode);
|
|
opriv = filep->f_priv;
|
|
inode = filep->f_inode;
|
|
DEBUGASSERT(inode->i_private);
|
|
priv = (FAR struct zc_upperhalf_s *)inode->i_private;
|
|
|
|
/* Get exclusive access to the device structures */
|
|
|
|
ret = nxsem_wait(&priv->exclsem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Handle built-in ioctl commands */
|
|
|
|
ret = -EINVAL;
|
|
switch (cmd)
|
|
{
|
|
/* Command: ZCIOC_REGISTER
|
|
* Description: Register to receive a signal whenever there is zero
|
|
* cross detection interrupt.
|
|
* Argument: A read-only pointer to an instance of struct
|
|
* zc_notify_s
|
|
* Return: Zero (OK) on success. Minus one will be returned on
|
|
* failure with the errno value set appropriately.
|
|
*/
|
|
|
|
case ZCIOC_REGISTER:
|
|
{
|
|
FAR struct sigevent *event =
|
|
(FAR struct sigevent *)((uintptr_t)arg);
|
|
|
|
if (event)
|
|
{
|
|
/* Save the notification events */
|
|
|
|
opriv->do_event = *event;
|
|
opriv->do_pid = getpid();
|
|
|
|
/* Enable/disable interrupt handling */
|
|
|
|
zerocross_enable(priv);
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
snerr("ERROR: Unrecognized cmd: %d arg: %ld\n", cmd, arg);
|
|
ret = -ENOTTY;
|
|
}
|
|
break;
|
|
}
|
|
|
|
nxsem_post(&priv->exclsem);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: zc_register
|
|
*
|
|
* Description:
|
|
* Register the Zero Cross character device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/zc0"
|
|
* 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 zc_register(FAR const char *devname, FAR struct zc_lowerhalf_s *lower)
|
|
{
|
|
FAR struct zc_upperhalf_s *priv;
|
|
int ret;
|
|
|
|
DEBUGASSERT(devname && lower);
|
|
|
|
/* Allocate a new zero cross driver instance */
|
|
|
|
priv = (FAR struct zc_upperhalf_s *)
|
|
kmm_zalloc(sizeof(struct zc_upperhalf_s));
|
|
|
|
if (!priv)
|
|
{
|
|
snerr("ERROR: Failed to allocate device structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Make sure that zero cross interrupt is disabled */
|
|
|
|
DEBUGASSERT(lower->zc_enable);
|
|
lower->zc_enable(lower, NULL, NULL);
|
|
|
|
/* Initialize the new zero cross driver instance */
|
|
|
|
priv->lower = lower;
|
|
nxsem_init(&priv->exclsem, 0, 1);
|
|
|
|
/* And register the zero cross driver */
|
|
|
|
ret = register_driver(devname, &g_zcops, 0666, priv);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: register_driver failed: %d\n", ret);
|
|
nxsem_destroy(&priv->exclsem);
|
|
kmm_free(priv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_SENSORS_ZEROCROSS */
|