All of lore.kernel.org
 help / color / mirror / Atom feed
* OSS emulation, period size, and start/stop thresholds
@ 2007-02-21 23:33 Michael K. Edwards
  2007-02-22 16:26 ` Takashi Iwai
  0 siblings, 1 reply; 5+ messages in thread
From: Michael K. Edwards @ 2007-02-21 23:33 UTC (permalink / raw)
  To: Alsa-devel

[-- Attachment #1: Type: text/plain, Size: 1289 bytes --]

I'm writing an application to run on an embedded ARM with very little
hardware buffering capacity on its audio interface.  I also wrote an
ALSA driver (to be released under the GPL) for this interface, which
works rather nicely with aplay as long as I tune the period size and
thresholds and so forth, more or less like so:

aplay -C -t wav -c 1 -d 3600 -f s16_le --period-size=40
--buffer-size=160 --start-delay=3500 | aplay -P -c 1 --period-size=40
--buffer-size=160 --avail-min=5500 --start-delay=10500

(That's with alsa-lib 1.0.14rc2, unaltered, and alsa-utils 1.0.14rc2,
with a patch to support real-time scheduling and fix all the places
where output that should go to stderr is sent to stdout.  That patch
is attached.)

Now, an earlier version of the application was implemented against a
one-off driver for different hardware, and just used read()/write() to
deliver data to the driver.  It would be nice to be able to open the
/dev/dsp OSS emulation device, ioctl it into the right format and
tunings, and leave the read()/write() alone, saving the rework for a
callback model for another day.

Does snd_pcm_oss have reasonable ioctl's to force renegotiation about
period size and start/stop thresholds, roughly equivalent to the
parameters above?

Thanks in advance,
- Michael

[-- Attachment #2: sched-fifo.patch --]
[-- Type: text/x-patch, Size: 5956 bytes --]

diff -ur alsa-utils-1.0.14rc2/aplay/aplay.c alsa-utils-1.0.14rc2-patched/aplay/aplay.c
--- alsa-utils-1.0.14rc2/aplay/aplay.c	2007-02-06 14:22:54.000000000 -0800
+++ alsa-utils-1.0.14rc2-patched/aplay/aplay.c	2007-02-06 14:29:37.000000000 -0800
@@ -27,6 +27,7 @@
  */
 
 #define _GNU_SOURCE
+#include <sched.h>
 #include <stdio.h>
 #include <malloc.h>
 #include <unistd.h>
@@ -149,7 +150,7 @@
 static void usage(char *command)
 {
 	snd_pcm_format_t k;
-	printf(
+	fprintf(stderr,
 _("Usage: %s [OPTION]... [FILE]...\n"
 "\n"
 "-h, --help              help\n"
@@ -177,17 +178,17 @@
 "-v, --verbose           show PCM structure and setup (accumulative)\n"
 "-I, --separate-channels one file for each channel\n")
 		, command);
-	printf(_("Recognized sample formats are:"));
+	fprintf(stderr, _("Recognized sample formats are:"));
 	for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
 		const char *s = snd_pcm_format_name(k);
 		if (s)
-			printf(" %s", s);
+			fprintf(stderr, " %s", s);
 	}
-	printf(_("\nSome of these may not be available on selected hardware\n"));
-	printf(_("The availabled format shortcuts are:\n"));
-	printf(_("-f cd (16 bit little endian, 44100, stereo)\n"));
-	printf(_("-f cdr (16 bit big endian, 44100, stereo)\n"));
-	printf(_("-f dat (16 bit little endian, 48000, stereo)\n"));
+	fprintf(stderr, _("\nSome of these may not be available on selected hardware\n"));
+	fprintf(stderr, _("The availabled format shortcuts are:\n"));
+	fprintf(stderr, _("-f cd (16 bit little endian, 44100, stereo)\n"));
+	fprintf(stderr, _("-f cdr (16 bit big endian, 44100, stereo)\n"));
+	fprintf(stderr, _("-f dat (16 bit little endian, 48000, stereo)\n"));
 }
 
 static void device_list(void)
@@ -204,7 +205,7 @@
 		error(_("no soundcards found..."));
 		return;
 	}
-	printf(_("**** List of %s Hardware Devices ****\n"),
+	fprintf(stderr, _("**** List of %s Hardware Devices ****\n"),
 	       snd_pcm_stream_name(stream));
 	while (card >= 0) {
 		char name[32];
@@ -233,20 +234,20 @@
 					error("control digital audio info (%i): %s", card, snd_strerror(err));
 				continue;
 			}
-			printf(_("card %i: %s [%s], device %i: %s [%s]\n"),
+			fprintf(stderr, _("card %i: %s [%s], device %i: %s [%s]\n"),
 				card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_name(info),
 				dev,
 				snd_pcm_info_get_id(pcminfo),
 				snd_pcm_info_get_name(pcminfo));
 			count = snd_pcm_info_get_subdevices_count(pcminfo);
-			printf( _("  Subdevices: %i/%i\n"),
+			fprintf(stderr, _("  Subdevices: %i/%i\n"),
 				snd_pcm_info_get_subdevices_avail(pcminfo), count);
 			for (idx = 0; idx < (int)count; idx++) {
 				snd_pcm_info_set_subdevice(pcminfo, idx);
 				if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
 					error("control digital audio playback info (%i): %s", card, snd_strerror(err));
 				} else {
-					printf(_("  Subdevice #%i: %s\n"),
+					fprintf(stderr, _("  Subdevice #%i: %s\n"),
 						idx, snd_pcm_info_get_subdevice_name(pcminfo));
 				}
 			}
@@ -276,17 +277,17 @@
 		io = snd_device_name_get_hint(*n, "IOID");
 		if (io != NULL && strcmp(io, filter) == 0)
 			goto __end;
-		printf("%s\n", name);
+		fprintf(stderr, "%s\n", name);
 		if ((descr1 = descr) != NULL) {
-			printf("    ");
+			fprintf(stderr, "    ");
 			while (*descr1) {
 				if (*descr1 == '\n')
-					printf("\n    ");
+					fprintf(stderr, "\n    ");
 				else
-					putchar(*descr1);
+					putc(*descr1, stderr);
 				descr1++;
 			}
-			putchar('\n');
+			putc('\n', stderr);
 		}
 	      __end:
 	      	if (name != NULL)
@@ -302,13 +303,13 @@
 
 static void version(void)
 {
-	printf("%s: version " SND_UTIL_VERSION_STR " by Jaroslav Kysela <perex@suse.cz>\n", command);
+	fprintf(stderr, "%s: version " SND_UTIL_VERSION_STR " by Jaroslav Kysela <perex@suse.cz>\n", command);
 }
 
 static void signal_handler(int sig)
 {
 	if (verbose==2)
-		putchar('\n');
+		putc('\n', stderr);
 	if (!quiet_mode)
 		fprintf(stderr, _("Aborted by signal %s...\n"), strsignal(sig));
 	if (stream == SND_PCM_STREAM_CAPTURE) {
@@ -515,10 +516,16 @@
 			interleaved = 0;
 			break;
 		case 'P':
+		{	struct sched_param param = { 0 };
+			param.sched_priority = 60;
+			sched_setscheduler(0, SCHED_FIFO, &param);	}
 			stream = SND_PCM_STREAM_PLAYBACK;
 			command = "aplay";
 			break;
 		case 'C':
+		{	struct sched_param param = { 0 };
+			param.sched_priority = 40;
+			sched_setscheduler(0, SCHED_FIFO, &param);	}
 			stream = SND_PCM_STREAM_CAPTURE;
 			command = "arecord";
 			start_delay = 1;
@@ -605,7 +612,7 @@
 			capturev(&argv[optind], argc - optind);
 	}
 	if (verbose==2)
-		putchar('\n');
+		putc('\n', stderr);
 	snd_pcm_close(handle);
 	free(audiobuf);
       __end:
@@ -1212,30 +1219,30 @@
 		if(perc>maxperc)
 			maxperc=perc;
 
-		putchar('\r');
+		putc('\r', stderr);
 		for (val = 0; val <= perc / 2 && val < 50; val++)
-			putchar('#');
+			putc('#', stderr);
 		for (; val < maxperc / 2 && val < 50; val++)
-			putchar(' ');
-		putchar('+');
+			putc(' ', stderr);
+		putc('+', stderr);
 		for (++val; val < 50; val++)
-			putchar(' ');
+			putc(' ', stderr);
 
-		printf("| %02i%%", maxperc);
+		fprintf(stderr, "| %02i%%", maxperc);
 		if (perc>99)
-			printf(_(" !clip  "));
+			fprintf(stderr, _(" !clip  "));
 
-		fflush(stdout);
+		fflush(stderr);
 	}
 	else if(verbose==3) {
-		printf(_("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak);
+		fprintf(stderr, _("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak);
 		for (val = 0; val < 20; val++)
 			if (val <= perc / 5)
-				putchar('#');
+				putc('#', stderr);
 			else
-				putchar(' ');
-		printf(" %i%%\n", perc);
-		fflush(stdout);
+				putc(' ', stderr);
+		fprintf(stderr, " %i%%\n", perc);
+		fflush(stderr);
 	}
 }
 
@@ -2124,7 +2131,7 @@
 
 	do {
 		/* open a file to write */
-		if(!tostdout) {
+		if (!tostdout) {
 			/* upon the second file we start the numbering scheme */
 			if (filecount) {
 				filecount = new_capture_file(orig_name, namebuf,

[-- Attachment #3: Type: text/plain, Size: 345 bytes --]

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

[-- Attachment #4: Type: text/plain, Size: 161 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: OSS emulation, period size, and start/stop thresholds
  2007-02-21 23:33 OSS emulation, period size, and start/stop thresholds Michael K. Edwards
@ 2007-02-22 16:26 ` Takashi Iwai
  2007-02-22 21:32   ` Michael K. Edwards
  0 siblings, 1 reply; 5+ messages in thread
From: Takashi Iwai @ 2007-02-22 16:26 UTC (permalink / raw)
  To: Michael K. Edwards; +Cc: Alsa-devel

At Wed, 21 Feb 2007 15:33:35 -0800,
Michael K. Edwards wrote:
> 
> I'm writing an application to run on an embedded ARM with very little
> hardware buffering capacity on its audio interface.  I also wrote an
> ALSA driver (to be released under the GPL) for this interface, which
> works rather nicely with aplay as long as I tune the period size and
> thresholds and so forth, more or less like so:
> 
> aplay -C -t wav -c 1 -d 3600 -f s16_le --period-size=40
> --buffer-size=160 --start-delay=3500 | aplay -P -c 1 --period-size=40
> --buffer-size=160 --avail-min=5500 --start-delay=10500
> 
> (That's with alsa-lib 1.0.14rc2, unaltered, and alsa-utils 1.0.14rc2,
> with a patch to support real-time scheduling and fix all the places
> where output that should go to stderr is sent to stdout.  That patch
> is attached.)

I think putting the usage and the information to stdout is normal.


> Now, an earlier version of the application was implemented against a
> one-off driver for different hardware, and just used read()/write() to
> deliver data to the driver.  It would be nice to be able to open the
> /dev/dsp OSS emulation device, ioctl it into the right format and
> tunings, and leave the read()/write() alone, saving the rework for a
> callback model for another day.
> 
> Does snd_pcm_oss have reasonable ioctl's to force renegotiation about
> period size and start/stop thresholds, roughly equivalent to the
> parameters above?

It should.  There are some mangles with buffer and period sizes
because of the limitation of OSS (sizes must be power-of-two), but the
lowlevel driver doesn't have to take care about it.

If you have problems actually, let us know :)


Takashi

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: OSS emulation, period size, and start/stop thresholds
  2007-02-22 16:26 ` Takashi Iwai
