diff --git a/sound/Kconfig b/sound/Kconfig index 439e15c8..7657c886 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -94,6 +94,8 @@ source "sound/spi/Kconfig" source "sound/mips/Kconfig" +source "sound/htcleo/Kconfig" + source "sound/sh/Kconfig" # the following will depend on the order of config. diff --git a/sound/Makefile b/sound/Makefile index ec467dec..22f4de0c 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o obj-$(CONFIG_SOUND_PRIME) += oss/ obj-$(CONFIG_DMASOUND) += oss/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ - sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ + sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ htcleo/ obj-$(CONFIG_SND_AOA) += aoa/ # This one must be compilable even if sound is configured out diff --git a/sound/htcleo/Kconfig b/sound/htcleo/Kconfig new file mode 100755 index 00000000..e8aedb6d --- /dev/null +++ b/sound/htcleo/Kconfig @@ -0,0 +1,7 @@ +# HTC LEO nonsoc ALSA driver + +config SND_HTCLEO + tristate "NonSoC Audio for the HTC LEO platform" + depends on MACH_HTCLEO + help + To add support ALSA for HTC LEO platform. diff --git a/sound/htcleo/Makefile b/sound/htcleo/Makefile new file mode 100644 index 00000000..db2341eb --- /dev/null +++ b/sound/htcleo/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# + +obj-$(CONFIG_SND_HTCLEO) += alsa-pcm-htc-leo.o +obj-$(CONFIG_SND_HTCLEO) += alsa-mix-htc-leo.o + + diff --git a/sound/htcleo/alsa-htc-leo.h b/sound/htcleo/alsa-htc-leo.h new file mode 100644 index 00000000..86eac572 --- /dev/null +++ b/sound/htcleo/alsa-htc-leo.h @@ -0,0 +1,147 @@ +/* +* Copyright (C) 2011 DFT, Cotulla +* Copyright (C) 2008 Google, Inc. +* Copyright (C) 2008 HTC Corporation +* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. +* +* 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. +* 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. +*/ + + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + + +#if defined(CONFIG_MACH_HTCLEO) + + #include + #include "../../../arch/arm/mach-msm/qdsp6_1550/dal_audio.h" + +#else + + #include + #include "../../../arch/arm/mach-msm/qdsp6/dal_audio.h" + +#endif + + + +#define USE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_RATE (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS) +//#define USE_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_44100) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 + +#define MAX_BUFFER_SIZE (4096*4) + +#define MAX_PERIOD_SIZE 4096 +#define MIN_PERIOD_SIZE 1024 + +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define PLAYBACK_STREAMS 2 +#define CAPTURE_STREAMS 1 + + +struct audio_locks +{ + spinlock_t alsa_lock; + struct mutex mixer_lock; + wait_queue_head_t eos_wait; +}; + + +struct qsd_ctl +{ + uint16_t tx_volume; /* Volume parameter */ + uint16_t rx_volume; /* Volume parameter */ + int32_t strm_volume; /* stream volume*/ + uint16_t update; + int16_t pan; + uint16_t device; /* Device parameter */ + uint16_t tx_mute; /* Mute parameter */ + uint16_t rx_mute; /* Mute parameter */ + + uint32_t acdb_tx_id; + uint32_t acdb_rx_id; + uint32_t dev_tx_id; + uint32_t dev_rx_id; +}; + + +extern struct audio_locks the_locks; +extern struct snd_pcm_ops qsd_pcm_ops; + + +struct qsd_audio +{ + struct snd_pcm_substream *substream; + spinlock_t lock; + + int dir; + int opened; + int enabled; + int running; + int stopped; + int start; + + unsigned int buf_curoff; + unsigned int buf_chunk; + unsigned int buf_maxoff; + + int thread_exit; + int fake_dma_started; + + struct audio_client *ac; + struct task_struct *fake_dma_thread; + wait_queue_head_t fake_wait; + struct mutex mlock; +}; + + +extern struct qsd_ctl qsd_glb_ctl; + + +extern int htcleo_alsa_init_pcm(struct snd_card *card); + + +/* Supported audio path router IDs */ + +#define AP_ROUTER_NONE 0 +#define AP_ROUTER_DEVICE 1 +#define AP_ROUTER_DEVICE_LOUD 2 +#define AP_ROUTER_HEADSET 3 + +#define AP_ROUTER_MIN AP_ROUTER_NONE +#define AP_ROUTER_MAX AP_ROUTER_HEADSET + + + +#define ACDB_ID_HAC_HANDSET_MIC 107 +#define ACDB_ID_HAC_HANDSET_SPKR 207 +#define ACDB_ID_EXT_MIC_REC 307 +#define ACDB_ID_HEADSET_PLAYBACK 407 +#define ACDB_ID_HEADSET_RINGTONE_PLAYBACK 408 +#define ACDB_ID_INT_MIC_REC 507 +#define ACDB_ID_CAMCORDER 508 +#define ACDB_ID_INT_MIC_VR 509 +#define ACDB_ID_SPKR_PLAYBACK 607 +#define ACDB_ID_ALT_SPKR_PLAYBACK 609 + + +#endif /*_MSM_PCM_H*/ diff --git a/sound/htcleo/alsa-mix-htc-leo.c b/sound/htcleo/alsa-mix-htc-leo.c new file mode 100644 index 00000000..e26171e0 --- /dev/null +++ b/sound/htcleo/alsa-mix-htc-leo.c @@ -0,0 +1,607 @@ +/* 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"); diff --git a/sound/htcleo/alsa-pcm-htc-leo.c b/sound/htcleo/alsa-pcm-htc-leo.c new file mode 100644 index 00000000..f1f1dc85 --- /dev/null +++ b/sound/htcleo/alsa-pcm-htc-leo.c @@ -0,0 +1,773 @@ +/* linux/sound/htcleo/alsa-htc-leo.c +* +* Copyright (c) 2011, DFT, 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. +*/ +/* + +Cotulla: + +implemented playback and capture +this file use q6audio_* function primary, some new was added + + +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "alsa-htc-leo.h" + + + +#if 1 + +#define DBG(fmt, arg...) \ + printk("[ALSA-PCM] %s " fmt "\n", __func__, ## arg) + +#else + +#define DBG(fmt, arg...) \ + do {} while (0); + +#endif + + + +static int qsd_audio_volume_update(struct qsd_audio *prtd); +static int qsd_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma); + + +//----------------------------------------------------------------------------------- +// Fake DMA code +//------------------------------------------------------------------------ +// +// this thread emulate DMA transfer by QDSP +// + +static int snd_qsd_fake_playback_dma_thread(void *data) +{ + int rc = 0; + int rcopy; + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + + struct audio_buffer *ab; + struct audio_client *ac; + unsigned char *ptr; + + while (prtd->thread_exit == 0) + { + DBG(" start handler"); + + // wait until state is changed + // at this point this thread waits until SNDRV_PCM_TRIGGER_START comes + // SNDRV_PCM_TRIGGER_STOP + // + wait_event_interruptible(prtd->fake_wait, (prtd->start == 1 || prtd->thread_exit == 1)); + DBG(" wake up %d %d", prtd->start, prtd->thread_exit); + + if (prtd->thread_exit) break; + + ac = prtd->ac; + while (prtd->start == 1) + { + // wait until new buffer appear + // + // DBG(" ac = %X, prtd = %X, substream = %X", ac, prtd, substream); + + ab = ac->buf + ac->cpu_buf; + if (ab->used) + { + DBG("wait a buffer %d", ac->cpu_buf); + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5 * HZ)) + { + DBG("timeout. dsp dead?\n"); + rc = -EFAULT; + goto fail; + } + + // some buffer become free, report about state change now + // + snd_pcm_period_elapsed(prtd->substream); + } + + mutex_lock(&prtd->mlock); + + if (prtd->start == 0) + { + DBG("flag is set - exit thread"); + mutex_unlock(&prtd->mlock); + break; // EXIT FROM LOOP + } + + // send buffer to DSP + // + ptr = runtime->dma_area + prtd->buf_curoff; + rcopy = prtd->buf_chunk; + if (rcopy > ab->size) + rcopy = ab->size; + + memcpy(ab->data, ptr, rcopy); + ab->used = rcopy; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + + // move fake dma pointer forward + // + spin_lock(&prtd->lock); + prtd->buf_curoff += prtd->buf_chunk; + if (prtd->buf_curoff >= prtd->buf_maxoff) + prtd->buf_curoff = 0; + spin_unlock(&prtd->lock); + + + // update stream settings if required + // + if (qsd_glb_ctl.update) + { + rc = qsd_audio_volume_update(prtd); + qsd_glb_ctl.update = 0; + } + + mutex_unlock(&prtd->mlock); + +fail: + ; // CotullaTODO: handle this fault somehow. + } + DBG(" end handler"); + } + DBG(" exit"); + return 0; +} + + +static int snd_qsd_fake_capture_dma_thread(void *data) +{ + int rc = 0; + int rcopy; + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + + struct audio_buffer *ab; + struct audio_client *ac; + unsigned char *ptr; + + while (prtd->thread_exit == 0) + { + DBG(" start handler"); + + // wait until state is changed + // at this point this thread waits until SNDRV_PCM_TRIGGER_START comes + // SNDRV_PCM_TRIGGER_STOP + // + wait_event_interruptible(prtd->fake_wait, (prtd->start == 1 || prtd->thread_exit == 1)); + DBG(" wake up %d %d", prtd->start, prtd->thread_exit); + + if (prtd->thread_exit) break; + + ac = prtd->ac; + while (prtd->start == 1) + { + // wait until new buffer appear + // + // DBG(" ac = %X, prtd = %X, substream = %X", ac, prtd, substream); + + ab = ac->buf + ac->cpu_buf; + if (ab->used) + { + DBG("wait a buffer %d", ac->cpu_buf); + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5 * HZ)) + { + DBG("timeout. dsp dead?\n"); + rc = -EFAULT; + goto fail; + } + + // some buffer become free, report about state change now + // + snd_pcm_period_elapsed(prtd->substream); + } + + mutex_lock(&prtd->mlock); + + if (prtd->start == 0) + { + DBG("flag is set - exit thread"); + mutex_unlock(&prtd->mlock); + break; // EXIT FROM LOOP + } + + // this buffer must contains recorded data + // copy it to dma buffer + // + ptr = runtime->dma_area + prtd->buf_curoff; + rcopy = prtd->buf_chunk; + if (rcopy > ab->size) + rcopy = ab->size; + memcpy(ptr, ab->data, rcopy); + + + // send this buffer to DSP queue again + // + ab->used = rcopy; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + + + // move fake dma pointer forward + // + spin_lock(&prtd->lock); + prtd->buf_curoff += prtd->buf_chunk; + if (prtd->buf_curoff >= prtd->buf_maxoff) + prtd->buf_curoff = 0; + spin_unlock(&prtd->lock); + + mutex_unlock(&prtd->mlock); + +fail: + ; // CotullaTODO: handle this fault somehow. + } + DBG(" end handler"); + } + DBG(" exit"); + return 0; +} + + + +//----------------------------------------------------------------------------------- + + +struct snd_qsd +{ + struct snd_card *card; + struct snd_pcm *pcm; +}; + + +struct qsd_ctl qsd_glb_ctl; +EXPORT_SYMBOL(qsd_glb_ctl); + + +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); + + + +static struct snd_pcm_hardware qsd_pcm_playback_hardware = +{ + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + + +static struct snd_pcm_hardware qsd_pcm_capture_hardware = +{ + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + + + +static int qsd_audio_volume_update(struct qsd_audio *prtd) +{ + int rc = 0; + + DBG("updating volume"); + + if (!prtd->ac) + { + DBG("prtd->ac == NULL"); + return -EINVAL; + } + + rc = q6audio_set_stream_volume(prtd->ac, qsd_glb_ctl.strm_volume); + if (rc) + { + DBG("set stream volume failed\n"); + } + return rc; +} + + + +static int qsd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + + DBG("%d %X %X", cmd, prtd, prtd->ac); + switch (cmd) + { + case SNDRV_PCM_TRIGGER_START: + DBG("TRIGGER_START"); + prtd->start = 1; + wake_up(&prtd->fake_wait); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + DBG("TRIGGER_STOP"); + prtd->start = 0; + wake_up(&prtd->fake_wait); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + + +static int hw_rule_periodsize_by_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *ps = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval ch; + + if (!ps || !r) + return 0; + + snd_interval_any(&ch); + + if (r->min > 8000) + { + ch.min = 512; + pr_debug("Minimum period size is adjusted to 512\n"); + return snd_interval_refine(ps, &ch); + } + return 0; +} + + +static int qsd_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd; + int rc = 0; + + DBG("+"); + + prtd = kzalloc(sizeof(struct qsd_audio), GFP_KERNEL); + if (prtd == NULL) + { + rc = -ENOMEM; + goto fail; + } + DBG(" prtd = %X", prtd); + + spin_lock_init(&prtd->lock); + init_waitqueue_head(&prtd->fake_wait); + mutex_init(&prtd->mlock); + + prtd->enabled = 0; + prtd->substream = substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + DBG("Stream = SNDRV_PCM_STREAM_PLAYBACK"); + runtime->hw = qsd_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + } + else + { + DBG("Stream = SNDRV_PCM_STREAM_CAPTURE"); + runtime->hw = qsd_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + } + + /* Ensure that buffer size is a multiple of period size */ + rc = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) + { + kfree(prtd); + goto fail; + } + + rc = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + hw_rule_periodsize_by_rate, substream, + SNDRV_PCM_HW_PARAM_RATE, -1); + + if (rc < 0) + { + kfree(prtd); + goto fail; + } + + runtime->private_data = prtd; + +fail: + DBG("-: %d", rc); + return rc; +} + + +static snd_pcm_uframes_t qsd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + unsigned int res; + +// DBG(""); + + res = prtd->buf_curoff; + if (res >= prtd->buf_maxoff) DBG("fatal overflow: %d > %d", prtd->buf_curoff, prtd->buf_maxoff); + + DBG("res = %d", res); + return bytes_to_frames(substream->runtime, res); +} + + + +static int qsd_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + int ret = 0; + + DBG("+"); + if (prtd->enabled) + { + if (prtd->ac) + { + q6audio_close(prtd->ac); + } + } + + prtd->enabled = 0; + kfree(prtd); + + DBG("-"); + return ret; +} + + + +//----------------------------------------------------------------------------------- +// Playback +//----------------------------------------------------------------------------------- + + + +static int qsd_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + unsigned long acdb_id = 0; + + DBG ("+"); + + if (prtd->enabled) + { + DBG("already enabled"); + goto fail; + } + + prtd->buf_maxoff = snd_pcm_lib_buffer_bytes(substream); + prtd->buf_chunk = snd_pcm_lib_period_bytes(substream); + prtd->start = 0; + + DBG("INFO:"); + DBG("\tMaxSize = %d, MaxPeriod = %d", prtd->buf_maxoff, prtd->buf_chunk); + DBG("\tRate = %d, Channels = %d", runtime->rate, runtime->channels); + + acdb_id = qsd_glb_ctl.acdb_tx_id; + prtd->ac = q6audio_open_pcm(prtd->buf_chunk, runtime->rate, runtime->channels, AUDIO_FLAG_WRITE, acdb_id); + if (prtd->ac == NULL) + { + DBG("q6audio_open_pcm() failed"); + rc = -ENOMEM; + goto fail; + } + + q6audio_set_stream_volume(prtd->ac, qsd_glb_ctl.strm_volume); + + prtd->enabled = 1; + +fail: + DBG ("-: %d", rc); + return rc; +} + + +//----------------------------------------------------------------------------------- +// Capture +//----------------------------------------------------------------------------------- + + +static int qsd_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + int rc = 0; + uint32_t acdb_id = 0; + + if (prtd->enabled) + { + DBG("already enabled"); + goto fail; + } + + prtd->buf_maxoff = snd_pcm_lib_buffer_bytes(substream); + prtd->buf_chunk = snd_pcm_lib_period_bytes(substream); + prtd->start = 0; + + DBG("INFO:"); + DBG("\tMaxSize = %d, MaxPeriod = %d", prtd->buf_maxoff, prtd->buf_chunk); + DBG("\tRate = %d, Channels = %d", runtime->rate, runtime->channels); + + acdb_id = qsd_glb_ctl.acdb_rx_id; + prtd->ac = q6audio_open_pcm(prtd->buf_chunk, runtime->rate, runtime->channels, AUDIO_FLAG_READ, acdb_id); + if (!prtd->ac) + { + DBG("q6audio_open_pcm fails"); + rc = -ENOMEM; + goto fail; + } + + prtd->enabled = 1; + +fail: + DBG ("-: %d", rc); + return rc; +} + + +//----------------------------------------------------------------------------------- +// ALSA thunks +//----------------------------------------------------------------------------------- + + +static int qsd_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = qsd_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = qsd_pcm_capture_prepare(substream); + return ret; +} + + +int qsd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + unsigned long totbytes = params_buffer_bytes(params); + + DBG("+: totbytes=%d", totbytes); + + snd_pcm_lib_malloc_pages(substream, totbytes); + + + if (prtd->fake_dma_started == 0) + { + prtd->fake_dma_started = 1; + DBG("create thread"); + + prtd->thread_exit = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + prtd->fake_dma_thread = kthread_run(snd_qsd_fake_playback_dma_thread, (void*)substream, "leo_alsa_fake_playback_dma"); + } + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + { + prtd->fake_dma_thread = kthread_run(snd_qsd_fake_capture_dma_thread, (void*)substream, "leo_alsa_fake_capture_dma"); + } + else + { + DBG("Wrong direction?"); + prtd->fake_dma_started = 0; + rc = -ENOMEM; + goto fail; + } + + if (IS_ERR(prtd->fake_dma_thread)) + { + rc = PTR_ERR(prtd->fake_dma_thread); + goto fail; + } + } + runtime->dma_bytes = totbytes; + +fail: + DBG("-: %d", rc); + return 0; +} + + +static int qsd_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qsd_audio *prtd = runtime->private_data; + + DBG("+"); + + // Cotulla: this prevents freeing memory during execution code + // inside capture thread. + // if copy data / put buffer code executed inside thread + // this will wait util it finishes. then start = 0 will cancel the next loop. + // + mutex_lock(&prtd->mlock); + mutex_unlock(&prtd->mlock); + + snd_pcm_lib_free_pages(substream); + + if (prtd->fake_dma_started == 1) + { + prtd->fake_dma_started = 0; + + prtd->thread_exit = 1; + wake_up(&prtd->fake_wait); + + // Cotulla: POSSIBLE DEADLOCK IF THREAD DOES NOT EXIT + // + kthread_stop(prtd->fake_dma_thread); + } + + DBG("-"); + return 0; +} + + + +struct snd_pcm_ops qsd_pcm_ops = +{ + .open = qsd_pcm_open, + .close = qsd_pcm_close, + .hw_params = qsd_pcm_hw_params, + .hw_free = qsd_pcm_hw_free, + .ioctl = snd_pcm_lib_ioctl, + .prepare = qsd_pcm_prepare, + .trigger = qsd_pcm_trigger, + .pointer = qsd_pcm_pointer, +}; +EXPORT_SYMBOL_GPL(qsd_pcm_ops); + + + +/* +static int qsd_pcm_remove(struct platform_device *devptr) +{ + struct snd_soc_device *socdev = platform_get_drvdata(devptr); + + DBG(""); + platform_set_drvdata(devptr, NULL); + return 0; +} +*/ + + +//================================================================================== +// Init +//================================================================================== + + +int htcleo_alsa_init_pcm(struct snd_card *card) +{ + struct snd_pcm *pcm; + int rc; + + DBG("+"); + + rc = snd_pcm_new(card, + /* ID */ "htcleo_snd", + /* device */ 0, + /* playback count */ 1, + /* capture count */ 1, &pcm); + if (rc < 0) + { + DBG("snd_pcm_new() failed %d", rc); + goto fail; + } + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &qsd_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &qsd_pcm_ops); + + pcm->private_data = 0; + pcm->info_flags = 0; + strcpy(pcm->name, card->shortname); + + rc = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 64*1024, 64*1024); + if (rc < 0) + { + DBG("snd_pcm_lib_preallocate_pages_for_all() failed %d", rc); + goto fail; + } + +fail: + DBG("-:%d", rc); + return rc; +} + + +MODULE_AUTHOR("DFT-Cotulla"); +MODULE_DESCRIPTION("HTC LEO ALSA PCM driver"); +MODULE_LICENSE("GPL v2");