summaryrefslogtreecommitdiff
path: root/drivers/misc/m4sensorhub_audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/m4sensorhub_audio.c')
-rw-r--r--drivers/misc/m4sensorhub_audio.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/drivers/misc/m4sensorhub_audio.c b/drivers/misc/m4sensorhub_audio.c
new file mode 100644
index 00000000000..511e2776bf8
--- /dev/null
+++ b/drivers/misc/m4sensorhub_audio.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2013 Motorola, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Adds ability to program periodic interrupts from user space that
+ * can wake the phone out of low power modes.
+ *
+ */
+
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/m4sensorhub.h>
+#include <linux/m4sensorhub_client_ioctl.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/m4sensorhub/MemMapAudio.h>
+#include <linux/spi/spi.h>
+
+
+#define AUDIO_CLIENT_DRIVER_NAME "m4sensorhub_audio"
+/* This is the number of total kernel buffers */
+#define AUDIO_NBFRAGS_READ 20
+#define AUDIO_SAMPLE_RATE 16000
+#define AUDIO_TIMEOUT HZ
+#define MIC_ENABLE 0x01
+#define MIC_DISABLE 0x00
+
+/* Mutex used to prevent mutiple calls to audio functions at same time */
+DEFINE_MUTEX(audio_lock);
+
+struct audio_client {
+ struct m4sensorhub_data *m4sensorhub;
+ struct spi_device *spi;
+ int dev_dsp;
+ int dev_dsp_open_count;
+ char *buffers[AUDIO_NBFRAGS_READ];
+ unsigned int usr_head; /* user index where app is reading from */
+ unsigned int buf_head; /* SPI index where SPI writing to */
+ unsigned int usr_offset; /* offset in usr_head buffer to read */
+ int read_buf_full; /* num buffers available for app */
+ u32 total_buf_cnt; /* total num of bufs read from since audio enable*/
+ wait_queue_head_t wq; /* wait till read buffer is available */
+ int active; /* Indicates if audio transfer is active */
+};
+
+struct audio_client *audio_data;
+
+static ssize_t audio_get_loglevel(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned long long loglevel;
+ struct spi_device *spi = to_spi_device(dev);
+ struct audio_client *audio_client_data = spi_get_drvdata(spi);
+
+ m4sensorhub_reg_read(audio_client_data->m4sensorhub,
+ M4SH_REG_LOG_LOGENABLE, (char *)&loglevel);
+ loglevel = get_log_level(loglevel, AUDIO_MASK_BIT_1);
+ return sprintf(buf, "%llu\n", loglevel);
+}
+static ssize_t audio_set_loglevel(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long level;
+ unsigned long long mask = 0, newlevel;
+ struct spi_device *spi = to_spi_device(dev);
+ struct audio_client *audio_client_data = spi_get_drvdata(spi);
+
+ if (strict_strtoul(buf, 10, &level) < 0)
+ return -1;
+ if (level > M4_MAX_LOG_LEVEL) {
+ KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level);
+ return -1;
+ }
+ mask = (1ULL << AUDIO_MASK_BIT_1) | (1ULL << AUDIO_MASK_BIT_2);
+ newlevel = (unsigned long long)level << AUDIO_MASK_BIT_1;
+ return m4sensorhub_reg_write(audio_client_data->m4sensorhub,
+ M4SH_REG_LOG_LOGENABLE, (char *)&newlevel,
+ (unsigned char *)&mask);
+}
+static DEVICE_ATTR(LogLevel, 0664, audio_get_loglevel, audio_set_loglevel);
+
+static void audio_client_spidma_read(struct audio_client *audio_client_data,
+ int len)
+{
+ int ret = 0;
+ struct spi_message msg;
+ struct spi_transfer rx;
+ unsigned char txbuff[AUDIO_BUFFER_SIZE];
+
+ memset(&rx, 0x00, sizeof(struct spi_transfer));
+ memset(&msg, 0x00, sizeof(struct spi_message));
+
+ rx.rx_buf = audio_client_data->buffers[
+ audio_client_data->buf_head];
+ rx.tx_buf = txbuff;
+ rx.len = len;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&rx, &msg);
+
+ ret = spi_sync(audio_client_data->spi, &msg);
+ if (ret < 0)
+ KDEBUG(M4SH_ERROR, "%s failed to read %d bytes, ret = %d\n",
+ __func__, len, ret);
+ else {
+ audio_data->read_buf_full++;
+ audio_data->total_buf_cnt++;
+ wake_up_interruptible(&audio_data->wq);
+
+ if (++audio_client_data->buf_head >= AUDIO_NBFRAGS_READ)
+ audio_client_data->buf_head = 0;
+ }
+
+}
+
+static void m4_handle_audio_irq(enum m4sensorhub_irqs int_event,
+ void *data)
+{
+ u32 m4_buf_cnt = 0;
+ u32 bufs_to_read = 0;
+ struct audio_client *audio_client_data = (struct audio_client *)data;
+ int ret = 0;
+
+ mutex_lock(&audio_lock);
+
+ /* Read the total buf count from M4 */
+ ret = m4sensorhub_reg_read(audio_client_data->m4sensorhub,
+ M4SH_REG_AUDIO_TOTALPACKETS,
+ (char *)&m4_buf_cnt);
+
+ if (ret != m4sensorhub_reg_getsize(audio_client_data->m4sensorhub,
+ M4SH_REG_AUDIO_TOTALPACKETS)) {
+ KDEBUG(M4SH_ERROR, "M4 packet count read failed %d\n", ret);
+ goto EXIT;
+ }
+
+ bufs_to_read = m4_buf_cnt - audio_data->total_buf_cnt;
+ KDEBUG(M4SH_DEBUG, "R = %u, m4_cnt = %u, omap_cnt = %u\n",
+ bufs_to_read, m4_buf_cnt, audio_data->total_buf_cnt);
+
+ /* If no free buffers, then skip reads from SPI */
+ while ((bufs_to_read) &&
+ (audio_data->read_buf_full < AUDIO_NBFRAGS_READ)) {
+ audio_client_spidma_read(audio_client_data, AUDIO_BUFFER_SIZE);
+ bufs_to_read--;
+ }
+
+EXIT:
+ mutex_unlock(&audio_lock);
+}
+
+static int audio_client_open(struct inode *inode, struct file *file)
+{
+ int ret = 0, i = 0;
+ mutex_lock(&audio_lock);
+
+ if (audio_data->dev_dsp_open_count == 1) {
+ KDEBUG(M4SH_ERROR, "Mic already opened, can't open again\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ for (i = 0; i < AUDIO_NBFRAGS_READ; i++) {
+ audio_data->buffers[i] = kmalloc(AUDIO_BUFFER_SIZE,
+ GFP_KERNEL | GFP_DMA);
+ if (!audio_data->buffers[i]) {
+ KDEBUG(M4SH_ERROR, "Can't allocate memory for mic\n");
+ ret = -ENOMEM;
+ goto free_buffers;
+ }
+ }
+
+ audio_data->active = 0;
+ audio_data->usr_head = 0;
+ audio_data->usr_offset = 0;
+ audio_data->buf_head = 0;
+ audio_data->read_buf_full = 0;
+
+ ret = m4sensorhub_irq_enable(audio_data->m4sensorhub,
+ M4SH_IRQ_MIC_DATA_READY);
+ if (ret < 0) {
+ KDEBUG(M4SH_ERROR, "Unable to enable mic irq, ret = %d\n",
+ ret);
+ goto free_buffers;
+ }
+
+ init_waitqueue_head(&audio_data->wq);
+
+ audio_data->dev_dsp_open_count = 1;
+ KDEBUG(M4SH_INFO, "M4 mic driver opened\n");
+ goto out;
+
+free_buffers:
+ for (i = 0; i < AUDIO_NBFRAGS_READ; i++) {
+ kfree((void *) audio_data->buffers[i]);
+ audio_data->buffers[i] = NULL;
+ }
+out:
+ mutex_unlock(&audio_lock);
+ return ret;
+}
+
+static int audio_client_release(struct inode *inode, struct file *file)
+{
+ int i = 0, ret;
+ mutex_lock(&audio_lock);
+
+ audio_data->active = 0;
+ ret = m4sensorhub_irq_disable(audio_data->m4sensorhub,
+ M4SH_IRQ_MIC_DATA_READY);
+ if (ret < 0)
+ KDEBUG(M4SH_ERROR, "Unable to disable mic, ret = %d\n", ret);
+ ret = m4sensorhub_reg_write_1byte(audio_data->m4sensorhub,
+ M4SH_REG_AUDIO_ENABLE, MIC_DISABLE, 0xFF);
+ /* Check that we wrote 1 byte */
+ if (ret != 1)
+ KDEBUG(M4SH_ERROR, "Unable to disable mic, size = %d\n", ret);
+
+ audio_data->dev_dsp_open_count = 0;
+
+ for (i = 0; i < AUDIO_NBFRAGS_READ; i++) {
+ kfree((void *) audio_data->buffers[i]);
+ audio_data->buffers[i] = NULL;
+ }
+
+ mutex_unlock(&audio_lock);
+ KDEBUG(M4SH_INFO, "M4 mic driver closed\n");
+ return 0;
+}
+
+static long audio_client_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ unsigned int samp_rate;
+
+ mutex_lock(&audio_lock);
+
+ switch (cmd) {
+ case OSS_GETVERSION:
+ ret = put_user(SOUND_VERSION, (int *)arg);
+ break;
+
+ case SNDCTL_DSP_SPEED:
+ if (copy_from_user(&samp_rate, (unsigned int *)arg,
+ sizeof(unsigned int)))
+ ret = -EFAULT;
+ else if (samp_rate != AUDIO_SAMPLE_RATE)
+ ret = -EINVAL;
+
+ break;
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ put_user(AUDIO_BUFFER_SIZE, (int *)arg);
+ break;
+
+ default:
+ break;
+ }
+ mutex_unlock(&audio_lock);
+ return ret;
+}
+
+static ssize_t audio_client_read(struct file *file, char *buffer, size_t size,
+ loff_t *nouse)
+{
+ int ret = 0;
+ int local_size = size;
+ int local_offset = 0; /* offset into output buffer */
+ int remainder_buff = 0; /* Indicates bytes remaining in input buffer */
+
+ mutex_lock(&audio_lock);
+
+ if (!audio_data->active) {
+ ret = m4sensorhub_reg_write_1byte(audio_data->m4sensorhub,
+ M4SH_REG_AUDIO_ENABLE, MIC_ENABLE, 0xFF);
+ /* Check that we wrote 1 byte */
+ if (ret != 1) {
+ KDEBUG(M4SH_ERROR, "Unable to enable mic, size = %d\n",
+ ret);
+ goto out;
+ }
+ audio_data->active = 1;
+ audio_data->total_buf_cnt = 0;
+ }
+
+ while (local_size > 0) {
+ mutex_unlock(&audio_lock);
+ ret = wait_event_interruptible_timeout(audio_data->wq,
+ audio_data->read_buf_full > 0, AUDIO_TIMEOUT);
+ mutex_lock(&audio_lock);
+ if (!ret) {
+ KDEBUG(M4SH_ERROR,
+ "Timed out waiting for mic buffer\n");
+ goto out;
+ }
+
+ remainder_buff = AUDIO_BUFFER_SIZE - audio_data->usr_offset;
+ if (local_size > remainder_buff) {
+
+ if (copy_to_user(buffer + local_offset,
+ audio_data->buffers
+ [audio_data->usr_head] +
+ audio_data->usr_offset,
+ remainder_buff)) {
+ KDEBUG(M4SH_ERROR,
+ "Mic driver: copy_to_user failed \n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (++audio_data->usr_head >= AUDIO_NBFRAGS_READ)
+ audio_data->usr_head = 0;
+
+ if (--audio_data->read_buf_full < 0)
+ audio_data->read_buf_full = 0;
+
+ local_size -= remainder_buff;
+ local_offset += remainder_buff;
+ audio_data->usr_offset = 0;
+ } else {
+
+ if (copy_to_user(buffer + local_offset,
+ audio_data->buffers
+ [audio_data->usr_head] +
+ audio_data->usr_offset, local_size)) {
+ KDEBUG(M4SH_ERROR,
+ "Mic driver: copy_to_user failed \n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (local_size == remainder_buff) {
+ if (++audio_data->usr_head >=
+ AUDIO_NBFRAGS_READ)
+ audio_data->usr_head = 0;
+
+ if (--audio_data->read_buf_full < 0)
+ audio_data->read_buf_full = 0;
+
+ audio_data->usr_offset = 0;
+
+ } else {
+ audio_data->usr_offset += local_size;
+ }
+
+ local_size = 0;
+ }
+ }
+ ret = size;
+
+out:
+ mutex_unlock(&audio_lock);
+ return ret;
+}
+
+/* File Ops structure */
+static const struct file_operations audio_client_fops = {
+ .owner = THIS_MODULE,
+ .open = audio_client_open,
+ .release = audio_client_release,
+ .unlocked_ioctl = audio_client_ioctl,
+ .read = audio_client_read,
+};
+
+static int audio_client_probe(struct spi_device *spi)
+{
+ int ret = -1;
+ struct audio_client *audio_client_data;
+ struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata();
+
+ if (!m4sensorhub)
+ return -EFAULT;
+
+ audio_client_data = kzalloc(sizeof(*audio_client_data), GFP_KERNEL);
+ if (!audio_client_data)
+ return -ENOMEM;
+ audio_client_data->m4sensorhub = m4sensorhub;
+ spi_set_drvdata(spi, audio_client_data);
+ audio_client_data->spi = spi;
+ audio_data = audio_client_data;
+
+ ret = register_sound_dsp(&audio_client_fops, -1);
+ if (ret < 0) {
+ KDEBUG(M4SH_ERROR, "Error registering %s driver\n",
+ AUDIO_CLIENT_DRIVER_NAME);
+ goto free_client_data;
+ }
+ audio_client_data->dev_dsp = ret;
+ audio_client_data->dev_dsp_open_count = 0;
+
+ ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_MIC_DATA_READY,
+ m4_handle_audio_irq, audio_client_data);
+ if (ret < 0) {
+ KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n",
+ M4SH_IRQ_MIC_DATA_READY, ret);
+ goto unregister_sound_device;
+ }
+
+ ret = device_create_file(&spi->dev, &dev_attr_LogLevel);
+ if (ret) {
+ KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n",
+ AUDIO_CLIENT_DRIVER_NAME);
+ goto unregister_irq;
+ }
+
+ KDEBUG(M4SH_ERROR, "Initialized %s driver\n", AUDIO_CLIENT_DRIVER_NAME);
+ return 0;
+
+unregister_irq:
+ m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MIC_DATA_READY);
+unregister_sound_device:
+ unregister_sound_dsp(audio_client_data->dev_dsp);
+free_client_data:
+ spi_set_drvdata(spi, NULL);
+ kfree(audio_client_data);
+ return ret;
+}
+
+static int __exit audio_client_remove(struct spi_device *spi)
+{
+ struct audio_client *audio_client_data = spi_get_drvdata(spi);
+
+ device_remove_file(&spi->dev, &dev_attr_LogLevel);
+ m4sensorhub_irq_disable(audio_client_data->m4sensorhub,
+ M4SH_IRQ_MIC_DATA_READY);
+ m4sensorhub_irq_unregister(audio_client_data->m4sensorhub,
+ M4SH_IRQ_MIC_DATA_READY);
+ unregister_sound_dsp(audio_client_data->dev_dsp);
+ spi_set_drvdata(spi, NULL);
+ kfree(audio_client_data);
+ return 0;
+}
+
+
+static struct of_device_id m4audio_match_tbl[] = {
+ {.compatible = "mot,m4audio"},
+ {},
+};
+
+static struct spi_driver audio_client_spi_driver = {
+ .driver = {
+ .name = AUDIO_CLIENT_DRIVER_NAME,
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(m4audio_match_tbl),
+ },
+ .suspend = NULL,
+ .resume = NULL,
+ .probe = audio_client_probe,
+ .remove = __exit_p(audio_client_remove),
+};
+
+static int __init audio_client_init(void)
+{
+ int ret = 0;
+
+ ret = spi_register_driver(&audio_client_spi_driver);
+
+ return ret;
+}
+
+static void __exit audio_client_exit(void)
+{
+ spi_unregister_driver(&audio_client_spi_driver);
+}
+
+module_init(audio_client_init);
+module_exit(audio_client_exit);
+
+MODULE_ALIAS("platform:audio_client");
+MODULE_DESCRIPTION("M4 Sensor Hub audio client driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
+