/**************************************************************************** * drivers/audio/i2schar.c * * This is a simple character driver for testing I2C. It is not an audio * driver but does conform to some of the buffer management heuristics of an * audio driver. It is not suitable for use in any real driver application * in its current form. * * Copyright (C) 2013 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Private Definitions ****************************************************************************/ /* Configuration ************************************************************/ #ifndef CONFIG_AUDIO_I2SCHAR_RXTIMEOUT # define CONFIG_AUDIO_I2SCHAR_RXTIMEOUT 0 #endif #ifndef CONFIG_AUDIO_I2SCHAR_TXTIMEOUT # define CONFIG_AUDIO_I2SCHAR_TXTIMEOUT 0 #endif /* Device naming ************************************************************/ #define DEVNAME_FMT "/dev/i2schar%d" #define DEVNAME_FMTLEN (12 + 3 + 1) /* Debug *******************************************************************/ /* Check if SSC debut is enabled (non-standard.. no support in * include/debug.h */ #ifndef CONFIG_DEBUG # undef CONFIG_DEBUG_VERBOSE # undef CONFIG_DEBUG_I2S #endif #ifdef CONFIG_DEBUG_I2S # define i2sdbg dbg # define i2slldbg lldbg # ifdef CONFIG_DEBUG_VERBOSE # define i2svdbg dbg # define i2sllvdbg lldbg # else # define i2svdbg(x...) # endif #else # define i2sdbg(x...) # define i2slldbg(x...) # define i2svdbg(x...) # define i2sllvdbg(x...) #endif /**************************************************************************** * Private Types ****************************************************************************/ struct i2schar_dev_s { FAR struct i2s_dev_s *i2s; /* The lower half i2s driver */ sem_t exclsem; /* Assures mutually exclusive access */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* I2S callback function */ static void i2schar_rxcallback(FAR struct i2s_dev_s *dev, FAR struct ap_buffer_s *apb, FAR void *arg, int result); static void i2schar_txcallback(FAR struct i2s_dev_s *dev, FAR struct ap_buffer_s *apb, FAR void *arg, int result); /* Character driver methods */ static ssize_t i2schar_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t i2schar_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations i2schar_fops = { NULL, /* open */ NULL, /* close */ i2schar_read, /* read */ i2schar_write, /* write */ NULL, /* seek */ NULL, /* ioctl */ #ifndef CONFIG_DISABLE_POLL NULL, /* poll */ #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: i2schar_rxcallback * * Description: * I2S RX transfer complete callback. * * NOTE: In this test driver, the RX is simply dumped in the bit bucket. * You would not do this in a real application. You would return the * received data to the caller via some IPC. * * Also, the test buffer is simply freed. This will work if this driver * has the sole reference to buffer; in that case the buffer will be freed. * Otherwise -- memory leak! A more efficient design would recyle the * audio buffers. * ****************************************************************************/ static void i2schar_rxcallback(FAR struct i2s_dev_s *dev, FAR struct ap_buffer_s *apb, FAR void *arg, int result) { FAR struct i2schar_dev_s *priv = (FAR struct i2schar_dev_s *)arg; DEBUGASSERT(priv && apb); i2svdbg("apb=%p nbytes=%d result=%d\n", apb, apb->nbytes, result); /* REVISIT: If you want this to actually do something other than * test I2S data transfer, then this is the point where you would * want to pass the received I2S to some application. */ /* Release our reference to the audio buffer. Hopefully it will be freed * now. */ i2svdbg("Freeing apb=%p crefs=%d\n", apb, apb->crefs); apb_free(apb); } /**************************************************************************** * Name: i2schar_txcallback * * Description: * I2S TX transfer complete callback * * NOTE: The test buffer is simply freed. This will work if this driver * has the sole reference to buffer; in that case the buffer will be freed. * Otherwise -- memory leak! A more efficient design would recyle the * audio buffers. * ****************************************************************************/ static void i2schar_txcallback(FAR struct i2s_dev_s *dev, FAR struct ap_buffer_s *apb, FAR void *arg, int result) { FAR struct i2schar_dev_s *priv = (FAR struct i2schar_dev_s *)arg; DEBUGASSERT(priv && apb); i2svdbg("apb=%p nbytes=%d result=%d\n", apb, apb->nbytes, result); /* REVISIT: If you want this to actually do something other than * test I2S data transfer, then this is the point where you would * want to let some application know that the transfer has complete. */ /* Release our reference to the audio buffer. Hopefully it will be freed * now. */ i2svdbg("Freeing apb=%p crefs=%d\n", apb, apb->crefs); apb_free(apb); } /**************************************************************************** * Name: i2schar_read * * Description: * Standard character driver read method * ****************************************************************************/ static ssize_t i2schar_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct i2schar_dev_s *priv; FAR struct ap_buffer_s *apb; size_t nbytes; int ret; i2svdbg("buffer=%p buflen=%d\n", buffer, (int)buflen); /* Get our private data structure */ DEBUGASSERT(filep && buffer); inode = filep->f_inode; DEBUGASSERT(inode); priv = (FAR struct i2schar_dev_s *)inode->i_private; DEBUGASSERT(priv); /* Verify that the buffer refers to one, correctly sized audio buffer */ DEBUGASSERT(buflen >= sizeof(struct ap_buffer_s)); apb = (FAR struct ap_buffer_s *)buffer; nbytes = apb->nmaxbytes; DEBUGASSERT(buflen >= (sizeof(struct ap_buffer_s) + nbytes)); /* Add a reference to the audio buffer */ apb_reference(apb); /* Get exclusive access to i2c character driver */ ret = sem_wait(&priv->exclsem); if (ret < 0) { ret = -errno; DEBUGASSERT(ret < 0); i2sdbg("ERROR: sem_wait returned: %d\n", ret); goto errout_with_reference; } /* Give the buffer to the I2S driver */ ret = I2S_RECEIVE(priv->i2s, apb, i2schar_rxcallback, priv, CONFIG_AUDIO_I2SCHAR_RXTIMEOUT); if (ret < 0) { i2sdbg("ERROR: I2S_RECEIVE returned: %d\n", ret); goto errout_with_reference; } /* Lie to the caller and tell them that all of the bytes have been * received */ sem_post(&priv->exclsem); return sizeof(struct ap_buffer_s) + nbytes; errout_with_reference: apb_free(apb); sem_post(&priv->exclsem); return ret; } /**************************************************************************** * Name: i2schar_write * * Description: * Standard character driver write method * ****************************************************************************/ static ssize_t i2schar_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct i2schar_dev_s *priv; FAR struct ap_buffer_s *apb; size_t nbytes; int ret; i2svdbg("buffer=%p buflen=%d\n", buffer, (int)buflen); /* Get our private data structure */ DEBUGASSERT(filep && buffer); inode = filep->f_inode; DEBUGASSERT(inode); priv = (FAR struct i2schar_dev_s *)inode->i_private; DEBUGASSERT(priv); /* Verify that the buffer refers to one, correctly sized audio buffer */ DEBUGASSERT(buflen >= sizeof(struct ap_buffer_s)); apb = (FAR struct ap_buffer_s *)buffer; nbytes = apb->nmaxbytes; DEBUGASSERT(buflen >= (sizeof(struct ap_buffer_s) + nbytes)); /* Add a reference to the audio buffer */ apb_reference(apb); /* Get exclusive access to i2c character driver */ ret = sem_wait(&priv->exclsem); if (ret < 0) { ret = -errno; DEBUGASSERT(ret < 0); i2sdbg("ERROR: sem_wait returned: %d\n", ret); goto errout_with_reference; } /* Give the audio buffer to the I2S driver */ ret = I2S_SEND(priv->i2s, apb, i2schar_txcallback, priv, CONFIG_AUDIO_I2SCHAR_TXTIMEOUT); if (ret < 0) { i2sdbg("ERROR: I2S_SEND returned: %d\n", ret); goto errout_with_reference; } /* Lie to the caller and tell them that all of the bytes have been * sent. */ sem_post(&priv->exclsem); return sizeof(struct ap_buffer_s) + nbytes; errout_with_reference: apb_free(apb); sem_post(&priv->exclsem); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: i2schar_register * * Description: * Create and register the I2S character driver. * * The I2S character driver is a simple character driver that supports I2S * transfers via a read() and write(). The intent of this driver is to * support I2S testing. It is not an audio driver but does conform to some * of the buffer management heuristics of an audio driver. It is not * suitable for use in any real driver application in its current form. * * Input Parameters: * i2s - An instance of the lower half I2S driver * minor - The device minor number. The I2S character device will be * registers as /dev/i2scharN where N is the minor number * * Returned Value: * OK if the driver was successfully register; A negated errno value is * returned on any failure. * ****************************************************************************/ int i2schar_register(FAR struct i2s_dev_s *i2s, int minor) { FAR struct i2schar_dev_s *priv; char devname[DEVNAME_FMTLEN]; int ret; /* Sanity check */ DEBUGASSERT(i2s != NULL && (unsigned)minor < 1000); /* Allocate a I2S character device structure */ priv = (FAR struct i2schar_dev_s *)kmm_zalloc(sizeof(struct i2schar_dev_s)); if (priv) { /* Initialize the I2S character device structure */ priv->i2s = i2s; sem_init(&priv->exclsem, 0, 1); /* Create the character device name */ snprintf(devname, DEVNAME_FMTLEN, DEVNAME_FMT, minor); ret = register_driver(devname, &i2schar_fops, 0666, priv); if (ret < 0) { /* Free the device structure if we failed to create the character * device. */ kmm_free(priv); } /* Return the result of the registration */ return OK; } return -ENOMEM; }