incubator-nuttx/drivers/sensors/zerocross.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 */