[ARM] tegra: stingray: platform driver for CPCAP audio codec

-- provides output-volume control
-- provides two output paths: speaker and headset

Signed-off-by: Iliyan Malchev <malchev@google.com>
This commit is contained in:
Iliyan Malchev 2010-07-29 14:09:06 -07:00 committed by Colin Cross
parent 5477bfe140
commit f765ab45cc
2 changed files with 283 additions and 1 deletions

View File

@ -84,6 +84,7 @@ cpcap-objs := cpcap-core.o \
cpcap-whisper.o \
cpcap-adc.o \
cpcap-uc.o \
cpcap-3mm5.o
cpcap-3mm5.o \
cpcap-audio.o
obj-$(CONFIG_MFD_CPCAP) += cpcap.o

281
drivers/mfd/cpcap-audio.c Normal file
View File

@ -0,0 +1,281 @@
/* drivers/mfd/cpcap-audio.c
*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Iliyan Malchev <malchev@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spi/cpcap-regbits.h>
#include <linux/spi/cpcap.h>
#include <linux/regulator/consumer.h>
#include <linux/cpcap_audio.h>
#include <linux/spi/cpcap.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#include <linux/cpcap_audio.h>
#include <linux/uaccess.h>
#include <mach/cpcap_audio.h>
static struct cpcap_device *cpcap;
static struct cpcap_audio_platform_data *pdata;
static unsigned current_output = CPCAP_AUDIO_OUT_SPEAKER;
static unsigned current_volume = 10;
static int cpcap_audio_set(const struct cpcap_audio_path *path, bool on)
{
int len, rc;
const struct cpcap_audio_config_table *entry;
pr_info("%s: %s\n", __func__, path->name);
if (!path) {
pr_info("%s: no path\n", __func__);
return -ENOSYS;
}
if (path->gpio >= 0) {
pr_info("%s: %s: enable gpio %d\n", __func__,
path->name, path->gpio);
rc = gpio_direction_output(path->gpio, on);
if (rc)
pr_err("%s: could not set gpio %d to %d\n", __func__,
path->gpio, on);
}
if (!on)
return 0;
if (!path->table) {
pr_info("%s: no config table for path %s\n", __func__,
path->name);
return -ENOSYS;
}
entry = path->table;
len = path->table_len;
while (len--) {
u16 val = entry->val | (pdata->master ? 0 : entry->slave_or);
int rc = cpcap_regacc_write(cpcap,
entry->reg,
val,
entry->mask);
if (rc) {
pr_err("%s: cpcap_regacc_write %d %x/%x %x failed: %d\n",
__func__,
entry->reg,
entry->val,
entry->slave_or,
entry->mask, rc);
rc = -EIO;
}
entry++;
}
return 0;
}
static int cpcap_set_volume(struct cpcap_device *cpcap, unsigned volume)
{
pr_info("%s\n", __func__);
volume &= 0xF;
volume = volume << 12 | volume << 8;
return cpcap_regacc_write(cpcap, CPCAP_REG_RXVC, volume, 0xFF00);
}
static int cpcap_audio_ctl_open(struct inode *inode, struct file *file)
{
return 0;
}
static int cpcap_audio_ctl_release(struct inode *inode, struct file *file)
{
return 0;
}
static DEFINE_MUTEX(cpcap_lock);
static long cpcap_audio_ctl_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int rc = 0;
mutex_lock(&cpcap_lock);
switch (cmd) {
case CPCAP_AUDIO_OUT_SET_OUTPUT:
if (arg > CPCAP_AUDIO_OUT_MAX) {
pr_err("%s: invalid audio path selector %ld\n",
__func__, arg);
goto done;
}
if (current_output == arg) {
pr_info("%s: no path change\n", __func__);
goto done;
}
if (current_output == CPCAP_AUDIO_OUT_SPEAKER) {
pr_info("%s: setting path to %s\n", __func__,
pdata->headset->name);
cpcap_audio_set(pdata->speaker, 0);
cpcap_audio_set(pdata->headset, 1);
} else {
pr_info("%s: setting path to %s\n", __func__,
pdata->speaker->name);
cpcap_audio_set(pdata->headset, 0);
cpcap_audio_set(pdata->speaker, 1);
}
current_output = arg;
break;
case CPCAP_AUDIO_OUT_GET_OUTPUT:
if (copy_to_user((void *)arg, &current_output,
sizeof(unsigned int))) {
rc = -EFAULT;
goto done;
}
break;
case CPCAP_AUDIO_OUT_SET_VOLUME:
if (arg > CPCAP_AUDIO_OUT_VOL_MAX) {
pr_err("%s: invalid audio volume selector %ld\n",
__func__, arg);
goto done;
}
if (current_volume == arg) {
pr_info("%s: no volume change\n", __func__);
goto done;
}
rc = cpcap_set_volume(cpcap, (unsigned)arg);
if (rc < 0) {
pr_err("%s: could not set audio volume to %ld: %d\n",
__func__, arg, rc);
goto done;
}
current_volume = arg;
break;
case CPCAP_AUDIO_OUT_GET_VOLUME:
if (copy_to_user((void *)arg, &current_volume,
sizeof(unsigned int))) {
rc = -EFAULT;
goto done;
}
break;
}
done:
mutex_unlock(&cpcap_lock);
return rc;
}
static const struct file_operations cpcap_audio_ctl_fops = {
.open = cpcap_audio_ctl_open,
.release = cpcap_audio_ctl_release,
.unlocked_ioctl = cpcap_audio_ctl_ioctl,
};
static struct miscdevice cpcap_audio_ctl = {
.name = "audio_ctl",
.minor = MISC_DYNAMIC_MINOR,
.fops = &cpcap_audio_ctl_fops,
};
static int cpcap_audio_probe(struct platform_device *pdev)
{
int rc;
struct regulator *audio_reg;
pr_info("%s\n", __func__);
cpcap = platform_get_drvdata(pdev);
BUG_ON(!cpcap);
pdata = pdev->dev.platform_data;
BUG_ON(!pdata);
audio_reg = regulator_get(NULL, "vaudio");
if (IS_ERR(audio_reg)) {
rc = PTR_ERR(audio_reg);
pr_err("%s: could not get vaudio regulator: %d\n", __func__,
rc);
return rc;
}
rc = regulator_enable(audio_reg);
if (rc) {
pr_err("%s: failed to enable vaudio regulator: %d\n", __func__,
rc);
regulator_put(audio_reg);
return rc;
}
if (pdata->speaker->gpio >= 0) {
tegra_gpio_enable(pdata->speaker->gpio);
rc = gpio_request(pdata->speaker->gpio, pdata->speaker->name);
if (rc) {
pr_err("%s: could not get speaker GPIO %d: %d\n",
__func__, pdata->speaker->gpio, rc);
goto fail;
}
}
if (pdata->headset->gpio >= 0) {
tegra_gpio_enable(pdata->headset->gpio);
rc = gpio_request(pdata->headset->gpio, pdata->headset->name);
if (rc) {
pr_err("%s: could not get headset GPIO %d: %d\n",
__func__, pdata->headset->gpio, rc);
goto fail2;
}
}
cpcap_audio_set(pdata->speaker, 1);
cpcap_set_volume(cpcap, current_volume);
rc = misc_register(&cpcap_audio_ctl);
if (rc < 0) {
pr_err("%s: failed to register misc device: %d\n", __func__,
rc);
goto fail3;
}
return rc;
fail3:
if (pdata->headset->gpio >= 0)
gpio_free(pdata->headset->gpio);
fail2:
if (pdata->speaker->gpio >= 0)
gpio_free(pdata->speaker->gpio);
fail:
regulator_put(audio_reg);
return rc;
}
static struct platform_driver cpcap_audio_driver = {
.probe = cpcap_audio_probe,
.driver = {
.name = "cpcap_audio",
.owner = THIS_MODULE,
},
};
static int __init cpcap_audio_init(void)
{
return cpcap_driver_register(&cpcap_audio_driver);
}
module_init(cpcap_audio_init);
MODULE_LICENSE("GPL");