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>
This commit is contained in:
tytung 2011-10-31 23:53:43 +08:00
parent 0aab481de3
commit e7f4d881c0
7 changed files with 1545 additions and 1 deletions

View File

@ -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.

View File

@ -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

7
sound/htcleo/Kconfig Executable file
View File

@ -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.

8
sound/htcleo/Makefile Normal file
View File

@ -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

147
sound/htcleo/alsa-htc-leo.h Normal file
View File

@ -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 <mach/msm_qdsp6_audio_1550.h>
#include "../../../arch/arm/mach-msm/qdsp6_1550/dal_audio.h"
#else
#include <mach/msm_qdsp6_audio.h>
#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*/

View File

@ -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 <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");

View File

@ -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 <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/kthread.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#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");