blob: 2a73fd675c3436899062371cbe74290cf6d24baa [file] [log] [blame]
/* linux/sound/soc/msm/msm7201.c
*
* Copyright (c) 2008-2009, 2011, 2012 Code Aurora Forum. All rights reserved.
*
* All source code in this file is licensed under the following license except
* where indicated.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* 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, you can find it at http://www.fsf.org.
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include "msm-pcm.h"
#include <asm/mach-types.h>
#include <mach/msm_rpcrouter.h>
static struct msm_rpc_endpoint *snd_ep;
static uint32_t snd_mute_ear_mute;
static uint32_t snd_mute_mic_mute;
struct msm_snd_rpc_ids {
unsigned long prog;
unsigned long vers;
unsigned long vers2;
unsigned long rpc_set_snd_device;
unsigned long rpc_set_device_vol;
int device;
};
static struct msm_snd_rpc_ids snd_rpc_ids;
static struct platform_device *msm_audio_snd_device;
static int snd_msm_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* Volume Param, in dB */
uinfo->value.integer.min = MIN_DB;
uinfo->value.integer.max = MAX_DB;
return 0;
}
static int snd_msm_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
spin_lock_irq(&the_locks.mixer_lock);
ucontrol->value.integer.value[0] = msm_vol_ctl.volume;
spin_unlock_irq(&the_locks.mixer_lock);
return 0;
}
static int snd_msm_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int change;
int volume;
volume = ucontrol->value.integer.value[0];
spin_lock_irq(&the_locks.mixer_lock);
change = (msm_vol_ctl.volume != volume);
if (change) {
msm_vol_ctl.volume = volume;
msm_audio_volume_update(PCMPLAYBACK_DECODERID,
msm_vol_ctl.volume, msm_vol_ctl.pan);
}
spin_unlock_irq(&the_locks.mixer_lock);
return 0;
}
static int snd_msm_device_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 3; /* Device */
/*
* The number of devices supported is 26 (0 to 25)
*/
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 36;
return 0;
}
static int snd_msm_device_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = (uint32_t)snd_rpc_ids.device;
ucontrol->value.integer.value[1] = snd_mute_ear_mute;
ucontrol->value.integer.value[2] = snd_mute_mic_mute;
return 0;
}
int msm_snd_init_rpc_ids(void)
{
snd_rpc_ids.prog = 0x30000002;
snd_rpc_ids.vers = 0x00020001;
snd_rpc_ids.vers2 = 0x00030001;
/*
* The magic number 2 corresponds to the rpc call
* index for snd_set_device
*/
snd_rpc_ids.rpc_set_snd_device = 2;
snd_rpc_ids.rpc_set_device_vol = 3;
return 0;
}
int msm_snd_rpc_connect(void)
{
if (snd_ep) {
printk(KERN_INFO "%s: snd_ep already connected\n", __func__);
return 0;
}
/* Initialize rpc ids */
if (msm_snd_init_rpc_ids()) {
printk(KERN_ERR "%s: snd rpc ids initialization failed\n"
, __func__);
return -ENODATA;
}
snd_ep = msm_rpc_connect_compatible(snd_rpc_ids.prog,
snd_rpc_ids.vers, 0);
if (IS_ERR(snd_ep)) {
printk(KERN_DEBUG "%s failed (compatible VERS = %ld) \
trying again with another API\n",
__func__, snd_rpc_ids.vers);
snd_ep =
msm_rpc_connect_compatible(snd_rpc_ids.prog,
snd_rpc_ids.vers2, 0);
}
if (IS_ERR(snd_ep)) {
printk(KERN_ERR "%s: failed (compatible VERS = %ld)\n",
__func__, snd_rpc_ids.vers2);
snd_ep = NULL;
return -EAGAIN;
}
return 0;
}
int msm_snd_rpc_close(void)
{
int rc = 0;
if (IS_ERR(snd_ep)) {
printk(KERN_ERR "%s: snd handle unavailable, rc = %ld\n",
__func__, PTR_ERR(snd_ep));
return -EAGAIN;
}
rc = msm_rpc_close(snd_ep);
snd_ep = NULL;
if (rc < 0) {
printk(KERN_ERR "%s: close rpc failed! rc = %d\n",
__func__, rc);
return -EAGAIN;
} else
printk(KERN_INFO "rpc close success\n");
return rc;
}
static int snd_msm_device_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
struct snd_start_req {
struct rpc_request_hdr hdr;
uint32_t rpc_snd_device;
uint32_t snd_mute_ear_mute;
uint32_t snd_mute_mic_mute;
uint32_t callback_ptr;
uint32_t client_data;
} req;
snd_rpc_ids.device = (int)ucontrol->value.integer.value[0];
if (ucontrol->value.integer.value[1] > 1)
ucontrol->value.integer.value[1] = 1;
if (ucontrol->value.integer.value[2] > 1)
ucontrol->value.integer.value[2] = 1;
req.hdr.type = 0;
req.hdr.rpc_vers = 2;
req.rpc_snd_device = cpu_to_be32(snd_rpc_ids.device);
req.snd_mute_ear_mute =
cpu_to_be32((int)ucontrol->value.integer.value[1]);
req.snd_mute_mic_mute =
cpu_to_be32((int)ucontrol->value.integer.value[2]);
req.callback_ptr = -1;
req.client_data = cpu_to_be32(0);
req.hdr.prog = snd_rpc_ids.prog;
req.hdr.vers = snd_rpc_ids.vers;
rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_snd_device ,
&req, sizeof(req), 5 * HZ);
if (rc < 0) {
printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n",
__func__, rc);
} else {
printk(KERN_INFO "snd device connected\n");
snd_mute_ear_mute = ucontrol->value.integer.value[1];
snd_mute_mic_mute = ucontrol->value.integer.value[2];
printk(KERN_ERR "%s: snd_mute_ear_mute =%d, snd_mute_mic_mute = %d\n",
__func__, snd_mute_ear_mute, snd_mute_mic_mute);
}
return rc;
}
static int snd_msm_device_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2; /* Device/Volume */
/*
* The number of devices supported is 37 (0 to 36)
*/
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 36;
return 0;
}
static int snd_msm_device_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
struct snd_vol_req {
struct rpc_request_hdr hdr;
uint32_t device;
uint32_t method;
uint32_t volume;
uint32_t cb_func;
uint32_t client_data;
} req;
snd_rpc_ids.device = (int)ucontrol->value.integer.value[0];
if ((ucontrol->value.integer.value[1] < 0) ||
(ucontrol->value.integer.value[1] > 6)) {
pr_err("Device volume should be in range of 1 to 6\n");
return -EINVAL;
}
if ((ucontrol->value.integer.value[0] > 36) ||
(ucontrol->value.integer.value[0] < 0)) {
pr_err("Device range supported is 0 to 36\n");
return -EINVAL;
}
req.device = cpu_to_be32((int)ucontrol->value.integer.value[0]);
req.method = cpu_to_be32(0);
req.volume = cpu_to_be32((int)ucontrol->value.integer.value[1]);
req.cb_func = -1;
req.client_data = cpu_to_be32(0);
rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_device_vol ,
&req, sizeof(req), 5 * HZ);
if (rc < 0) {
printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n",
__func__, rc);
} else {
printk(KERN_ERR "%s: device [%d] volume set to [%d]\n",
__func__, (int)ucontrol->value.integer.value[0],
(int)ucontrol->value.integer.value[1]);
}
return rc;
}
/* Supported range -50dB to 18dB */
static const DECLARE_TLV_DB_LINEAR(db_scale_linear, -5000, 1800);
#define MSM_EXT(xname, xindex, fp_info, fp_get, fp_put, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.name = xname, .index = xindex, \
.info = fp_info,\
.get = fp_get, .put = fp_put, \
.private_value = addr, \
}
#define MSM_EXT_TLV(xname, xindex, fp_info, fp_get, fp_put, addr, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE), \
.name = xname, .index = xindex, \
.info = fp_info,\
.get = fp_get, .put = fp_put, .tlv.p = tlv_array, \
.private_value = addr, \
}
static struct snd_kcontrol_new snd_msm_controls[] = {
MSM_EXT_TLV("PCM Playback Volume", 0, snd_msm_volume_info, \
snd_msm_volume_get, snd_msm_volume_put, 0, db_scale_linear),
MSM_EXT("device", 0, snd_msm_device_info, snd_msm_device_get, \
snd_msm_device_put, 0),
MSM_EXT("Device Volume", 0, snd_msm_device_vol_info, NULL, \
snd_msm_device_vol_put, 0),
};
static int msm_new_mixer(struct snd_soc_codec *codec)
{
unsigned int idx;
int err;
pr_err("msm_soc: ALSA MSM Mixer Setting\n");
strcpy(codec->card->snd_card->mixername, "MSM Mixer");
for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) {
err = snd_ctl_add(codec->card->snd_card,
snd_ctl_new1(&snd_msm_controls[idx], NULL));
if (err < 0)
return err;
}
return 0;
}
static int msm_soc_dai_init(
struct snd_soc_pcm_runtime *rtd)
{
int ret = 0;
struct snd_soc_codec *codec = rtd->codec;
mutex_init(&the_locks.lock);
mutex_init(&the_locks.write_lock);
mutex_init(&the_locks.read_lock);
spin_lock_init(&the_locks.read_dsp_lock);
spin_lock_init(&the_locks.write_dsp_lock);
spin_lock_init(&the_locks.mixer_lock);
init_waitqueue_head(&the_locks.eos_wait);
init_waitqueue_head(&the_locks.write_wait);
init_waitqueue_head(&the_locks.read_wait);
msm_vol_ctl.volume = MSM_PLAYBACK_DEFAULT_VOLUME;
msm_vol_ctl.pan = MSM_PLAYBACK_DEFAULT_PAN;
ret = msm_new_mixer(codec);
if (ret < 0) {
pr_err("msm_soc: ALSA MSM Mixer Fail\n");
}
return ret;
}
static struct snd_soc_dai_link msm_dai[] = {
{
.name = "MSM Primary I2S",
.stream_name = "DSP 1",
.cpu_dai_name = "msm-cpu-dai.0",
.platform_name = "msm-dsp-audio.0",
.codec_name = "msm-codec-dai.0",
.codec_dai_name = "msm-codec-dai",
.init = &msm_soc_dai_init,
},
};
static struct snd_soc_card snd_soc_card_msm = {
.name = "msm-audio",
.dai_link = msm_dai,
.num_links = ARRAY_SIZE(msm_dai),
};
static int __init msm_audio_init(void)
{
int ret;
msm_audio_snd_device = platform_device_alloc("soc-audio", -1);
if (!msm_audio_snd_device)
return -ENOMEM;
platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm);
ret = platform_device_add(msm_audio_snd_device);
if (ret) {
platform_device_put(msm_audio_snd_device);
return ret;
}
ret = msm_snd_rpc_connect();
snd_mute_ear_mute = 0;
snd_mute_mic_mute = 0;
return ret;
}
static void __exit msm_audio_exit(void)
{
msm_snd_rpc_close();
platform_device_unregister(msm_audio_snd_device);
}
module_init(msm_audio_init);
module_exit(msm_audio_exit);
MODULE_DESCRIPTION("PCM module");
MODULE_LICENSE("GPL v2");