Commit a9f00d8d authored by Joachim Foerster's avatar Joachim Foerster Committed by Jaroslav Kysela
Browse files

[ALSA] Xilinx ML403 AC97 Controller Reference device driver

Add ALSA support for the opb_ac97_controller_ref_v1_00_a ip core found
in Xilinx' ML403 reference design.
Known issue: Currently this driver hits a WARN_ON_ONCE(1) statement in
kernel/irq/resend.c (line 70). According to Linus
(http://lkml.org/lkml/2007/8/5/5

) this may be ignored, right? I haven't
had a look into this 'problem' yet.

Signed-off-by: default avatarJoachim Foerster <JOFT@gmx.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@perex.cz>
parent f1f208d0
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -120,4 +120,16 @@ config SND_PORTMAN2X4
	  To compile this driver as a module, choose M here: the module
	  will be called snd-portman2x4.

config SND_ML403_AC97CR
	tristate "Xilinx ML403 AC97 Controller Reference"
	depends on SND && XILINX_VIRTEX
	select SND_AC97_CODEC
	help
	  Say Y here to include support for the
	  opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403
	  reference design.

	  To compile this driver as a module, choose M here: the module
	  will be called snd-ml403_ac97cr.

endmenu
+2 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ snd-mts64-objs := mts64.o
snd-portman2x4-objs := portman2x4.o
snd-serial-u16550-objs := serial-u16550.o
snd-virmidi-objs := virmidi.o
snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o

# Toplevel Module Dependency
obj-$(CONFIG_SND_DUMMY) += snd-dummy.o
@@ -17,5 +18,6 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
obj-$(CONFIG_SND_MTS64) += snd-mts64.o
obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o

obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/
+1353 −0

File added.

Preview size limit exceeded, changes collapsed.

+591 −0
Original line number Diff line number Diff line
/*
 * Helper functions for indirect PCM data transfer to a simple FIFO in
 * hardware (small, no possibility to read "hardware io position",
 * updating position done by interrupt, ...)
 *
 *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.de>
 *
 *  Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
 *
 *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
 *                   Jaroslav Kysela <perex@suse.cz>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* #dependency of sound/core.h# */
#include <sound/driver.h>
/* snd_printk/d() */
#include <sound/core.h>
/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
 * snd_pcm_period_elapsed() */
#include <sound/pcm.h>

#include "pcm-indirect2.h"

#ifdef SND_PCM_INDIRECT2_STAT
/* jiffies */
#include <linux/jiffies.h>

void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
			    struct snd_pcm_indirect2 *rec)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	int i;
	int j;
	int k;
	int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ;

	snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
		   "irq_occured: %d\n",
		   rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured);
	snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n",
		   rec->min_multiple);
	snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, "
		   "firstzerotime: %lu\n",
		 rec->firstbytetime, rec->lastbytetime, rec->firstzerotime);
	snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) "
		   "length: %d s\n",
		 rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate);
	snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => "
		   "rate: %d Bytes/s = %d Frames/s|Hz\n",
		   seconds, rec->bytes2hw / seconds,
		   rec->bytes2hw / 2 / 2 / seconds);
	snd_printk(KERN_DEBUG
		   "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
		   rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) /
		   runtime->rate,
		   rec->zeros2hw / (rec->hw_buffer_size / 2),
		   (rec->hw_buffer_size / 2));
	snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n",
		   rec->pointer_calls, rec->lastdifftime);
	snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io,
		   rec->sw_data);
	snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n");
	k = 0;
	for (j = 0; j < 8; j++) {
		for (i = j * 8; i < (j + 1) * 8; i++)
			if (rec->byte_sizes[i] != 0) {
				snd_printk(KERN_DEBUG "%u: %u",
					   i, rec->byte_sizes[i]);
				k++;
			}
		if (((k % 8) == 0) && (k != 0)) {
			snd_printk(KERN_DEBUG "\n");
			k = 0;
		}
	}
	snd_printk(KERN_DEBUG "\n");
	snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n");
	for (j = 0; j < 8; j++) {
		k = 0;
		for (i = j * 8; i < (j + 1) * 8; i++)
			if (rec->zero_sizes[i] != 0)
				snd_printk(KERN_DEBUG "%u: %u",
					   i, rec->zero_sizes[i]);
			else
				k++;
		if (!k)
			snd_printk(KERN_DEBUG "\n");
	}
	snd_printk(KERN_DEBUG "\n");
	snd_printk(KERN_DEBUG "STAT: min_adds[]:\n");
	for (j = 0; j < 8; j++) {
		if (rec->min_adds[j] != 0)
			snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]);
	}
	snd_printk(KERN_DEBUG "\n");
	snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n");
	for (j = 0; j < 8; j++) {
		if (rec->mul_adds[j] != 0)
			snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]);
	}
	snd_printk(KERN_DEBUG "\n");
	snd_printk(KERN_DEBUG
		   "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
		   rec->zero_times_saved, rec->zero_times_notsaved);
	/* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
	i = 0;
	for (j = 0; j < 3750; j++) {
		if (rec->zero_times[j] != 0) {
			snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
			i++;
		}
		if (((i % 8) == 0) && (i != 0))
			snd_printk(KERN_DEBUG "\n");
	}
	snd_printk(KERN_DEBUG "\n"); */
	return;
}
#endif