@ 2007-02-22 21:32   ` Michael K. Edwards
  2007-02-23 11:30     ` Takashi Iwai
  0 siblings, 1 reply; 5+ messages in thread
From: Michael K. Edwards @ 2007-02-22 21:32 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: Alsa-devel

On 2/22/07, Takashi Iwai <tiwai@suse.de> wrote:
> I think putting the usage and the information to stdout is normal.

Usage, maybe.  But nothing else -- not if you want piping stdout to
stdin to work.  Which I do, because it's by far the sanest way to hook
two processes together in Unix.

> It should.  There are some mangles with buffer and period sizes
> because of the limitation of OSS (sizes must be power-of-two), but the
> lowlevel driver doesn't have to take care about it.

Aha.  _That's_ the problem.  OK, I won't be able to use the OSS
emulation, because I need to chunk in multiples of 5ms.  Thanks!

> If you have problems actually, let us know :)

Well, it took quite a while to figure out how the trigger() and
pointer() hooks were supposed to work for a non-DMA device.  (DMA is
of limited use on this device because it can only interact with
on-chip SRAM.)  When I have my ducks in a row, I'll post this driver
along with some theory of operation; it would probably be nice for the
next poor sod if it made it into the documentation.

Cheers,
- Michael

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: OSS emulation, period size, and start/stop thresholds
  2007-02-22 21:32   ` Michael K. Edwards
@ 2007-02-23 11:30     ` Takashi Iwai
  2007-02-23 22:34       ` Michael K. Edwards
  0 siblings, 1 reply; 5+ messages in thread
From: Takashi Iwai @ 2007-02-23 11:30 UTC (permalink / raw)
  To: Michael K. Edwards; +Cc: Alsa-devel

At Thu, 22 Feb 2007 13:32:58 -0800,
Michael K. Edwards wrote:
> 
> On 2/22/07, Takashi Iwai <tiwai@suse.de> wrote:
> > I think putting the usage and the information to stdout is normal.
> 
> Usage, maybe.  But nothing else -- not if you want piping stdout to
> stdin to work.  Which I do, because it's by far the sanest way to hook
> two processes together in Unix.

Hm, but someone may want to parse the information by -l or -L as
well for scripting, too.  The VU-meter output can be stderr as
default, though.

> > It should.  There are some mangles with buffer and period sizes
> > because of the limitation of OSS (sizes must be power-of-two), but the
> > lowlevel driver doesn't have to take care about it.
> 
> Aha.  _That's_ the problem.  OK, I won't be able to use the OSS
> emulation, because I need to chunk in multiples of 5ms.  Thanks!
> 
> > If you have problems actually, let us know :)
> 
> Well, it took quite a while to figure out how the trigger() and
> pointer() hooks were supposed to work for a non-DMA device.  (DMA is
> of limited use on this device because it can only interact with
> on-chip SRAM.)  When I have my ducks in a row, I'll post this driver
> along with some theory of operation; it would probably be nice for the
> next poor sod if it made it into the documentation.

Great.  Thanks.


Takashi

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: OSS emulation, period size, and start/stop thresholds
  2007-02-23 11:30     ` Takashi Iwai
