Commit 719f82d3 authored by Eliot Blennerhassett's avatar Eliot Blennerhassett Committed by Takashi Iwai

ALSA: Add support of AudioScience ASI boards

Added the support of AudioScience ASI boards.
The driver has been tested for years on alsa-driver external tree,
now finally got merged to the kernel.
Signed-off-by: default avatarEliot Blennerhassett <eblennerhassett@audioscience.com>
Signed-off-by: Takashi Iwai's avatarTakashi Iwai <tiwai@suse.de>
parent cf0dbba5
......@@ -227,6 +227,16 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
The power-management is supported.
Module snd-asihpi
-----------------
Module for AudioScience ASI soundcards
enable_hpi_hwdep - enable HPI hwdep for AudioScience soundcard
This module supports multiple cards.
The driver requires the firmware loader support on kernel.
Module snd-atiixp
-----------------
......
......@@ -58,6 +58,18 @@ config SND_ALI5451
To compile this driver as a module, choose M here: the module
will be called snd-ali5451.
config SND_ASIHPI
tristate "AudioScience ASIxxxx"
depends on X86
select FW_LOADER
select SND_PCM
select SND_HWDEP
help
Say Y here to include support for AudioScience ASI sound cards.
To compile this driver as a module, choose M here: the module
will be called snd-asihpi.
config SND_ATIIXP
tristate "ATI IXP AC97 Controller"
select SND_AC97_CODEC
......
......@@ -57,6 +57,7 @@ obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
obj-$(CONFIG_SND) += \
ac97/ \
ali5451/ \
asihpi/ \
au88x0/ \
aw2/ \
ctxfi/ \
......
snd-asihpi-objs := asihpi.o hpioctl.o hpimsginit.o\
hpicmn.o hpifunc.o hpidebug.o hpidspcd.o\
hpios.o hpi6000.o hpi6205.o hpimsgx.o
obj-$(CONFIG_SND_ASIHPI) += snd-asihpi.o
/*
* Asihpi soundcard
* Copyright (c) by AudioScience Inc <alsa@audioscience.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* The following is not a condition of use, merely a request:
* If you modify this program, particularly if you fix errors, AudioScience Inc
* would appreciate it if you grant us the right to use those modifications
* for any purpose including commercial applications.
*/
/* >0: print Hw params, timer vars. >1: print stream write/copy sizes */
#define REALLY_VERBOSE_LOGGING 0
#if REALLY_VERBOSE_LOGGING
#define VPRINTK1 snd_printd
#else
#define VPRINTK1(...)
#endif
#if REALLY_VERBOSE_LOGGING > 1
#define VPRINTK2 snd_printd
#else
#define VPRINTK2(...)
#endif
#ifndef ASI_STYLE_NAMES
/* not sure how ALSA style name should look */
#define ASI_STYLE_NAMES 1
#endif
#include "hpi_internal.h"
#include "hpimsginit.h"
#include "hpioctl.h"
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/info.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/hwdep.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AudioScience inc. <support@audioscience.com>");
MODULE_DESCRIPTION("AudioScience ALSA ASI5000 ASI6000 ASI87xx ASI89xx");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static int enable_hpi_hwdep = 1;
module_param_array(index, int, NULL, S_IRUGO);
MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard.");
module_param_array(id, charp, NULL, S_IRUGO);
MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard.");
module_param_array(enable, bool, NULL, S_IRUGO);
MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard.");
module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(enable_hpi_hwdep,
"ALSA enable HPI hwdep for AudioScience soundcard ");
/* identify driver */
#ifdef KERNEL_ALSA_BUILD
static char *build_info = "built using headers from kernel source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "built using headers from kernel source");
#else
static char *build_info = "built within ALSA source";
module_param(build_info, charp, S_IRUGO);
MODULE_PARM_DESC(build_info, "built within ALSA source");
#endif
/* set to 1 to dump every control from adapter to log */
static const int mixer_dump;
#define DEFAULT_SAMPLERATE 44100
static int adapter_fs = DEFAULT_SAMPLERATE;
static struct hpi_hsubsys *ss; /* handle to HPI audio subsystem */
/* defaults */
#define PERIODS_MIN 2
#define PERIOD_BYTES_MIN 2304
#define BUFFER_BYTES_MAX (512 * 1024)
/*#define TIMER_MILLISECONDS 20
#define FORCE_TIMER_JIFFIES ((TIMER_MILLISECONDS * HZ + 999)/1000)
*/
#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7)
struct clk_source {
int source;
int index;
char *name;
};
struct clk_cache {
int count;
int has_local;
struct clk_source s[MAX_CLOCKSOURCES];
};
/* Per card data */
struct snd_card_asihpi {
struct snd_card *card;
struct pci_dev *pci;
u16 adapter_index;
u32 serial_number;
u16 type;
u16 version;
u16 num_outstreams;
u16 num_instreams;
u32 h_mixer;
struct clk_cache cc;
u16 support_mmap;
u16 support_grouping;
u16 support_mrx;
u16 update_interval_frames;
u16 in_max_chans;
u16 out_max_chans;
};
/* Per stream data */
struct snd_card_asihpi_pcm {
struct timer_list timer;
unsigned int respawn_timer;
unsigned int hpi_buffer_attached;
unsigned int pcm_size;
unsigned int pcm_count;
unsigned int bytes_per_sec;
unsigned int pcm_irq_pos; /* IRQ position */
unsigned int pcm_buf_pos; /* position in buffer */
struct snd_pcm_substream *substream;
u32 h_stream;
struct hpi_format format;
};
/* universal stream verbs work with out or in stream handles */
/* Functions to allow driver to give a buffer to HPI for busmastering */
static u16 hpi_stream_host_buffer_attach(
struct hpi_hsubsys *hS,
u32 h_stream, /* handle to outstream. */
u32 size_in_bytes, /* size in bytes of bus mastering buffer */
u32 pci_address
)
{
struct hpi_message hm;
struct hpi_response hr;
unsigned int obj = hpi_handle_object(h_stream);
if (!h_stream)
return HPI_ERROR_INVALID_OBJ;
hpi_init_message_response(&hm, &hr, obj,
obj == HPI_OBJ_OSTREAM ?
HPI_OSTREAM_HOSTBUFFER_ALLOC :
HPI_ISTREAM_HOSTBUFFER_ALLOC);
hpi_handle_to_indexes(h_stream, &hm.adapter_index,
&hm.obj_index);
hm.u.d.u.buffer.buffer_size = size_in_bytes;
hm.u.d.u.buffer.pci_address = pci_address;
hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER;
hpi_send_recv(&hm, &hr);
return hr.error;
}
static u16 hpi_stream_host_buffer_detach(
struct hpi_hsubsys *hS,
u32 h_stream
)
{
struct hpi_message hm;
struct hpi_response hr;
unsigned int obj = hpi_handle_object(h_stream);
if (!h_stream)
return HPI_ERROR_INVALID_OBJ;
hpi_init_message_response(&hm, &hr, obj,
obj == HPI_OBJ_OSTREAM ?
HPI_OSTREAM_HOSTBUFFER_FREE :
HPI_ISTREAM_HOSTBUFFER_FREE);
hpi_handle_to_indexes(h_stream, &hm.adapter_index,
&hm.obj_index);
hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER;
hpi_send_recv(&hm, &hr);
return hr.error;
}
static inline u16 hpi_stream_start(struct hpi_hsubsys *hS, u32 h_stream)
{
if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM)
return hpi_outstream_start(hS, h_stream);
else
return hpi_instream_start(hS, h_stream);
}
static inline u16 hpi_stream_stop(struct hpi_hsubsys *hS, u32 h_stream)
{
if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM)
return hpi_outstream_stop(hS, h_stream);
else
return hpi_instream_stop(hS, h_stream);
}
static inline u16 hpi_stream_get_info_ex(
struct hpi_hsubsys *hS,
u32 h_stream,
u16 *pw_state,
u32 *pbuffer_size,
u32 *pdata_in_buffer,
u32 *psample_count,
u32 *pauxiliary_data
)
{
if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM)
return hpi_outstream_get_info_ex(hS, h_stream, pw_state,
pbuffer_size, pdata_in_buffer,
psample_count, pauxiliary_data);
else
return hpi_instream_get_info_ex(hS, h_stream, pw_state,
pbuffer_size, pdata_in_buffer,
psample_count, pauxiliary_data);
}
static inline u16 hpi_stream_group_add(struct hpi_hsubsys *hS,
u32 h_master,
u32 h_stream)
{
if (hpi_handle_object(h_master) == HPI_OBJ_OSTREAM)
return hpi_outstream_group_add(hS, h_master, h_stream);
else
return hpi_instream_group_add(hS, h_master, h_stream);
}
static inline u16 hpi_stream_group_reset(struct hpi_hsubsys *hS,
u32 h_stream)
{
if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM)
return hpi_outstream_group_reset(hS, h_stream);
else
return hpi_instream_group_reset(hS, h_stream);
}
static inline u16 hpi_stream_group_get_map(struct hpi_hsubsys *hS,
u32 h_stream, u32 *mo, u32 *mi)
{
if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM)
return hpi_outstream_group_get_map(hS, h_stream, mo, mi);
else
return hpi_instream_group_get_map(hS, h_stream, mo, mi);
}
static u16 handle_error(u16 err, int line, char *filename)
{
if (err)
printk(KERN_WARNING
"in file %s, line %d: HPI error %d\n",
filename, line, err);
return err;
}
#define hpi_handle_error(x) handle_error(x, __LINE__, __FILE__)
/***************************** GENERAL PCM ****************/
#if REALLY_VERBOSE_LOGGING
static void print_hwparams(struct snd_pcm_hw_params *p)
{
snd_printd("HWPARAMS \n");
snd_printd("samplerate %d \n", params_rate(p));
snd_printd("channels %d \n", params_channels(p));
snd_printd("format %d \n", params_format(p));
snd_printd("subformat %d \n", params_subformat(p));
snd_printd("buffer bytes %d \n", params_buffer_bytes(p));
snd_printd("period bytes %d \n", params_period_bytes(p));
snd_printd("access %d \n", params_access(p));
snd_printd("period_size %d \n", params_period_size(p));
snd_printd("periods %d \n", params_periods(p));
snd_printd("buffer_size %d \n", params_buffer_size(p));
}
#else
#define print_hwparams(x)
#endif
static snd_pcm_format_t hpi_to_alsa_formats[] = {
-1, /* INVALID */
SNDRV_PCM_FORMAT_U8, /* HPI_FORMAT_PCM8_UNSIGNED 1 */
SNDRV_PCM_FORMAT_S16, /* HPI_FORMAT_PCM16_SIGNED 2 */
-1, /* HPI_FORMAT_MPEG_L1 3 */
SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L2 4 */
SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L3 5 */
-1, /* HPI_FORMAT_DOLBY_AC2 6 */
-1, /* HPI_FORMAT_DOLBY_AC3 7 */
SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN 8 */
-1, /* HPI_FORMAT_AA_TAGIT1_HITS 9 */
-1, /* HPI_FORMAT_AA_TAGIT1_INSERTS 10 */
SNDRV_PCM_FORMAT_S32, /* HPI_FORMAT_PCM32_SIGNED 11 */
-1, /* HPI_FORMAT_RAW_BITSTREAM 12 */
-1, /* HPI_FORMAT_AA_TAGIT1_HITS_EX1 13 */
SNDRV_PCM_FORMAT_FLOAT, /* HPI_FORMAT_PCM32_FLOAT 14 */
#if 1
/* ALSA can't handle 3 byte sample size together with power-of-2
* constraint on buffer_bytes, so disable this format
*/
-1
#else
/* SNDRV_PCM_FORMAT_S24_3LE */ /* { HPI_FORMAT_PCM24_SIGNED 15 */
#endif
};
static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsa_format,
u16 *hpi_format)
{
u16 format;
for (format = HPI_FORMAT_PCM8_UNSIGNED;
format <= HPI_FORMAT_PCM24_SIGNED; format++) {
if (hpi_to_alsa_formats[format] == alsa_format) {
*hpi_format = format;
return 0;
}
}
snd_printd(KERN_WARNING "failed match for alsa format %d\n",
alsa_format);
*hpi_format = 0;
return -EINVAL;
}
static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi,
struct snd_pcm_hardware *pcmhw)
{
u16 err;
u32 h_control;
u32 sample_rate;
int idx;
unsigned int rate_min = 200000;
unsigned int rate_max = 0;
unsigned int rates = 0;
if (asihpi->support_mrx) {
rates |= SNDRV_PCM_RATE_CONTINUOUS;
rates |= SNDRV_PCM_RATE_8000_96000;
rate_min = 8000;
rate_max = 100000;
} else {
/* on cards without SRC,
valid rates are determined by sampleclock */
err = hpi_mixer_get_control(ss, asihpi->h_mixer,
HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
HPI_CONTROL_SAMPLECLOCK, &h_control);
if (err) {
snd_printk(KERN_ERR
"no local sampleclock, err %d\n", err);
}
for (idx = 0; idx < 100; idx++) {
if (hpi_sample_clock_query_local_rate(ss,
h_control, idx, &sample_rate)) {
if (!idx)
snd_printk(KERN_ERR
"local rate query failed\n");
break;
}
rate_min = min(rate_min, sample_rate);
rate_max = max(rate_max, sample_rate);
switch (sample_rate) {
case 5512:
rates |= SNDRV_PCM_RATE_5512;
break;
case 8000:
rates |= SNDRV_PCM_RATE_8000;
break;
case 11025:
rates |= SNDRV_PCM_RATE_11025;
break;
case 16000:
rates |= SNDRV_PCM_RATE_16000;
break;
case 22050:
rates |= SNDRV_PCM_RATE_22050;
break;
case 32000:
rates |= SNDRV_PCM_RATE_32000;
break;
case 44100:
rates |= SNDRV_PCM_RATE_44100;
break;
case 48000:
rates |= SNDRV_PCM_RATE_48000;
break;
case 64000:
rates |= SNDRV_PCM_RATE_64000;
break;
case 88200:
rates |= SNDRV_PCM_RATE_88200;
break;
case 96000:
rates |= SNDRV_PCM_RATE_96000;
break;
case 176400:
rates |= SNDRV_PCM_RATE_176400;
break;
case 192000:
rates |= SNDRV_PCM_RATE_192000;
break;
default: /* some other rate */
rates |= SNDRV_PCM_RATE_KNOT;
}
}
}
/* printk(KERN_INFO "Supported rates %X %d %d\n",
rates, rate_min, rate_max); */
pcmhw->rates = rates;
pcmhw->rate_min = rate_min;
pcmhw->rate_max = rate_max;
}
static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
int err;
u16 format;
unsigned int bytes_per_sec;
print_hwparams(params);
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (err < 0)
return err;
err = snd_card_asihpi_format_alsa2hpi(params_format(params), &format);
if (err)
return err;
VPRINTK1(KERN_INFO "format %d, %d chans, %d_hz\n",
format, params_channels(params),
params_rate(params));
hpi_handle_error(hpi_format_create(&dpcm->format,
params_channels(params),
format, params_rate(params), 0, 0));
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (hpi_instream_reset(ss, dpcm->h_stream) != 0)
return -EINVAL;
if (hpi_instream_set_format(ss,
dpcm->h_stream, &dpcm->format) != 0)
return -EINVAL;
}
dpcm->hpi_buffer_attached = 0;
if (card->support_mmap) {
err = hpi_stream_host_buffer_attach(ss, dpcm->h_stream,
params_buffer_bytes(params), runtime->dma_addr);
if (err == 0) {
snd_printd(KERN_INFO
"stream_host_buffer_attach succeeded %u %lu\n",
params_buffer_bytes(params),
(unsigned long)runtime->dma_addr);
} else {
snd_printd(KERN_INFO
"stream_host_buffer_attach error %d\n",
err);
return -ENOMEM;
}
err = hpi_stream_get_info_ex(ss, dpcm->h_stream, NULL,
&dpcm->hpi_buffer_attached,
NULL, NULL, NULL);
snd_printd(KERN_INFO "stream_host_buffer_attach status 0x%x\n",
dpcm->hpi_buffer_attached);
}
bytes_per_sec = params_rate(params) * params_channels(params);
bytes_per_sec *= snd_pcm_format_width(params_format(params));
bytes_per_sec /= 8;
if (bytes_per_sec <= 0)
return -EINVAL;
dpcm->bytes_per_sec = bytes_per_sec;
dpcm->pcm_size = params_buffer_bytes(params);
dpcm->pcm_count = params_period_bytes(params);
snd_printd(KERN_INFO "pcm_size=%d, pcm_count=%d, bps=%d\n",
dpcm->pcm_size, dpcm->pcm_count, bytes_per_sec);
dpcm->pcm_irq_pos = 0;
dpcm->pcm_buf_pos = 0;
return 0;
}
static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream *
substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
int expiry;
expiry = (dpcm->pcm_count * HZ / dpcm->bytes_per_sec);
/* wait longer the first time, for samples to propagate */
expiry = max(expiry, 20);
dpcm->timer.expires = jiffies + expiry;
dpcm->respawn_timer = 1;
add_timer(&dpcm->timer);
}
static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
dpcm->respawn_timer = 0;
del_timer(&dpcm->timer);
}
static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;
struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
struct snd_pcm_substream *s;
u16 e;
snd_printd("trigger %dstream %d\n",
substream->stream, substream->number);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_pcm_group_for_each_entry(s, substream) {
struct snd_card_asihpi_pcm *ds;
ds = s->runtime->private_data;
if (snd_pcm_substream_chip(s) != card)
continue;
if ((s->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
(card->support_mmap)) {
/* How do I know how much valid data is present
* in buffer? Just guessing 2 periods, but if
* buffer is bigger it may contain even more
* data??
*/
unsigned int preload = ds->pcm_count * 2;
VPRINTK2("preload %d\n", preload);
hpi_handle_error(hpi_outstream_write_buf(
ss, ds->h_stream,
&s->runtime->dma_area[0],
preload,
&ds->format));
}