android_kernel_cmhtcleo/sound/htcleo/alsa-mix-htc-leo.c
tytung e7f4d881c0 ALSA: Add new ALSA driver for QDSP6 sound system
This driver works on the htc leo.

The driver was written by Cotulla

Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@no-log.org>
2011-10-31 23:53:43 +08:00

608 lines
14 KiB
C

/* linux/sound/htcleo/alsa-mix-htc-leo.c
*
* Copyright (c) 2011 Cotulla
* Copyright (c) 2009, 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 <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <asm/mach-types.h>
#include "alsa-htc-leo.h"
int q6audio_set_tx_volume(int level);
#if 1
#define DBG(fmt, arg...) \
printk("[ALSA-MIX] %s " fmt "\n", __func__, ## arg)
#else
#define DBG(fmt, arg...) \
do {} while (0);
#endif
/*
#define CHRC(exp, ret) \
rc = (exp); if (rc < 0) { DBG(#exp " = %d", rc); goto fail; }
*/
static struct snd_card *g_card;
//-----------------------------------------------------------------------------------------
// get range and information functions
//-----------------------------------------------------------------------------------------
static int snd_qsd_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
DBG("");
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* Device */
uinfo->value.integer.min = (int)AP_ROUTER_MIN;
uinfo->value.integer.max = (int)AP_ROUTER_MAX;
return 0;
}
static int snd_qsd_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.device);
ucontrol->value.integer.value[0] = (uint32_t) qsd_glb_ctl.device;
return 0;
}
static int snd_tx_mute_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
DBG("");
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* MUTE */
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_rx_mute_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
DBG("");
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* MUTE */
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
DBG("");
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* Volume */
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int snd_rx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.rx_volume);
ucontrol->value.integer.value[0] = (uint32_t) qsd_glb_ctl.rx_volume;
return 0;
}
static int snd_tx_vol_get(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.tx_volume);
ucontrol->value.integer.value[0] = (uint32_t) qsd_glb_ctl.tx_volume;
return 0;
}
static int snd_tx_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.tx_mute);
ucontrol->value.integer.value[0] = (uint32_t) qsd_glb_ctl.tx_mute;
return 0;
}
static int snd_rx_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.rx_mute);
ucontrol->value.integer.value[0] = (uint32_t) qsd_glb_ctl.rx_mute;
return 0;
}
static int snd_strm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
DBG("%d", qsd_glb_ctl.strm_volume);
ucontrol->value.integer.value[0] = qsd_glb_ctl.strm_volume;
return 0;
}
//-----------------------------------------------------------------------------------------
// set functions
//-----------------------------------------------------------------------------------------
static int update_routing(int device)
{
int rc = 0;
uint32_t acdb_tx_id;
uint32_t acdb_rx_id;
uint32_t dev_tx_id;
uint32_t dev_rx_id;
DBG("%d", device);
if (device < AP_ROUTER_MIN || device > AP_ROUTER_MAX)
{
return -EINVAL;
}
// CotullaTODO: finish this part
switch (device)
{
case AP_ROUTER_DEVICE:
dev_rx_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC;
dev_tx_id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR;
acdb_tx_id = ACDB_ID_SPKR_PLAYBACK;
acdb_rx_id = ACDB_ID_INT_MIC_REC;
break;
case AP_ROUTER_DEVICE_LOUD:
dev_tx_id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO;
acdb_tx_id = ACDB_ID_SPKR_PLAYBACK;
dev_rx_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC;
acdb_rx_id = ACDB_ID_INT_MIC_REC;
break;
case AP_ROUTER_HEADSET:
dev_rx_id = ADSP_AUDIO_DEVICE_ID_HEADSET_MIC;
dev_tx_id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO;
acdb_tx_id = ACDB_ID_HEADSET_PLAYBACK;
acdb_rx_id = ACDB_ID_EXT_MIC_REC;
break;
case AP_ROUTER_NONE:
default:
rc = -EINVAL;
goto fail;
}
qsd_glb_ctl.dev_tx_id = dev_tx_id;
qsd_glb_ctl.dev_rx_id = dev_rx_id;
qsd_glb_ctl.acdb_tx_id = acdb_tx_id;
qsd_glb_ctl.acdb_rx_id = acdb_rx_id;
qsd_glb_ctl.device = device;
rc = q6audio_do_routing(qsd_glb_ctl.dev_tx_id, qsd_glb_ctl.acdb_tx_id);
if (rc < 0)
{
goto fail;
}
rc = q6audio_do_routing(qsd_glb_ctl.dev_rx_id, qsd_glb_ctl.acdb_rx_id);
if (rc < 0)
{
goto fail;
}
fail:
DBG("rc = %d", rc);
return rc;
}
static int snd_qsd_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int device;
device = ucontrol->value.integer.value[0];
if (qsd_glb_ctl.device == device)
{
return 0;
}
return update_routing(device);
}
static int snd_rx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
int val;
DBG("%d", ucontrol->value.integer.value[0]);
val = ucontrol->value.integer.value[0];
if (val < 0 || val > 100)
{
return -EINVAL;
}
rc = q6audio_set_rx_volume(val);
if (rc)
DBG("q6audio_set_rx_volume failed");
else
qsd_glb_ctl.rx_volume = ucontrol->value.integer.value[0];
return rc;
}
static int snd_tx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
int val;
DBG("%d", ucontrol->value.integer.value[0]);
val = ucontrol->value.integer.value[0];
if (val < 0 || val > 100)
{
return -EINVAL;
}
rc = q6audio_set_tx_volume(val);
if (rc)
DBG("q6audio_set_tx_volume failed");
else
qsd_glb_ctl.tx_volume = ucontrol->value.integer.value[0];
return rc;
}
static int snd_tx_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
int mute;
DBG("%d", ucontrol->value.integer.value[0]);
mute = ucontrol->value.integer.value[0] ? 1 : 0;
rc = q6audio_set_tx_mute(mute);
if (rc)
DBG("Capture device mute failed");
else
qsd_glb_ctl.tx_mute = ucontrol->value.integer.value[0];
return rc;
}
static int snd_rx_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int rc = 0;
int mute;
DBG("%d", ucontrol->value.integer.value[0]);
mute = ucontrol->value.integer.value[0] ? 1 : 0;
rc = q6audio_set_rx_mute(mute);
if (rc)
printk(KERN_ERR "Playback device mute failed\n");
else
qsd_glb_ctl.rx_mute = ucontrol->value.integer.value[0];
return rc;
}
#define CAD_STREAM_MIN_GAIN 0
#define CAD_STREAM_MAX_GAIN 100
static int snd_strm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
DBG("");
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1; /* Volume Param, in gain */
uinfo->value.integer.min = CAD_STREAM_MIN_GAIN;
uinfo->value.integer.max = CAD_STREAM_MAX_GAIN;
return 0;
}
static int snd_strm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
int change;
int volume;
DBG("");
if (ucontrol->value.integer.value[0] > CAD_STREAM_MAX_GAIN)
ucontrol->value.integer.value[0] = CAD_STREAM_MAX_GAIN;
if (ucontrol->value.integer.value[0] < CAD_STREAM_MIN_GAIN)
ucontrol->value.integer.value[0] = CAD_STREAM_MIN_GAIN;
volume = ucontrol->value.integer.value[0];
change = (qsd_glb_ctl.strm_volume != volume);
mutex_lock(&the_locks.mixer_lock);
if (change)
{
qsd_glb_ctl.strm_volume = volume;
qsd_glb_ctl.update = 1;
}
mutex_unlock(&the_locks.mixer_lock);
return 0;
}
#define QSD_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, \
}
static struct snd_kcontrol_new snd_qsd_controls[] =
{
QSD_EXT(" Route", 1, snd_qsd_route_info, snd_qsd_route_get, snd_qsd_route_put, 0),
QSD_EXT("Master Volume Playback", 2, snd_vol_info, snd_rx_vol_get, snd_rx_vol_put, 0),
QSD_EXT("Master Volume Capture", 3, snd_vol_info, snd_tx_vol_get, snd_tx_vol_put, 0),
QSD_EXT("Master Mute Playback", 4, snd_rx_mute_info, snd_rx_mute_get, snd_rx_mute_put, 0),
QSD_EXT("Master Mute Capture", 5, snd_tx_mute_info, snd_tx_mute_get, snd_tx_mute_put, 0),
QSD_EXT("Stream Volume", 6, snd_strm_vol_info, snd_strm_vol_get, snd_strm_vol_put, 0),
};
static int htcleo_alsa_init_mixer(struct snd_card *card)
{
unsigned int idx;
int err;
DBG("");
strcpy(card->mixername, "LEO Mixer");
for (idx = 0; idx < ARRAY_SIZE(snd_qsd_controls); idx++)
{
err = snd_ctl_add(card, snd_ctl_new1(&snd_qsd_controls[idx], NULL));
if (err < 0)
return err;
}
return 0;
}
//==================================================================================
// Probe / remove
//==================================================================================
static int htcleo_alsa_dev_free(struct snd_device *device)
{
return 0;
}
static struct snd_device_ops htcleo_alsa_dev_ops =
{
.dev_free = htcleo_alsa_dev_free,
};
static int htcleo_alsa_probe(struct platform_device *pdev)
{
int rc = 0;
struct snd_card *card = NULL;
DBG("+");
if (g_card)
{
DBG("Already inited!");
return -1;
}
mutex_init(&the_locks.mixer_lock);
init_waitqueue_head(&the_locks.eos_wait);
spin_lock_init(&the_locks.alsa_lock);
qsd_glb_ctl.tx_volume = 100;
qsd_glb_ctl.rx_volume = 100;
qsd_glb_ctl.strm_volume = 100;
qsd_glb_ctl.device = AP_ROUTER_DEVICE_LOUD;
qsd_glb_ctl.tx_mute = 0;
qsd_glb_ctl.rx_mute = 0;
qsd_glb_ctl.update = 0;
update_routing(qsd_glb_ctl.device);
q6audio_set_rx_volume(100);
rc = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card);
if (rc < 0)
{
DBG("snd_card_create() failed %d", rc);
goto fail;
}
strcpy(card->driver, "HTC LEO");
strcpy(card->shortname, "HTCLEO");
strcpy(card->longname, "HTC LEO via QDSP6/1550");
// Cotulla: pdev here unused, but otherwise it crash inside
//
rc = snd_device_new(card, SNDRV_DEV_LOWLEVEL, (void*)pdev, &htcleo_alsa_dev_ops);
if (rc < 0)
{
DBG("snd_device_new() failed %d", rc);
goto fail;
}
rc = htcleo_alsa_init_mixer(card);
if (rc < 0)
{
DBG("htcleo_alsa_init_mixer() failed %d", rc);
goto fail;
}
rc = htcleo_alsa_init_pcm(card);
if (rc < 0)
{
DBG("htcleo_alsa_init_pcm() failed %d", rc);
goto fail;
}
rc = snd_card_register(card);
if (rc < 0)
{
DBG("snd_card_register() failed %d", rc);
goto fail;
}
g_card = card;
DBG("-: %d", rc);
return rc;
fail:
if (card)
{
snd_card_free(card);
}
DBG("-: %d", rc);
return rc;
}
static int htcleo_alsa_remove(struct platform_device *dev)
{
if (g_card)
{
snd_card_free(g_card);
g_card = NULL;
}
return 0;
}
static struct platform_driver htcleo_alsa_driver =
{
.probe = htcleo_alsa_probe,
.remove = htcleo_alsa_remove,
.driver =
{
.name = "htcleo_alsa",
.owner = THIS_MODULE,
},
};
static int __init htcleo_alsa_init(void)
{
int rc = 0;
if (!machine_is_htcleo())
{
return 0;
}
rc = platform_driver_register(&htcleo_alsa_driver);
if (rc < 0) goto fail;
{
struct platform_device *qsd_audio_snd_device = platform_device_alloc("htcleo_alsa", -1);
if (!qsd_audio_snd_device) goto fail;
rc = platform_device_add(qsd_audio_snd_device);
if (rc)
{
platform_device_put(qsd_audio_snd_device);
goto fail;
}
}
fail:
return 0;
}
static void __exit htcleo_alsa_exit(void)
{
platform_driver_unregister(&htcleo_alsa_driver);
}
module_init(htcleo_alsa_init);
module_exit(htcleo_alsa_exit);
MODULE_AUTHOR("DFT-Cotulla");
MODULE_DESCRIPTION("HTC LEO ALSA Mixer module");
MODULE_LICENSE("GPL v2");