From: Iliyan Malchev Date: Thu, 29 Jul 2010 21:09:06 +0000 (-0700) Subject: [ARM] tegra: stingray: platform driver for CPCAP audio codec X-Git-Tag: firefly_0821_release~9834^2~734 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=f765ab45ccdd26b4ec9e4eb079c9bb42640901aa;p=firefly-linux-kernel-4.4.55.git [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 --- diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 713f8176d506..ece21b1a98a2 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -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 diff --git a/drivers/mfd/cpcap-audio.c b/drivers/mfd/cpcap-audio.c new file mode 100644 index 000000000000..5d11c703be6c --- /dev/null +++ b/drivers/mfd/cpcap-audio.c @@ -0,0 +1,281 @@ +/* drivers/mfd/cpcap-audio.c + * + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Iliyan Malchev + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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, ¤t_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, ¤t_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");