diff options
| author | Doug Zobel <dzobel1@motorola.com> | 2013-11-15 14:29:07 -0600 |
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-03-05 17:46:52 -0600 |
| commit | d2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch) | |
| tree | 8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers/misc/m4sensorhub_audio.c | |
| parent | 32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff) | |
| download | olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.tar.xz olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.zip | |
CW integration and minnow bringup
* create minnow machine type
* create Android makefile
* add pre-commit syntax check
* enable -Werror
* Add drivers: CPCAP, TPS65xxx, m4sensorhub, atmxt, lm3535,
usb gadget, minnow display, TI 12xx wireless
Change-Id: I7962f5e1256715f2452aed5a62a4f2f2383d5046
Diffstat (limited to 'drivers/misc/m4sensorhub_audio.c')
| -rw-r--r-- | drivers/misc/m4sensorhub_audio.c | 496 |
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"); + |