/*
 * _internal_ helper function for playback/capture transfer function
 */
static void
snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream,
				       struct snd_pcm_indirect2 *rec,
				       int isplay, int iscopy,
				       unsigned int bytes)
{
	if (rec->min_periods >= 0) {
		if (iscopy) {
			rec->sw_io += bytes;
			if (rec->sw_io >= rec->sw_buffer_size)
				rec->sw_io -= rec->sw_buffer_size;
		} else if (isplay) {
			/* If application does not write data in multiples of
			 * a period, move sw_data to the next correctly aligned
			 * position, so that sw_io can converge to it (in the
			 * next step).
			 */
			if (!rec->check_alignment) {
				if (rec->bytes2hw %
				    snd_pcm_lib_period_bytes(substream)) {
					unsigned bytes2hw_aligned =
					    (1 +
					     (rec->bytes2hw /
					      snd_pcm_lib_period_bytes
					      (substream))) *
					    snd_pcm_lib_period_bytes
					    (substream);
					rec->sw_data =
					    bytes2hw_aligned %
					    rec->sw_buffer_size;
#ifdef SND_PCM_INDIRECT2_STAT
					snd_printk(KERN_DEBUG
						   "STAT: @re-align: aligned "
						   "bytes2hw to next period "
						   "size boundary: %d "
						   "(instead of %d)\n",
						   bytes2hw_aligned,
						   rec->bytes2hw);
					snd_printk(KERN_DEBUG
						   "STAT: @re-align: sw_data "
						   "moves to: %d\n",
						   rec->sw_data);
#endif
				}
				rec->check_alignment = 1;
			}
			/* We are at the end and are copying zeros into the
			 * fifo.
			 * Now, we have to make sure that sw_io is increased
			 * until the position of sw_data: Filling the fifo with
			 * the first zeros means, the last bytes were played.
			 */
			if (rec->sw_io != rec->sw_data) {
				unsigned int diff;
				if (rec->sw_data > rec->sw_io)
					diff = rec->sw_data - rec->sw_io;
				else
					diff = (rec->sw_buffer_size -
						rec->sw_io) +
						rec->sw_data;
				if (bytes >= diff)
					rec->sw_io = rec->sw_data;
				else {
					rec->sw_io += bytes;
					if (rec->sw_io >= rec->sw_buffer_size)
						rec->sw_io -=
						    rec->sw_buffer_size;
				}
			}
		}
		rec->min_period_count += bytes;
		if (rec->min_period_count >= (rec->hw_buffer_size / 2)) {
			rec->min_periods += (rec->min_period_count /
					     (rec->hw_buffer_size / 2));
#ifdef SND_PCM_INDIRECT2_STAT
			if ((rec->min_period_count /
			     (rec->hw_buffer_size / 2)) > 7)
				snd_printk(KERN_DEBUG
					   "STAT: more than 7 (%d) min_adds "
					   "at once - too big to save!\n",
					   (rec->min_period_count /
					    (rec->hw_buffer_size / 2)));
			else
				rec->min_adds[(rec->min_period_count /
					       (rec->hw_buffer_size / 2))]++;
#endif
			rec->min_period_count = (rec->min_period_count %
						 (rec->hw_buffer_size / 2));
		}
	} else if (isplay && iscopy)
		rec->min_periods = 0;
}

