| /**************************************************************************** |
| * 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 <nuttx/config.h> |
| |
| #include <sys/types.h> |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/mutex.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/audio/audio.h> |
| #include <nuttx/audio/i2s.h> |
| |
| /**************************************************************************** |
| * 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 g_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(buffer != NULL); |
| |
| inode = filep->f_inode; |
| |
| priv = 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(buffer); |
| |
| inode = filep->f_inode; |
| |
| priv = 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 */ |
| |
| inode = filep->f_inode; |
| |
| priv = 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 = 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, &g_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; |
| } |