@ 2007-02-23 22:34       ` Michael K. Edwards
  0 siblings, 0 replies; 5+ messages in thread
From: Michael K. Edwards @ 2007-02-23 22:34 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: Alsa-devel

[-- Attachment #1: Type: text/plain, Size: 2499 bytes --]

On 2/23/07, Takashi Iwai <tiwai@suse.de> wrote:
> At Thu, 22 Feb 2007 13:32:58 -0800,
> Michael K. Edwards wrote:
> >
> > On 2/22/07, Takashi Iwai <tiwai@suse.de> wrote:
> > > I think putting the usage and the information to stdout is normal.
> >
> > Usage, maybe.  But nothing else -- not if you want piping stdout to
> > stdin to work.  Which I do, because it's by far the sanest way to hook
> > two processes together in Unix.
>
> Hm, but someone may want to parse the information by -l or -L as
> well for scripting, too.  The VU-meter output can be stderr as
> default, though.

I've found it very useful to be able to turn on -v -v and watch stderr
while sending stdout to a named pipe.  You're right that -l (lowercase
L) -and -L should do the same thing as -h, which is probably to send
things to stdout.

> > Well, it took quite a while to figure out how the trigger() and
> > pointer() hooks were supposed to work for a non-DMA device.  (DMA is
> > of limited use on this device because it can only interact with
> > on-chip SRAM.)  When I have my ducks in a row, I'll post this driver
> > along with some theory of operation; it would probably be nice for the
> > next poor sod if it made it into the documentation.
>
> Great.  Thanks.

I've been really pleased with the ALSA design generally, but it took
some guru meditation to get the tx_pointer / rx_pointer arithmetic
straight.  It now seems to be pretty stable with a 4ms audio chunk
size and these aplay tunings:

aplay -C -t wav -c 1 -d 3600 -f s16_le --period-size=32
--buffer-size=256 --start-delay=3500 | aplay -P -c 1 --period-size=32
--buffer-size=256 --avail-min=5500 --start-delay=25500

I think I now have the fragment and buffer sizes right on the OSS
emulation device, but I haven't figured out how to set the trigger
levels.  Any insight?  I could also use help figuring out why underrun
and overrun sometimes messes up the device to where I have to unload
and load the driver, and why the drain ioctl doesn't return promptly
at the end of playing back a WAV file.

I've attached a draft of the driver, which is released under GPL with
authorization from my client.  It's still a bit rough in places and
you probably can't even compile it without some chip vendor header
files that I am not yet confident I have clearance to give you, but
you can at least eyeball it.  It seems like it's been a long time
since a new non-DMA driver was added to ALSA, and DMA isn't always the
right choice on an SoC.

Cheers,
- Michael

[-- Attachment #2: snd-mv88w8xx8.c --]
[-- Type: text/x-csrc, Size: 19299 bytes --]

/*
 * linux/sound/arm/snd-mover1-pcm.c -- ALSA PCM interface for the Marvell 88W8618 chip
 *
 * Author:	Michael K. Edwards
 * 		(modeled on drivers written by Nicolas Pitre and Anders Torger)
 * Created:	Jan 2007 (model: Nov 30, 2004)
 * Copyright:	(C) 2007 Plantronics, Inc.
 * 		Portions (C) 2004 MontaVista Software, Inc.
 * 		Portions (C) 2000, 2001 Anders Torger <torger@ludd.luth.se>
 *
 * 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.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>

#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>

#include <linux/config.h>
#include <linux/spinlock.h>
#include <asm/system.h>
#include <asm/scatterlist.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>

/**
 * copy_u32s_to_user_fromio - copy data from mmio-space to user-space
 * @dst: the destination pointer on user-space
 * @src: the source pointer on mmio
 * @count: the data size to copy in u32s
 *
 * Copies the data from mmio-space to user-space.
 *
 * Returns zero if successful, or non-zero on failure.
 */
static int copy_u32s_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
{
	u32 buf[64];
	count <<= 2;
	while (count) {
		size_t c = count;
		if (c > sizeof(buf))
			c = sizeof(buf);
		ioread32_rep(src, buf, c>>2);
		if (copy_to_user(dst, buf, c))
			return -EFAULT;
		count -= c;
		dst += c;
		src += c;
	}
	return 0;
}

/**
 * copy_u32s_from_user_toio - copy data from user-space to mmio-space
 * @dst: the destination pointer on mmio-space
 * @src: the source pointer on user-space
 * @count: the data size to copy in u32s
 *
 * Copies the data from user-space to mmio-space.
 *
 * Returns zero if successful, or non-zero on failure.
 */
static int copy_u32s_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
{
	u32 buf[64];
	count <<= 2;
	while (count) {
		size_t c = count;
		if (c > sizeof(buf))
			c = sizeof(buf);
		if (copy_from_user(buf, src, c))
			return -EFAULT;
		iowrite32_rep(dst, buf, c>>2);
		count -= c;
		dst += c;
		src += c;
	}
	return 0;
}

/**
 * copy_u16s_to_user_fromio - copy data from mmio-space to user-space
 * @dst: the destination pointer on user-space
 * @src: the source pointer on mmio
 * @count: the data size to copy in u16s
 *
 * Copies the data from mmio-space to user-space.
 *
 * Returns zero if successful, or non-zero on failure.
 */
static int copy_u16s_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count)
{
	u32 buf32[64];
	u16 buf16[64];
	count <<= 1;
	while (count) {
		int i;
		size_t c = count;
		if (c > sizeof(buf16))
			c = sizeof(buf16);
		ioread32_rep(src, buf32, (c>>1));
		/*** OPTIMIZE ME ***/
		for (i = (c>>1) - 1; i >= 0; i--)
			buf16[i] = buf32[i];
		if (copy_to_user(dst, buf16, c))
			return -EFAULT;
		count -= c;
		dst += c;
		src += c<<1;
	}
	return 0;
}

/**
 * copy_u16s_from_user_toio - copy data from user-space to mmio-space
 * @dst: the destination pointer on mmio-space
 * @src: the source pointer on user-space
 * @count: the data size to copy in u16s
 *
 * Copies the data from user-space to mmio-space.
 *
 * Returns zero if successful, or non-zero on failure.
 */
static int copy_u16s_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count)
{
	u32 buf32[64];
	u16 buf16[64];
	count <<= 1;
	while (count) {
		int i;
		size_t c = count;
		if (c > sizeof(buf16))
			c = sizeof(buf16);
		if (copy_from_user(buf16, src, c))
			return -EFAULT;
		/*** OPTIMIZE ME ***/
		for (i = (c>>1) - 1; i >= 0; i--)
			buf32[i] = buf16[i];
		iowrite32_rep(dst, buf32, (c>>1));
		count -= c;
		dst += c<<1;
		src += c;
	}
	return 0;
}

static struct platform_device *device;

#define VERSION_STRING "V0.5"

#define TX_MODE_ENABLE_BITS (0x0188)
#define RX_MODE_ENABLE_BITS (0x0188)
#define MCLK_DISABLE_BIT    (0x0010)
#define AIU_SOFT_RESET_BIT  (0x8000)
#define TX_INT_ENABLE_BIT   (0x0080)
#define RX_INT_ENABLE_BIT   (0x0020)
#define INT_STATUS_MASK     (0x00F3)

#define DEBUG_LEVEL1	1
#define DEBUG_LEVEL2	2
#define DEBUG_LEVEL3	4
#define DEBUG_LEVEL4	8
#define DEBUG_LEVEL5	16

//
// PRINTK macro that conditionally print to the kernel debug message
// memory based upon
//      the flag settings. It takes a variable number of arguments for
//      allowing printf style
//      formatting and argument lists.
//
#define PRINTK(flag, string, ...)  {if (iPrintk & flag) {printk(KERN_ERR string, ##__VA_ARGS__);}}

static uint iPrintk	= 0x0000;		// bit oriented debug flag DEBUG_LEVELx
module_param(iPrintk, uint, 0600);		// bit definitions defined above as DEBUG_LEVELx

static uint iPlayMode	= 0x1a11;		// Stereo, hardware pointer control
module_param(iPlayMode, uint, 0600);

static uint iPlayCtrl	= 0x0000;
module_param(iPlayCtrl, uint, 0600);

static uint iRecMode	= 0x0001;
module_param(iRecMode, uint, 0600);

static uint iClkDiv	= 0x5f00;
module_param(iClkDiv, uint, 0600);

static uint iBitRate	= 0x000f;
module_param(iBitRate, uint, 0600);

static uint iTxThresh	= 0x0040;
module_param(iTxThresh, uint, 0600);

static uint iRxThresh	= 0x0040;
module_param(iRxThresh, uint, 0600);

static const char *regnames[] = {
	"Playback Mode Register",
	"Playback Control Register",
 	"Playback Pause Register",
	"Recording Mode Register",
	"Recording Control Register",
	"Recording Pause Register",
	"Clock Divider Control Register",
	"Bit Rate Register",
	"AIU Interrupt Status Register",
	"AIU Interrupt Enable Register",
	"Playback Buffer Starting Address Register",
	"Tx Buffer Threshold Control Register",
	"Recording Buffer Starting Address Register",
	"Rx Buffer Threshold Control Register",
	"Tx Buffer Count Status Register",
	"Rx Buffer Count Status Register",
	"Playback Buffer Starting Address, Upper 16 bits",
	"Recording Buffer Starting Address, Upper 16 bits"
};

/*
 * Use a GPIO bit to controls the clk to the bs250 chip
 *  NOTE: This is specific to the Mover Platform.
 */
inline static void dsp_clk_enable(int on_off)
{
	MV88W8XX8_GPIO_ALTER(5, on_off);
	MV88W8XX8_GPIO_MAKE_OUTPUT(5);
}

/*
 * Control the GPIO bit that turns on the power to the bs250 chip
 *  NOTE: This is specific to the Mover Platform.
 *  NOTE: Coded for active low output; verify?
 */
inline static void dsp_power_enable(int on_off)
{
	MV88W8XX8_GPIO_ALTER(11, !on_off);
	MV88W8XX8_GPIO_MAKE_OUTPUT(11);
}

/*
 * Setup the GPIOs and enable the DSP
 *
 */
inline static void init_gpio_reg(void)
{
	dsp_clk_enable(1);
	dsp_power_enable(1);
}

/* dump the contents of the audio registers */
inline static void dumpAudioRegs(int debug_level)
{
	int i;

	if (!(iPrintk & debug_level))
		return;

	for (i=0; i < sizeof(regnames)/sizeof(regnames[0]); i++) {
		volatile u32 __iomem *preg = &__REG2(MV88W8XX8_AIU_BASE, i*4);
		PRINTK(debug_level, "[%p]=0x%8.8lx\t\"%s\"\n", preg, (unsigned long)*preg, regnames[i]);
	}

}

#define BUFFER_SIZE (0x100)		// Size of Rx/Tx buffers in DWORDS

int __init snd_mover1_pcm_init(void)
{
	volatile u32 __iomem *pstat = &__REG(MV88W8XX8_AIU_INT_STATUS);
	volatile u32 __iomem *ptx = &__REG(MV88W8XX8_AIU_TX_DATA);
	unsigned long txcnt;
	int i, j;

	PRINTK(DEBUG_LEVEL1, "%s: Initializing snd_mover1_pcm driver version %s\n", __FUNCTION__, VERSION_STRING);

	init_gpio_reg();

	/* RESET the interface will reset the pointers and registers */
	__REG(MV88W8XX8_AIU_BIT_RATE) = AIU_SOFT_RESET_BIT;

    {
        volatile int x;
        for (x = 0; x < 100; ++x)
            ;
    }

	__REG(MV88W8XX8_AIU_INT_ENABLE) = 0;
	__REG(MV88W8XX8_AIU_INT_STATUS) = INT_STATUS_MASK;
	__REG(MV88W8XX8_AIU_PLAY_MODE) = iPlayMode & ~TX_MODE_ENABLE_BITS;
	__REG(MV88W8XX8_AIU_REC_MODE) = iRecMode & ~RX_MODE_ENABLE_BITS;
	__REG(MV88W8XX8_AIU_CLK_DIV) = iClkDiv | MCLK_DISABLE_BIT;
	__REG(MV88W8XX8_AIU_BIT_RATE) = iBitRate;
	__REG(MV88W8XX8_AIU_TX_THOLD) = iTxThresh;
	__REG(MV88W8XX8_AIU_RX_THOLD) = iRxThresh;
	__REG(MV88W8XX8_AIU_PLAY_CTRL) = iPlayCtrl;

	dumpAudioRegs(DEBUG_LEVEL1);

	return 0;
}

static const struct snd_pcm_hardware mover1_pcm_hardware = {
	.info			= SNDRV_PCM_INFO_INTERLEAVED,
	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
	.rates			= SNDRV_PCM_RATE_8000,
	.rate_min		= 8000,
	.rate_max		= 8000,
	.channels_min		= 1,
	.channels_max		= 2,
	.period_bytes_min	= 0x20,
	.period_bytes_max	= 0x200,
	.periods_min		= 2,
	.periods_max		= 16,
	.buffer_bytes_max	= 0x400,
	.fifo_size		= 0,
};

struct mover1_runtime_data {
	int isMono;
	int bufSize;
	int frameCount;
	int irq;
};

static int
mover1_pcm_tx_silence(struct snd_pcm_substream *substream,
		      int channel, /* not used (interleaved data) */
		      snd_pcm_uframes_t pos,
		      snd_pcm_uframes_t count)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	volatile u32 __iomem *p = &__REG2(MV88W8XX8_AIU_TX_DATA, pos << 2);
	PRINTK(DEBUG_LEVEL2, "tx_silence (%d, %d)\n", pos, count);
	rtd->frameCount += count;
	while (count--)
		*(p++) = 0;
	dumpAudioRegs(DEBUG_LEVEL3);
	return 0;
}

static int
mover1_pcm_tx_copy(struct snd_pcm_substream *substream,
		   int channel, /* not used (interleaved data) */
		   snd_pcm_uframes_t pos,
		   void __user *src,
		   snd_pcm_uframes_t count)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	PRINTK(DEBUG_LEVEL2, "tx_copy (%d, %d)\n", pos, count);
	rtd->frameCount += count;
	if (rtd->isMono)
		copy_u16s_from_user_toio(&__REG2(MV88W8XX8_AIU_TX_DATA, pos<<2), src, count);
	else
		copy_u32s_from_user_toio(&__REG2(MV88W8XX8_AIU_TX_DATA, pos<<2), src, count);
	dumpAudioRegs(DEBUG_LEVEL3);
	return 0;
}

static int
mover1_pcm_rx_copy(struct snd_pcm_substream *substream,
		   int channel, /* not used (interleaved data) */
		   snd_pcm_uframes_t pos,
		   void __user *dst,
		   snd_pcm_uframes_t count)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	PRINTK(DEBUG_LEVEL2, "rx_copy (%d, %d)\n", pos, count);
	rtd->frameCount += count;
	if (rtd->isMono)
		copy_u16s_to_user_fromio(dst, &__REG2(MV88W8XX8_AIU_RX_DATA, pos<<2), count);
	else
		copy_u32s_to_user_fromio(dst, &__REG2(MV88W8XX8_AIU_RX_DATA, pos<<2), count);
	dumpAudioRegs(DEBUG_LEVEL3);
        return 0;
}

static irqreturn_t mover1_pcm_tx_irq(int irq, void *dev_id)
{
	struct snd_pcm_substream *substream = dev_id;
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	u32 intstatus = __REG(MV88W8XX8_AIU_INT_STATUS) & 0xC2;

	if (intstatus & 0x80) {
		PRINTK(DEBUG_LEVEL2, "tx_irq period elapsed (%02x)\n", intstatus);
		snd_pcm_period_elapsed(substream);
	} else {
		PRINTK(DEBUG_LEVEL1, "PCM_TX: error (INT_STATUS=%02x)\n", intstatus);
		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
	}

	__REG(MV88W8XX8_AIU_INT_STATUS) = intstatus;
	dumpAudioRegs(DEBUG_LEVEL4);
	return IRQ_HANDLED;
}

static irqreturn_t mover1_pcm_rx_irq(int irq, void *dev_id)
{
	struct snd_pcm_substream *substream = dev_id;
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	u32 intstatus = __REG(MV88W8XX8_AIU_INT_STATUS) & 0x31;

	if (intstatus & 0x20) {
		PRINTK(DEBUG_LEVEL2, "rx_irq period elapsed (%02x)\n", intstatus);
		snd_pcm_period_elapsed(substream);
	} else {
		PRINTK(DEBUG_LEVEL1, "PCM_RX: error (INT_STATUS=%02x)\n", intstatus);
		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
	}

	__REG(MV88W8XX8_AIU_INT_STATUS) = intstatus;
	dumpAudioRegs(DEBUG_LEVEL4);
	return IRQ_HANDLED;
}

static int mover1_pcm_tx_hw_params(struct snd_pcm_substream *substream,
				   struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mover1_runtime_data *rtd = runtime->private_data;
	size_t totsize = params_buffer_bytes(params);
	size_t period = params_period_bytes(params);

	rtd->isMono = (params_channels(params) == 1);
	if (rtd->isMono)
		rtd->bufSize = totsize >> 1;
	else
		rtd->bufSize = totsize >> 2;

	if (rtd->irq == -1) {
		if (request_irq(IRQ_AUDIO_TX, mover1_pcm_tx_irq, SA_INTERRUPT, "PCM_TX", (void *)substream)) {
			snd_printk(KERN_ERR "unable to grab IRQ %d\n", IRQ_AUDIO_TX);
			return -EBUSY;
		}
		if (iPrintk & DEBUG_LEVEL1)
			snd_printk(KERN_ERR "grabbed IRQ %d\n", IRQ_AUDIO_TX);
		rtd->irq = IRQ_AUDIO_TX;
	}
 
	return 0;
}

static int mover1_pcm_rx_hw_params(struct snd_pcm_substream *substream,
				   struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mover1_runtime_data *rtd = runtime->private_data;
	size_t totsize = params_buffer_bytes(params);
	size_t period = params_period_bytes(params);

	rtd->isMono = (params_channels(params) == 1);
	if (rtd->isMono)
		rtd->bufSize = totsize >> 1;
	else
		rtd->bufSize = totsize >> 2;

	if (rtd->irq == -1) {
		if (request_irq(IRQ_AUDIO_RX, mover1_pcm_rx_irq, SA_INTERRUPT, "PCM_RX", (void *)substream)) {
			snd_printk(KERN_ERR "unable to grab IRQ %d\n", IRQ_AUDIO_RX);
			return -EBUSY;
		}
		if (iPrintk & DEBUG_LEVEL1)
			snd_printk(KERN_ERR "grabbed IRQ %d\n", IRQ_AUDIO_RX);
		rtd->irq = IRQ_AUDIO_RX;
	}
 
	return 0;
}

static int mover1_pcm_hw_free(struct snd_pcm_substream *substream)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;

	__REG(MV88W8XX8_AIU_CLK_DIV) |= MCLK_DISABLE_BIT;

	if (rtd->irq != -1) {
		free_irq(rtd->irq, (void *)substream);
		rtd->irq = -1;
	}

	snd_pcm_set_runtime_buffer(substream, NULL);
	return 0;
}

static int mover1_pcm_prepare(struct snd_pcm_substream *substream)
{
	__REG(MV88W8XX8_AIU_CLK_DIV) &= ~MCLK_DISABLE_BIT;
	return 0;
}

static int mover1_pcm_tx_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	int ret = 0;
	int i;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		__REG(MV88W8XX8_AIU_INT_ENABLE) |= TX_INT_ENABLE_BIT;
		__REG(MV88W8XX8_AIU_PLAY_MODE) |= TX_MODE_ENABLE_BITS;
		PRINTK(DEBUG_LEVEL1, "tx_trigger START\n");
		PRINTK(DEBUG_LEVEL5, "depth: %d\n", __REG(MV88W8XX8_AIU_TX_BUF_CNT));
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		__REG(MV88W8XX8_AIU_PLAY_MODE) &= ~TX_MODE_ENABLE_BITS;
		PRINTK(DEBUG_LEVEL1, "tx_trigger STOP\n");
		PRINTK(DEBUG_LEVEL5, "depth: %d\n", __REG(MV88W8XX8_AIU_TX_BUF_CNT));
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		__REG(MV88W8XX8_AIU_PLAY_MODE) |= TX_MODE_ENABLE_BITS;
		break;

	default:
		ret = -EINVAL;
	}

	return ret;
}

static int mover1_pcm_rx_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;
	int ret = 0;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		__REG(MV88W8XX8_AIU_INT_ENABLE) |= RX_INT_ENABLE_BIT;
		__REG(MV88W8XX8_AIU_REC_MODE) |= RX_MODE_ENABLE_BITS;
		PRINTK(DEBUG_LEVEL1, "rx_trigger START\n");
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		__REG(MV88W8XX8_AIU_REC_MODE) &= ~RX_MODE_ENABLE_BITS;
		PRINTK(DEBUG_LEVEL1, "rx_trigger STOP\n");
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		__REG(MV88W8XX8_AIU_REC_MODE) |= RX_MODE_ENABLE_BITS;
		break;

	default:
		ret = -EINVAL;
	}

	return ret;
}

static snd_pcm_uframes_t mover1_pcm_tx_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mover1_runtime_data *rtd = runtime->private_data;
	int n = (rtd->frameCount - __REG(MV88W8XX8_AIU_TX_BUF_CNT)) % rtd->bufSize;
	PRINTK(DEBUG_LEVEL2, "tx_pointer is %d\n", n);
	return n;
}

static snd_pcm_uframes_t mover1_pcm_rx_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mover1_runtime_data *rtd = runtime->private_data;
	int n = (rtd->frameCount + __REG(MV88W8XX8_AIU_RX_BUF_CNT)) % rtd->bufSize;
	PRINTK(DEBUG_LEVEL2, "rx_pointer is %d\n", n);
	return n;
}

static int mover1_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mover1_runtime_data *rtd;

	runtime->hw = mover1_pcm_hardware;

	rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
	if (!rtd)
		return -ENOMEM;
	runtime->private_data = rtd;

	__REG(MV88W8XX8_AIU_PLAY_MODE) &= ~TX_MODE_ENABLE_BITS;
	__REG(MV88W8XX8_AIU_REC_MODE) &= ~RX_MODE_ENABLE_BITS;
	rtd->irq = -1;

	return 0;
}

static int mover1_pcm_close(struct snd_pcm_substream *substream)
{
	struct mover1_runtime_data *rtd = substream->runtime->private_data;

	kfree(rtd);
	return 0;
}

static struct snd_pcm_ops mover1_pcm_tx_ops = {
	.open		= mover1_pcm_open,
	.close		= mover1_pcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= mover1_pcm_tx_hw_params,
	.hw_free	= mover1_pcm_hw_free,
	.prepare	= mover1_pcm_prepare,
	.trigger	= mover1_pcm_tx_trigger,
	.pointer	= mover1_pcm_tx_pointer,
	.copy		= mover1_pcm_tx_copy,
	.silence	= mover1_pcm_tx_silence,
};

static struct snd_pcm_ops mover1_pcm_rx_ops = {
	.open		= mover1_pcm_open,
	.close		= mover1_pcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= mover1_pcm_rx_hw_params,
	.hw_free	= mover1_pcm_hw_free,
	.prepare	= mover1_pcm_prepare,
	.trigger	= mover1_pcm_rx_trigger,
	.pointer	= mover1_pcm_rx_pointer,
	.copy		= mover1_pcm_rx_copy,
};

static int __init mover1_aiu_probe(struct platform_device *devptr)
{
	struct snd_card *card;
	struct snd_pcm *pcm;
	int err;

	card = snd_card_new(-1, "Mover1-AIU", THIS_MODULE, 0);
	if (card == 0) {
		return -ENOMEM;
	}
	card->private_free = 0;

	err = snd_pcm_new(card, "Mover1-PCM", 0, 1, 1, &pcm);
	if (err < 0) {
		snd_card_free(card);
		return err;
	}

	strcpy(pcm->name, "Mover1 PCM");
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &mover1_pcm_tx_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &mover1_pcm_rx_ops);

	strcpy(card->driver, "mover1-aiu");
	strcpy(card->shortname, "Mover1 AIU");
	strcpy(card->longname, "Mover1 AIU at 0x90007000, irq 30");

	snd_card_set_dev(card, &devptr->dev);

	if ((err = snd_card_register(card)) < 0) {
		snd_card_free(card);
		return err;
	}

	platform_set_drvdata(devptr, card);
	return snd_mover1_pcm_init();
}

static int __devexit mover1_aiu_remove(struct platform_device *devptr)
{
	snd_card_free(platform_get_drvdata(devptr));
	platform_set_drvdata(devptr, NULL);

	/* RESET the interface will reset the pointers and registers */
	__REG(MV88W8XX8_AIU_BIT_RATE) = AIU_SOFT_RESET_BIT;

	return 0;
}

static struct platform_driver mover1_aiu_driver = {
	.probe		= mover1_aiu_probe,
	.remove		= __devexit_p(mover1_aiu_remove),
	.driver		= {
		.name	= "mover1_aiu",
	},
};

static int __init mover1_aiu_init(void)
{
	int err;

	if ((err = platform_driver_register(&mover1_aiu_driver)) < 0)
		return err;
	device = platform_device_register_simple("mover1_aiu", -1, NULL, 0);
	if (IS_ERR(device)) {
		platform_driver_unregister(&mover1_aiu_driver);
		return PTR_ERR(device);
	}
	return 0;
}

static void __exit mover1_aiu_exit(void)
{
	platform_device_unregister(device);
	platform_driver_unregister(&mover1_aiu_driver);
}

module_init(mover1_aiu_init);
module_exit(mover1_aiu_exit);

MODULE_AUTHOR("Michael K. Edwards");
MODULE_DESCRIPTION("Marvell/Plantronics Mover PCM module");
MODULE_LICENSE("GPL");

[-- Attachment #3: Type: text/plain, Size: 345 bytes --]

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

[-- Attachment #4: Type: text/plain, Size: 161 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2007-02-23 22:34 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-02-21 23:33 OSS emulation, period size, and start/stop thresholds Michael K. Edwards
2007-02-22 16:26 ` Takashi Iwai
2007-02-22 21:32   ` Michael K. Edwards
2007-02-23 11:30     ` Takashi Iwai
2007-02-23 22:34       ` Michael K. Edwards

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.