diff options
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"); + |