/*
 * helper function for playback/capture pointer callback
 */
snd_pcm_uframes_t
snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
			  struct snd_pcm_indirect2 *rec)
{
#ifdef SND_PCM_INDIRECT2_STAT
	rec->pointer_calls++;
#endif
	return bytes_to_frames(substream->runtime, rec->sw_io);
}

/*
 * _internal_ helper function for playback interrupt callback
 */
static void
snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream,
				    struct snd_pcm_indirect2 *rec,
				    snd_pcm_indirect2_copy_t copy,
				    snd_pcm_indirect2_zero_t zero)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;

	/* runtime->control->appl_ptr: position where ALSA will write next time
	 * rec->appl_ptr: position where ALSA was last time
	 * diff: obviously ALSA wrote that much bytes into the intermediate
	 * buffer since we checked last time
	 */
	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;

	if (diff) {
#ifdef SND_PCM_INDIRECT2_STAT
		rec->lastdifftime = jiffies;
#endif
		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
			diff += runtime->boundary;
		/* number of bytes "added" by ALSA increases the number of
		 * bytes which are ready to "be transfered to HW"/"played"
		 * Then, set rec->appl_ptr to not count bytes twice next time.
		 */
		rec->sw_ready += (int)frames_to_bytes(runtime, diff);
		rec->appl_ptr = appl_ptr;
	}
	if (rec->hw_ready && (rec->sw_ready <= 0)) {
		unsigned int bytes;

#ifdef SND_PCM_INDIRECT2_STAT
		if (rec->firstzerotime == 0) {
			rec->firstzerotime = jiffies;
			snd_printk(KERN_DEBUG
				   "STAT: @firstzerotime: mul_elapsed: %d, "
				   "min_period_count: %d\n",
				   rec->mul_elapsed, rec->min_period_count);
			snd_printk(KERN_DEBUG
				   "STAT: @firstzerotime: sw_io: %d, "
				   "sw_data: %d, appl_ptr: %u\n",
				   rec->sw_io, rec->sw_data,
				   (unsigned int)appl_ptr);
		}
		if ((jiffies - rec->firstzerotime) < 3750) {
			rec->zero_times[(jiffies - rec->firstzerotime)]++;
			rec->zero_times_saved++;
		} else
			rec->zero_times_notsaved++;
#endif
		bytes = zero(substream, rec);

#ifdef SND_PCM_INDIRECT2_STAT
		rec->zeros2hw += bytes;
		if (bytes < 64)
			rec->zero_sizes[bytes]++;
		else
			snd_printk(KERN_DEBUG
				   "STAT: %d zero Bytes copied to hardware at "
				   "once - too big to save!\n",
				   bytes);
#endif
		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0,
						       bytes);
		return;
	}
	while (rec->hw_ready && (rec->sw_ready > 0)) {
		/* sw_to_end: max. number of bytes that can be read/take from
		 * the current position (sw_data) in _one_ step
		 */
		unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;

		/* bytes: number of bytes we have available (for reading) */
		unsigned int bytes = rec->sw_ready;

		if (sw_to_end < bytes)
			bytes = sw_to_end;
		if (!bytes)
			break;

#ifdef SND_PCM_INDIRECT2_STAT
		if (rec->firstbytetime == 0)
			rec->firstbytetime = jiffies;
		rec->lastbytetime = jiffies;
#endif
		/* copy bytes from intermediate buffer position sw_data to the
		 * HW and return number of bytes actually written
		 * Furthermore, set hw_ready to 0, if the fifo isn't empty
		 * now => more could be transfered to fifo
		 */
		bytes = copy(substream, rec, bytes);
		rec->bytes2hw += bytes;

#ifdef SND_PCM_INDIRECT2_STAT
		if (bytes < 64)
			rec->byte_sizes[bytes]++;
		else
			snd_printk(KERN_DEBUG
				   "STAT: %d Bytes copied to hardware at once "
				   "- too big to save!\n",
				   bytes);
#endif
		/* increase sw_data by the number of actually written bytes
		 * (= number of taken bytes from intermediate buffer)
		 */
		rec->sw_data += bytes;
		if (rec->sw_data == rec->sw_buffer_size)
			rec->sw_data = 0;
		/* now sw_data is the position where ALSA is going to write
		 * in the intermediate buffer next time = position we are going
		 * to read from next time
		 */

		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1,
						       bytes);

		/* we read bytes from intermediate buffer, so we need to say
		 * that the number of bytes ready for transfer are decreased
		 * now
		 */
		rec->sw_ready -= bytes;
	}
	return;
}

