/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");