| /**************************************************************************** |
| * drivers/analog/dac.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 <unistd.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/analog/dac.h> |
| |
| #include <nuttx/irq.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define HALF_SECOND_MSEC 500 |
| #define HALF_SECOND_USEC 500000L |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static int dac_open(FAR struct file *filep); |
| static int dac_close(FAR struct file *filep); |
| static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer, |
| size_t buflen); |
| static int dac_ioctl(FAR struct file *filep, int cmd, unsigned long arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const struct file_operations g_dac_fops = |
| { |
| dac_open, /* open */ |
| dac_close, /* close */ |
| NULL, /* read */ |
| dac_write, /* write */ |
| NULL, /* seek */ |
| dac_ioctl, /* ioctl */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: dac_open |
| * |
| * Description: |
| * This function is called whenever the DAC device is opened. |
| * |
| ****************************************************************************/ |
| |
| static int dac_open(FAR struct file *filep) |
| { |
| FAR struct inode *inode = filep->f_inode; |
| FAR struct dac_dev_s *dev = inode->i_private; |
| uint8_t tmp; |
| int ret; |
| |
| /* If the port is in the middle of closing, wait until the close is |
| * finished. |
| */ |
| |
| ret = nxmutex_lock(&dev->ad_closelock); |
| if (ret >= 0) |
| { |
| /* 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 = dev->ad_ocount + 1; |
| if (tmp == 0) |
| { |
| /* More than 255 opens; uint8_t overflows to zero */ |
| |
| ret = -EMFILE; |
| } |
| else |
| { |
| /* Check if this is the first time that the driver has been |
| * opened. |
| */ |
| |
| if (tmp == 1) |
| { |
| /* Yes.. perform one time hardware initialization. */ |
| |
| irqstate_t flags = enter_critical_section(); |
| ret = dev->ad_ops->ao_setup(dev); |
| if (ret == OK) |
| { |
| /* Mark the FIFOs empty */ |
| |
| dev->ad_xmit.af_head = 0; |
| dev->ad_xmit.af_tail = 0; |
| |
| /* Save the new open count on success */ |
| |
| dev->ad_ocount = tmp; |
| } |
| |
| leave_critical_section(flags); |
| } |
| } |
| |
| nxmutex_unlock(&dev->ad_closelock); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: dac_close |
| * |
| * Description: |
| * This routine is called when the DAC device is closed. |
| * It waits for the last remaining data to be sent. |
| * |
| ****************************************************************************/ |
| |
| static int dac_close(FAR struct file *filep) |
| { |
| FAR struct inode *inode = filep->f_inode; |
| FAR struct dac_dev_s *dev = inode->i_private; |
| irqstate_t flags; |
| int ret; |
| |
| ret = nxmutex_lock(&dev->ad_closelock); |
| if (ret >= 0) |
| { |
| /* Decrement the references to the driver. If the reference count will |
| * decrement to 0, then uninitialize the driver. |
| */ |
| |
| if (dev->ad_ocount > 1) |
| { |
| dev->ad_ocount--; |
| nxmutex_unlock(&dev->ad_closelock); |
| } |
| else |
| { |
| /* There are no more references to the port */ |
| |
| dev->ad_ocount = 0; |
| |
| /* Now we wait for the transmit FIFO to clear */ |
| |
| while (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) |
| { |
| nxsched_usleep(HALF_SECOND_USEC); |
| } |
| |
| /* Free the IRQ and disable the DAC device */ |
| |
| flags = enter_critical_section(); /* Disable interrupts */ |
| dev->ad_ops->ao_shutdown(dev); /* Disable the DAC */ |
| leave_critical_section(flags); |
| |
| nxmutex_unlock(&dev->ad_closelock); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: dac_xmit |
| * |
| * Description: |
| * Send the message at the head of the ad_xmit FIFO |
| * |
| * Assumptions: |
| * Called with interrupts disabled |
| * |
| ****************************************************************************/ |
| |
| static int dac_xmit(FAR struct dac_dev_s *dev) |
| { |
| bool enable = false; |
| int ret = OK; |
| |
| /* Check if the xmit FIFO is empty */ |
| |
| if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) |
| { |
| /* Send the next message at the head of the FIFO */ |
| |
| ret = dev->ad_ops->ao_send(dev, |
| &dev->ad_xmit.af_buffer[dev->ad_xmit.af_head]); |
| |
| /* Make sure the TX done interrupts are enabled */ |
| |
| enable = (ret == OK ? true : false); |
| } |
| |
| dev->ad_ops->ao_txint(dev, enable); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: dac_write |
| ****************************************************************************/ |
| |
| static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer, |
| size_t buflen) |
| { |
| FAR struct inode *inode = filep->f_inode; |
| FAR struct dac_dev_s *dev = inode->i_private; |
| FAR struct dac_fifo_s *fifo = &dev->ad_xmit; |
| FAR struct dac_msg_s *msg; |
| bool empty; |
| ssize_t nsent = 0; |
| irqstate_t flags; |
| int nexttail; |
| int msglen; |
| int ret = 0; |
| |
| /* Interrupts must be disabled throughout the following */ |
| |
| flags = enter_critical_section(); |
| |
| /* Check if the TX FIFO was empty when we started. That is a clue that we |
| * have to kick off a new TX sequence. |
| */ |
| |
| empty = (fifo->af_head == fifo->af_tail); |
| |
| /* Add the messages to the FIFO. Ignore any trailing messages that are |
| * shorter than the minimum. |
| */ |
| |
| if (buflen % 5 == 0) |
| { |
| msglen = 5; |
| } |
| else if (buflen % 4 == 0) |
| { |
| msglen = 4; |
| } |
| else if (buflen % 3 == 0) |
| { |
| msglen = 3; |
| } |
| else if (buflen % 2 == 0) |
| { |
| msglen = 2; |
| } |
| else if (buflen == 1) |
| { |
| msglen = 1; |
| } |
| else |
| { |
| msglen = 5; |
| } |
| |
| while ((buflen - nsent) >= msglen) |
| { |
| /* Check if adding this new message would over-run the drivers ability |
| * to enqueue xmit data. |
| */ |
| |
| nexttail = fifo->af_tail + 1; |
| if (nexttail >= CONFIG_DAC_FIFOSIZE) |
| { |
| nexttail = 0; |
| } |
| |
| /* If the XMIT FIFO becomes full, then wait for space to become |
| * available. |
| */ |
| |
| while (nexttail == fifo->af_head) |
| { |
| /* The transmit FIFO is full -- was non-blocking mode selected? */ |
| |
| if (filep->f_oflags & O_NONBLOCK) |
| { |
| if (nsent == 0) |
| { |
| ret = -EAGAIN; |
| } |
| else |
| { |
| ret = nsent; |
| } |
| |
| goto return_with_irqdisabled; |
| } |
| |
| /* If the FIFO was empty when we started, then we will have to |
| * start the XMIT sequence to clear the FIFO. |
| */ |
| |
| if (empty) |
| { |
| dac_xmit(dev); |
| } |
| |
| /* Wait for a message to be sent */ |
| |
| ret = nxsem_wait_uninterruptible(&fifo->af_sem); |
| if (ret < 0) |
| { |
| goto return_with_irqdisabled; |
| } |
| |
| /* Re-check the FIFO state */ |
| |
| empty = (fifo->af_head == fifo->af_tail); |
| } |
| |
| /* We get here if there is space at the end of the FIFO. Add the new |
| * DAC message at the tail of the FIFO. |
| */ |
| |
| if (msglen == 5) |
| { |
| msg = (FAR struct dac_msg_s *)&buffer[nsent]; |
| memcpy(&fifo->af_buffer[fifo->af_tail], msg, msglen); |
| } |
| else if (msglen == 4) |
| { |
| fifo->af_buffer[fifo->af_tail].am_channel = buffer[nsent]; |
| fifo->af_buffer[fifo->af_tail].am_data = |
| *(FAR uint32_t *)&buffer[nsent]; |
| fifo->af_buffer[fifo->af_tail].am_data &= 0xffffff00; |
| } |
| else if (msglen == 3) |
| { |
| fifo->af_buffer[fifo->af_tail].am_channel = buffer[nsent]; |
| fifo->af_buffer[fifo->af_tail].am_data = |
| (*(FAR uint16_t *)&buffer[nsent + 1]); |
| fifo->af_buffer[fifo->af_tail].am_data <<= 16; |
| } |
| else if (msglen == 2) |
| { |
| fifo->af_buffer[fifo->af_tail].am_channel = 0; |
| fifo->af_buffer[fifo->af_tail].am_data = |
| (*(FAR uint16_t *)&buffer[nsent]); |
| fifo->af_buffer[fifo->af_tail].am_data <<= 16; |
| } |
| else if (msglen == 1) |
| { |
| fifo->af_buffer[fifo->af_tail].am_channel = 0; |
| fifo->af_buffer[fifo->af_tail].am_data = buffer[nsent]; |
| fifo->af_buffer[fifo->af_tail].am_data <<= 24; |
| } |
| |
| /* Increment the tail of the circular buffer */ |
| |
| fifo->af_tail = nexttail; |
| |
| /* Increment the number of bytes that were sent */ |
| |
| nsent += msglen; |
| } |
| |
| /* We get here after all messages have been added to the FIFO. Check if |
| * we need to kick of the XMIT sequence. |
| */ |
| |
| if (empty) |
| { |
| dac_xmit(dev); |
| } |
| |
| /* Return the number of bytes that were sent */ |
| |
| ret = nsent; |
| |
| return_with_irqdisabled: |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: dac_ioctl |
| ****************************************************************************/ |
| |
| static int dac_ioctl(FAR struct file *filep, int cmd, unsigned long arg) |
| { |
| FAR struct inode *inode = filep->f_inode; |
| FAR struct dac_dev_s *dev = inode->i_private; |
| int ret; |
| |
| ret = dev->ad_ops->ao_ioctl(dev, cmd, arg); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: dac_txdone |
| * |
| * Description: |
| * Called from the DAC interrupt handler at the completion of a send |
| * operation. |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| int dac_txdone(FAR struct dac_dev_s *dev) |
| { |
| int ret = -ENOENT; |
| int sval; |
| |
| /* Verify that the xmit FIFO is not empty */ |
| |
| if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) |
| { |
| /* Remove the message at the head of the xmit FIFO */ |
| |
| if (++dev->ad_xmit.af_head >= CONFIG_DAC_FIFOSIZE) |
| { |
| dev->ad_xmit.af_head = 0; |
| } |
| |
| /* Send the next message in the FIFO */ |
| |
| ret = dac_xmit(dev); |
| if (ret == OK) |
| { |
| /* Inform any waiting threads that new xmit space is available */ |
| |
| ret = nxsem_get_value(&dev->ad_xmit.af_sem, &sval); |
| if (ret == OK && sval <= 0) |
| { |
| ret = nxsem_post(&dev->ad_xmit.af_sem); |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: dac_register |
| * |
| * Description: |
| * Register a dac driver. |
| * |
| * Input Parameters: |
| * path - The full path to the DAC device to be registered. This could |
| * be, as an example, "/dev/dac0" |
| * dev - An instance of the device-specific DAC interface |
| * |
| * Returned Value: |
| * Zero on success; A negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| int dac_register(FAR const char *path, FAR struct dac_dev_s *dev) |
| { |
| /* Initialize the DAC device structure */ |
| |
| dev->ad_ocount = 0; |
| |
| /* Initialize semaphores & mutex */ |
| |
| nxsem_init(&dev->ad_xmit.af_sem, 0, 0); |
| nxmutex_init(&dev->ad_closelock); |
| |
| dev->ad_ops->ao_reset(dev); |
| |
| return register_driver(path, &g_dac_fops, 0222, dev); |
| } |