/*
 * helper function for playback interrupt routine
 */
void
snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
				     struct snd_pcm_indirect2 *rec,
				     snd_pcm_indirect2_copy_t copy,
				     snd_pcm_indirect2_zero_t zero)
{
#ifdef SND_PCM_INDIRECT2_STAT
	rec->irq_occured++;
#endif
	/* hardware played some bytes, so there is room again (in fifo) */
	rec->hw_ready = 1;

	/* don't call ack() now, instead call transfer() function directly
	 * (normally called by ack() )
	 */
	snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero);

	if (rec->min_periods >= rec->min_multiple) {
#ifdef SND_PCM_INDIRECT2_STAT
		if ((rec->min_periods / rec->min_multiple) > 7)
			snd_printk(KERN_DEBUG
				   "STAT: more than 7 (%d) mul_adds - too big "
				   "to save!\n",
				   (rec->min_periods / rec->min_multiple));
		else
			rec->mul_adds[(rec->min_periods /
				       rec->min_multiple)]++;
		rec->mul_elapsed_real += (rec->min_periods /
					  rec->min_multiple);
		rec->mul_elapsed++;
#endif
		rec->min_periods = 0;
		snd_pcm_period_elapsed(substream);
	}
}

/*
 * _internal_ helper function for capture interrupt callback
 */
static void
snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream,
				   struct snd_pcm_indirect2 *rec,
				   snd_pcm_indirect2_copy_t copy,
				   snd_pcm_indirect2_zero_t null)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;

	if (diff) {
#ifdef SND_PCM_INDIRECT2_STAT
		rec->lastdifftime = jiffies;
#endif
		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
			diff += runtime->boundary;
		rec->sw_ready -= frames_to_bytes(runtime, diff);
		rec->appl_ptr = appl_ptr;
	}
	/* if hardware has something, but the intermediate buffer is full
	 * => skip contents of buffer
	 */
	if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) {
		unsigned int bytes;

#ifdef SND_PCM_INDIRECT2_STAT
		if (rec->firstzerotime == 0) {
			rec->firstzerotime = jiffies;
			snd_printk(KERN_DEBUG "STAT: (capture) "
				   "@firstzerotime: mul_elapsed: %d, "
				   "min_period_count: %d\n",
				   rec->mul_elapsed, rec->min_period_count);
			snd_printk(KERN_DEBUG "STAT: (capture) "
				   "@firstzerotime: sw_io: %d, sw_data: %d, "
				   "appl_ptr: %u\n",
				   rec->sw_io, rec->sw_data,
				   (unsigned int)appl_ptr);
		}
		if ((jiffies - rec->firstzerotime) < 3750) {
			rec->zero_times[(jiffies - rec->firstzerotime)]++;
			rec->zero_times_saved++;
		} else
			rec->zero_times_notsaved++;
#endif
		bytes = null(substream, rec);

#ifdef SND_PCM_INDIRECT2_STAT
		rec->zeros2hw += bytes;
		if (bytes < 64)
			rec->zero_sizes[bytes]++;
		else
			snd_printk(KERN_DEBUG
				   "STAT: (capture) %d zero Bytes copied to "
				   "hardware at once - too big to save!\n",
				   bytes);
#endif
		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0,
						       bytes);
		/* report an overrun */
		rec->sw_io = SNDRV_PCM_POS_XRUN;
		return;
	}
	while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) {
		/* sw_to_end: max. number of bytes that we can write to the
		 *  intermediate buffer (until it's end)
		 */
		size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;

		/* bytes: max. number of bytes, which may be copied to the
		 *  intermediate buffer without overflow (in _one_ step)
		 */
		size_t bytes = rec->sw_buffer_size - rec->sw_ready;

		/* limit number of bytes (for transfer) by available room in
		 * the intermediate buffer
		 */
		if (sw_to_end < bytes)
			bytes = sw_to_end;
		if (!bytes)
			break;

#ifdef SND_PCM_INDIRECT2_STAT
		if (rec->firstbytetime == 0)
			rec->firstbytetime = jiffies;
		rec->lastbytetime = jiffies;
#endif
		/* copy bytes from the intermediate buffer (position sw_data)
		 * to the HW at most and return number of bytes actually copied
		 * from HW
		 * Furthermore, set hw_ready to 0, if the fifo is empty now.
		 */
		bytes = copy(substream, rec, bytes);
		rec->bytes2hw += bytes;

#ifdef SND_PCM_INDIRECT2_STAT
		if (bytes < 64)
			rec->byte_sizes[bytes]++;
		else
			snd_printk(KERN_DEBUG
				   "STAT: (capture) %d Bytes copied to "
				   "hardware at once - too big to save!\n",
				   bytes);
#endif
		/* increase sw_data by the number of actually copied bytes from
		 * HW
		 */
		rec->sw_data += bytes;
		if (rec->sw_data == rec->sw_buffer_size)
			rec->sw_data = 0;

		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1,
						       bytes);

		/* number of bytes in the intermediate buffer, which haven't
		 * been fetched by ALSA yet.
		 */
		rec->sw_ready += bytes;
	}
	return;
}

