/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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");