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:
parent
0aab481de3
commit
e7f4d881c0
@ -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.
|
||||
|
@ -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
7
sound/htcleo/Kconfig
Executable 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
8
sound/htcleo/Makefile
Normal 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
147
sound/htcleo/alsa-htc-leo.h
Normal 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*/
|
607
sound/htcleo/alsa-mix-htc-leo.c
Normal file
607
sound/htcleo/alsa-mix-htc-leo.c
Normal 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");
|
773
sound/htcleo/alsa-pcm-htc-leo.c
Normal file
773
sound/htcleo/alsa-pcm-htc-leo.c
Normal 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");
|
Loading…
Reference in New Issue
Block a user