aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorEliot Blennerhassett <eblennerhassett@audioscience.com>2010-04-21 18:17:39 +0200
committerTakashi Iwai <tiwai@suse.de>2010-04-22 07:21:53 +0200
commit719f82d3987aad4cc9f46d19c35f362672545cad (patch)
tree3f9edf749da5e5d11bfa82336db99a01dd489979 /sound
parentcf0dbba515415bb19b11f9323d5f7bebd7f24fd6 (diff)
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: Eliot Blennerhassett <eblennerhassett@audioscience.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound')
-rw-r--r--sound/pci/Kconfig12
-rw-r--r--sound/pci/Makefile1
-rw-r--r--sound/pci/asihpi/Makefile5
-rw-r--r--sound/pci/asihpi/asihpi.c3002
-rw-r--r--sound/pci/asihpi/hpi.h2001
-rw-r--r--sound/pci/asihpi/hpi6000.c1841
-rw-r--r--sound/pci/asihpi/hpi6000.h70
-rw-r--r--sound/pci/asihpi/hpi6205.c2332
-rw-r--r--sound/pci/asihpi/hpi6205.h93
-rw-r--r--sound/pci/asihpi/hpi_internal.h1641
-rw-r--r--sound/pci/asihpi/hpicmn.c643
-rw-r--r--sound/pci/asihpi/hpicmn.h64
-rw-r--r--sound/pci/asihpi/hpidebug.c225
-rw-r--r--sound/pci/asihpi/hpidebug.h385
-rw-r--r--sound/pci/asihpi/hpidspcd.c172
-rw-r--r--sound/pci/asihpi/hpidspcd.h104
-rw-r--r--sound/pci/asihpi/hpifunc.c3864
-rw-r--r--sound/pci/asihpi/hpimsginit.c130
-rw-r--r--sound/pci/asihpi/hpimsginit.h40
-rw-r--r--sound/pci/asihpi/hpimsgx.c907
-rw-r--r--sound/pci/asihpi/hpimsgx.h36
-rw-r--r--sound/pci/asihpi/hpioctl.c484
-rw-r--r--sound/pci/asihpi/hpioctl.h38
-rw-r--r--sound/pci/asihpi/hpios.c114
-rw-r--r--sound/pci/asihpi/hpios.h178
-rw-r--r--sound/pci/asihpi/hpipcida.h37
26 files changed, 18419 insertions, 0 deletions
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 1298c68d6bf..517ae65ffc8 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -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
diff --git a/sound/pci/Makefile b/sound/pci/Makefile
index ecfc609d2b9..9cf4348ec13 100644
--- a/sound/pci/Makefile
+++ b/sound/pci/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
obj-$(CONFIG_SND) += \
ac97/ \
ali5451/ \
+ asihpi/ \
au88x0/ \
aw2/ \
ctxfi/ \
diff --git a/sound/pci/asihpi/Makefile b/sound/pci/asihpi/Makefile
new file mode 100644
index 00000000000..391830a4556
--- /dev/null
+++ b/sound/pci/asihpi/Makefile
@@ -0,0 +1,5 @@
+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
diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c
new file mode 100644
index 00000000000..f74c7372b3d
--- /dev/null
+++ b/sound/pci/asihpi/asihpi.c
@@ -0,0 +1,3002 @@
+/*
+ * 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));
+ }
+
+ if (card->support_grouping) {
+ VPRINTK1("\t_group %dstream %d\n", s->stream,
+ s->number);
+ e = hpi_stream_group_add(ss,
+ dpcm->h_stream,
+ ds->h_stream);
+ if (!e) {
+ snd_pcm_trigger_done(s, substream);
+ } else {
+ hpi_handle_error(e);
+ break;
+ }
+ } else
+ break;
+ }
+ snd_printd("start\n");
+ /* start the master stream */
+ snd_card_asihpi_pcm_timer_start(substream);
+ hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream));
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_card_asihpi_pcm_timer_stop(substream);
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (snd_pcm_substream_chip(s) != card)
+ continue;
+
+ /*? workaround linked streams don't
+ transition to SETUP 20070706*/
+ s->runtime->status->state = SNDRV_PCM_STATE_SETUP;
+
+ if (card->support_grouping) {
+ VPRINTK1("\t_group %dstream %d\n", s->stream,
+ s->number);
+ snd_pcm_trigger_done(s, substream);
+ } else
+ break;
+ }
+ snd_printd("stop\n");
+
+ /* _prepare and _hwparams reset the stream */
+ hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream));
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ hpi_handle_error(
+ hpi_outstream_reset(ss, dpcm->h_stream));
+
+ if (card->support_grouping)
+ hpi_handle_error(hpi_stream_group_reset(ss,
+ dpcm->h_stream));
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_printd("pause release\n");
+ hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream));
+ snd_card_asihpi_pcm_timer_start(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ snd_printd("pause\n");
+ snd_card_asihpi_pcm_timer_stop(substream);
+ hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream));
+ break;
+ default:
+ snd_printd("\tINVALID\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+snd_card_asihpi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+ if (dpcm->hpi_buffer_attached)
+ hpi_stream_host_buffer_detach(ss, dpcm->h_stream);
+
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime)
+{
+ struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+ kfree(dpcm);
+}
+
+/*algorithm outline
+ Without linking degenerates to getting single stream pos etc
+ Without mmap 2nd loop degenerates to snd_pcm_period_elapsed
+*/
+/*
+buf_pos=get_buf_pos(s);
+for_each_linked_stream(s) {
+ buf_pos=get_buf_pos(s);
+ min_buf_pos = modulo_min(min_buf_pos, buf_pos, pcm_size)
+ new_data = min(new_data, calc_new_data(buf_pos,irq_pos)
+}
+timer.expires = jiffies + predict_next_period_ready(min_buf_pos);
+for_each_linked_stream(s) {
+ s->buf_pos = min_buf_pos;
+ if (new_data > pcm_count) {
+ if (mmap) {
+ irq_pos = (irq_pos + pcm_count) % pcm_size;
+ if (playback) {
+ write(pcm_count);
+ } else {
+ read(pcm_count);
+ }
+ }
+ snd_pcm_period_elapsed(s);
+ }
+}
+*/
+
+/** Minimum of 2 modulo values. Works correctly when the difference between
+* the values is less than half the modulus
+*/
+static inline unsigned int modulo_min(unsigned int a, unsigned int b,
+ unsigned long int modulus)
+{
+ unsigned int result;
+ if (((a-b) % modulus) < (modulus/2))
+ result = b;
+ else
+ result = a;
+
+ return result;
+}
+
+/** Timer function, equivalent to interrupt service routine for cards
+*/
+static void snd_card_asihpi_timer_function(unsigned long data)
+{
+ struct snd_card_asihpi_pcm *dpcm = (struct snd_card_asihpi_pcm *)data;
+ struct snd_card_asihpi *card = snd_pcm_substream_chip(dpcm->substream);
+ struct snd_pcm_runtime *runtime;
+ struct snd_pcm_substream *s;
+ unsigned int newdata = 0;
+ unsigned int buf_pos, min_buf_pos = 0;
+ unsigned int remdata, xfercount, next_jiffies;
+ int first = 1;
+ u16 state;
+ u32 buffer_size, data_avail, samples_played, aux;
+
+ /* find minimum newdata and buffer pos in group */
+ snd_pcm_group_for_each_entry(s, dpcm->substream) {
+ struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
+ runtime = s->runtime;
+
+ if (snd_pcm_substream_chip(s) != card)
+ continue;
+
+ hpi_handle_error(hpi_stream_get_info_ex(ss,
+ ds->h_stream, &state,
+ &buffer_size, &data_avail,
+ &samples_played, &aux));
+
+ /* number of bytes in on-card buffer */
+ runtime->delay = aux;
+
+ if (state == HPI_STATE_DRAINED) {
+ snd_printd(KERN_WARNING "outstream %d drained\n",
+ s->number);
+ snd_pcm_stop(s, SNDRV_PCM_STATE_XRUN);
+ return;
+ }
+
+ if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ buf_pos = frames_to_bytes(runtime, samples_played);
+ } else {
+ buf_pos = data_avail + ds->pcm_irq_pos;
+ }
+
+ if (first) {
+ /* can't statically init min when wrap is involved */
+ min_buf_pos = buf_pos;
+ newdata = (buf_pos - ds->pcm_irq_pos) % ds->pcm_size;
+ first = 0;
+ } else {
+ min_buf_pos =
+ modulo_min(min_buf_pos, buf_pos, UINT_MAX+1L);
+ newdata = min(
+ (buf_pos - ds->pcm_irq_pos) % ds->pcm_size,
+ newdata);
+ }
+
+ VPRINTK1("PB timer hw_ptr x%04lX, appl_ptr x%04lX\n",
+ (unsigned long)frames_to_bytes(runtime,
+ runtime->status->hw_ptr),
+ (unsigned long)frames_to_bytes(runtime,
+ runtime->control->appl_ptr));
+ VPRINTK1("%d S=%d, irq=%04X, pos=x%04X, left=x%04X,"
+ " aux=x%04X space=x%04X\n", s->number,
+ state, ds->pcm_irq_pos, buf_pos, (int)data_avail,
+ (int)aux, buffer_size-data_avail);
+ }
+
+ remdata = newdata % dpcm->pcm_count;
+ xfercount = newdata - remdata; /* a multiple of pcm_count */
+ next_jiffies = ((dpcm->pcm_count-remdata) * HZ / dpcm->bytes_per_sec)+1;
+ next_jiffies = max(next_jiffies, 2U * HZ / 1000U);
+ dpcm->timer.expires = jiffies + next_jiffies;
+ VPRINTK1("jif %d buf pos x%04X newdata x%04X xc x%04X\n",
+ next_jiffies, min_buf_pos, newdata, xfercount);
+
+ snd_pcm_group_for_each_entry(s, dpcm->substream) {
+ struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
+ ds->pcm_buf_pos = min_buf_pos;
+
+ if (xfercount) {
+ if (card->support_mmap) {
+ ds->pcm_irq_pos = ds->pcm_irq_pos + xfercount;
+ if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ VPRINTK2("write OS%d x%04x\n",
+ s->number,
+ ds->pcm_count);
+ hpi_handle_error(
+ hpi_outstream_write_buf(
+ ss, ds->h_stream,
+ &s->runtime->
+ dma_area[0],
+ xfercount,
+ &ds->format));
+ } else {
+ VPRINTK2("read IS%d x%04x\n",
+ s->number,
+ dpcm->pcm_count);
+ hpi_handle_error(
+ hpi_instream_read_buf(
+ ss, ds->h_stream,
+ NULL, xfercount));
+ }
+ } /* else R/W will be handled by read/write callbacks */
+ snd_pcm_period_elapsed(s);
+ }
+ }
+
+ if (dpcm->respawn_timer)
+ add_timer(&dpcm->timer);
+}
+
+/***************************** PLAYBACK OPS ****************/
+static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream,
+ unsigned int cmd, void *arg)
+{
+ /* snd_printd(KERN_INFO "Playback ioctl %d\n", cmd); */
+ return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream *
+ substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+ snd_printd(KERN_INFO "playback prepare %d\n", substream->number);
+
+ hpi_handle_error(hpi_outstream_reset(ss, dpcm->h_stream));
+ dpcm->pcm_irq_pos = 0;
+ dpcm->pcm_buf_pos = 0;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+ snd_pcm_uframes_t ptr;
+
+ u32 samples_played;
+ u16 err;
+
+ if (!snd_pcm_stream_linked(substream)) {
+ /* NOTE, can use samples played for playback position here and
+ * in timer fn because it LAGS the actual read pointer, and is a
+ * better representation of actual playout position
+ */
+ err = hpi_outstream_get_info_ex(ss, dpcm->h_stream, NULL,
+ NULL, NULL,
+ &samples_played, NULL);
+ hpi_handle_error(err);
+
+ dpcm->pcm_buf_pos = frames_to_bytes(runtime, samples_played);
+ }
+ /* else must return most conservative value found in timer func
+ * by looping over all streams
+ */
+
+ ptr = bytes_to_frames(runtime, dpcm->pcm_buf_pos % dpcm->pcm_size);