/*
 * helper function for capture interrupt routine
 */
void
snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
				    struct snd_pcm_indirect2 *rec,
				    snd_pcm_indirect2_copy_t copy,
				    snd_pcm_indirect2_zero_t null)
{
#ifdef SND_PCM_INDIRECT2_STAT
	rec->irq_occured++;
#endif
	/* hardware recorded some bytes, so there is something to read from the
	 * record fifo:
	 */
	rec->hw_ready = 1;

	/* don't call ack() now, instead call transfer() function directly
	 * (normally called by ack() )
	 */
	snd_pcm_indirect2_capture_transfer(substream, rec, copy, null);

	if (rec->min_periods >= rec->min_multiple) {

#ifdef SND_PCM_INDIRECT2_STAT
		if ((rec->min_periods / rec->min_multiple) > 7)
			snd_printk(KERN_DEBUG
				   "STAT: more than 7 (%d) mul_adds - "
				   "too big to save!\n",
				   (rec->min_periods / rec->min_multiple));
		else
			rec->mul_adds[(rec->min_periods /
				       rec->min_multiple)]++;
		rec->mul_elapsed_real += (rec->min_periods /
					  rec->min_multiple);
		rec->mul_elapsed++;

		if (!(rec->mul_elapsed % 4)) {
			struct snd_pcm_runtime *runtime = substream->runtime;
			unsigned int appl_ptr =
			    frames_to_bytes(runtime,
					    (unsigned int)runtime->control->
					    appl_ptr) % rec->sw_buffer_size;
			int diff = rec->sw_data - appl_ptr;
			if (diff < 0)
				diff += rec->sw_buffer_size;
			snd_printk(KERN_DEBUG
				   "STAT: mul_elapsed: %d, sw_data: %u, "
				   "appl_ptr (bytes): %u, diff: %d\n",
				   rec->mul_elapsed, rec->sw_data, appl_ptr,
				   diff);
		}
#endif
		rec->min_periods = 0;
		snd_pcm_period_elapsed(substream);
	}
}
+140 −0
Original line number Diff line number Diff line
/*
 * Helper functions for indirect PCM data transfer to a simple FIFO in
 * hardware (small, no possibility to read "hardware io position",
 * updating position done by interrupt, ...)
 *
 *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.de>
 *
 *  Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
 *
 *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
 *                   Jaroslav Kysela <perex@suse.cz>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef __SOUND_PCM_INDIRECT2_H
#define __SOUND_PCM_INDIRECT2_H

/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t */
#include <sound/pcm.h>

