blob: 7f6c3d313a4c880af1f85a45a48aed5827c24428 [file] [log] [blame]
/*
* 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 */
};
static struct audio_client *audio_data;
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_driver_init(struct init_calldata *p_arg)
{
int ret;
struct m4sensorhub_data *m4sensorhub = p_arg->p_m4sensorhub_data;
ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_MIC_DATA_READY,
m4_handle_audio_irq, audio_data, 0);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n",
M4SH_IRQ_MIC_DATA_READY, ret);
}
return ret;
}
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_register_initcall(audio_driver_init,
audio_client_data);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "Unable to register init function "
"for audio client = %d\n", ret);
goto unregister_sound_device;
}
KDEBUG(M4SH_ERROR, "Initialized %s driver\n", AUDIO_CLIENT_DRIVER_NAME);
return 0;
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);
m4sensorhub_irq_disable(audio_client_data->m4sensorhub,
M4SH_IRQ_MIC_DATA_READY);
m4sensorhub_irq_unregister(audio_client_data->m4sensorhub,
M4SH_IRQ_MIC_DATA_READY);
m4sensorhub_unregister_initcall(audio_driver_init);
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");