/**************************************************************************** * drivers/i2s/i2schar.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. * ****************************************************************************/ /**************************************************************************** * * 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor 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) /**************************************************************************** * Private Types ****************************************************************************/ struct i2schar_dev_s { FAR struct i2s_dev_s *i2s; /* The lower half i2s driver */ mutex_t lock; /* 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); static int i2schar_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations i2schar_fops = { NULL, /* open */ NULL, /* close */ i2schar_read, /* read */ i2schar_write, /* write */ NULL, /* seek */ i2schar_ioctl, /* ioctl */ }; /**************************************************************************** * 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 recycle 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 != NULL && apb != NULL); UNUSED(priv); i2sinfo("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. */ i2sinfo("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 recycle 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 != NULL && apb != NULL); UNUSED(priv); i2sinfo("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. */ i2sinfo("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; FAR struct i2schar_dev_s *priv; FAR struct ap_buffer_s *apb; size_t nbytes; int ret; i2sinfo("buffer=%p buflen=%d\n", buffer, (int)buflen); /* Get our private data structure */ DEBUGASSERT(filep != NULL && buffer != NULL); inode = filep->f_inode; DEBUGASSERT(inode != NULL); priv = (FAR struct i2schar_dev_s *)inode->i_private; DEBUGASSERT(priv != NULL); /* 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 = nxmutex_lock(&priv->lock); if (ret < 0) { i2serr("ERROR: nxsem_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) { i2serr("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 */ nxmutex_unlock(&priv->lock); return sizeof(struct ap_buffer_s) + nbytes; errout_with_reference: apb_free(apb); nxmutex_unlock(&priv->lock); 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; FAR struct i2schar_dev_s *priv; FAR struct ap_buffer_s *apb; size_t nbytes; int ret; i2sinfo("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 = nxmutex_lock(&priv->lock); if (ret < 0) { i2serr("ERROR: nxsem_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) { i2serr("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. */ nxmutex_unlock(&priv->lock); return sizeof(struct ap_buffer_s) + nbytes; errout_with_reference: apb_free(apb); nxmutex_unlock(&priv->lock); return ret; } /**************************************************************************** * Name: i2char_ioctl * * Description: * Perform I2S device ioctl if exists * ****************************************************************************/ static int i2schar_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct i2schar_dev_s *priv; int ret = -ENOTTY; /* Get our private data structure */ DEBUGASSERT(filep != NULL); inode = filep->f_inode; DEBUGASSERT(inode != NULL); priv = (FAR struct i2schar_dev_s *)inode->i_private; DEBUGASSERT(priv != NULL && priv->i2s && priv->i2s->ops); if (priv->i2s->ops->i2s_ioctl) { ret = priv->i2s->ops->i2s_ioctl(priv->i2s, cmd, arg); } 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 */ size_t dev_size = sizeof(struct i2schar_dev_s); priv = (FAR struct i2schar_dev_s *)kmm_zalloc(dev_size); if (priv) { /* Initialize the I2S character device structure */ priv->i2s = i2s; nxmutex_init(&priv->lock); /* 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. */ nxmutex_destroy(&priv->lock); kmm_free(priv); return ret; } /* Return the result of the registration */ return OK; } return -ENOMEM; }