/* Debug options for code which may be removed completely in a final version */
#ifdef CONFIG_SND_DEBUG
#define SND_PCM_INDIRECT2_STAT    /* turn on some "statistics" about the
				   * process of copying bytes from the
				   * intermediate buffer to the hardware
				   * fifo and the other way round
				   */
#endif

struct snd_pcm_indirect2 {
	unsigned int hw_buffer_size;  /* Byte size of hardware buffer */
	int hw_ready;		      /* playback: 1 = hw fifo has room left,
				       * 0 = hw fifo is full
				       */
	unsigned int min_multiple;
	int min_periods;	      /* counts number of min. periods until
				       * min_multiple is reached
				       */
	int min_period_count;	      /* counts bytes to count number of
				       * min. periods
				       */

	unsigned int sw_buffer_size;  /* Byte size of software buffer */

	/* sw_data: position in intermediate buffer, where we will read (or
	 *          write) from/to next time (to transfer data to/from HW)
	 */
	unsigned int sw_data;         /* Offset to next dst (or src) in sw
				       * ring buffer
				       */
	/* easiest case (playback):
	 * sw_data is nearly the same as ~ runtime->control->appl_ptr, with the
	 * exception that sw_data is "behind" by the number if bytes ALSA wrote
	 * to the intermediate buffer last time.
	 * A call to ack() callback synchronizes both indirectly.
	 */

	/* We have no real sw_io pointer here. Usually sw_io is pointing to the
	 * current playback/capture position _inside_ the hardware. Devices
	 * with plain FIFOs often have no possibility to publish this position.
	 * So we say: if sw_data is updated, that means bytes were copied to
	 * the hardware, we increase sw_io by that amount, because there have
	 * to be as much bytes which were played. So sw_io will stay behind
	 * sw_data all the time and has to converge to sw_data at the end of
	 * playback.
	 */
	unsigned int sw_io;           /* Current software pointer in bytes */

	/* sw_ready: number of bytes ALSA copied to the intermediate buffer, so
	 * it represents the number of bytes which wait for transfer to the HW
	 */
	int sw_ready;		  /* Bytes ready to be transferred to/from hw */

	/* appl_ptr: last known position of ALSA (where ALSA is going to write
	 * next time into the intermediate buffer
	 */
	snd_pcm_uframes_t appl_ptr;   /* Last seen appl_ptr */

	unsigned int bytes2hw;
	int check_alignment;

#ifdef SND_PCM_INDIRECT2_STAT
	unsigned int zeros2hw;
	unsigned int mul_elapsed;
	unsigned int mul_elapsed_real;
	unsigned long firstbytetime;
	unsigned long lastbytetime;
	unsigned long firstzerotime;
	unsigned int byte_sizes[64];
	unsigned int zero_sizes[64];
	unsigned int min_adds[8];
	unsigned int mul_adds[8];
	unsigned int zero_times[3750];	/* = 15s */
	unsigned int zero_times_saved;
	unsigned int zero_times_notsaved;
	unsigned int irq_occured;
	unsigned int pointer_calls;
	unsigned int lastdifftime;
#endif
};

typedef size_t (*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream *substream,
					   struct snd_pcm_indirect2 *rec,
					   size_t bytes);
typedef size_t (*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream *substream,
					   struct snd_pcm_indirect2 *rec);

#ifdef SND_PCM_INDIRECT2_STAT
void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
				   struct snd_pcm_indirect2 *rec);
#endif

snd_pcm_uframes_t
snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
			  struct snd_pcm_indirect2 *rec);
void
snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
				     struct snd_pcm_indirect2 *rec,
				     snd_pcm_indirect2_copy_t copy,
				     snd_pcm_indirect2_zero_t zero);
void
snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
				    struct snd_pcm_indirect2 *rec,
				    snd_pcm_indirect2_copy_t copy,
				    snd_pcm_indirect2_zero_t null);

#endif /* __SOUND_PCM_INDIRECT2_H */