summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@suse.cz>2003-01-27 20:31:11 +0100
committerJaroslav Kysela <perex@suse.cz>2003-01-27 20:31:11 +0100
commitb8038484bf9c60e6e8669d098bb705deda099445 (patch)
treee9cb19e14bffab7ab8b84ad1341be1c91c841bec
parent191c011abbb58966ab1ec30421e082f9802f2c8d (diff)
ALSA update
- added DocBook documentation - added many source comments - simplified proc style interface (per card) - updated PCM scatter-gather routines - moved PM locking outside callbacks
-rw-r--r--Documentation/sound/alsa/DocBook/alsa-driver-api.tmpl99
-rw-r--r--Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl5262
-rw-r--r--include/sound/ac97_codec.h4
-rw-r--r--include/sound/ad1848.h1
-rw-r--r--include/sound/ak4531_codec.h1
-rw-r--r--include/sound/core.h3
-rw-r--r--include/sound/cs46xx.h2
-rw-r--r--include/sound/emu10k1.h21
-rw-r--r--include/sound/gus.h8
-rw-r--r--include/sound/info.h16
-rw-r--r--include/sound/pcm.h12
-rw-r--r--include/sound/pcm_sgbuf.h3
-rw-r--r--include/sound/sb.h1
-rw-r--r--include/sound/sb16_csp.h1
-rw-r--r--include/sound/sndmagic.h56
-rw-r--r--include/sound/trident.h6
-rw-r--r--include/sound/version.h2
-rw-r--r--sound/core/control.c120
-rw-r--r--sound/core/device.c66
-rw-r--r--sound/core/hwdep.c13
-rw-r--r--sound/core/info.c189
-rw-r--r--sound/core/init.c57
-rw-r--r--sound/core/isadma.c21
-rw-r--r--sound/core/memory.c237
-rw-r--r--sound/core/pcm.c33
-rw-r--r--sound/core/pcm_lib.c327
-rw-r--r--sound/core/pcm_memory.c342
-rw-r--r--sound/core/pcm_misc.c86
-rw-r--r--sound/core/pcm_native.c15
-rw-r--r--sound/core/pcm_sgbuf.c133
-rw-r--r--sound/core/rawmidi.c73
-rw-r--r--sound/core/seq/Makefile1
-rw-r--r--sound/core/sound.c33
-rw-r--r--sound/drivers/mpu401/mpu401_uart.c31
-rw-r--r--sound/i2c/l3/uda1341.c50
-rw-r--r--sound/isa/ad1848/ad1848_lib.c7
-rw-r--r--sound/isa/cmi8330.c152
-rw-r--r--sound/isa/cs423x/cs4231_lib.c21
-rw-r--r--sound/isa/es18xx.c17
-rw-r--r--sound/isa/gus/gus_irq.c22
-rw-r--r--sound/isa/gus/gus_mem.c17
-rw-r--r--sound/isa/gus/gus_mem_proc.c41
-rw-r--r--sound/isa/gus/gus_reset.c4
-rw-r--r--sound/isa/sb/sb16_csp.c23
-rw-r--r--sound/isa/sb/sb16_main.c7
-rw-r--r--sound/isa/sb/sb_mixer.c9
-rw-r--r--sound/pci/ac97/ac97_codec.c173
-rw-r--r--sound/pci/ac97/ak4531_codec.c24
-rw-r--r--sound/pci/cmipci.c39
-rw-r--r--sound/pci/cs4281.c59
-rw-r--r--sound/pci/cs46xx/cs46xx_lib.c68
-rw-r--r--sound/pci/emu10k1/emu10k1.c1
-rw-r--r--sound/pci/emu10k1/emu10k1_main.c1
-rw-r--r--sound/pci/emu10k1/emupcm.c2
-rw-r--r--sound/pci/emu10k1/emuproc.c94
-rw-r--r--sound/pci/emu10k1/memory.c14
-rw-r--r--sound/pci/ens1370.c42
-rw-r--r--sound/pci/es1938.c40
-rw-r--r--sound/pci/es1968.c10
-rw-r--r--sound/pci/ice1712/ice1712.c28
-rw-r--r--sound/pci/intel8x0.c47
-rw-r--r--sound/pci/korg1212/korg1212.c25
-rw-r--r--sound/pci/maestro3.c10
-rw-r--r--sound/pci/nm256/nm256.c10
-rw-r--r--sound/pci/rme32.c37
-rw-r--r--sound/pci/rme96.c172
-rw-r--r--sound/pci/rme9652/hammerfall_mem.c4
-rw-r--r--sound/pci/rme9652/hdsp.c25
-rw-r--r--sound/pci/rme9652/rme9652.c25
-rw-r--r--sound/pci/sonicvibes.c24
-rw-r--r--sound/pci/trident/trident_main.c603
-rw-r--r--sound/pci/trident/trident_memory.c42
-rw-r--r--sound/pci/ymfpci/ymfpci_main.c34
-rw-r--r--sound/ppc/pmac.c10
-rw-r--r--sound/usb/usbaudio.c138
75 files changed, 7972 insertions, 1474 deletions
diff --git a/Documentation/sound/alsa/DocBook/alsa-driver-api.tmpl b/Documentation/sound/alsa/DocBook/alsa-driver-api.tmpl
new file mode 100644
index 000000000000..6421246ddac0
--- /dev/null
+++ b/Documentation/sound/alsa/DocBook/alsa-driver-api.tmpl
@@ -0,0 +1,99 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
+
+<book>
+<?dbhtml filename="index.html">
+
+<!-- ****************************************************** -->
+<!-- Header -->
+<!-- ****************************************************** -->
+ <bookinfo>
+ <title>The ALSA Driver API</title>
+
+ <legalnotice>
+ <para>
+ This document is free; 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.
+ </para>
+
+ <para>
+ This document is distributed in the hope that it will be useful,
+ but <emphasis>WITHOUT ANY WARRANTY</emphasis>; without even the
+ implied warranty of <emphasis>MERCHANTABILITY or FITNESS FOR A
+ PARTICULAR PURPOSE</emphasis>. See the GNU General Public License
+ for more details.
+ </para>
+
+ <para>
+ 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
+ </para>
+ </legalnotice>
+
+ </bookinfo>
+
+ <chapter><title>Management of Cards and Devices</title>
+ <sect1><title>Card Managment</title>
+!Esound/core/init.c
+ </sect1>
+ <sect1><title>Device Components</title>
+!Esound/core/device.c
+ </sect1>
+ <sect1><title>KMOD and Device File Entries</title>
+!Esound/core/sound.c
+ </sect1>
+ <sect1><title>Memory Management Helpers</title>
+!Esound/core/memory.c
+!Iinclude/sound/sndmagic.h
+ </sect1>
+ </chapter>
+ <chapter><title>PCM API</title>
+ <sect1><title>PCM Core</title>
+!Esound/core/pcm.c
+!Esound/core/pcm_lib.c
+!Esound/core/pcm_native.c
+ </sect1>
+ <sect1><title>PCM Format Helpers</title>
+!Esound/core/pcm_misc.c
+ </sect1>
+ <sect1><title>PCM Memory Managment</title>
+!Esound/core/pcm_memory.c
+ </sect1>
+ <sect1><title>SG-Buffer Helpers</title>
+!Esound/core/pcm_sgbuf.c
+ </sect1>
+ </chapter>
+ <chapter><title>Control/Mixer API</title>
+ <sect1><title>General Control Interface</title>
+!Esound/core/control.c
+ </sect1>
+ <sect1><title>AC97 Codec API</title>
+!Esound/pci/ac97/ac97_codec.c
+ </sect1>
+ </chapter>
+ <chapter><title>MIDI API</title>
+ <sect1><title>Raw MIDI API</title>
+!Esound/core/rawmidi.c
+ </sect1>
+ <sect1><title>MPU401-UART API</title>
+!Esound/drivers/mpu401/mpu401_uart.c
+ </sect1>
+ </chapter>
+ <chapter><title>Proc Info API</title>
+ <sect1><title>Proc Info Interface</title>
+!Esound/core/info.c
+ </sect1>
+ </chapter>
+ <chapter><title>Miscellaneous Functions</title>
+ <sect1><title>Hardware-Dependent Devices API</title>
+!Esound/core/hwdep.c
+ </sect1>
+ <sect1><title>ISA DMA Helpers</title>
+!Esound/core/isadma.c
+ </sect1>
+ </chapter>
+
+</book>
diff --git a/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl
new file mode 100644
index 000000000000..8cf02dfa80d2
--- /dev/null
+++ b/Documentation/sound/alsa/DocBook/writing-an-alsa-driver.tmpl
@@ -0,0 +1,5262 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
+
+<book>
+<?dbhtml filename="index.html">
+
+<!-- ****************************************************** -->
+<!-- Header -->
+<!-- ****************************************************** -->
+ <bookinfo>
+ <title>Writing an ALSA Driver</title>
+ <author>
+ <firstname>Takashi</firstname>
+ <surname>Iwai</surname>
+ <affiliation>
+ <address>
+ <email>tiwai@suse.de</email>
+ </address>
+ </affiliation>
+ </author>
+
+ <date>Dec. 27, 2002</date>
+ <edition>0.2 (reborn at Christmas)</edition>
+
+ <abstract>
+ <para>
+ This document describes how to write an ALSA (Advanced Linux
+ Sound Architecture) driver.
+ </para>
+ </abstract>
+
+ <legalnotice>
+ <para>
+ Copyright (c) 2002 Takashi Iwai <email>tiwai@suse.de</email>
+ </para>
+
+ <para>
+ This document is free; 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.
+ </para>
+
+ <para>
+ This document is distributed in the hope that it will be useful,
+ but <emphasis>WITHOUT ANY WARRANTY</emphasis>; without even the
+ implied warranty of <emphasis>MERCHANTABILITY or FITNESS FOR A
+ PARTICULAR PURPOSE</emphasis>. See the GNU General Public License
+ for more details.
+ </para>
+
+ <para>
+ 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
+ </para>
+ </legalnotice>
+
+ </bookinfo>
+
+<!-- ****************************************************** -->
+<!-- Preface -->
+<!-- ****************************************************** -->
+ <preface id="preface">
+ <title>Preface</title>
+ <para>
+ This document describes how to write an
+ <ulink url="http://www.alsa-project.org/"><citetitle>
+ ALSA (Advanced Linux Sound Architecture)</citetitle></ulink>
+ driver. The document focuses mainly on the PCI soundcard.
+ In the case of other device types, the API might
+ be different, too. However, at least the ALSA kernel API is
+ consistent, and therefore it would be still a bit help for
+ writing them.
+ </para>
+
+ <para>
+ The target of this document is ones who already have enough
+ skill of C language and have the basic knowledge of linux
+ kernel programming. This document doesn't explain the general
+ topics of linux kernel codes and doesn't cover the detail of
+ implementation of each low-level driver. It describes only how is
+ the standard way to write a PCI sound driver on ALSA.
+ </para>
+
+ <para>
+ If you are already familiar with the older ALSA ver.0.5.x, you
+ can check the drivers such as <filename>es1938.c</filename> or
+ <filename>maestro3.c</filename> which have also almost the same
+ code-base in the ALSA 0.5.x tree, so you can compare the differences.
+ </para>
+
+ <para>
+ This document is still a draft version. Any feedbacks and
+ corrections, please!!
+ </para>
+ </preface>
+
+
+<!-- ****************************************************** -->
+<!-- File Tree Structure -->
+<!-- ****************************************************** -->
+ <chapter id="file-tree">
+ <title>File Tree Structure</title>
+
+ <section id="file-tree-general">
+ <title>General</title>
+ <para>
+ The ALSA drivers are provided in the two ways.
+ </para>
+
+ <para>
+ One is the the trees provided as a tarball or via cvs from the
+ ALSA's ftp site, and another is the 2.5 (or later) Linux kernel
+ tree. To synchronize both, the ALSA driver tree is split to
+ two different trees: alsa-kernel and alsa-driver. The former
+ contains purely the source codes for the Linux 2.5 (or later)
+ tree. This tree is designed only for compilation on 2.5 or
+ later environment. The latter, alsa-driver, contains many subtle
+ files for compiling the ALSA driver on the outside of Linux
+ kernel like configure script, the wrapper functions for older,
+ 2.2 and 2.4 kernels, to adapt the latest kernel API,
+ and additional drivers which are still in development or in
+ tests. The drivers in alsa-driver tree will be moved to
+ alsa-kernel (eventually 2.5 kernel tree) once when they are
+ finished and confirmed to work fine.
+ </para>
+
+ <para>
+ The file tree structure of ALSA driver is depicted below. Both
+ alsa-kernel and alsa-driver have almost the same file
+ structure, except for <quote>core</quote> directory. It's
+ named as <quote>acore</quote> in alsa-driver tree.
+
+ <example>
+ <title>ALSA File Tree Structure</title>
+ <literallayout>
+ sound
+ /core
+ /oss
+ /seq
+ /oss
+ /instr
+ /ioctl32
+ /include
+ /drivers
+ /mpu401
+ /opl3
+ /i2c
+ /l3
+ /synth
+ /emux
+ /pci
+ /(cards)
+ /isa
+ /(cards)
+ /arm
+ /ppc
+ /sparc
+ /usb
+ /pcmcia /(cards)
+ /oss
+ </literallayout>
+ </example>
+ </para>
+ </section>
+
+ <section id="file-tree-core-directory">
+ <title>core directory</title>
+ <para>
+ This directory contains the middle layer, that is, the heart
+ of ALSA drivers. In this directory, the native ALSA modules are
+ stored. The sub-directories contain different modules and are
+ dependent upon the kernel config.
+ </para>
+
+ <section id="file-tree-core-directory-oss">
+ <title>core/oss</title>
+
+ <para>
+ The codes for PCM and mixer OSS emulation modules are stored
+ in this directory. The rawmidi OSS emulation is included in
+ the ALSA rawmidi code since it's quite small. The sequencer
+ code is stored in core/seq/oss directory (see
+ <link linkend="file-tree-core-directory-seq-oss"><citetitle>
+ below</citetitle></link>).
+ </para>
+ </section>
+
+ <section id="file-tree-core-directory-ioctl32">
+ <title>core/ioctl32</title>
+
+ <para>
+ This directory contains the 32bit-ioctl wrappers for 64bit
+ architectures such like x86-64, ppc64 and sparc64. For 32bit
+ and alpha architectures, these are not compiled.
+ </para>
+ </section>
+
+ <section id="file-tree-core-directory-seq">
+ <title>core/seq</title>
+ <para>
+ This and its sub-directories are for the ALSA
+ sequencer. This directory contains the sequencer core and
+ primary sequencer modules such like snd-seq-midi,
+ snd-seq-virmidi, etc. They are compiled only when
+ <constant>CONFIG_SND_SEQUENCER</constant> is set in the kernel
+ config.
+ </para>
+ </section>
+
+ <section id="file-tree-core-directory-seq-oss">
+ <title>core/seq/oss</title>
+ <para>
+ This contains the OSS sequencer emulation codes.
+ </para>
+ </section>
+
+ <section id="file-tree-core-directory-deq-instr">
+ <title>core/seq/instr</title>
+ <para>
+ This directory contains the modules for the sequencer
+ instrument layer.
+ </para>
+ </section>
+ </section>
+
+ <section id="file-tree-include-directory">
+ <title>include directory</title>
+ <para>
+ This is the place for the public header files of ALSA drivers,
+ which are to be exported to the user-space, or included by
+ several files at different directories. Basically, the private
+ header files should not be placed in this directory, but you may
+ still find files there, due to historical reason :)
+ </para>
+ </section>
+
+ <section id="file-tree-drivers-directory">
+ <title>drivers directory</title>
+ <para>
+ This directory contains the non-architecture-specific
+ codes. For example, the dummy pcm driver and the serial MIDI
+ driver are found in this directory. In the sub-directories,
+ there are the codes for components which are independent from
+ bus and cpu architectures.
+ </para>
+
+ <section id="file-tree-drivers-directory-mpu401">
+ <title>drivers/mpu401</title>
+ <para>
+ The MPU401 and MPU401-UART modules are stored here.
+ </para>
+ </section>
+
+ <section id="file-tree-drivers-directory-opl3">
+ <title>drivers/opl3</title>
+ <para>
+ The OPL3 FM-synth stuff is found here.
+ </para>
+ </section>
+ </section>
+
+ <section id="file-tree-i2c-directory">
+ <title>i2c directory</title>
+ <para>
+ This contains the ALSA i2c components.
+ </para>
+
+ <para>
+ Although there is a standard i2c layer on Linux, ALSA uses its
+ own i2c codes for some cards, because the soundcard needs only a
+ simple operation and the standard API is too complicated for
+ such a purpose.
+ </para>
+
+ <section id="file-tree-i2c-directory-l3">
+ <title>i2c/l3</title>
+ <para>
+ This is a sub-directory for ARM L3 i2c.
+ </para>
+ </section>
+ </section>
+
+ <section id="file-tree-synth-directory">
+ <title>synth directory</title>
+ <para>
+ This contains the synth middle-level modules.
+ </para>
+
+ <para>
+ So far, there is only Emu8000/Emu10k1 synth driver under
+ synth/emux sub-directory.
+ </para>
+ </section>
+
+ <section id="file-tree-pci-directory">
+ <title>pci directory</title>
+ <para>
+ This and its sub-directories hold the top-level card modules
+ for PCI soundcards.
+ </para>
+
+ <para>
+ The drivers compiled from a single file is stored directly on
+ pci directory, while the drivers with several source files are
+ stored on its own sub-directory (e.g. emu10k1, ice1712).
+ </para>
+ </section>
+
+ <section id="file-tree-isa-directory">
+ <title>isa directory</title>
+ <para>
+ This and its sub-directories hold the top-level card modules
+ for ISA soundcards.
+ </para>
+ </section>
+
+ <section id="file-tree-arm-ppc-sparc-directories">
+ <title>arm, ppc, and sparc directories</title>
+ <para>
+ These are for the top-level card modules which are
+ architecture specific.
+ </para>
+ </section>
+
+ <section id="file-tree-usb-directory">
+ <title>usb directory</title>
+ <para>
+ This contains the USB-audio driver. On the latest version, the
+ USB MIDI driver is integrated together with usb-audio driver.
+ </para>
+ </section>
+
+ <section id="file-tree-pcmcia-directory">
+ <title>pcmcia directory</title>
+ <para>
+ The PCMCIA, especially PCCard drivers will go here. CardBus
+ drivers will be on pci directory, because its API is identical
+ with the standard PCI cards.
+ </para>
+
+ <para>
+ At this moment, only VX-pocket driver exists.
+ </para>
+ </section>
+
+ <section id="file-tree-oss-directory">
+ <title>oss directory</title>
+ <para>
+ The OSS/Lite source files are stored here on Linux 2.5 (or
+ later) tree. (In the ALSA driver tarball, it's empty, of course :)
+ </para>
+ </section>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Basic Flow for PCI Drivers -->
+<!-- ****************************************************** -->
+ <chapter id="basic-flow">
+ <title>Basic Flow for PCI Drivers</title>
+
+ <section id="basic-flow-outline">
+ <title>Outline</title>
+ <para>
+ The minimum flow of PCI soundcard is like the following:
+
+ <itemizedlist>
+ <listitem><para>define the PCI ID table (see the section
+ <link linkend="pci-resource-entries"><citetitle>PCI Entries
+ </citetitle></link>).</para></listitem>
+ <listitem><para>create <function>probe()</function> callback.</para></listitem>
+ <listitem><para>create <function>remove()</function> callback.</para></listitem>
+ <listitem><para>create pci_driver table which contains the three pointers above.</para></listitem>
+ <listitem><para>create <function>init()</function> function just calling <function>pci_module_init()</function> to register the pci_driver table defined above.</para></listitem>
+ <listitem><para>create <function>exit()</function> function to call <function>pci_unregister_driver()</function> function.</para></listitem>
+ </itemizedlist>
+ </para>
+ </section>
+
+ <section id="basic-flow-example">
+ <title>Full Code Example</title>
+ <para>
+ The code example is shown below. Some parts are kept
+ unimplemented at this moment but will be filled in the
+ succeeding sections. The numbers in comment lines of
+ <function>snd_mychip_probe()</function> function are the
+ markers.
+
+ <example>
+ <title>Basic Flow for PCI Drivers Example</title>
+ <programlisting>
+<![CDATA[
+ #include <sound/driver.h>
+ #include <linux/init.h>
+ #include <linux/pci.h>
+ #include <linux/slab.h>
+ #include <sound/core.h>
+ #define SNDRV_GET_ID
+ #include <sound/initval.h>
+
+ // module parameters (see "Module Parameters")
+ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+ static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+ // definition of the chip-specific record
+ typedef struct snd_mychip mychip_t;
+ struct snd_mychip {
+ snd_card_t *card;
+ // rest of implementation will be in the section
+ // "PCI Resource Managements"
+ };
+
+ // this should be go into <sound/sndmagic.h>
+ // (see "Management of Cards and Components")
+ #define mychip_t_magic 0xa15a4501
+
+ // chip-specific destructor
+ // (see "PCI Resource Managements")
+ static int snd_mychip_free(mychip_t *chip)
+ {
+ // will be implemented later...
+ }
+
+ // component-destructor
+ // (see "Management of Cards and Components")
+ static int snd_mychip_dev_free(snd_device_t *device)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ device->device_data, return -ENXIO);
+ return snd_mychip_free(chip);
+ }
+
+ // chip-specific constructor
+ // (see "Management of Cards and Components")
+ static int __devinit snd_mychip_create(snd_card_t *card,
+ struct pci_device *pci,
+ mychip_t *rchip)
+ {
+ mychip_t *chip;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_mychip_dev_free,
+ };
+
+ *rchip = NULL;
+
+ // check PCI availability here
+ // (see "PCI Resource Managements")
+
+ // allocate a chip-specific data with magic-alloc
+ chip = snd_magic_kcalloc(mychip_t, 0, GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ chip->card = card;
+
+ // rest of initialization here; will be implemented
+ // later, see "PCI Resource Managements"
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ chip, &ops)) < 0) {
+ snd_mychip_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ return 0;
+ }
+
+ // constructor -- see "Constructor" sub-section
+ static int __devinit snd_mychip_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+ {
+ static int dev;
+ snd_card_t *card;
+ mychip_t *chip;
+ int err;
+
+ // (1)
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ // (2)
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+ if (card == NULL)
+ return -ENOMEM;
+
+ // (3)
+ if ((err = snd_mychip_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ // (4)
+ // implemented later
+
+ // (5)
+ strcpy(card->driver, "My Chip");
+ strcpy(card->shortname, "My Own Chip 123");
+ sprintf(card->longname, "%s at 0x%lx irq %i",
+ card->shortname, chip->ioport, chip->irq);
+
+ // (6)
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ // (7)
+ pci_set_drvdata(pci, chip);
+ dev++;
+ return 0;
+ }
+
+ // destructor -- see "Destructor" sub-section
+ static void __devexit snd_mychip_remove(struct pci_dev *pci)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(pci), return);
+ if (chip)
+ snd_card_free(chip->card);
+ pci_set_drvdata(pci, NULL);
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor">
+ <title>Constructor</title>
+ <para>
+ The real constructor of PCI drivers is probe callback. The
+ probe callback and other component-constructors which are called
+ from probe callback should be defined with
+ <parameter>__devinit</parameter> prefix. You
+ cannot use <parameter>__init</parameter> prefix for them,
+ because any PCI device could be a hotplug device.
+ </para>
+
+ <para>
+ In the probe callback, the following scheme is often used.
+ </para>
+
+ <section id="basic-flow-constructor-device-index">
+ <title>1) Check and increment the device index.</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int dev;
+ ....
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ where enable[dev] is the module option.
+ </para>
+
+ <para>
+ At each time probe callback is called, check the
+ availability of the device. If not available, simply increment
+ the device index and returns. dev will be incremented also
+ later (<link
+ linkend="basic-flow-constructor-set-pci"><citetitle>step
+ 7</citetitle></link>).
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-create-card">
+ <title>2) Create a card instance</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_card_t *card;
+ ....
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The detail will be explained in the section
+ <link linkend="card-management-card-instance"><citetitle>
+ Management of Cards and Components</citetitle></link>.
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-create-main">
+ <title>3) Create a main component</title>
+ <para>
+ In this part, the PCI resources are allocated.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mychip_t *chip;
+ ....
+ if ((err = snd_mychip_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ The detail will be explained in the section <link
+ linkend="pci-resource"><citetitle>PCI Resource
+ Managements</citetitle></link>.
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-create-other">
+ <title>4) Create other components, such as mixer, MIDI, etc.</title>
+ <para>
+ Here you define the basic components such as
+ <link linkend="pcm-interface"><citetitle>PCM</citetitle></link>,
+ mixer (e.g. <link linkend="api-ac97"><citetitle>AC97</citetitle></link>),
+ MIDI (e.g. <link linkend="midi-interface"><citetitle>MPU-401</citetitle></link>),
+ and other interfaces.
+ Also, if you want a <link linkend="proc-interface"><citetitle>proc
+ file</citetitle></link>, define it here, too.
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-main-component">
+ <title>5) Set the driver ID and name strings.</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ strcpy(card->driver, "My Chip");
+ strcpy(card->shortname, "My Own Chip 123");
+ sprintf(card->longname, "%s at 0x%lx irq %i",
+ card->shortname, chip->ioport, chip->irq);
+]]>
+ </programlisting>
+ </informalexample>
+
+ The driver field holds the minimal ID string of the
+ chip. This is referred by alsa-lib's configurator, so keep it
+ simple but unique.
+ Even the same driver can have different driver IDs to
+ distinguish the functionality of each chip type.
+ </para>
+
+ <para>
+ The shortname field is a string shown as more verbose
+ name. The longname field contains the information which is
+ shown in <filename>/proc/asound/cards</filename>.
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-register-card">
+ <title>6) Register the card instance.</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Will be explained in the section <link
+ linkend="card-management-registration"><citetitle>Management
+ of Cards and Components</citetitle></link>, too.
+ </para>
+ </section>
+
+ <section id="basic-flow-constructor-set-pci">
+ <title>7) Set the PCI driver data and return zero.</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ pci_set_drvdata(pci, chip);
+ dev++;
+ return 0;
+]]>
+ </programlisting>
+ </informalexample>
+
+ In the above, the chip record is stored. This pointer is
+ referred in the remove callback and power-management
+ callbacks, too.
+ If the card doesn't support the suspend/resume, you can store
+ the card pointer instead of the chip pointer, so that
+ <function>snd_card_free</function> can be called directly
+ without cast in the remove callback. But anyway, be sure
+ which pointer is used.
+ </para>
+ </section>
+ </section>
+
+ <section id="basic-flow-destructor">
+ <title>Destructor</title>
+ <para>
+ The destructor, remove callback, simply releases the card
+ instance. Then the ALSA middle layer will release all the
+ attached components automatically.
+ </para>
+
+ <para>
+ It would be typically like the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void __devexit snd_mychip_remove(struct pci_dev *pci)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(pci), return);
+ if (chip)
+ snd_card_free(chip->card);
+ pci_set_drvdata(pci, NULL);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ The above code assumes that the chip is allocated
+ with snd_magic stuff and
+ has the field to hold the card pointer (see <link
+ linkend="card-management"><citetitle>the next
+ section</citetitle></link>).
+ </para>
+ </section>
+
+ <section id="basic-flow-header-files">
+ <title>Header Files</title>
+ <para>
+ For the above example, at least the following include files
+ are necessary.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #include <sound/driver.h>
+ #include <linux/init.h>
+ #include <linux/pci.h>
+ #include <linux/slab.h>
+ #include <sound/core.h>
+ #define SNDRV_GET_ID
+ #include <sound/initval.h>
+]]>
+ </programlisting>
+ </informalexample>
+
+ where the last twos are necessary only when module options are
+ defined in the source file. If the codes are split to several
+ files, the file without module options don't need them.
+ </para>
+
+ <para>
+ In addition to them, you'll need
+ <filename>&lt;linux/interrupt.h&gt;</filename> for the interrupt
+ handling, and <filename>&lt;asm/io.h&gt;</filename> for the i/o
+ access. If you use <function>mdelay()</function> or
+ <function>udelay()</function> functions, you'll need to include
+ <filename>&lt;linux/delay.h&gt;</filename>, too.
+ </para>
+
+ <para>
+ The ALSA interfaces like PCM or control API are define in other
+ header files as <filename>&lt;sound/xxx.h&gt;</filename>.
+ They have to be included after
+ <filename>&lt;sound/core.h&gt;</filename>.
+ </para>
+
+ </section>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Management of Cards and Components -->
+<!-- ****************************************************** -->
+ <chapter id="card-management">
+ <title>Management of Cards and Components</title>
+
+ <section id="card-management-card-instance">
+ <title>Card Instance</title>
+ <para>
+ For each soundcard, a <quote>card</quote> record must be allocated.
+ </para>
+
+ <para>
+ A card record is the headquarters of the soundcard. It manages
+ the list of whole devices (components) on the soundcard, such as
+ PCM, mixers, MIDI, synthesizer, and so on. Also, the card
+ record holds the ID and the name strings of the card, manages
+ the root of proc files, and controls the power-management states
+ and hotplug disconnections. The component list on the card
+ record is used to manage the proper releases of resources at
+ destruction.
+ </para>
+
+ <para>
+ As mentioned above, to create a card instance, call
+ <function>snd_card_new()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_card_t *card;
+ card = snd_card_new(index, id, module, extra_size);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The function takes four arguments, the card-index number, the
+ id string, the module pointer (usually
+ <constant>THIS_MODULE</constant>),
+ and the size of extra-data space. The last argument is used to
+ allocate card-&gt;private_data for the
+ chip-specific data. Note that this data
+ <emphasis>is</emphasis> allocated by
+ <function>snd_card_new()</function>.
+ </para>
+ </section>
+
+ <section id="card-management-component">
+ <title>Components</title>
+ <para>
+ After the card is created, you can attach the components
+ (devices) to the card instance. On ALSA driver, a component is
+ represented as a <type>snd_device_t</type> object.
+ A component can be a PCM instance, a control interface, a raw
+ MIDI interface, etc. Each of such instances has one component
+ entry.
+ </para>
+
+ <para>
+ A component can be created via
+ <function>snd_device_new()</function> function.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_device_new(card, SNDRV_DEV_XXX, chip, &ops);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ This takes the card pointer, the device-level
+ (<constant>SNDRV_DEV_XXX</constant>), the data pointer, and the
+ callback pointers (<parameter>&amp;ops</parameter>). The
+ device-level defines the type of components and the order of
+ registration and de-registration. For most of components, the
+ device-level is already defined. For a user-defined component,
+ you can use <constant>SNDRV_DEV_LOWLEVEL</constant>.
+ </para>
+
+ <para>
+ This function itself doesn't allocate the data space. The data
+ must be allocated manually beforehand, and its pointer is passed
+ as the argument. This pointer is used as the identifier
+ (<parameter>chip</parameter> in the above example) for the
+ instance.
+ </para>
+
+ <para>
+ Each ALSA pre-defined component such as ac97 or pcm calls
+ <function>snd_device_new()</function> inside its
+ constructor. The destructor for each component is defined in the
+ callback pointers. Hence, you don't need to take care of
+ calling a destructor for such a component.
+ </para>
+
+ <para>
+ If you would like to create your own component, you need to
+ set the destructor function to dev_free callback in
+ <parameter>ops</parameter>, so that it can be released
+ automatically via <function>snd_card_free()</function>. The
+ example will be shown later as an implementation of a
+ chip-specific data.
+ </para>
+ </section>
+
+ <section id="card-management-chip-specific">
+ <title>Chip-Specific Data</title>
+ <para>
+ The chip-specific information, e.g. the i/o port address, its
+ resource pointer, or the irq number, is stored in the
+ chip-specific record.
+ Usually, the chip-specific record is typedef'ed as
+ <type>xxx_t</type> like the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ typedef struct snd_mychip mychip_t;
+ struct snd_mychip {
+ ....
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ You might have objections against such a typedef, but this
+ typedef is necessary if you use a <quote>magic-cast</quote>
+ (explained <link
+ linkend="card-management-chip-what-advantage"><citetitle>later</citetitle></link>).
+ </para>
+
+ <para>
+ In general, there are two ways to allocate the chip record.
+ </para>
+
+ <section id="card-management-chip-specific-snd-card-new">
+ <title>1. Allocating via <function>snd_card_new()</function>.</title>
+ <para>
+ As mentioned above, you can pass the extra-data-length to the 4th argument of <function>snd_card_new()</function>, i.e.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, sizeof(mychip_t));
+]]>
+ </programlisting>
+ </informalexample>
+
+ whether <type>mychip_t</type> is the type of the chip record.
+ </para>
+
+ <para>
+ In return, the allocated record can be accessed as
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mychip_t *chip = (mychip_t *)card->private_data;
+]]>
+ </programlisting>
+ </informalexample>
+
+ With this method, you don't have to allocate twice. But you
+ cannot use <quote>magic-cast</quote> for this record pointer,
+ instead.
+ </para>
+ </section>
+
+ <section id="card-management-chip-specific-allocate-extra">
+ <title>2. Allocating an extra device.</title>
+
+ <para>
+ After allocating a card instance via
+ <function>snd_card_new()</function> (with
+ <constant>NULL</constant> on the 4th arg), call
+ <function>snd_magic_kcalloc()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_card_t *card;
+ mychip_t *chip;
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, NULL);
+ .....
+ chip = snd_magic_kcalloc(mychip_t, 0, GFP_KERNEL);
+]]>
+ </programlisting>
+ </informalexample>
+
+ Once when the record is allocated via snd_magic stuff, you
+ can use <quote>magic-cast</quote> for the void pointer.
+ </para>
+
+ <para>
+ The chip record should have the field to hold the card
+ pointer at least,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ struct snd_mychip {
+ snd_card_t *card;
+ ....
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Then, set the card pointer in the returned chip instance.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ chip->card = card;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Also, you need to define a magic-value for <type>mychip_t</type>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #define mychip_t_magic 0xa15a4501
+]]>
+ </programlisting>
+ </informalexample>
+ (the detail will be described in the
+ <link linkend="card-management-chip-what-advantage"><citetitle>
+ next</citetitle></link> subsection).
+ </para>
+
+ <para>
+ Next, initialize the fields, and register this chip
+ record as a low-level device with a specified
+ <parameter>ops</parameter>,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static snd_device_ops_t ops = {
+ .dev_free = snd_mychip_dev_free,
+ };
+ ....
+ snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+]]>
+ </programlisting>
+ </informalexample>
+
+ <function>snd_mychip_dev_free()</function> is the
+ device-destructor function, which will call the real
+ destructor.
+ </para>
+
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_mychip_dev_free(snd_device_t *device)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t, device->device_data,
+ return -ENXIO);
+ return snd_mychip_free(chip);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <function>snd_mychip_free()</function> is the real destructor.
+ </para>
+ </section>
+
+ <section id="card-management-chip-what-advantage">
+ <title>Not a magic but a logic</title>
+
+ <para>Now, you might have a question: What is the advantage of the
+ second method? Obviously, it looks far more complicated.</para>
+ <para>
+ As I wrote many times, the second method allows a
+ <quote>magic-cast</quote> for <type>mychip_t</type>. If you
+ have a void pointer (such as
+ pcm-&gt;private_data), the pointer type
+ is unknown at the compile time, and you cannot know even if a
+ wrong pointer type is passed. The compiler would accept
+ it. The magic-cast checks the pointer type at the runtime (and
+ whether it's a null pointer, too). Hence, the cast will be
+ much safer and good for debugging.
+ </para>
+
+ <para>
+ As you have already seen, allocation with a magic-header can
+ be done via <function>snd_magic_kmalloc()</function> or
+ <function>snd_magic_kcalloc()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mychip_t *chip;
+ chip = snd_magic_kmalloc(mychip_t, 0, GFP_KERNEL);
+ chip = snd_magic_kcalloc(mychip_t, 0, GFP_KERNEL);
+]]>
+ </programlisting>
+ </informalexample>
+
+ The difference of these two functions is whether the area is
+ zero-cleared (<function>kcalloc</function>) or not
+ (<function>kmalloc</function>).
+ </para>
+
+ <para>
+ The first argument of the allocator is the type of the
+ record. The magic-constant has to be defined for this type
+ beforehand. In this case, we'll need to define
+ <constant>mychip_t_magic</constant>, for example, as already
+ seen,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #define mychip_t_magic 0xa15a4501
+]]>
+ </programlisting>
+ </informalexample>
+
+ The value is arbitrary but should be unique.
+ This is usually defined in
+ <filename>&lt;include/sndmagic.h&gt;</filename> or
+ <filename>&lt;include/amagic.h&gt;</filename> for alsa-driver tree,
+ but you may define it locally in the code at the early
+ development stage, since changing
+ <filename>sndmagic.h</filename> will lead to the recompilation
+ of the whole driver codes.
+ </para>
+
+ <para>
+ The second argument is the extra-data length. It is usually
+ zero. The third argument is the flags to be passed to kernel
+ memory allocator, <constant>GFP_XXX</constant>. Normally,
+ <constant>GFP_KERNEL</constant> is passed.
+ </para>
+
+ <para>
+ For casting a pointer, use
+ <function>snd_magic_cast()</function> macro:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mychip_t *chip = snd_magic_cast(mychip_t, source_pointer, action);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <parameter>source_pointer</parameter> is the pointer to
+ be casted (e.g. pcm-&gt;private_data), and
+ <parameter>action</parameter> is the action to do if the cast
+ fails (e.g. return <constant>-EINVAL</constant>).
+ </para>
+
+ <para>
+ For releasing the magic-allocated data, you need to call
+ <function>snd_magic_kfree()</function> function instead of
+ <function>kfree()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_magic_kfree(chip);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ If you call <function>kfree()</function> for the
+ magic-allocated value, it will lead to memory leaks.
+ When the ALSA drivers are compiled with
+ <constant>CONFIG_SND_DEBUG_MEMORY</constant> kernel config (or
+ configured with <option>--with-debug=full</option>), the
+ non-matching free will be checked and you'll see warning
+ messages.
+ </para>
+
+ <para>
+ If you are 100% sure that your code is bug-free, you can
+ compile the driver without
+ <constant>CONFIG_SND_DEBUG_MEMORY</constant> kernel config,
+ so that the magic-allocator and the magic-cast will be
+ replaced to the normal kmalloc and cast.
+ </para>
+ </section>
+ </section>
+
+ <section id="card-management-registration">
+ <title>Registration and Release</title>
+ <para>
+ After all components are assigned, register the card instance
+ by calling <function>snd_card_register()</function>. The access
+ to the device files are enabled at this point. That is, before
+ <function>snd_card_register()</function> is called, the
+ components are safely inaccessible from external side. If this
+ call fails, exit the probe function after releasing the card via
+ <function>snd_card_free()</function>.
+ </para>
+
+ <para>
+ For releasing the card instance, you can call simply
+ <function>snd_card_free()</function>. As already mentioned, all
+ components are released automatically by this call.
+ </para>
+
+ <para>
+ As further notes, the destructors (both
+ <function>snd_mychip_dev_free</function> and
+ <function>snd_mychip_free</function>) cannot be defined with
+ <parameter>__devexit</parameter> prefix, because they may be
+ called from the constructor, too, at the false path.
+ </para>
+
+ <para>
+ For a device which allows hotplugging, you can use
+ <function>snd_card_free_in_thread</function>. This one will
+ postpone the destruction and wait in a kernel-thread until all
+ devices are closed.
+ </para>
+
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- PCI Resource Managements -->
+<!-- ****************************************************** -->
+ <chapter id="pci-resource">
+ <title>PCI Resource Managements</title>
+
+ <section id="pci-resource-example">
+ <title>Full Code Example</title>
+ <para>
+ In this section, we'll finish the chip-specific constructor,
+ destructor and PCI entries. The example code is shown first,
+ below.
+
+ <example>
+ <title>PCI Resource Managements Example</title>
+ <programlisting>
+<![CDATA[
+ struct snd_mychip {
+ snd_card_t *card;
+ struct pci_dev *pci;
+
+ unsigned long port;
+ struct resource *res_port;
+
+ int irq;
+ };
+
+ static int snd_mychip_free(mychip_t *chip)
+ {
+ // disable hardware here if any
+ // (not implemented in this document)
+
+ // release the i/o port
+ if (chip->res_port) {
+ release_resource(chip->res_port);
+ kfree_nocheck(chip->res_port);
+ }
+ // release the irq
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+ // release the data
+ snd_magic_kfree(chip);
+ return 0;
+ }
+
+ // chip-specific constructor
+ static int __devinit snd_mychip_create(snd_card_t *card,
+ struct pci_dev *pci,
+ mychip_t **rchip)
+ {
+ mychip_t *chip;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_mychip_dev_free,
+ };
+
+ *rchip = NULL;
+
+ // check PCI availability (28bit DMA)
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (!pci_dma_supported(pci, 0x0fffffff)) {
+ printk(KERN_ERR "error to set 28bit mask DMA\n");
+ return -ENXIO;
+ }
+ pci_set_dma_mask(pci, 0x0fffffff);
+
+ chip = snd_magic_kcalloc(mychip_t, 0, GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ // initialize the stuff
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ // (1) PCI resource allocation
+ chip->port = pci_resource_start(pci, 0);
+ if ((chip->res_port = request_region(chip->port, 8,
+ "My Chip")) == NULL) {
+ snd_mychip_free(chip);
+ printk(KERN_ERR "cannot allocate the port\n");
+ return -EBUSY;
+ }
+ if (request_irq(pci->irq, snd_mychip_interrupt,
+ SA_INTERRUPT|SA_SHIRQ, "My Chip",
+ (void *)chip)) {
+ snd_mychip_free(chip);
+ printk(KERN_ERR "cannot grab irq\n");
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+
+ // (2) initialization of the chip hardware
+ // (not implemented in this document)
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+ chip, &ops)) < 0) {
+ snd_mychip_free(chip);
+ return err;
+ }
+ *rchip = chip;
+ return 0;
+ }
+
+ // PCI IDs
+ static struct pci_device_id snd_mychip_ids[] __devinitdata = {
+ { PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
+ ....
+ { 0, }
+ };
+ MODULE_DEVICE_TABLE(pci, snd_mychip_ids);
+
+ // pci_driver definition
+ static struct pci_driver driver = {
+ .name = "My Own Chip",
+ .id_table = snd_mychip_ids,
+ .probe = snd_mychip_probe,
+ .remove = __devexit_p(snd_mychip_remove),
+ };
+
+ // initialization of the module
+ static int __init alsa_card_mychip_init(void)
+ {
+ int err;
+
+ if ((err = pci_module_init(&driver)) < 0) {
+ #ifdef MODULE
+ printk(KERN_ERR "My chip soundcard not found "
+ "or device busy\n");
+ #endif
+ return err;
+ }
+ return 0;
+ }
+
+ // clean up the module
+ static void __exit alsa_card_mychip_exit(void)
+ {
+ pci_unregister_driver(&driver);
+ }
+
+ module_init(alsa_card_mychip_init)
+ module_exit(alsa_card_mychip_exit)
+
+ EXPORT_NO_SYMBOLS; /* for old kernels only */
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="pci-resource-some-haftas">
+ <title>Some Hafta's</title>
+ <para>
+ The allocation of PCI resources is done in the
+ <function>probe()</function> function, and usually an extra
+ <function>xxx_create()</function> function is written for this
+ purpose.
+ </para>
+
+ <para>
+ In the case of PCI devices, you have to call at first
+ <function>pci_enable_device()</function> function before
+ allocating resources. Also, you need to set the proper PCI DMA
+ mask to limit the accessed i/o range. In some cases, you might
+ need to call <function>pci_set_master()</function> function,
+ too.
+ </para>
+
+ <para>
+ Suppose the 28bit mask, and the code to be added would be like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+ if (!pci_dma_supported(pci, 0x0fffffff)) {
+ printk(KERN_ERR "error to set 28bit mask DMA\n");
+ return -ENXIO;
+ }
+ pci_set_dma_mask(pci, 0x0fffffff);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </section>
+
+ <section id="pci-resource-resource-allocation">
+ <title>Resource Allocation</title>
+ <para>
+ The allocation of ports and irqs are done via standard kernel
+ functions. Unlike ALSA ver.0.5.x., there are no helpers for
+ that. And these resources must be released in the destructor
+ function (see below). Also, on ALSA 0.9.x, you don't need
+ allocate (pseudo-)DMA for PCI like 0.5.x.
+ </para>
+
+ <para>
+ Now assume that this PCI device has an I/O port with 8 bytes
+ and an interrupt. Then <type>mychip_t</type> will have the
+ following fields:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ struct snd_mychip {
+ snd_card_t *card;
+
+ unsigned long port;
+ struct resource *res_port;
+
+ int irq;
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ For an i/o port (and also a memory region), you need to have
+ the resource pointer for the standard resource management. For
+ an irq, you have to keep only the irq number (integer). But you
+ need to initialize this number as -1 before actual allocation,
+ since irq 0 is valid. The port address and its resource pointer
+ can be initialized as null by
+ <function>snd_magic_kcalloc()</function> automatically, so you
+ don't have to take care of it.
+ </para>
+
+ <para>
+ The allocation of an i/o port is done like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ chip->port = pci_resource_start(pci, 0);
+ if ((chip->res_port = request_region(chip->port, 8,
+ "My Chip")) == NULL) {
+ printk(KERN_ERR "cannot allocate the port 0x%lx\n",
+ chip->port);
+ snd_mychip_free(chip);
+ return -EBUSY;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ It will reserve the i/o port region of 8 bytes of the given
+ PCI device. The returned value, chip-&gt;res_port, is allocated
+ via <function>kmalloc()</function> by
+ <function>request_region()</function>. The pointer must be
+ released via <function>kfree()</function>, but there is some
+ problem regarding this. This issue will be explained more below.
+ </para>
+
+ <para>
+ The allocation of an interrupt source is done like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if (request_irq(pci->irq, snd_mychip_interrupt,
+ SA_INTERRUPT|SA_SHIRQ, "My Chip",
+ (void *)chip)) {
+ snd_mychip_free(chip);
+ printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <function>snd_mychip_interrupt()</function> is the
+ interrupt handler defined <link
+ linkend="pcm-interface-interrupt-handler"><citetitle>later</citetitle></link>.
+ Note that chip-&gt;irq should be defined
+ only when <function>request_irq()</function> succeeded.
+ </para>
+
+ <para>
+ On the PCI bus, the interrupts can be shared. Thus,
+ <constant>SA_SHIRQ</constant> is given as the interrupt flag of
+ <function>request_irq()</function>.
+ </para>
+
+ <para>
+ The last argument of <function>request_irq()</function> is the
+ data pointer passed to the interrupt handler. Usually, the
+ chip-specific record is used for that, but you can use what you
+ like, too.
+ </para>
+
+ <para>
+ I won't define the detail of the interrupt handler at this
+ point, but at least its appearance can be explained now. The
+ interrupt handler looks usually like the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void snd_mychip_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t, dev_id, return);
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ Again the magic-cast is used here to get the correct pointer
+ from the second argument.
+ </para>
+
+ <para>
+ Now let's write the corresponding destructor for the resources
+ above. The role of destructor is simple: disable the hardware
+ (if already activated) and release the resources. So far, we
+ have no hardware part, so the disabling is not written here.
+ </para>
+
+ <para>
+ For releasing the resources, <quote>check-and-release</quote>
+ method is a safer way. For the i/o port, do like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if (chip->res_port) {
+ release_resource(chip->res_port);
+ kfree_nocheck(chip->res_port);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ As you can see, the i/o resource pointer is also to be freed
+ via <function>kfree_nocheck()</function> after
+ <function>release_resource()</function> is called. You
+ cannot use <function>kfree()</function> here, because on ALSA,
+ <function>kfree()</function> may be a wrapper to its own
+ allocator with the memory debugging. Since the resource pointer
+ is allocated externally outside the ALSA, it must be released
+ via the native
+ <function>kfree()</function>.
+ <function>kfree_nocheck()</function> is used for that; it calls
+ the native <function>kfree()</function> without wrapper.
+ </para>
+
+ <para>
+ For releasing the interrupt, do like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+]]>
+ </programlisting>
+ </informalexample>
+
+ And finally, release the chip-specific record.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_magic_kfree(chip);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The chip instance is freed via
+ <function>snd_magic_kfree()</function>. Please use this function
+ for the object allocated by
+ <function>snd_magic_kmalloc()</function>. If you free it with
+ <function>kfree()</function>, it won't work properly and will
+ result in the memory leak. Also, again, remember that you cannot
+ set <parameter>__devexit</parameter> prefix for this destructor.
+ </para>
+
+ <para>
+ We didn't implement the hardware-disabling part in the above.
+ If you need to do this, please note that the destructor may be
+ called even before the initialization of the chip is completed.
+ It would be better to have a flag to skip the hardware-disabling
+ if the hardware was not initialized yet.
+ </para>
+
+ <para>
+ When the chip-data is assigned to the card using
+ <function>snd_device_new()</function> with
+ <constant>SNDRV_DEV_LOWLELVEL</constant> , its destructor is
+ called at the last. that is, it is assured that all other
+ components like PCMs and controls have been already released.
+ You don't have to call stopping PCMs, etc. explicitly, but just
+ stop the hardware in the low-level.
+ </para>
+
+ <para>
+ The management of a memory-mapped region is almost as same as
+ the management of an i/o port. You'll need three fields like
+ the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ struct snd_mychip {
+ ....
+ unsigned long iobase_phys;
+ unsigned long iobase_virt;
+ struct resource *res_iobase;
+ };
+]]>
+ </programlisting>
+ </informalexample>
+
+ and the allocation would be (assuming its size is 512 bytes):
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ chip->iobase_phys = pci_resource_start(pci, 0);
+ chip->iobase_virt = (unsigned long)
+ ioremap_nocache(chip->iobase_phys, 512);
+ if ((chip->res_port = request_mem_region(chip->port, 512,
+ "My Chip")) == NULL) {
+ printk(KERN_ERR "cannot allocate the memory region\n");
+ snd_mychip_free(chip);
+ return -EBUSY;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ and the corresponding destructor would be:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_mychip_free(mychip_t *chip)
+ {
+ ....
+ if (chip->iobase_virt)
+ iounmap((void *)chip->iobase_virt);
+ if (chip->res_iobase) {
+ release_resource(chip->res_iobase);
+ kfree_nocheck(chip->res_iobase);
+ }
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ </section>
+
+ <section id="pci-resource-entries">
+ <title>PCI Entries</title>
+ <para>
+ So far, so good. Let's finish the rest of missing PCI
+ stuffs. At first, we need a
+ <structname>pci_device_id</structname> table for this
+ chipset. It's a table of PCI vendor/device ID number, and some
+ masks.
+ </para>
+
+ <para>
+ For example,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static struct pci_device_id snd_mychip_ids[] __devinitdata = {
+ { PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
+ ....
+ { 0, }
+ };
+ MODULE_DEVICE_TABLE(pci, snd_mychip_ids);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The first and second fields of
+ <structname>pci_device_id</structname> struct are the vendor and
+ device IDs. If you have nothing special to filter the matching
+ devices, you can use the rest of fields like above. The last
+ field of <structname>pci_device_id</structname> struct is a
+ private data for this entry. You can specify any value here, for
+ example, to tell the type of different operations per each
+ device IDs. Such an example is found in intel8x0 driver.
+ </para>
+
+ <para>
+ The last entry of this list is the terminator. You must
+ specify this all-zero entry.
+ </para>
+
+ <para>
+ Then, prepare the <structname>pci_driver</structname> record:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static struct pci_driver driver = {
+ .name = "My Own Chip",
+ .id_table = snd_mychip_ids,
+ .probe = snd_mychip_probe,
+ .remove = __devexit_p(snd_mychip_remove),
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The <structfield>probe</structfield> and
+ <structfield>remove</structfield> functions are what we already
+ defined in
+ the previous sections. The <structfield>remove</structfield> should
+ be defined with
+ <function>__devexit_p()</function> macro, so that it's not
+ defined for built-in (and non-hot-pluggable) case. The
+ <structfield>name</structfield>
+ field is the name string of this device. Note that you must not
+ use a slash <quote>/</quote> in this string.
+ </para>
+
+ <para>
+ And at last, the module entries:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int __init alsa_card_mychip_init(void)
+ {
+ int err;
+
+ if ((err = pci_module_init(&driver)) < 0) {
+ #ifdef MODULE
+ printk(KERN_ERR "My chip soundcard not found"
+ " or device busy\n");
+ #endif
+ return err;
+ }
+ return 0;
+ }
+
+ static void __exit alsa_card_mychip_exit(void)
+ {
+ pci_unregister_driver(&driver);
+ }
+
+ module_init(alsa_card_mychip_init)
+ module_exit(alsa_card_mychip_exit)
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Note that these module entries are tagged with
+ <parameter>__init</parameter> and
+ <parameter>__exit</parameter> prefixes, not
+ <parameter>__devinit</parameter> nor
+ <parameter>__devexit</parameter>.
+ </para>
+
+ <para>
+ Oh, one thing was forgotten. If you have no exported symbols,
+ you need to declare it on 2.2 or 2.4 kernels (on 2.5 kernels
+ it's not necessary, though).
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ EXPORT_NO_SYMBOLS;
+]]>
+ </programlisting>
+ </informalexample>
+
+ That's all!
+ </para>
+ </section>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- PCM Interface -->
+<!-- ****************************************************** -->
+ <chapter id="pcm-interface">
+ <title>PCM Interface</title>
+
+ <section id="pcm-interface-general">
+ <title>General</title>
+ <para>
+ The PCM middle layer of ALSA is quite powerful and it is only
+ necessary for each driver to implement the low-level functions
+ to access its hardware.
+ </para>
+
+ <para>
+ For accessing to the PCM layer, you need to include
+ <filename>&lt;sound/pcm.h&gt;</filename> above all. In addition,
+ <filename>&lt;sound/pcm_params.h&gt;</filename> might be needed
+ if you access to some functions related with hw_param.
+ </para>
+
+ <para>
+ Each card device can have up to four pcm instances. A pcm
+ instance corresponds to a pcm device file. The limitation of
+ number of instances comes only from the available bit size of
+ the linux's device number. Once when 64bit device number is
+ used, we'll have more available pcm instances.
+ </para>
+
+ <para>
+ A pcm instance consists of pcm playback and capture streams,
+ and each pcm stream consists of one or more pcm substreams. Some
+ soundcard supports the multiple-playback function. For example,
+ emu10k1 has a PCM playback of 32 stereo substreams. In this case, at
+ each open, a free substream is (usually) automatically chosen
+ and opened. Meanwhile, when only one substream exists and it was
+ already opened, the succeeding open will result in the blocking
+ or the error with <constant>EAGAIN</constant> according to the
+ file open mode. But you don't have to know the detail in your
+ driver. The PCM middle layer will take all such jobs.
+ </para>
+ </section>
+
+ <section id="pcm-interface-example">
+ <title>Full Code Example</title>
+ <para>
+ The example code below does not include any hardware access
+ routines but shows only the skeleton, how to build up the PCM
+ interfaces.
+
+ <example>
+ <title>PCM Example Code</title>
+ <programlisting>
+<![CDATA[
+ #include <sound/pcm.h>
+ ....
+
+ #define chip_t mychip_t
+ ....
+
+ /* hardware definition */
+ static snd_pcm_hardware_t snd_mychip_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 32768,
+ .period_bytes_min = 4096,
+ .period_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 1024,
+ };
+
+ /* open callback */
+ static int snd_mychip_pcm_open(snd_pcm_substream_t *subs)
+ {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ runtime->hw = snd_mychip_playback_hw;
+ // more hardware-initialization will be done here
+ return 0;
+ }
+
+ /* close callback */
+ static int snd_mychip_pcm_close(snd_pcm_substream_t *substream)
+ {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ // the hardware-specific codes will be here
+ return 0;
+
+ }
+
+ /* hw_params callback */
+ static int snd_mychip_pcm_hw_params(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t * hw_params)
+ {
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ }
+
+ /* hw_free callback */
+ static int snd_mychip_pcm_hw_free(snd_pcm_substream_t *substream)
+ {
+ return snd_pcm_lib_free_pages(substream);
+ }
+
+ /* prepare callback */
+ static int snd_mychip_pcm_prepare(snd_pcm_substream_t *substream)
+ {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ // set up the hardware with the current configuration
+ // for example...
+ mychip_set_sample_format(chip, runtime->format);
+ mychip_set_sample_rate(chip, runtime->rate);
+ mychip_set_channels(chip, runtime->channels);
+ mychip_set_dma_setup(chip, runtime->dma_area,
+ chip->buffer_size,
+ chip->period_size);
+ return 0;
+ }
+
+ /* trigger callback */
+ static int snd_mychip_pcm_trigger(snd_pcm_substream_t *substream,
+ int cmd)
+ {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ // do something to start the PCM engine
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ // do something to stop the PCM engine
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* pointer callback */
+ static snd_pcm_uframes_t
+ snd_mychip_pcm_pointer(snd_pcm_substream_t *substream)
+ {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ unsigned int current_ptr;
+
+ // get the current hardware pointer
+ current_ptr = mychip_get_hw_pointer(chip);
+ return current_ptr;
+ }
+
+ /* operators */
+ static snd_pcm_ops_t snd_mychip_playback_ops = {
+ .open = snd_mychip_playback_open,
+ .close = snd_mychip_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mychip_pcm_hw_params,
+ .hw_free = snd_mychip_pcm_hw_free,
+ .prepare = snd_mychip_pcm_prepare,
+ .trigger = snd_mychip_pcm_trigger,
+ .pointer = snd_mychip_pcm_pointer,
+ };
+
+ /*
+ * definitions of capture are omitted here...
+ */
+
+ /* create a pcm device */
+ static int __devinit snd_mychip_new_pcm(mychip_t *chip)
+ {
+ snd_pcm_t *pcm;
+ int err;
+
+ if ((err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1,
+ &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ strcpy(pcm->name, "My Chip");
+ chip->pcm = pcm;
+ /* set operators */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_mychip_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_mychip_capture_ops);
+ /* pre-allocation of buffers */
+ snd_pcm_lib_preallocate_pci_pages_for_all(chip->pci, pcm,
+ 64*1024, 64*1024);
+ return 0;
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="pcm-interface-constructor">
+ <title>Constructor</title>
+ <para>
+ A pcm instance is allocated <function>snd_pcm_new()</function>
+ function. It would be better to create a constructor for pcm,
+ namely,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int __devinit snd_mychip_new_pcm(mychip_t *chip)
+ {
+ snd_pcm_t *pcm;
+ int err;
+
+ if ((err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1,
+ &pcm)) < 0)
+ return err;
+ pcm->private_data = chip;
+ strcpy(pcm->name, "My Chip");
+ chip->pcm = pcm;
+ ....
+ return 0;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The <function>snd_pcm_new()</function> function takes the four
+ arguments. The first argument is the card pointer to which this
+ pcm is assigned, and the second is the ID string.
+ </para>
+
+ <para>
+ The third argument (<parameter>index</parameter>, 0 in the
+ above) is the index of this new pcm. It begins from zero. When
+ you will create more than one pcm instances, specify the
+ different numbers in this argument. For example,
+ <parameter>index</parameter> = 1 for the second PCM device.
+ </para>
+
+ <para>
+ The fourth and fifth arguments are the number of substreams
+ for playback and capture, respectively. Here both 1 are given in
+ the above example. When no playback or no capture is available,
+ pass 0 to the corresponding argument.
+ </para>
+
+ <para>
+ If a chip supports multiple playbacks or captures, you can
+ specify more numbers, but they must be handled properly in
+ open/close, etc. callbacks. When you need to know which
+ substream you are referring to, then it can be obtained from
+ <type>snd_pcm_substream_t</type> data passed to each callback
+ as follows:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_substream_t *substream;
+ int index = substream->number;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ After the pcm is created, you need to set operators for each
+ pcm stream.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_mychip_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_mychip_capture_ops);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The operators are defined typically like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static snd_pcm_ops_t snd_mychip_playback_ops = {
+ .open = snd_mychip_pcm_open,
+ .close = snd_mychip_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mychip_pcm_hw_params,
+ .hw_free = snd_mychip_pcm_hw_free,
+ .prepare = snd_mychip_pcm_prepare,
+ .trigger = snd_mychip_pcm_trigger,
+ .pointer = snd_mychip_pcm_pointer,
+ };
+]]>
+ </programlisting>
+ </informalexample>
+
+ Each of callbacks is explained in the subsection
+ <link linkend="pcm-interface-operators"><citetitle>
+ Operators</citetitle></link>.
+ </para>
+
+ <para>
+ After setting the operators, most likely you'd like to
+ pre-allocate the buffer. For the pre-allocation, simply call
+ the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_lib_preallocate_pci_pages_for_all(chip->pci, pcm,
+ 64*1024, 64*1024);
+]]>
+ </programlisting>
+ </informalexample>
+
+ It will allocate up to 64kB buffer as default. The details of
+ buffer management will be described in the later section <link
+ linkend="buffer-and-memory"><citetitle>Buffer and Memory
+ Management</citetitle></link>.
+ </para>
+
+ <para>
+ Additionally, you can set some extra information for this pcm
+ in pcm-&gt;info_flags.
+ The available values are defined as
+ <constant>SNDRV_PCM_INFO_XXX</constant> in
+ <filename>&lt;sound/asound.h&gt;</filename>, which is used for
+ the hardware definition (described later). When your soundchip
+ supports only half-duplex, specify like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </section>
+
+ <section id="pcm-interface-destructor">
+ <title>... And the Destructor?</title>
+ <para>
+ The destructor for a pcm instance is not always
+ necessary. Since the pcm device will be released by the middle
+ layer code automatically, you don't have to call destructor
+ explicitly.
+ </para>
+
+ <para>
+ The destructor would be necessary when you created some
+ special records internally and need to release them. In such a
+ case, set the destructor function to
+ pcm-&gt;private_free:
+
+ <example>
+ <title>PCM Instance with a Destructor</title>
+ <programlisting>
+<![CDATA[
+ static void mychip_pcm_free(snd_pcm_t *pcm)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pcm->private_data, return);
+ // free your own data
+ kfree(chip->my_private_pcm_data);
+ // do what you like else...
+ }
+
+ static int __devinit snd_mychip_new_pcm(mychip_t *chip)
+ {
+ snd_pcm_t *pcm;
+ ....
+ // allocate your own data
+ chip->my_private_pcm_data = kmalloc(...);
+ // set the destructor
+ pcm->private_data = chip;
+ pcm->private_free = mychip_pcm_free;
+ ....
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators">
+ <title>Operators</title>
+ <para>
+ OK, now let me explain the detail of each pcm callback
+ (<parameter>ops</parameter>). In general, every callback must
+ return 0 if successful, or a negative number with the error
+ number such as <constant>-EINVAL</constant> at any
+ error.
+ </para>
+
+ <para>
+ The callback function takes at least the argument with
+ <type>snd_pcm_substream_t</type> pointer. For retrieving the
+ chip record from the given substream instance, you can use the
+ following macro.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #define chip_t mychip_t
+
+ int xxx() {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ It's expanded with a magic-cast, so the cast-error is
+ automatically checked. You should define <type>chip_t</type> at
+ the beginning of the code, since this will be referred in many
+ places of pcm and control interfaces.
+ </para>
+
+ <section id="pcm-interface-operators-open-callback">
+ <title>open callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_open(snd_pcm_substream_t *subs);
+]]>
+ </programlisting>
+ </informalexample>
+
+ This is called when a pcm substream is opened.
+ </para>
+
+ <para>
+ At least, here you have to initialize the runtime hardware
+ record. Typically, this is done by like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_open(snd_pcm_substream_t *substream)
+ {
+ mychip_t *chip = snd_pcm_substream_chip(substream);
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ runtime->hw = snd_mychip_playback_hw;
+ return 0;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <parameter>snd_mychip_playback_hw</parameter> is the
+ pre-defined hardware record.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static snd_pcm_hardware_t snd_mychip_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 32768,
+ .period_bytes_min = 4096,
+ .period_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 1024,
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The similar struct exists on ALSA 0.5.x driver, so you can
+ guess the values if you already wrote a driver.
+ </para>
+
+ <para>
+ The <structfield>info</structfield> field contains the type and
+ capabilities of this pcm. The bit flags are defined in
+ <filename>&lt;sound/asound.h&gt;</filename> as
+ <constant>SNDRV_PCM_INFO_XXX</constant>. Here, at least, you
+ have to specify whether the mmap is supported and which
+ interleaved format is supported.
+ When the mmap is supported, add
+ <constant>SNDRV_PCM_INFO_MMAP</constant> flag here. When the
+ hardware supports the interleaved or the non-interleaved
+ format, <constant>SNDRV_PCM_INFO_INTERLEAVED</constant> or
+ <constant>SNDRV_PCM_INFO_NONINTERLEAVED</constant> flag must
+ be set, respectively. If both are supported, you can set both,
+ too.
+ </para>
+
+ <para>
+ In the above example, <constant>MMAP_VALID</constant> and
+ <constant>BLOCK_TRANSFER</constant> are specified for OSS mmap
+ mode. Usually both are set. Of course,
+ <constant>MMAP_VALID</constant> is set only if the mmap is
+ really supported.
+ </para>
+
+ <para>
+ The other possible flags are
+ <constant>SNDRV_PCM_INFO_PAUSE</constant> and
+ <constant>SNDRV_PCM_INFO_RESUME</constant>. The
+ <constant>PAUSE</constant> bit means that the pcm supports the
+ <quote>pause</quote> operation, while the
+ <constant>RESUME</constant> bit means that the pcm supports
+ the <quote>suspend/resume</quote> operation. If these flags
+ are set, the <structfield>trigger</structfield> callback below
+ must handle the corresponding commands.
+ </para>
+
+ <para>
+ <structfield>formats</structfield> field contains the bit-flags
+ of supported formats (<constant>SNDRV_PCM_FMTBIT_XXX</constant>).
+ If the hardware supports more than one format, give all or'ed
+ bits. In the example above, the signed 16bit little-endian
+ format is specified.
+ </para>
+
+ <para>
+ <structfield>rates</structfield> field contains the bit-flags of
+ supported rates (<constant>SNDRV_PCM_RATE_XXX</constant>).
+ When the chip supports continuous rates, pass
+ <constant>CONTINUOUS</constant> bit additionally.
+ The pre-defined rate bits are only for typical rates. If your
+ chip supports unconventional rates, you need to add
+ <constant>KNOT</constant> bit and set up the
+ constraint manually (explained later).
+ </para>
+
+ <para>
+ There have been many changes of terminology between
+ ALSA 0.5.x and 0.9.x.
+ On the ALSA 0.9.x world, <quote>period</quote> means what is
+ known as <quote>fragment</quote> in the OSS. It's the least
+ size of (a part of) the buffer to generate an interrupt.
+ </para>
+
+ <para>
+ Now, taking the new terminology into account, the other fields
+ are self-explanatory (I hope). Please note that here, both
+ min/max buffer and period sizes are specified in bytes.
+ </para>
+
+ <para>
+ Some drivers allocate the private instance for each pcm
+ substream. It can be stored in
+ <constant>substream->runtime-&gt;private_data</constant>.
+ Since it's a void pointer, you
+ should use magic-kmalloc and magic-cast for such an object.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_open(snd_pcm_substream_t *substream)
+ {
+ my_pcm_data_t *data;
+ ....
+ data = snd_magic_kmalloc(my_pcm_data_t, 0, GFP_KERNEL);
+ substream->runtime->private_data = data;
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The allocated object must be released in the close callback below.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-close-callback">
+ <title>close callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_close(snd_pcm_substream_t *subs);
+]]>
+ </programlisting>
+ </informalexample>
+
+ Obviously, this is called when a pcm substream is closed.
+ </para>
+
+ <para>
+ Any private instance for a pcm substream allocated in the
+ open callback will be released here.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_close(snd_pcm_substream_t *substream)
+ {
+ ....
+ snd_magic_kfree(substream->runtime->private_data);
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-ioctl-callback">
+ <title>ioctl callback</title>
+ <para>
+ This is used for any special action to pcm ioctls. But
+ usually you can pass a generic ioctl callback,
+ <function>snd_pcm_lib_ioctl</function>.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-hw-params-callback">
+ <title>hw_params callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_hw_params(snd_pcm_substream_t * substream,
+ snd_pcm_hw_params_t * hw_params);
+]]>
+ </programlisting>
+ </informalexample>
+
+ This and <structfield>hw_free</structfield> callbacks exist
+ only on ALSA 0.9.x.
+ </para>
+
+ <para>
+ This is called when the hardware parameter
+ (<structfield>hw_params</structfield>) is set
+ up by the application,
+ that is, once when the buffer size, the period size, the
+ format, etc. are defined for the pcm substream.
+ </para>
+
+ <para>
+ Many hardware set-up should be done in this callback,
+ including the allocation of buffers.
+ </para>
+
+ <para>
+ Parameters to be initialized are retrieved by
+ <function>params_xxx()</function> macros. For allocating a
+ buffer, you can call a helper function,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+]]>
+ </programlisting>
+ </informalexample>
+
+ <function>snd_pcm_lib_malloc_pages()</function> is available
+ only when the DMA buffers have been pre-allocated.
+ See the section <link
+ linkend="buffer-and-memory-buffer-types"><citetitle>
+ Buffer Types</citetitle></link> for more details.
+ </para>
+
+ <para>
+ Note that this and <structfield>prepare</structfield> callbacks
+ may be called multiple times per initialization.
+ For example, the OSS emulation may
+ call these callbacks at each change via its ioctl.
+ </para>
+
+ <para>
+ Thus, you need to take care not to allocate the same buffers
+ many times, which will lead to memory leak! Calling the
+ helper function above many times is OK. It will release the
+ previous buffer automatically when it was already allocated.
+ </para>
+
+ <para>
+ Another note is that this callback is non-atomic
+ (schedulable). This is important, because the
+ <structfield>prepare</structfield> callback
+ is atomic (non-schedulable). That is, mutex or any
+ schedule-related functions are available only in
+ <structfield>hw_params</structfield> callback.
+ Please see the subsection
+ <link linkend="pcm-interface-atomicity"><citetitle>
+ Atomicity</citetitle></link> for details.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-hw-free-callback">
+ <title>hw_free callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_hw_free(snd_pcm_substream_t * substream);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ This is called to release the resources allocated via
+ <structfield>hw_params</structfield>. For example, releasing the
+ buffer via
+ <function>snd_pcm_lib_malloc_pages()</function> is done by
+ calling the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_lib_free_pages(substream);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ This callback may be called multiple times, too.
+ Keep track whether the resource was already released.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-prepare-callback">
+ <title>prepare callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_prepare(snd_pcm_substream_t * substream);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ This callback is called when the pcm is
+ <quote>prepared</quote>. You can set the format type, sample
+ rate, etc. here. The difference from
+ <structfield>hw_params</structfield> is that the
+ <structfield>prepare</structfield> callback will be called at each
+ time
+ <function>snd_pcm_prepare()</function> is called, i.e. when
+ recovered after underruns, etc.
+ </para>
+
+ <para>
+ As mentioned above, this callback is atomic.
+ </para>
+
+ <para>
+ In this and the following callbacks, you can refer to the
+ values via the runtime record,
+ substream-&gt;runtime.
+ For example, to get the current
+ rate, format or channels, access to
+ runtime-&gt;rate,
+ runtime-&gt;format or
+ runtime-&gt;channels, respectively.
+ The physical address of the allocated buffer is set to
+ runtime-&gt;dma_area. The buffer and period sizes are
+ in runtime-&gt;buffer_size and runtime-&gt;period_size,
+ respectively.
+ </para>
+
+ <para>
+ Note that the period and the buffer sizes are stored in
+ <quote>frames</quote>. In the ALSA world, 1 frame = channels
+ * samples-size. For conversion between frames and bytes, you
+ can use the helper functions,
+ <function>frames_to_bytes()</function> and
+ <function>bytes_to_frames()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ period_bytes = frames_to_bytes(runtime, runtime->period_size);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Be careful that this callback will be called many times at
+ each set up, too.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-trigger-callback">
+ <title>trigger callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_xxx_trigger(snd_pcm_substream_t * substream, int cmd);
+]]>
+ </programlisting>
+ </informalexample>
+
+ This is called when the pcm is started, stopped or paused.
+ </para>
+
+ <para>
+ Which action is specified in the second argument,
+ <constant>SNDRV_PCM_TRIGGER_XXX</constant> in
+ <filename>&lt;sound/pcm.h&gt;</filename>. At least,
+ <constant>START</constant> and <constant>STOP</constant>
+ commands must be defined in this callback.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ // do something to start the PCM engine
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ // do something to stop the PCM engine
+ break;
+ default:
+ return -EINVAL;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ When the pcm supports the pause operation (given in info
+ field of the hardware table), <constant>PAUSE_PUSE</constant>
+ and <constant>PAUSE_RELEASE</constant> commands must be
+ handled here, too. The former is the command to pause the pcm,
+ and the latter to restart the pcm again.
+ </para>
+
+ <para>
+ When the pcm supports the suspend/resume operation,
+ <constant>SUSPEND</constant> and <constant>RESUME</constant>
+ commands must be handled, too. Obviously it does suspend and
+ resume of the pcm substream. Usually, the
+ <constant>SUSPEND</constant> is identical with
+ <constant>STOP</constant> command and the
+ <constant>RESUME</constant> is identical with
+ <constant>START</constant> command.
+ </para>
+
+ <para>
+ This callback is also atomic.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-pointer-callback">
+ <title>pointer callback</title>
+ <para>
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static snd_pcm_uframes_t snd_xxx_pointer(snd_pcm_substream_t * substream)
+]]>
+ </programlisting>
+ </informalexample>
+
+ This callback is called when the PCM middle layer inquires
+ the current hardware position on the buffer. The position must
+ be returned in frames (which was in bytes on ALSA 0.5.x),
+ ranged from 0 to buffer_size - 1.
+ </para>
+
+ <para>
+ This is called usually from the buffer-update routine in the
+ pcm middle layer, which is invoked when
+ <function>snd_pcm_period_elapsed()</function> is called in the
+ interrupt routine. Then the pcm middle layer updates the
+ position and calculates the available space, and wakes up the
+ sleeping poll threads, etc.
+ </para>
+
+ <para>
+ This callback is also atomic.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-copy-silence">
+ <title>copy and silence callbacks</title>
+ <para>
+ These callbacks are not mandatory, and can be omitted in
+ most cases. These callbacks are used when the hardware buffer
+ cannot be on the normal memory space. Some chips have their
+ own buffer on the hardware which is not mappable. In such a
+ case, you have to transfer the data manually from the memory
+ buffer to the hardware buffer. Or, if the buffer is
+ non-contiguous on both physical and virtual memory spaces,
+ these callbacks must be defined, too.
+ </para>
+
+ <para>
+ If these two callbacks are defined, copy and set-silence
+ operations are done by them. The detailed will be described in
+ the later section <link
+ linkend="buffer-and-memory"><citetitle>Buffer and Memory
+ Management</citetitle></link>.
+ </para>
+ </section>
+
+ <section id="pcm-interface-operators-page-callback">
+ <title>page callback</title>
+
+ <para>
+ This callback is also not mandatory. This callback is used
+ mainly for the non-contiguous buffer. The mmap calls this
+ callback to get the page address. Some examples will be
+ explained in the later section <link
+ linkend="buffer-and-memory"><citetitle>Buffer and Memory
+ Management</citetitle></link>, too.
+ </para>
+ </section>
+ </section>
+
+ <section id="pcm-interface-interrupt-handler">
+ <title>Interrupt Handler</title>
+ <para>
+ The rest of pcm stuff is the PCM interrupt handler. The
+ role of PCM interrupt handler in the sound driver is to update
+ the buffer position and to tell the PCM middle layer when the
+ buffer position goes across the prescribed period size. To
+ inform this, call <function>snd_pcm_period_elapsed()</function>
+ function.
+ </para>
+
+ <para>
+ There are several types of sound chips to generate the interrupts.
+ </para>
+
+ <section id="pcm-interface-interrupt-handler-boundary">
+ <title>Interrupts at the period (fragment) boundary</title>
+ <para>
+ This is the most frequently found type: the hardware
+ generates an interrupt at each period boundary.
+ In this case, you can call
+ <function>snd_pcm_period_elapsed()</function> at each
+ interrupt.
+ </para>
+
+ <para>
+ <function>snd_pcm_period_elapsed()</function> takes the
+ substream pointer as its argument. Thus, you need to keep the
+ substream pointer accessible from the chip instance. For
+ example, define substream field in the chip record to hold the
+ current running substream pointer, and set the pointer value
+ at open callback (and reset at close callback).
+ </para>
+
+ <para>
+ If you aquire a spinlock in the interrupt handler, and the
+ lock is used in other pcm callbacks, too, then you have to
+ release the lock before calling
+ <function>snd_pcm_period_elapsed()</function>, because
+ <function>snd_pcm_period_elapsed()</function> calls other pcm
+ callbacks inside.
+ </para>
+
+ <para>
+ A typical coding would be like:
+
+ <example>
+ <title>Interrupt Handler Case #1</title>
+ <programlisting>
+<![CDATA[
+ static void snd_mychip_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t, dev_id, return);
+ spin_lock(&chip->lock);
+ ....
+ if (pcm_irq_invoked(chip)) {
+ // call updater, unlock before it
+ spin_unlock(&chip->lock);
+ snd_pcm_period_elapsed(chip->substream);
+ spin_lock(&chip->lock);
+ }
+ ....
+ spin_unlock(&chip->lock);
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="pcm-interface-interrupt-handler-timer">
+ <title>High-frequent timer interrupts</title>
+ <para>
+ This is the case when the hardware doesn't generate interrupts
+ at the period boundary but do timer-interrupts at the fixed
+ timer rate (e.g. es1968 or ymfpci drivers).
+ In this case, you need to check the current hardware
+ position and accumulates the processed sample length at each
+ interrupt. When the accumulated size overcomes the period
+ size, call
+ <function>snd_pcm_period_elapsed()</function> and reset the
+ accumulator.
+ </para>
+
+ <para>
+ A typical coding would be like the following.
+
+ <example>
+ <title>Interrupt Handler Case #2</title>
+ <programlisting>
+<![CDATA[
+ static void snd_mychip_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t, dev_id, return);
+ spin_lock(&chip->lock);
+ ....
+ if (pcm_irq_invoked(chip)) {
+ unsigned int last_ptr, size;
+ // get the current hardware pointer (in frames)
+ last_ptr = get_hw_ptr(chip);
+ // calculate the processed frames since the
+ // last update
+ if (last_ptr < chip->last_ptr)
+ size = runtime->buffer_size + last_ptr
+ - chip->last_ptr;
+ else
+ size = last_ptr - chip->last_ptr;
+ // remember the last updated point
+ chip->last_ptr = last_ptr;
+ // accumulate the size
+ chip->size += size;
+ // over the period boundary?
+ if (chip->size >= runtime->period_size) {
+ // reset the accumulator
+ chip->size %= runtime->period_size;
+ // call updater
+ spin_unlock(&chip->lock);
+ snd_pcm_period_elapsed(substream);
+ spin_lock(&chip->lock);
+ }
+ }
+ ....
+ spin_unlock(&chip->lock);
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="pcm-interface-interrupt-handler-both">
+ <title>On calling <function>snd_pcm_period_elapsed()</function></title>
+ <para>
+ In both cases, even if more than one period are elapsed, you
+ don't have to call
+ <function>snd_pcm_period_elapsed()</function> many times. Call
+ only once. And the pcm layer will check the current hardware
+ pointer and update to the latest status.
+ </para>
+ </section>
+ </section>
+
+ <section id="pcm-interface-atomicity">
+ <title>Atomicity</title>
+ <para>
+ One of the most important (and thus difficult to debug) problem
+ on the kernel programming is the race condition.
+ On linux kernel, usually it's solved via spin-locks or
+ semaphores. In general, if the race condition may
+ happen in the interrupt handler, it's handled as atomic, and you
+ have to use spinlock for protecting the critical session. If it
+ never happens in the interrupt and it may take relatively long
+ time, you should use semaphore.
+ </para>
+
+ <para>
+ As already seen, some pcm callbacks are atomic and some are
+ not. For example, <parameter>hw_params</parameter> callback is
+ non-atomic, while <parameter>prepare</parameter> callback is
+ atomic. This means, the latter is called already in a spinlock
+ held by the PCM middle layer. Please take this atomicity into
+ account when you use a spinlock or a semaphore in the callbacks.
+ </para>
+
+ <para>
+ In the atomic callbacks, you cannot use functions which may call
+ <function>schedule</function> or go to
+ <function>sleep</function>. The semaphore and mutex do sleep,
+ and hence they cannot be used inside the atomic callbacks
+ (e.g. <parameter>prepare</parameter> callback).
+ For taking a certain delay in such a callback, please use
+ <function>udelay()</function> or <function>mdelay()</function>.
+ </para>
+
+ <para>
+ This atomicity problem appears also in the initialization of the
+ hardware when the power-management is supported. The functions
+ for suspending and resuming the chip must be atomic, i.e. no
+ mutex nor sleep can be used in them.
+ </para>
+
+ </section>
+ <section id="pcm-interface-constraints">
+ <title>Constraints</title>
+ <para>
+ If your chip supports unconventional sample rates, or only the
+ limited samples, you need to set a constraint for the
+ condition.
+ </para>
+
+ <para>
+ For example, in order to restrict the sample rates in the some
+ supported values, use
+ <function>snd_pcm_hw_constraint_list()</function>.
+ You need to call this function in the open callback.
+
+ <example>
+ <title>Example of Hardware Constraints</title>
+ <programlisting>
+<![CDATA[
+ static unsigned int rates[] =
+ {4000, 10000, 22050, 44100};
+ static snd_pcm_hw_constraint_list_t constraints_rates = {
+ .count = sizeof(rates) / sizeof(rates[0]),
+ .list = rates,
+ .mask = 0,
+ };
+
+ static int snd_mychip_pcm_open(snd_pcm_substream_t *substream)
+ {
+ int err;
+ ....
+ err = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_rates);
+ if (err < 0)
+ return err;
+ ....
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+
+ <para>
+ There are many different constraints. You can even define your
+ own constraint rules. I won't explain the details here, rather I
+ would like to say, <quote>Luke, use the source.</quote>
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Control Interface -->
+<!-- ****************************************************** -->
+ <chapter id="control-interface">
+ <title>Control Interface</title>
+
+ <section id="control-interface-general">
+ <title>General</title>
+ <para>
+ The control interface is used widely for many switches,
+ sliders, etc. which are accessed from the user-space. Its most
+ important use is the mixer interface. In other words, on ALSA
+ 0.9.x, all the mixer stuff is implemented on the control kernel
+ API (while there was an independent mixer kernel API on 0.5.x).
+ </para>
+
+ <para>
+ ALSA has a well-defined AC97 control module. If your chip
+ supports only the AC97 and nothing else, you can skip this
+ section.
+ </para>
+
+ <para>
+ The control API is defined in
+ <filename>&lt;sound/control.h&gt;</filename>.
+ Include this file if you add your own controls.
+ </para>
+ </section>
+
+ <section id="control-interface-definition">
+ <title>Definition of Controls</title>
+ <para>
+ For creating a new control, you need to define the three
+ callbacks: <structfield>info</structfield>,
+ <structfield>get</structfield> and
+ <structfield>put</structfield>. Then, define a
+ <type>snd_kcontrol_new_t</type> record, such as:
+
+ <example>
+ <title>Definition of a Control</title>
+ <programlisting>
+<![CDATA[
+ static snd_kcontrol_new_t my_control __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_values = 0xffff,
+ .info = my_control_info,
+ .get = my_control_get,
+ .put = my_control_put
+ };
+]]>
+ </programlisting>
+ </example>
+ </para>
+
+ <para>
+ Most likely the control is created via
+ <function>snd_ctl_new1()</function>, and in such a case, you can
+ add <parameter>__devinitdata</parameter> prefix to the
+ definition like above.
+ </para>
+
+ <para>
+ The <structfield>iface</structfield> field specifies the type of
+ the control,
+ <constant>SNDRV_CTL_ELEM_IFACE_XXX</constant>. There are
+ <constant>MIXER</constant>, <constant>PCM</constant>,
+ <constant>CARD</constant>, etc.
+ </para>
+
+ <para>
+ The <structfield>name</structfield> is the name identifier
+ string. On ALSA 0.9.x, the control name is very important,
+ because its role is classified from its name. There are
+ pre-defined standard control names. The details are described in
+ the subsection
+ <link linkend="control-interface-control-names"><citetitle>
+ Control Names</citetitle></link>.
+ </para>
+
+ <para>
+ The <structfield>index</structfield> field holds the index number
+ of this control. If there are several different controls with
+ the same name, they can be distinguished by the index
+ number. This is the case when
+ several codecs exist on the card. If the index is zero, you can
+ omit the definition above.
+ </para>
+
+ <para>
+ The <structfield>access</structfield> field contains the access
+ type of this control. Give the combination of bit masks,
+ <constant>SNDRV_CTL_ELEM_ACCESS_XXX</constant>, there.
+ The detailed will be explained in the subsection
+ <link linkend="control-interface-access-flags"><citetitle>
+ Access Flags</citetitle></link>.
+ </para>
+
+ <para>
+ The <structfield>private_values</structfield> field contains
+ an arbitrary long integer value for this record. When using
+ generic <structfield>info</structfield>,
+ <structfield>get</structfield> and
+ <structfield>put</structfield> callbacks, you can pass a value
+ through this field. If several small numbers are necessary, you can
+ combine them in bitwise. Or, it's possible to give a pointer
+ (casted to unsigned long) of some record to this field, too.
+ </para>
+
+ <para>
+ The other three are
+ <link linkend="control-interface-callbacks"><citetitle>
+ callback functions</citetitle></link>.
+ </para>
+ </section>
+
+ <section id="control-interface-control-names">
+ <title>Control Names</title>
+ <para>
+ There are some standards for defining the control names. A
+ control is usually defined from the three parts as
+ <quote>SOURCE DIRECTION FUNCTION</quote>.
+ </para>
+
+ <para>
+ The first, <constant>SOURCE</constant>, specifies the source
+ of the control, and is a string such as <quote>Master</quote>,
+ <quote>PCM</quote>, <quote>CD</quote> or
+ <quote>Line</quote>. There are many pre-defined sources.
+ </para>
+
+ <para>
+ The second, <constant>DIRECTION</constant>, is one of the
+ following strings according to the direction of the control:
+ <quote>Playback</quote>, <quote>Capture</quote>, <quote>Bypass
+ Playback</quote> and <quote>Bypass Capture</quote>. Or, it can
+ be omitted, meaning both playback and capture directions.
+ </para>
+
+ <para>
+ The third, <constant>FUNCTION</constant>, is one of the
+ following strings according to the function of the control:
+ <quote>Switch</quote>, <quote>Volume</quote> and
+ <quote>Route</quote>.
+ </para>
+
+ <para>
+ The example of control names are, thus, <quote>Master Capture
+ Switch</quote> or <quote>PCM Playback Volume</quote>.
+ </para>
+
+ <para>
+ There are some exceptions:
+ </para>
+
+ <section id="control-interface-control-names-global">
+ <title>Global capture and playback</title>
+ <para>
+ <quote>Capture Source</quote>, <quote>Capture Switch</quote>
+ and <quote>Capture Volume</quote> are used for the global
+ capture (input) source, switch and volume. Similarly,
+ <quote>Playback Switch</quote> and <quote>Playback
+ Volume</quote> are used for the global output gain switch and
+ volume.
+ </para>
+ </section>
+
+ <section id="control-interface-control-names-tone">
+ <title>Tone-controls</title>
+ <para>
+ tone-control switch and volumes are specified like
+ <quote>Tone Control - XXX</quote>, e.g. <quote>Tone Control -
+ Switch</quote>, <quote>Tone Control - Bass</quote>,
+ <quote>Tone Control - Center</quote>.
+ </para>
+ </section>
+
+ <section id="control-interface-control-names-3d">
+ <title>3D controls</title>
+ <para>
+ 3D-control switches and volumes are specified like <quote>3D
+ Control - XXX</quote>, e.g. <quote>3D Control -
+ Switch</quote>, <quote>3D Control - Center</quote>, <quote>3D
+ Control - Space</quote>.
+ </para>
+ </section>
+
+ <section id="control-interface-control-names-mic">
+ <title>Mic boost</title>
+ <para>
+ Mic-boost switch is set as <quote>Mic Boost</quote> or
+ <quote>Mic Boost (6dB)</quote>.
+ </para>
+
+ <para>
+ More precise information can be found in
+ <filename>alsa-kernel/Documentation/ControlNames.txt</filename>.
+ </para>
+ </section>
+ </section>
+
+ <section id="control-interface-access-flags">
+ <title>Access Flags</title>
+
+ <para>
+ The access flag is the bit-flags which specifies the access type
+ of the given control. The default access type is
+ <constant>SNDRV_CTL_ELEM_ACCESS_READWRITE</constant>,
+ which means both read and write are allowed to this control.
+ When the access flag is omitted (i.e. = 0), it is
+ regarded as <constant>READWRITE</constant> access as default.
+ </para>
+
+ <para>
+ When the control is read-only, pass
+ <constant>SNDRV_CTL_ELEM_ACCESS_READ</constant> instead.
+ In this case, you don't have to define
+ <structfield>put</structfield> callback.
+ Similarly, when the control is write-only (although it's a rare
+ case), you can use <constant>WRITE</constant> flag instead, and
+ you don't need <structfield>get</structfield> callback.
+ </para>
+
+ <para>
+ If the control value changes frequently (e.g. the VU meter),
+ <constant>VOLATILE</constant> flag should be given. This means
+ that the control may be changed without
+ <link linkend="control-interface-change-notification"><citetitle>
+ notification</citetitle></link>. Applications should poll such
+ a control constantly.
+ </para>
+
+ <para>
+ When the control is inactive, set
+ <constant>INACTIVE</constant> flag, too.
+ There are <constant>LOCK</constant> and
+ <constant>OWNER</constant> flags for changing the write
+ permissions.
+ </para>
+
+ </section>
+
+ <section id="control-interface-callbacks">
+ <title>Callbacks</title>
+
+ <section id="control-interface-callbacks-info">
+ <title>info callback</title>
+ <para>
+ The <structfield>info</structfield> callback is used to get
+ the detailed information of this control. This must store the
+ values of the given <type>snd_ctl_elem_info_t</type>
+ object. For example, for a boolean control with a single
+ element will be:
+
+ <example>
+ <title>Example of info callback</title>
+ <programlisting>
+<![CDATA[
+ static int snd_myctl_info(snd_kcontrol_t *kcontrol,
+ snd_ctl_elem_info_t *uinfo)
+ {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+
+ <para>
+ The <structfield>type</structfield> field specifies the type
+ of the control. There are <constant>BOOLEAN</constant>,
+ <constant>INTEGER</constant>, <constant>ENUMERATED</constant>,
+ <constant>BYTES</constant>, <constant>IEC958</constant> and
+ <constant>INTEGER64</constant>. The
+ <structfield>count</structfield> field specifies the
+ number of elements in this control. For example, a stereo
+ volume would have count = 2. The
+ <structfield>value</structfield> field is a union, and
+ the values stored are depending on the type. The boolean and
+ integer are identical.
+ </para>
+
+ <para>
+ The enumerated type is a bit different from others. You'll
+ need to set the string for the currently given item index.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_myctl_info(snd_kcontrol_t *kcontrol,
+ static char *texts[4] = {
+ "First", "Second", "Third", "Fourth"
+ };
+ uinfo->value.enumerated.items = 4;
+ if (uinfo->value.enumerated.item > 3)
+ uinfo->value.enumerated.item = 3;
+ strcpy(uinfo->value.enumerated.name,
+ texts[uinfo->value.enumerated.item]);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </section>
+
+ <section id="control-interface-callbacks-get">
+ <title>get callback</title>
+
+ <para>
+ This callback is used to read the current value of the
+ control and to return to the user-space.
+ </para>
+
+ <para>
+ For example,
+
+ <example>
+ <title>Example of get callback</title>
+ <programlisting>
+<![CDATA[
+ static int snd_myctl_get(snd_kcontrol_t *kcontrol,
+ snd_ctl_elem_value_t *ucontrol)
+ {
+ mychip_t *chip = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = get_some_value(chip);
+ return 0;
+ }
+]]>
+ </programlisting>
+ </example>
+ </para>
+
+ <para>
+ Here, the chip instance is retrieved via
+ <function>snd_kcontrol_chip()</function> macro. This macro
+ converts from kcontrol-&gt;private_data to the type defined by
+ <type>chip_t</type>. The
+ kcontrol-&gt;private_data field is
+ given as the argument of <function>snd_ctl_new()</function>
+ (see the later subsection
+ <link linkend="control-interface-constructor"><citetitle>Constructor</citetitle></link>).
+ </para>
+
+ <para>
+ The <structfield>value</structfield> field is depending on
+ the type of control as well as on info callback. For example,
+ the sb driver uses this field to store the register offset,
+ the bit-shift and the bit-mask. The
+ <structfield>private_value</structfield> is set like
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ .private_value = reg | (shift << 16) | (mask << 24)
+]]>
+ </programlisting>
+ </informalexample>
+ and is retrieved in callbacks like
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_sbmixer_get_single(snd_kcontrol_t *kcontrol,
+ snd_ctl_elem_value_t *ucontrol)
+ {
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 16) & 0xff;
+ int mask = (kcontrol->private_value >> 24) & 0xff;
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ In <structfield>get</structfield> callback, you have to fill all the elements if the
+ control has more than one elements,
+ i.e. <structfield>count</structfield> &gt; 1.
+ In the example above, we filled only one element
+ (<structfield>value.integer.value[0]</structfield>) since it's
+ assumed as <structfield>count</structfield> = 1.
+ </para>
+ </section>
+
+ <section id="control-interface-callbacks-put">
+ <title>put callback</title>
+
+ <para>
+ This callback is used to write a value from the user-space.
+ </para>
+
+ <para>
+ For example,
+
+ <example>
+ <title>Example of put callback</title>
+ <programlisting>
+<![CDATA[
+ static int snd_myctl_put(snd_kcontrol_t *kcontrol,
+ snd_ctl_elem_value_t *ucontrol)
+ {
+ mychip_t *chip = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ if (chip->current_value !=
+ ucontrol->value.integer.value[0]) {
+ change_current_value(chip,
+ ucontrol->value.integer.value[0]);
+ changed = 1;
+ }
+ return changed;
+ }
+]]>
+ </programlisting>
+ </example>
+
+ As seen above, you have to return 1 if the value is
+ changed. If the value is not changed, return 0 instead.
+ If any fatal error happens, return a negative error code as
+ usual.
+ </para>
+
+ <para>
+ Like <structfield>get</structfield> callback,
+ when the control has more than one elements,
+ all elemehts must be evaluated in this callback, too.
+ </para>
+ </section>
+
+ <section id="control-interface-callbacks-all">
+ <title>Callbacks are not atomic</title>
+ <para>
+ All these three callbacks are basically not atomic.
+ </para>
+ </section>
+ </section>
+
+ <section id="control-interface-constructor">
+ <title>Constructor</title>
+ <para>
+ When everything is ready, finally we can create a new
+ control. For creating a control, there are two functions to be
+ called, <function>snd_ctl_new1()</function> and
+ <function>snd_ctl_add()</function>.
+ </para>
+
+ <para>
+ In the simplest way, you can do like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ if ((err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip))) < 0)
+ return err;
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <parameter>my_control</parameter> is the
+ <type>snd_kcontrol_new_t</type> object defined above, and chip
+ is the object pointer to be passed to
+ kcontrol-&gt;private_data
+ which can be referred in callbacks.
+ </para>
+
+ <para>
+ <function>snd_ctl_new1()</function> allocates a new
+ <type>snd_kcontrol_t</type> instance (that's why the definition
+ of <parameter>my_control</parameter> can be with
+ <parameter>__devinitdata</parameter>
+ prefix), and <function>snd_ctl_add</function> assigns the given
+ control component to the card.
+ </para>
+ </section>
+
+ <section id="control-interface-change-notification">
+ <title>Change Notification</title>
+ <para>
+ If you need to change and update a control in the interrupt
+ routine, you can call <function>snd_ctl_notify()</function>. For
+ example,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
+]]>
+ </programlisting>
+ </informalexample>
+
+ This function takes the card pointer, the event-mask, and the
+ control id pointer for the notification. The event-mask
+ specifies the types of notification, for example, in the above
+ example, the change of control values is notified.
+ The id pointer is the pointer of <type>snd_ctl_elem_id_t</type>
+ to be notified.
+ You can find some examples in <filename>es1938.c</filename> or
+ <filename>es1968.c</filename> for hardware volume interrupts.
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- API for AC97 Codec -->
+<!-- ****************************************************** -->
+ <chapter id="api-ac97">
+ <title>API for AC97 Codec</title>
+
+ <section>
+ <title>General</title>
+ <para>
+ The ALSA AC97 codec layer is a well-defined one, and you don't
+ have to write many codes to control it. Only low-level control
+ routines are necessary. The AC97 codec API is defined in
+ <filename>&lt;sound/ac97_codec.h&gt;</filename>.
+ </para>
+ </section>
+
+ <section id="api-ac97-example">
+ <title>Full Code Example</title>
+ <para>
+ <example>
+ <title>Example of AC97 Interface</title>
+ <programlisting>
+<![CDATA[
+ struct snd_mychip {
+ ....
+ ac97_t *ac97;
+ ....
+ };
+
+ static unsigned short snd_mychip_ac97_read(ac97_t *ac97,
+ unsigned short reg)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ ac97->private_data, return 0);
+ ....
+ // read a register value here from the codec
+ return the_register_value;
+ }
+
+ static void snd_mychip_ac97_write(ac97_t *ac97,
+ unsigned short reg, unsigned short val)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ ac97->private_data, return 0);
+ ....
+ // write the given register value to the codec
+ }
+
+ static int snd_mychip_ac97(mychip_t *chip)
+ {
+ ac97_t ac97;
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.write = snd_mychip_ac97_write;
+ ac97.read = snd_mychip_ac97_read;
+ ac97.private_data = chip;
+ return snd_ac97_mixer(card, &ac97, &chip->ac97);
+ }
+
+]]>
+ </programlisting>
+ </example>
+ </para>
+ </section>
+
+ <section id="api-ac97-constructor">
+ <title>Constructor</title>
+ <para>
+ For creating an ac97 instance, call
+ <function>snd_ac97_mixer()</function> with an <type>ac97_t</type>
+ record, in which the callbacks and the private_data is set.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ ac97_t ac97;
+ int err;
+
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.write = snd_mychip_ac97_write;
+ ac97.read = snd_mychip_ac97_read;
+ ac97.private_data = chip;
+ snd_ac97_mixer(card, &ac97, &chip->ac97);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where chip-&gt;ac97 is the pointer of a newly created
+ <type>ac97_t</type> instance.
+ This instance is not necessarily stored in the chip
+ record. When you need to change the register values from the
+ driver, or need the suspend/resume of ac97 codecs, keep this
+ pointer to pass to the corresponding functions.
+ </para>
+ </section>
+
+ <section id="api-ac97-callbacks">
+ <title>Callbacks</title>
+ <para>
+ The standard callbacks are <structfield>read</structfield> and
+ <structfield>write</structfield>. Obviously they
+ correspond to the functions for read and write accesses to the
+ hardware low-level codes.
+ </para>
+
+ <para>
+ The <structfield>read</structfield> callback returns the
+ register value specified in the argument.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static unsigned short snd_mychip_ac97_read(ac97_t *ac97,
+ unsigned short reg)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ ac97->private_data, return 0);
+ ....
+ return the_register_value;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+
+ Here, the chip can be cast from ac97-&gt;private_data.
+ </para>
+
+ <para>
+ Meanwhile, the <structfield>write</structfield> callback is
+ used to set the register value.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void snd_mychip_ac97_write(ac97_t *ac97,
+ unsigned short reg, unsigned short val)
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Note that these callbacks are atomic unlike the callbacks of
+ control API since the ac97 middle-layer routine takes a
+ spinlock before calling these callbacks. Therefore you cannot
+ use mutex or schedule in them. However, these are never called
+ from interrupts unless you do by yourself.
+ Thus it's not necessarily protected with
+ <function>spin_lock_irqsave()</function> usually.
+ </para>
+
+ <para>
+ There are also other callbacks:
+ <structfield>reset</structfield>,
+ <structfield>wait</structfield> and
+ <structfield>init</structfield>.
+ </para>
+
+ <para>
+ The <structfield>reset</structfield> callback is used to reset
+ the codec. If the chip requires a special way of reset, you can
+ define this callback.
+ This callback is atomic, since it can be called in the suspend
+ phase, too.
+ </para>
+
+ <para>
+ The <structfield>wait</structfield> callback is used for a
+ certain wait at the standard initialization of the codec. If the
+ chip requires the extra wait-time, define this callback.
+ This callback is non-atomic.
+ </para>
+
+ <para>
+ The <structfield>init</structfield> callback is used for
+ additional initialization of the codec. This callback is called
+ after the reset phase, and should be atomic, since it's called
+ from the resume handler, too.
+ </para>
+ </section>
+
+ <section id="api-ac97-updating-registers">
+ <title>Updating Registers in The Driver</title>
+ <para>
+ If you need to access to the codec from the driver, you can
+ call the following functions:
+ <function>snd_ac97_write()</function>,
+ <function>snd_ac97_read()</function>,
+ <function>snd_ac97_update()</function> and
+ <function>snd_ac97_update_bits()</function>.
+ </para>
+
+ <para>
+ Both <function>snd_ac97_write()</function> and
+ <function>snd_ac97_update()</function> functions are used to
+ set a value to the given register
+ (<constant>AC97_XXX</constant>). The different between them is
+ that <function>snd_ac97_update()</function> doesn't write a
+ value if the given value has been already set, while
+ <function>snd_ac97_write()</function> always rewrites the
+ value.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_ac97_write(ac97, AC97_MASTER, 0x8080);
+ snd_ac97_update(ac97, AC97_MASTER, 0x8080);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ <function>snd_ac97_read()</function> is used to read the value
+ of the given register. For example,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ value = snd_ac97_read(ac97, AC97_MASTER);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ <function>snd_ac97_update_bits()</function> is used to update
+ some bits of the given register.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_ac97_update_bits(ac97, reg, mask, value);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Also, there is a function to change the sample rate (of a
+ certain register such as
+ <constant>AC97_PCM_FRONT_DAC_RATE</constant>) when VRA is
+ supported by the codec:
+ <function>snd_ac97_set_rate()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_ac97_set_rate(ac97, AC97_PCM_FRONT_DAC_RATE, 44100);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The following registers are available for setting the rate:
+ <constant>AC97_PCM_MIC_ADC_RATE</constant>,
+ <constant>AC97_PCM_FRONT_DAC_RATE</constant>,
+ <constant>AC97_PCM_LR_ADC_RATE</constant>,
+ <constant>AC97_SPDIF</constant>. When the
+ <constant>AC97_SPDIF</constant> is specified, the register is
+ not really changed but the corresponding IEC958 status bits will
+ be updated.
+ </para>
+ </section>
+
+ <section id="api-ac97-clock-adjustment">
+ <title>Clock Adjustment</title>
+ <para>
+ On some chip, the clock of the codec isn't 48000 but using a
+ PCI clock (to save a quartz!). In this case, change the field
+ ac97-&gt;clock to the corresponding
+ value. For example, intel8x0
+ and es1968 drivers have the auto-measurement function of the
+ clock.
+ </para>
+ </section>
+
+ <section id="api-ac97-proc-files">
+ <title>Proc Files</title>
+ <para>
+ The ALSA AC97 interface will create a proc file such as
+ <filename>/proc/asound/card0/ac97#0</filename> and
+ <filename>ac97#0regs</filename>. You can refer to these files to
+ see the current status and registers of the codec.
+ </para>
+ </section>
+
+ <section id="api-ac97-multiple-codecs">
+ <title>Multiple Codecs</title>
+ <para>
+ When there are several codecs on the same card, you need to
+ call <function>snd_ac97_new()</function> multiple times with
+ ac97.num=1 or greater. The <structfield>num</structfield> field
+ specifies the codec
+ number.
+ </para>
+
+ <para>
+ If you have set up multiple codecs, you need to either write
+ different callbacks for each codec or check
+ ac97-&gt;num in the
+ callback routines.
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- MIDI (MPU401-UART) Interface -->
+<!-- ****************************************************** -->
+ <chapter id="midi-interface">
+ <title>MIDI (MPU401-UART) Interface</title>
+
+ <section id="midi-interface-general">
+ <title>General</title>
+ <para>
+ Many soundcards have built-in MIDI (MPU401-UART)
+ interfaces. When the soundcard supports the standard MPU401-UART
+ interface, most likely you can use the ALSA MPU401-UART API. The
+ MPU401-UART API is defined in
+ <filename>&lt;sound/mpu401.h&gt;</filename>.
+ </para>
+
+ <para>
+ Some soundchips have similar but a little bit different
+ implementation of mpu401 stuff. For example, emu10k1 has its own
+ mpu401 routines.
+ </para>
+
+ <para>
+ In this document, I won't explain the rawmidi interface API,
+ which is the basis of MPU401-UART implementation.
+ </para>
+
+ <para>
+ For details, please check the source,
+ <filename>core/rawmidi.c</filename>, and examples such as
+ <filename>drivers/mpu401/mpu401_uart.c</filename> or
+ <filename>usb/usbmidi.c</filename>.
+ </para>
+ </section>
+
+ <section id="midi-interface-constructor">
+ <title>Constructor</title>
+ <para>
+ For creating a rawmidi object, call
+ <function>snd_mpu401_uart_new()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_rawmidi_t *rmidi;
+ snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port, integrated,
+ irq, irq_flags, &rmidi);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The first argument is the card pointer, and the second is the
+ index of this component. You can create up to 8 rawmidi
+ devices.
+ </para>
+
+ <para>
+ The third argument is the type of the hardware,
+ <constant>MPU401_HW_XXX</constant>. If it's not a special one,
+ you can use <constant>MPU401_HW_MPU401</constant>.
+ </para>
+
+ <para>
+ The 4th argument is the i/o port address. Many
+ backward-compatible MPU401 has an i/o port such as 0x330. Or, it
+ might be a part of its own PCI i/o region. It depends on the
+ chip design.
+ </para>
+
+ <para>
+ When the i/o port address above is a part of the PCI i/o
+ region, the MPU401 i/o port might have been already allocated
+ (reserved) by the driver itself. In such a case, pass non-zero
+ to the 5th argument
+ (<parameter>integrated</parameter>). Otherwise, pass 0 to it,
+ and
+ the mpu401-uart layer will allocate the i/o ports by itself.
+ </para>
+
+ <para>
+ Usually, the port address corresponds to the command port and
+ port + 1 corresponds to the data port. If not, you may change
+ the <structfield>cport</structfield> field of
+ <type>mpu401_t</type> manually
+ afterward. However, <type>mpu401_t</type> pointer is not
+ returned explicitly by
+ <function>snd_mpu401_uart_new()</function>. You need to cast
+ rmidi-&gt;private_data to
+ <type>mpu401_t</type> explicitly,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mpu401_t *mpu;
+ mpu = snd_magic_cast(mpu401_t, rmidi->private_data, );
+]]>
+ </programlisting>
+ </informalexample>
+
+ and reset the cport as you like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mpu->cport = my_own_control_port;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The 6th argument specifies the irq number for UART. If the irq
+ is already allocated, pass 0 to the 7th argument
+ (<parameter>irq_flags</parameter>). Otherwise, pass the flags
+ for irq allocation
+ (<constant>SA_XXX</constant> bits) to it, and the irq will be
+ reserved by the mpu401-uart layer. If the card doesn't generates
+ UART interrupts, pass -1 as the irq number. Then a timer
+ interrupt will be invoked for polling.
+ </para>
+ </section>
+
+ <section id="midi-interface-interrupt-handler">
+ <title>Interrupt Handler</title>
+ <para>
+ When the interrupt is allocated in
+ <function>snd_mpu401_uart_new()</function>, the private
+ interrupt handler is used, hence you don't have to do nothing
+ else than creating the mpu401 stuff. Otherwise, you have to call
+ <function>snd_mpu401_uart_interrupt()</function> explicitly when
+ a UART interrupt is invoked and checked in your own interrupt
+ handler.
+ </para>
+
+ <para>
+ In this case, you need to pass the private_data of the
+ returned rawmidi object from
+ <function>snd_mpu401_uart_new()</function> as the second
+ argument of <function>snd_mpu401_uart_interrupt()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_mpu401_uart_interrupt(irq, rmidi->private_data, regs);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Miscellaneous Devices -->
+<!-- ****************************************************** -->
+ <chapter id="misc-devices">
+ <title>Miscellaneous Devices</title>
+
+ <section id="misc-devices-opl3">
+ <title>FM OPL3</title>
+ <para>
+ The FM OPL3 is still used on many chips (mainly for backward
+ compatibility). ALSA has a nice OPL3 FM control layer, too. The
+ OPL3 API is defined in
+ <filename>&lt;sound/opl3.h&gt;</filename>.
+ </para>
+
+ <para>
+ FM registers can be directly accessed through direct-FM API,
+ defined in <filename>&lt;sound/asound_fm.h&gt;</filename>. In
+ ALSA native mode, FM registers are accessed through
+ Hardware-Dependant Device direct-FM extension API, whereas in
+ OSS compatible mode, FM registers can be accessed with OSS
+ direct-FM compatible API on <filename>/dev/dmfmX</filename> device.
+ </para>
+
+ <para>
+ For creating the OPL3 component, you have two functions to
+ call. The first one is a constructor for <type>opl3_t</type>
+ instance.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ opl3_t *opl3;
+ snd_opl3_create(card, lport, rport, OPL3_HW_OPL3_XXX,
+ integrated, &opl3);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The first argument is the card pointer, the second one is the
+ left port address, and the third is the right port address. In
+ most cases, the right port is placed at the left port + 2.
+ </para>
+
+ <para>
+ The fourth argument is the hardware type.
+ </para>
+
+ <para>
+ When the left and right ports have been already allocated by
+ the card driver, pass non-zero to the fifth argument
+ (<parameter>integrated</parameter>). Otherwise, opl3 module will
+ allocate the specified ports by itself.
+ </para>
+
+ <para>
+ If this function returns successfully with 0, then create a
+ hwdep device for this opl3.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_hwdep_t *opl3hwdep;
+ snd_opl3_hwdep_new(opl3, 0, 1, &opl3hwdep);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The first argument is the <type>opl3_t</type> instance you
+ created, and the second is the index number, usually 0.
+ </para>
+
+ <para>
+ The third argument is the index-offset for the sequencer
+ client assigned to the OPL3 port. When there is an MPU401-UART,
+ give 1 for here (UART always takes 0).
+ </para>
+ </section>
+
+ <section id="misc-devices-hardware-dependent">
+ <title>Hardware-Dependent Devices</title>
+ <para>
+ Some chips need the access from the user-space for special
+ controls or for loading the micro code. In such a case, you can
+ create a hwdep (hardware-dependent) device. The hwdep API is
+ defined in <filename>&lt;sound/hwdep.h&gt;</filename>. You can
+ find examples in opl3 driver or
+ <filename>isa/sb/sb16_csp.c</filename>.
+ </para>
+
+ <para>
+ Creation of the <type>hwdep</type> instance is done via
+ <function>snd_hwdep_new()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_hwdep_t *hw;
+ snd_hwdep_new(card, "My HWDEP", 0, &hw);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where the third argument is the index number.
+ </para>
+
+ <para>
+ You can then pass any pointer value to the
+ <parameter>private_data</parameter>. Again, it should be a
+ magic-allocated record, so that the cast can be checked more
+ safely. If you assign a private data, you should define the
+ destructor, too. The destructor function is set to
+ <structfield>private_free</structfield> field.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ mydata_t *p = snd_magic_kmalloc(mydata_t, 0, GFP_KERNEL);
+ hw->private_data = p;
+ hw->private_free = mydata_free;
+]]>
+ </programlisting>
+ </informalexample>
+
+ and the implementation of destructor would be:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void mydata_free(snd_hwdep_t *hw)
+ {
+ mydata_t *p = snd_magic_cast(mydata_csp_t,
+ hw->private_data, return);
+ snd_magic_kfree(p);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The arbitrary file operations can be defined for this
+ instance. The file operators are defined in
+ <parameter>ops</parameter> table. For example, assume that
+ this chip needs an ioctl.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ hw->ops.open = mydata_open;
+ hw->ops.ioctl = mydata_ioctl;
+ hw->ops.release = mydata_release;
+]]>
+ </programlisting>
+ </informalexample>
+
+ And implement the callback functions as you like.
+ </para>
+ </section>
+
+ <section id="misc-devices-IEC958">
+ <title>IEC958 (S/PDIF)</title>
+ <para>
+ Usually the controls for IEC958 devices are implemented via
+ control interface. There is a macro to compose a name string for
+ IEC958 controls, <function>SNDRV_CTL_NAME_IEC958()</function>
+ defined in <filename>&lt;include/asound.h&gt;</filename>.
+ </para>
+
+ <para>
+ There are some standard controls for IEC958 status bits. These
+ controls use the type <type>SNDRV_CTL_ELEM_TYPE_IEC958</type>,
+ and the size of element is fixed as 4 bytes array
+ (value.iec958.status[x]). For <structfield>info</structfield>
+ callback, you don't specify
+ the value field for this type (the count field must be set,
+ though).
+ </para>
+
+ <para>
+ <quote>IEC958 Playback Con Mask</quote> is used to return the
+ bit-mask for the IEC958 status bits of consumer mode. Similarly,
+ <quote>IEC958 Playback Pro Mask</quote> returns the bitmask for
+ professional mode. They are read-only controls, and are defined
+ as MIXER controls (iface =
+ <constant>SNDRV_CTL_ELEM_IFACE_MIXER</constant>).
+ </para>
+
+ <para>
+ Meanwhile, <quote>IEC958 Playback Default</quote> control is
+ defined for getting and setting the current default IEC958
+ bits. Note that this one is usually defined as a PCM control
+ (iface = <constant>SNDRV_CTL_ELEM_IFACE_PCM</constant>),
+ although in some places it's defined as a MIXER control.
+ </para>
+
+ <para>
+ In addition, you can define the control switches to
+ enable/disable or to set the raw bit mode. The implementation
+ will depend on the chip, but the control should be named as
+ <quote>IEC958 xxx</quote>, preferably using
+ <function>SNDRV_CTL_NAME_IEC958()</function> macro.
+ </para>
+
+ <para>
+ You can find several cases, for example,
+ <filename>pci/emu10k1</filename>,
+ <filename>pci/ice1712</filename>, or
+ <filename>pci/cmipci.c</filename>.
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Buffer and Memory Management -->
+<!-- ****************************************************** -->
+ <chapter id="buffer-and-memory">
+ <title>Buffer and Memory Management</title>
+
+ <section id="buffer-and-memory-buffer-types">
+ <title>Buffer Types</title>
+ <para>
+ ALSA provides several different buffer allocation functions
+ depending on the bus and the architecture. All these have a
+ consistent API. The allocation of physically-contiguous pages is
+ done via
+ <function>snd_malloc_xxx_pages()</function> function, where xxx
+ is the bus type.
+ </para>
+
+ <para>
+ The allocation of pages with fallback is
+ <function>snd_malloc_xxx_pages_fallback()</function>. This
+ function tries to allocate the specified pages but if the pages
+ are not available, it tries to reduce the page sizes until the
+ enough space is found.
+ </para>
+
+ <para>
+ For releasing the space, call
+ <function>snd_free_xxx_pages()</function> function.
+ </para>
+
+ <para>
+ Usually, ALSA drivers try to allocate and reserve
+ a large contiguous physical space
+ at the time the module is loaded for the later use.
+ This is called <quote>pre-allocation</quote>.
+ As already written, you can call the following function at the
+ construction of pcm instance (in the case of PCI bus).
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_lib_preallocate_pci_pages_for_all(pci, pcm, size, max);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where <parameter>size</parameter> is the byte size to be
+ pre-allocated and the <parameter>max</parameter> is the maximal
+ size to be changed via <filename>prealloc</filename> proc file.
+ The allocator will try to get as the large area as possible
+ within the given size.
+ There are different versions of pre-allocator for different
+ buses.
+ </para>
+
+ <para>
+ Once when the buffer is pre-allocated, you can use the
+ allocator in the <structfield>hw_params</structfield> callback
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_lib_malloc_pages(substream, size);
+]]>
+ </programlisting>
+ </informalexample>
+
+ Note that you have to pre-allocate to use this function
+ (i.e. you cannot use this function for
+ <link linkend="buffer-and-memory-non-contiguous"><citetitle>
+ a scatter-gather buffer</citetitle></link>).
+ </para>
+ </section>
+
+ <section id="buffer-and-memory-external-hardware">
+ <title>External Hardware Buffers</title>
+ <para>
+ Some chips have their own hardware buffers and the DMA
+ transfer from the host memory is not available. In such a case,
+ you need to either 1) copy/set the audio data directly to the
+ external hardware buffer, or 2) make an intermediate buffer and
+ copy/set the data from it to the external hardware buffer in
+ interrupts (or in tasklets, preferably).
+ </para>
+
+ <para>
+ The first case works fine if the external hardware buffer is enough
+ large. This method doesn't need any extra buffers and thus is
+ more effective. You need to define the
+ <structfield>copy</structfield> and
+ <structfield>silence</structfield> callbacks for
+ the data transfer. However, there is a drawback: it cannot
+ be mmapped. The examples are GUS's GF1 PCM or emu8000's
+ wavetable PCM.
+ </para>
+
+ <para>
+ The second case allows the mmap of the buffer, although you have
+ to handle an interrupt or a tasklet for transferring the data
+ from the intermediate buffer to the hardware buffer. You can find an
+ example in vxpocket driver.
+ </para>
+
+ <para>
+ Another case is that the chip uses a PCI memory-map
+ region for the buffer instead of the host memory. In this case,
+ mmap is available only on certain architectures like intel. In
+ non-mmap mode, the data cannot be transferred as the normal
+ way. Thus you need to define <structfield>copy</structfield> and
+ <structfield>silence</structfield> callbacks as well
+ as in the cases above. The examples are found in
+ <filename>rme32.c</filename> and <filename>rme96.c</filename>.
+ </para>
+
+ <para>
+ The implementation of <structfield>copy</structfield> and
+ <structfield>silence</structfield> callbacks depends upon
+ whether the hardware supports interleaved or non-interleaved
+ samples. The <structfield>copy</structfield> callback is
+ defined like below, a bit
+ differently depending whether the direction is playback or
+ capture:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int playback_copy(snd_pcm_substream_t *substream, int channel,
+ snd_pcm_uframes_t pos, void *src, snd_pcm_uframes_t count);
+ static int capture_copy(snd_pcm_substream_t *substream, int channel,
+ snd_pcm_uframes_t pos, void *dst, snd_pcm_uframes_t count);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ In the case of interleaved samples, the second argument
+ (<parameter>channel</parameter>) is not used. The third argument
+ (<parameter>pos</parameter>) points the
+ current position offset in frames.
+ </para>
+
+ <para>
+ The meaning of the fourth argument is different between
+ playback and capture. For playback, it holds the source data
+ pointer, and for capture, it's the destination data pointer.
+ </para>
+
+ <para>
+ The last argument is the number of frames to be copied.
+ </para>
+
+ <para>
+ What you have to do in this callback is again different
+ between playback and capture directions. In the case of
+ playback, you do: copy the given amount of data
+ (<parameter>count</parameter>) at the specified pointer
+ (<parameter>src</parameter>) to the specified offset
+ (<parameter>pos</parameter>) on the hardware buffer. When
+ coded like memcpy-like way, the copy would be like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ my_memcpy(my_buffer + frames_to_bytes(runtime, pos), src,
+ frames_to_bytes(runtime, count));
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ For the capture direction, you do: copy the given amount of
+ data (<parameter>count</parameter>) at the specified offset
+ (<parameter>pos</parameter>) on the hardware buffer to the
+ specified pointer (<parameter>dst</parameter>).
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ my_memcpy(dst, my_buffer + frames_to_bytes(runtime, pos),
+ frames_to_bytes(runtime, count));
+]]>
+ </programlisting>
+ </informalexample>
+
+ Note that both of the position and the data amount are given
+ in frames.
+ </para>
+
+ <para>
+ In the case of non-interleaved samples, the implementation
+ will be a bit more complicated.
+ </para>
+
+ <para>
+ You need to check the channel argument, and if it's -1, copy
+ the whole channels. Otherwise, you have to copy only the
+ specified channel. Please check
+ <filename>isa/gus/gus_pcm.c</filename> as an example.
+ </para>
+
+ <para>
+ The <structfield>silence</structfield> callback is also
+ implemented in a similar way.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int silence(snd_pcm_substream_t *substream, int channel,
+ snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The meanings of arguments are identical with the
+ <structfield>copy</structfield>
+ callback, although there is no <parameter>src/dst</parameter>
+ argument. In the case of interleaved samples, the channel
+ argument has no meaning, as well as on
+ <structfield>copy</structfield> callback.
+ </para>
+
+ <para>
+ The role of <structfield>silence</structfield> callback is to
+ set the given amount
+ (<parameter>count</parameter>) of silence data at the
+ specified offset (<parameter>pos</parameter>) on the hardware
+ buffer. Suppose that the data format is signed (that is, the
+ silent-data is 0), and the implementation using a memset-like
+ function would be like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ my_memcpy(my_buffer + frames_to_bytes(runtime, pos), 0,
+ frames_to_bytes(runtime, count));
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ In the case of non-interleaved samples, again, the
+ implementation becomes a bit more complicated. See, for example,
+ <filename>isa/gus/gus_pcm.c</filename>.
+ </para>
+ </section>
+
+ <section id="buffer-and-memory-non-contiguous">
+ <title>Non-Contiguous Buffers</title>
+ <para>
+ If your hardware supports the page table like emu10k1 or the
+ buffer descriptors like via82xx, you can use the scatter-gather
+ (SG) DMA. ALSA provides an interface for handling SG-buffers.
+ The API is provided in <filename>&lt;sound/pcm_sgbuf.h&gt;</filename>.
+ </para>
+
+ <para>
+ For creating the SG-buffer handler, call
+ <function>snd_pcm_sgbuf_init()</function> in the
+ <structfield>open</structfield> callback
+ of a pcm substream (or in the constructor of the pcm).
+ You need to pass the
+ <structname>pci_dev</structname> struct pointer of the chip
+ and the default table size (which can be changed
+ dynamically). The <type>snd_sg_buf_t</type> instance is created as
+ substream-&gt;dma_private. You can cast
+ the pointer like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_pcm_sgbuf_t *sgbuf = snd_magic_cast(snd_pcm_sgbuf_t,
+ substream->dma_private, return -EINVAL);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Then call <function>snd_pcm_sgbuf_alloc()</function> instead
+ of normal <function>snd_pcm_lib_malloc_pages()</function> in
+ <structfield>hw_params</structfield> callback. The SG-handler
+ will allocate single pages
+ and build the table on sgbuf-&gt;table. The pointer and the
+ physical address of each page is stored in this table. You can
+ get the physical address at a certain offset via
+ <function>snd_pcm_sgbuf_get_addr()</function>.
+ </para>
+
+ <para>
+ When a SG-handler is used, you need to set
+ <function>snd_pcm_sgbuf_ops_copy_playback</function> and
+ <function>snd_pcm_ops_silence</function> as the
+ <structfield>copy</structfield> and the
+ <structfield>silence</structfield> callbacks, respectively.
+ All the jobs described in the previous section will be done
+ by these helper functions.
+ Also, the <structfield>page</structfield> callback must be set as
+ <function>snd_pcm_sgbuf_ops_page</function>.
+ </para>
+
+ <para>
+ For releasing the data, call
+ <function>snd_pcm_sgbuf_free()</function> in the
+ <structfield>hw_free</structfield> callback, and call
+ <function>snd_pcm_sgbuf_delete()</function> in the
+ <structfield>close</structfield> callback (or in the destructor,
+ if <function>snd_pcm_sgbuf_init()</function> was called in the
+ constructor).
+ </para>
+
+ <para>
+ Note that you must not do pre-allocation if a SG-handler is used.
+ They conflict with each other.
+ </para>
+ </section>
+
+ <section id="buffer-and-memory-vmalloced">
+ <title>Vmalloc'ed Buffers</title>
+ <para>
+ It's possible to use a buffer allocated via
+ <function>vmalloc</function>, for example, for an intermediate
+ buffer. Since the allocated pages are not contiguous, you need
+ to set the <structfield>page</structfield> callback to obtain
+ the physical address at every offset.
+ </para>
+
+ <para>
+ The implementation of <structfield>page</structfield> callback
+ would be like this:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #include <linux/vmalloc.h>
+ #include <linux/pagemap.h>
+
+ /* get the physical page pointer on the given offset */
+ static struct page *mychip_page(snd_pcm_substream_t *subs,
+ unsigned long offset)
+ {
+ pgd_t *pgd;
+ pmd_t *pmd;
+ pte_t *pte;
+ unsigned long lpage;
+ void *pageptr = subs->runtime->dma_area + offset;
+ struct page *page = NOPAGE_SIGBUS;
+
+ lpage = VMALLOC_VMADDR(pageptr);
+ spin_lock(&init_mm.page_table_lock);
+ pgd = pgd_offset(&init_mm, lpage);
+ pmd = pmd_offset(pgd, lpage);
+ pte = pte_offset(pmd, lpage);
+ page = pte_page(*pte);
+ spin_unlock(&init_mm.page_table_lock);
+
+ return page;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ You don't need <structfield>copy</structfield> and
+ <structfield>silence</structfield> callbacks in this case,
+ because the copy/set operations are available on the virtual
+ address.
+ </para>
+ </section>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Proc Interface -->
+<!-- ****************************************************** -->
+ <chapter id="proc-interface">
+ <title>Proc Interface</title>
+ <para>
+ ALSA provides an easy interface for procfs. The proc files are
+ very useful for debugging. I recommend you set up proc files if
+ you write a driver and want to get a running status or register
+ dumps. The API is found in
+ <filename>&lt;sound/info.h&gt;</filename>.
+ </para>
+
+ <para>
+ For creating a proc file, call
+ <function>snd_card_proc_new()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_info_entry_t *entry;
+ int err = snd_card_proc_new(card, "my-file", &entry);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where the second argument specifies the proc-file name to be
+ created. The above example will create a file
+ <filename>my-file</filename> under the card directory,
+ e.g. <filename>/proc/asound/card0/my-file</filename>.
+ </para>
+
+ <para>
+ Like other components, the proc entry created via
+ <function>snd_card_proc_new()</function> will be registered and
+ released automatically in the card registration and release
+ functions.
+ </para>
+
+ <para>
+ When the creation is successful, the function stores a new
+ instance at the pointer given in the third argument.
+ It is initialized as a text proc file for read only. For using
+ this proc file as a read-only text file as it is, set the read
+ callback with a private data via
+ <function>snd_info_set_text_ops()</function>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_info_set_text_ops(entry, chip, my_proc_read);
+]]>
+ </programlisting>
+ </informalexample>
+
+ where the second argument (<parameter>chip</parameter>) is the
+ private data to be used in the callbacks and the third
+ (<parameter>my_proc_read</parameter>) is the callback function, which
+ is defined like
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void my_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t *buffer);
+]]>
+ </programlisting>
+ </informalexample>
+
+ </para>
+
+ <para>
+ In the read callback, use <function>snd_iprintf()</function> for
+ output strings, which works just like normal
+ <function>printf()</function>. For example,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void my_proc_read(snd_info_entry_t *entry,
+ snd_info_buffer_t *buffer)
+ {
+ chip_t *cm = snd_magic_cast(mychip_t,
+ entry->private_data, return);
+
+ snd_iprintf(buffer, "This is my chip!\n");
+ snd_iprintf(buffer, "Port = %ld\n", chip->port);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The file permission can be changed afterwards. As default, it's
+ set as read only for all users. If you want to add the write
+ permission to the user (root as default), set like below:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+]]>
+ </programlisting>
+ </informalexample>
+
+ and set the write buffer size and the callback
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ entry->c.text.write_size = 256;
+ entry->c.text.write = my_proc_write;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The buffer size for read is set to 1024 implicitly by
+ <function>snd_info_set_text_ops()</function>. It should suffice
+ in most cases (the size will be aligned to
+ <constant>PAGE_SIZE</constant> anyway), but if you need to handle
+ very large text files, you can set it explicitly, too.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ entry->c.text.read_size = 65536;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ For the write callback, you can use
+ <function>snd_info_get_line()</function> to get a text line, and
+ <function>snd_info_get_str()</function> to retrieve a string from
+ the line. Some examples are found in
+ <filename>core/oss/mixer_oss.c</filename>, core/oss/and
+ <filename>pcm_oss.c</filename>.
+ </para>
+
+ <para>
+ For a raw-data proc-file, set the attributes like the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static struct snd_info_entry_ops my_file_io_ops = {
+ .read = my_file_io_read,
+ };
+
+ entry->content = SNDRV_INFO_CONTENT_DATA;
+ entry->private_data = chip;
+ entry->c.ops = &my_file_io_ops;
+ entry->size = 4096;
+ entry->mode = S_IFREG | S_IRUGO;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The callback is much more complicated than the text-file
+ version. You need to use a low-level i/o functions such as
+ <function>copy_from/to_user()</function> to transfer the
+ data. Also, you have to keep tracking the file position, too.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static long my_file_io_read(snd_info_entry_t *entry,
+ void *file_private_data,
+ struct file *file,
+ char *buf, long count)
+ {
+ long size = count;
+ if (file->f_pos + size > local_max_size)
+ size = local_max_size - file->f_pos;
+ if (copy_to_user(buf, local_data + file->f_pos, size))
+ return -EFAULT;
+ file->f_pos += size;
+ return size;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Power Management -->
+<!-- ****************************************************** -->
+ <chapter id="power-management">
+ <title>Power Management</title>
+ <para>
+ If the chip is supposed to work with with suspend/resume
+ functions, you need to add the power-management codes to the
+ driver. The additional codes for the power-management should be
+ <function>ifdef</function>'ed with
+ <constant>CONFIG_PM</constant>.
+ </para>
+
+ <para>
+ Basic jobs of suspend/resume are done in
+ <structfield>suspend</structfield> and
+ <structfield>resume</structfield> callbacks of
+ <structname>pci_driver</structname> struct. Unfortunately, the
+ API of these callbacks was changed at the middle time of Linux
+ 2.4.x, if you want to keep the support for older kernels, you
+ have to write two different callbacks. The example below is the
+ skeleton callbacks which just call the real suspend and resume
+ functions.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #ifndef PCI_OLD_SUSPEND
+ static int snd_my_suspend(struct pci_dev *dev, u32 state)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(dev), return -ENXIO);
+ mychip_suspend(chip);
+ return 0;
+ }
+ static int snd_my_resume(struct pci_dev *dev)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(dev), return -ENXIO);
+ mychip_resume(chip);
+ return 0;
+ }
+ #else
+ static void snd_my_suspend(struct pci_dev *dev)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(dev), return);
+ mychip_suspend(chip);
+ }
+ static void snd_mychip_resume(struct pci_dev *dev)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ pci_get_drvdata(dev), return);
+ mychip_resume(chip);
+ }
+ #endif
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The scheme of the real suspend job is as following.
+
+ <orderedlist>
+ <listitem><para>Check whether the power-state is already D3hot. If yes, skip the job.</para></listitem>
+ <listitem><para>Call <function>snd_pcm_suspend_all()</function> to suspend the running PCM streams.</para></listitem>
+ <listitem><para>Save the register values if necessary.</para></listitem>
+ <listitem><para>Stop the hardware if necessary.</para></listitem>
+ <listitem><para>Set the power-state as D3hot by calling <function>snd_power_change_state()</function>.</para></listitem>
+ </orderedlist>
+ </para>
+
+ <para>
+ A typical code would be like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void mychip_suspend(mychip_t *chip)
+ {
+ snd_card_t *card = chip->card;
+ // (1)
+ if (card->power_state == SNDRV_CTL_POWER_D3hot)
+ return;
+ // (2)
+ snd_pcm_suspend_all(chip->pcm);
+ // (3)
+ snd_mychip_save_registers(chip);
+ // (4)
+ snd_mychip_stop_hardware(chip);
+ // (5)
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The scheme of the real resume job is as following.
+
+ <orderedlist>
+ <listitem><para>Check whether the power-state is already D0.
+ If yes, skip the job.</para></listitem>
+ <listitem><para>Enable the pci device again by calling
+ <function>pci_enable_device()</function>.</para></listitem>
+ <listitem><para>Re-initialize the chip.</para></listitem>
+ <listitem><para>Restore the saved registers if necessary.</para></listitem>
+ <listitem><para>Resume the mixer, e.g. calling
+ <function>snd_ac97_resume()</function>.</para></listitem>
+ <listitem><para>Restart the hardware (if any).</para></listitem>
+ <listitem><para>Set the power-state as D0 by calling
+ <function>snd_power_change_state()</function>.</para></listitem>
+ </orderedlist>
+ </para>
+
+ <para>
+ A typical code would be like:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static void mychip_resume(mychip_t *chip)
+ {
+ snd_card_t *card = chip->card;
+ // (1)
+ if (card->power_state == SNDRV_CTL_POWER_D0)
+ return;
+ // (2)
+ pci_enable_device(chip->pci);
+ // (3)
+ snd_mychip_reinit_chip(chip);
+ // (4)
+ snd_mychip_restore_registers(chip);
+ // (5)
+ snd_ac97_resume(chip->ac97);
+ // (6)
+ snd_mychip_restart_chip(chip);
+ // (7)
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ In addition to the callbacks above, you should define a callback
+ for the changes via the ALSA control interface. It's defined
+ like below:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int snd_mychip_set_power_state(snd_card_t *card,
+ unsigned int power_state)
+ {
+ mychip_t *chip = snd_magic_cast(mychip_t,
+ card->power_state_private_data, return -ENXIO);
+ switch (power_state) {
+ case SNDRV_CTL_POWER_D0:
+ case SNDRV_CTL_POWER_D1:
+ case SNDRV_CTL_POWER_D2:
+ mychip_resume(chip);
+ break;
+ case SNDRV_CTL_POWER_D3hot:
+ case SNDRV_CTL_POWER_D3cold:
+ mychip_suspend(chip);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ OK, we have all callbacks now. Let's set up them now. In the
+ initialization of the card, add the following:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int __devinit snd_mychip_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+ {
+ ....
+ snd_card_t *card;
+ ....
+ #ifdef CONFIG_PM
+ card->set_power_state = snd_mychip_set_power_state;
+ card->power_state_private_data = chip;
+ #endif
+ ....
+ }
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ If you need a space for saving the registers, you'll need to
+ allocate the buffer for it here, too, since you cannot call
+ <function>kmalloc()</function> with
+ <constant>GFP_KERNEL</constant> flag or
+ <function>vmalloc()</function> in the suspend callback.
+ The allocated buffer should be released in the corresponding
+ destructor.
+ </para>
+
+ <para>
+ And next, set suspend/resume callbacks to the pci_driver,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static struct pci_driver driver = {
+ .name = "My Chip",
+ ....
+ #ifdef CONFIG_PM
+ .suspend = snd_mychip_suspend,
+ .resume = snd_mychip_resume,
+ #endif
+ };
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Last but not least: Please keep in mind that you cannot call
+ <function>schedule()</function> during the suspend and the resume
+ callbacks. If any delay is necessary, you have to use
+ <function>mdelay()</function> or <function>udelay()</function>
+ instead of <function>schedule_timeout()</function>!
+ Of course, semaphores cannot be used, too, which will invoke sleep
+ inside.
+ </para>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Module Parameters -->
+<!-- ****************************************************** -->
+ <chapter id="module-parameters">
+ <title>Module Parameters</title>
+ <para>
+ There are standard module options for ALSA. At least, each
+ module should have <parameter>index</parameter>,
+ <parameter>id</parameter> and <parameter>enable</parameter>
+ options.
+ </para>
+
+ <para>
+ If the module supports multiple cards (usually up to
+ 8 = <constant>SNDRV_CARDS</constant> cards), they should be
+ arrays. The default initial values are defined already as
+ constants for ease of programming:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+ static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ If the module supports only a single card, they could be single
+ variables, instead. <parameter>enable</parameter> option is not
+ always necessary in this case, but it wouldn't be so bad to have a
+ dummy option for compatibility.
+ </para>
+
+ <para>
+ The module parameters must be declared with the standard
+ <function>MODULE_PARM()</function> and
+ <function>MODULE_PARM_DESC()</function> macros. The ALSA provides
+ an additional macro, <function>MODULE_PARM_SYNTAX()</function>,
+ for describing its syntax. The strings will be written to
+ <filename>/lib/modules/XXX/modules.generic_string</filename>
+ file.
+ </para>
+
+ <para>
+ For convenience, the typical string arguments given to
+ <function>MODULE_PARM_SYNTAX()</function> are defined in
+ <filename>&lt;sound/initval.h&gt;</filename>, such as
+ <constant>SNDRV_ID_DESC</constant> or
+ <constant>SNDRV_ENABLED</constant>.
+ </para>
+
+ <para>
+ The typical coding would be like below:
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #define CARD_NAME "My Chip"
+
+ MODULE_PARM(index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
+ MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+ MODULE_PARM_SYNTAX(index, SNDRV_INDEX_DESC);
+ MODULE_PARM(id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
+ MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+ MODULE_PARM_SYNTAX(id, SNDRV_ID_DESC);
+ MODULE_PARM(enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
+ MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+ MODULE_PARM_SYNTAX(enable, SNDRV_ENABLE_DESC);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ Also, don't forget to define the module description, classes,
+ license and devices. Especially, the recent modprobe requires to
+ define the module license as GPL, etc., otherwise the system is
+ shown as <quote>tainted</quote>.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ MODULE_DESCRIPTION("My Chip");
+ MODULE_CLASSES("{sound}");
+ MODULE_LICENSE("GPL");
+ MODULE_DEVICES("{{Vendor,My Chip Name}}");
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ For building the driver into kernel, you should define the
+ <function>setup()</function> function in addition, too.
+ ALSA provides <function>get_id()</function> function to retrieve
+ a string argument from the kernel boot parameters.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ #ifndef MODULE
+
+ /* format is: snd-mychip=enable,index,id */
+
+ static int __init alsa_card_mychip_setup(char *str)
+ {
+ static unsigned __initdata nr_dev = 0;
+
+ if (nr_dev >= SNDRV_CARDS)
+ return 0;
+ (void)(get_option(&str,&enable[nr_dev]) == 2 &&
+ get_option(&str,&index[nr_dev]) == 2 &&
+ get_id(&str,&id[nr_dev]) == 2);
+ nr_dev++;
+ return 1;
+ }
+
+ __setup("snd-mychip=", alsa_card_mychip_setup);
+
+ #endif /* ifndef MODULE */
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Useful Functions -->
+<!-- ****************************************************** -->
+ <chapter id="useful-functions">
+ <title>Useful Functions</title>
+
+ <section id="useful-functions-snd-printk">
+ <title><function>snd_printk()</function> and friends</title>
+ <para>
+ ALSA provides a verbose version of
+ <function>printk()</function> function. If a kernel config
+ <constant>CONFIG_SND_VERBOSE_PRINTK</constant> is set, this
+ function prints the given message together with the file name
+ and the line of the caller. The <constant>KERN_XXX</constant>
+ prefix is processed as
+ well as the original <function>printk()</function> does, so it's
+ recommended to add this prefix, e.g.
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_printk(KERN_ERR "Oh my, sorry, it's extremely bad!\n");
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ There are also <function>printk()</function>'s for
+ debugging. <function>snd_printd()</function> can be used for
+ general debugging purposes. If
+ <constant>CONFIG_SND_DEBUG</constant> is set, this function is
+ compiled, and works just like
+ <function>snd_printk()</function>. If the ALSA is compiled
+ without the debugging flag, it's ignored.
+ </para>
+
+ <para>
+ <function>snd_printdd()</function> is compiled in only when
+ <constant>CONFIG_SND_DEBUG_DETECT</constant> is set. Please note
+ that <constant>DEBUG_DETECT</constant> is not set as default
+ even if you configure the alsa-driver with
+ <option>--with-debug=full</option> option. You need to give
+ explicitly <option>--with-debug=detect</option> option instead.
+ </para>
+ </section>
+
+ <section id="useful-functions-snd-assert">
+ <title><function>snd_assert()</function></title>
+ <para>
+ <function>snd_assert()</function> macro is similar with the
+ normal <function>assert()</function> macro. For example,
+
+ <informalexample>
+ <programlisting>
+<![CDATA[
+ snd_assert(pointer != NULL, return -EINVAL);
+]]>
+ </programlisting>
+ </informalexample>
+ </para>
+
+ <para>
+ The first argument is the expression to evaluate, and the
+ second argument is the action if it fails. When
+ <constant>CONFIG_SND_DEBUG</constant>, is set, it will show an
+ error message such as <computeroutput>BUG? (xxx) (called from
+ yyy)</computeroutput>. When no debug flag is set, this is
+ ignored.
+ </para>
+ </section>
+
+ <section id="useful-functions-snd-runtime-check">
+ <title><function>snd_runtime_check()</function></title>
+ <para>
+ This macro is quite similar with
+ <function>snd_assert()</function>. Unlike
+ <function>snd_assert()</function>, the expression is always
+ evaluated regardless of
+ <constant>CONFIG_SND_DEBUG</constant>. When
+ <constant>CONFIG_SND_DEBUG</constant> is set, the macro will
+ show a message like <computeroutput>ERROR (xx) (called from
+ yyy)</computeroutput>.
+ </para>
+ </section>
+
+ <section id="useful-functions-snd-bug">
+ <title><function>snd_BUG()</function></title>
+ <para>
+ It calls <function>snd_assert(0,)</function> -- that is, just
+ prints the error message at the point. It's useful to show that
+ a fatal error happens there.
+ </para>
+ </section>
+ </chapter>
+
+
+<!-- ****************************************************** -->
+<!-- Acknowledgments -->
+<!-- ****************************************************** -->
+ <chapter id="acknowledments">
+ <title>Acknowledgments</title>
+ <para>
+ I would like to thank Phil Kerr for his help for improvement and
+ corrections of this document.
+ </para>
+ <para>
+ Kevin Conder reformatted the original plain-text to the
+ DocBook format.
+ </para>
+ </chapter>
+
+
+</book>
diff --git a/include/sound/ac97_codec.h b/include/sound/ac97_codec.h
index fd6c403d0828..47e5e67e990e 100644
--- a/include/sound/ac97_codec.h
+++ b/include/sound/ac97_codec.h
@@ -196,6 +196,8 @@
#define AC97_CXR_SPDIF_AC3 0x2
/* specific - ALC */
+#define AC97_ALC650_SURR_DAC_VOL 0x64
+#define AC97_ALC650_LFE_DAC_VOL 0x66
#define AC97_ALC650_MULTICH 0x6a
#define AC97_ALC650_CLOCK 0x7a
@@ -235,8 +237,6 @@ struct _snd_ac97 {
unsigned short (*read) (ac97_t *ac97, unsigned short reg);
void (*wait) (ac97_t *ac97);
void (*init) (ac97_t *ac97);
- snd_info_entry_t *proc_entry;
- snd_info_entry_t *proc_regs_entry;
void *private_data;
void (*private_free) (ac97_t *ac97);
/* --- */
diff --git a/include/sound/ad1848.h b/include/sound/ad1848.h
index 9ae8eb32db5f..18f1fec77e2d 100644
--- a/include/sound/ad1848.h
+++ b/include/sound/ad1848.h
@@ -162,6 +162,7 @@ int snd_ad1848_create(snd_card_t * card,
ad1848_t ** chip);
int snd_ad1848_pcm(ad1848_t * chip, int device, snd_pcm_t **rpcm);
+const snd_pcm_ops_t *snd_ad1848_get_pcm_ops(int direction);
int snd_ad1848_mixer(ad1848_t * chip);
void snd_ad1848_interrupt(int irq, void *dev_id, struct pt_regs *regs);
diff --git a/include/sound/ak4531_codec.h b/include/sound/ak4531_codec.h
index bf8294d6722c..ad7b52bc2627 100644
--- a/include/sound/ak4531_codec.h
+++ b/include/sound/ak4531_codec.h
@@ -68,7 +68,6 @@ typedef struct _snd_ak4531 ak4531_t;
struct _snd_ak4531 {
void (*write) (ak4531_t *ak4531, unsigned short reg, unsigned short val);
- snd_info_entry_t *proc_entry;
void *private_data;
void (*private_free) (ak4531_t *ak4531);
/* --- */
diff --git a/include/sound/core.h b/include/sound/core.h
index 9d705453eca1..68b3a692eff7 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -55,6 +55,7 @@ typedef enum {
SNDRV_DEV_TIMER,
SNDRV_DEV_SEQUENCER,
SNDRV_DEV_HWDEP,
+ SNDRV_DEV_INFO,
SNDRV_DEV_LOWLEVEL = (2*SNDRV_DEV_TYPE_RANGE_SIZE)
} snd_device_type_t;
@@ -281,6 +282,8 @@ void snd_free_pages(void *ptr, unsigned long size);
void *snd_malloc_pci_pages(struct pci_dev *pci, unsigned long size, dma_addr_t *dma_addr);
void *snd_malloc_pci_pages_fallback(struct pci_dev *pci, unsigned long size, dma_addr_t *dma_addr, unsigned long *res_size);
void snd_free_pci_pages(struct pci_dev *pci, unsigned long size, void *ptr, dma_addr_t dma_addr);
+void *snd_malloc_pci_page(struct pci_dev *pci, dma_addr_t *dma_addr);
+#define snd_free_pci_page(pci,ptr,addr) snd_free_pci_pages(pci,PAGE_SIZE,ptr,addr)
#endif
#ifdef CONFIG_SBUS
void *snd_malloc_sbus_pages(struct sbus_dev *sdev, unsigned long size, dma_addr_t *dma_addr);
diff --git a/include/sound/cs46xx.h b/include/sound/cs46xx.h
index dc849a634aec..f60ea070bacd 100644
--- a/include/sound/cs46xx.h
+++ b/include/sound/cs46xx.h
@@ -1673,7 +1673,6 @@ typedef struct {
unsigned long remap_addr;
unsigned long size;
struct resource *resource;
- void *proc_entry;
} snd_cs46xx_region_t;
struct _snd_cs46xx {
@@ -1726,7 +1725,6 @@ struct _snd_cs46xx {
spinlock_t reg_lock;
unsigned int midcr;
unsigned int uartm;
- snd_info_entry_t *proc_entry;
int amplifier;
void (*amplifier_ctrl)(cs46xx_t *, int);
diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
index 7d6b34144c0d..afdb277148c0 100644
--- a/include/sound/emu10k1.h
+++ b/include/sound/emu10k1.h
@@ -25,12 +25,11 @@
#ifdef __KERNEL__
-#include "pcm.h"
-#include "pcm_sgbuf.h"
-#include "rawmidi.h"
-#include "hwdep.h"
-#include "ac97_codec.h"
-#include "util_mem.h"
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/ac97_codec.h>
+#include <sound/util_mem.h>
#include <asm/io.h>
#ifndef PCI_VENDOR_ID_CREATIVE
@@ -993,13 +992,6 @@ struct _snd_emu10k1 {
emu10k1_midi_t midi2; /* for audigy */
unsigned int efx_voices_mask[2];
-
- snd_info_entry_t *proc_entry;
- snd_info_entry_t *proc_entry_fx8010_gpr;
- snd_info_entry_t *proc_entry_fx8010_tram_data;
- snd_info_entry_t *proc_entry_fx8010_tram_addr;
- snd_info_entry_t *proc_entry_fx8010_code;
- snd_info_entry_t *proc_entry_fx8010_iblocks;
};
int snd_emu10k1_create(snd_card_t * card,
@@ -1045,7 +1037,7 @@ unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate);
unsigned char snd_emu10k1_sum_vol_attn(unsigned int value);
/* memory allocation */
-snd_util_memblk_t *snd_emu10k1_alloc_pages(emu10k1_t *emu, struct snd_sg_buf *sgbuf);
+snd_util_memblk_t *snd_emu10k1_alloc_pages(emu10k1_t *emu, snd_pcm_substream_t *substream);
int snd_emu10k1_free_pages(emu10k1_t *emu, snd_util_memblk_t *blk);
snd_util_memblk_t *snd_emu10k1_synth_alloc(emu10k1_t *emu, unsigned int size);
int snd_emu10k1_synth_free(emu10k1_t *emu, snd_util_memblk_t *blk);
@@ -1063,7 +1055,6 @@ int snd_emu10k1_audigy_midi(emu10k1_t * emu);
/* proc interface */
int snd_emu10k1_proc_init(emu10k1_t * emu);
-int snd_emu10k1_proc_done(emu10k1_t * emu);
#endif /* __KERNEL__ */
diff --git a/include/sound/gus.h b/include/sound/gus.h
index 2e84f2687588..a896bf9ff626 100644
--- a/include/sound/gus.h
+++ b/include/sound/gus.h
@@ -210,7 +210,6 @@ typedef struct _snd_gf1_mem {
snd_gf1_bank_info_t banks_16[4];
snd_gf1_mem_block_t *first;
snd_gf1_mem_block_t *last;
- snd_info_entry_t *info_entry;
struct semaphore memory_mutex;
} snd_gf1_mem_t;
@@ -332,8 +331,6 @@ struct _snd_gf1 {
unsigned int rom_banks; /* GUS's ROM banks */
snd_gf1_mem_t mem_alloc;
- snd_info_entry_t *ram_entries[4];
- snd_info_entry_t *rom_entries[4];
/* registers */
unsigned short reg_page;
@@ -452,9 +449,6 @@ struct _snd_gus_card {
int timer_dev; /* timer device */
struct _snd_gf1 gf1; /* gf1 specific variables */
-#ifdef CONFIG_SND_DEBUG
- snd_info_entry_t *irq_entry;
-#endif
snd_pcm_t *pcm;
snd_pcm_substream_t *pcm_cap_substream;
unsigned int c_dma_size;
@@ -601,7 +595,6 @@ int snd_gf1_mem_done(snd_gus_card_t * gus);
/* gus_mem_proc.c */
int snd_gf1_mem_proc_init(snd_gus_card_t * gus);
-int snd_gf1_mem_proc_done(snd_gus_card_t * gus);
/* gus_dma.c */
@@ -676,7 +669,6 @@ int snd_gus_initialize(snd_gus_card_t * gus);
void snd_gus_interrupt(int irq, void *dev_id, struct pt_regs *regs);
#ifdef CONFIG_SND_DEBUG
void snd_gus_irq_profile_init(snd_gus_card_t *gus);
-void snd_gus_irq_profile_done(snd_gus_card_t *gus);
#endif
/* gus_uart.c */
diff --git a/include/sound/info.h b/include/sound/info.h
index 6984c6e736dc..c9bb51aedb04 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -141,6 +141,19 @@ struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode,
void snd_remove_proc_entry(struct proc_dir_entry *parent,
struct proc_dir_entry *de);
+/* for card drivers */
+int snd_card_proc_new(snd_card_t *card, const char *name, snd_info_entry_t **entryp);
+
+inline static void snd_info_set_text_ops(snd_info_entry_t *entry,
+ void *private_data,
+ void (*read)(snd_info_entry_t *, snd_info_buffer_t *))
+{
+ entry->private_data = private_data;
+ entry->c.text.read_size = 1024;
+ entry->c.text.read = read;
+}
+
+
#else
#define snd_seq_root NULL
@@ -169,6 +182,9 @@ static inline struct proc_dir_entry *snd_create_proc_entry(const char *name, mod
static inline void snd_remove_proc_entry(struct proc_dir_entry *parent,
struct proc_dir_entry *de) { ; }
+#define snd_card_proc_new(card,name,entryp) 0 /* always success */
+#define snd_info_set_text_ops(entry,private_data,read) /*NOP*/
+
#endif
/*
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index d97f7e751d9a..0bc5fef9456e 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -50,7 +50,7 @@ typedef struct sndrv_pcm_mmap_status snd_pcm_mmap_status_t;
typedef struct sndrv_pcm_mmap_control snd_pcm_mmap_control_t;
typedef struct sndrv_mask snd_mask_t;
-#define _snd_pcm_substream_chip(substream) ((substream)->pcm->private_data)
+#define _snd_pcm_substream_chip(substream) ((substream)->private_data)
#define snd_pcm_substream_chip(substream) snd_magic_cast1(chip_t, _snd_pcm_substream_chip(substream), return -ENXIO)
#define _snd_pcm_chip(pcm) ((pcm)->private_data)
#define snd_pcm_chip(pcm) snd_magic_cast1(chip_t, _snd_pcm_chip(pcm), return -ENXIO)
@@ -120,10 +120,11 @@ typedef struct _snd_pcm_ops {
#define SNDRV_PCM_TRIGGER_SUSPEND 5
#define SNDRV_PCM_TRIGGER_RESUME 6
-#define SNDRV_PCM_DMA_TYPE_CONTINUOUS 0 /* continuous no-DMA memory */
-#define SNDRV_PCM_DMA_TYPE_ISA 1 /* ISA continuous */
-#define SNDRV_PCM_DMA_TYPE_PCI 2 /* PCI continuous */
-#define SNDRV_PCM_DMA_TYPE_SBUS 3 /* SBUS continuous */
+#define SNDRV_PCM_DMA_TYPE_UNKNOWN 0 /* not defined */
+#define SNDRV_PCM_DMA_TYPE_CONTINUOUS 1 /* continuous no-DMA memory */
+#define SNDRV_PCM_DMA_TYPE_ISA 2 /* ISA continuous */
+#define SNDRV_PCM_DMA_TYPE_PCI 3 /* PCI continuous */
+#define SNDRV_PCM_DMA_TYPE_SBUS 4 /* SBUS continuous */
/* If you change this don't forget to change rates[] table in pcm_native.c */
#define SNDRV_PCM_RATE_5512 (1<<0) /* 5512Hz */
@@ -363,6 +364,7 @@ struct _snd_pcm_runtime {
struct _snd_pcm_substream {
snd_pcm_t *pcm;
snd_pcm_str_t *pstr;
+ void *private_data; /* copied from pcm->private_data */
int number;
char name[32]; /* substream name */
int stream; /* stream (direction) */
diff --git a/include/sound/pcm_sgbuf.h b/include/sound/pcm_sgbuf.h
index bc6474559c57..e5dda1d053af 100644
--- a/include/sound/pcm_sgbuf.h
+++ b/include/sound/pcm_sgbuf.h
@@ -63,5 +63,8 @@ int snd_pcm_sgbuf_ops_copy_capture(snd_pcm_substream_t *substream, int channel,
int snd_pcm_sgbuf_ops_silence(snd_pcm_substream_t *substream, int channel, snd_pcm_uframes_t hwoff, snd_pcm_uframes_t count);
struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset);
+#define _snd_pcm_substream_sgbuf(substream) ((substream)->dma_private)
+#define snd_pcm_substream_sgbuf(substream) snd_magic_cast(snd_pcm_sgbuf_t, _snd_pcm_substream_sgbuf(substream), return -ENXIO)
+
#endif /* __SOUND_PCM_SGBUF_H */
diff --git a/include/sound/sb.h b/include/sound/sb.h
index 53f243b7bf64..4b9b902e9ea7 100644
--- a/include/sound/sb.h
+++ b/include/sound/sb.h
@@ -305,6 +305,7 @@ int snd_sb8dsp_midi(sb_t *chip, int device, snd_rawmidi_t ** rrawmidi);
/* sb16_init.c */
int snd_sb16dsp_pcm(sb_t *chip, int device, snd_pcm_t ** rpcm);
+const snd_pcm_ops_t *snd_sb16dsp_get_pcm_ops(int direction);
int snd_sb16dsp_configure(sb_t *chip);
/* sb16.c */
void snd_sb16dsp_interrupt(int irq, void *dev_id, struct pt_regs *regs);
diff --git a/include/sound/sb16_csp.h b/include/sound/sb16_csp.h
index dfdb71dc6a7d..eb8368b56b16 100644
--- a/include/sound/sb16_csp.h
+++ b/include/sound/sb16_csp.h
@@ -159,7 +159,6 @@ struct snd_sb_csp {
snd_kcontrol_t *qsound_space;
struct semaphore access_mutex; /* locking */
- snd_info_entry_t *proc; /* proc interface */
};
int snd_sb_csp_new(sb_t *chip, int device, snd_hwdep_t ** rhwdep);
diff --git a/include/sound/sndmagic.h b/include/sound/sndmagic.h
index 83495c4ce9e5..560f248b9cdf 100644
--- a/include/sound/sndmagic.h
+++ b/include/sound/sndmagic.h
@@ -27,10 +27,45 @@
void *_snd_magic_kcalloc(unsigned long magic, size_t size, int flags);
void *_snd_magic_kmalloc(unsigned long magic, size_t size, int flags);
-void snd_magic_kfree(void *ptr);
-#define snd_magic_kcalloc(type, extra, flags) (type *) _snd_magic_kcalloc(type##_magic, sizeof(type) + extra, flags)
-#define snd_magic_kmalloc(type, extra, flags) (type *) _snd_magic_kmalloc(type##_magic, sizeof(type) + extra, flags)
+/**
+ * snd_magic_kmalloc - allocate a record with a magic-prefix
+ * @type: the type to allocate a record (like xxx_t)
+ * @extra: the extra size to allocate in bytes
+ * @flags: the allocation condition (GFP_XXX)
+ *
+ * Allocates a record of the given type with the extra space and
+ * returns its pointer. The allocated record has a secret magic-key
+ * to be checked via snd_magic_cast() for safe casts.
+ *
+ * The allocated pointer must be released via snd_magic_kfree().
+ *
+ * The "struct xxx" style cannot be used as the type argument
+ * because the magic-key constant is generated from the type-name
+ * string.
+ */
+#define snd_magic_kmalloc(type, extra, flags) \
+ (type *) _snd_magic_kmalloc(type##_magic, sizeof(type) + extra, flags)
+/**
+ * snd_magic_kcalloc - allocate a record with a magic-prefix and initialize
+ * @type: the type to allocate a record (like xxx_t)
+ * @extra: the extra size to allocate in bytes
+ * @flags: the allocation condition (GFP_XXX)
+ *
+ * Works like snd_magic_kmalloc() but this clears the area with zero
+ * automatically.
+ */
+#define snd_magic_kcalloc(type, extra, flags) \
+ (type *) _snd_magic_kcalloc(type##_magic, sizeof(type) + extra, flags)
+
+/**
+ * snd_magic_kfree - release the allocated area
+ * @ptr: the pointer allocated via snd_magic_kmalloc() or snd_magic_kcalloc()
+ *
+ * Releases the memory area allocated via snd_magic_kmalloc() or
+ * snd_magic_kcalloc() function.
+ */
+void snd_magic_kfree(void *ptr);
static inline unsigned long _snd_magic_value(void *obj)
{
@@ -44,7 +79,19 @@ static inline int _snd_magic_bad(void *obj, unsigned long magic)
#define snd_magic_cast1(t, expr, cmd) snd_magic_cast(t, expr, cmd)
-#define snd_magic_cast(type, ptr, action...) (type *) ({\
+/**
+ * snd_magic_cast - check and cast the magic-allocated pointer
+ * @type: the type of record to cast
+ * @ptr: the magic-allocated pointer
+ * @action...: the action to do if failed
+ *
+ * This macro provides a safe cast for the given type, which was
+ * allocated via snd_magic_kmalloc() or snd_magic_kcallc().
+ * If the pointer is invalid, i.e. the cast-type doesn't match,
+ * the action arguments are called with a debug message.
+ */
+#define snd_magic_cast(type, ptr, action...) \
+ (type *) ({\
void *__ptr = ptr;\
unsigned long __magic = _snd_magic_value(__ptr);\
if (__magic != type##_magic) {\
@@ -64,6 +111,7 @@ static inline int _snd_magic_bad(void *obj, unsigned long magic)
#define snd_pcm_sgbuf_t_magic 0xa15a0107
#define snd_info_private_data_t_magic 0xa15a0201
+#define snd_info_entry_t_magic 0xa15a0202
#define snd_ctl_file_t_magic 0xa15a0301
#define snd_kcontrol_t_magic 0xa15a0302
#define snd_rawmidi_t_magic 0xa15a0401
diff --git a/include/sound/trident.h b/include/sound/trident.h
index bf4ed45f1a63..f4f4042f6e7f 100644
--- a/include/sound/trident.h
+++ b/include/sound/trident.h
@@ -431,7 +431,8 @@ struct _snd_trident {
int ChanPCM; /* max number of PCM channels */
int ChanPCMcnt; /* actual number of PCM channels */
- int ac97_detect; /* 1 = AC97 in detection phase */
+ unsigned int ac97_detect: 1; /* 1 = AC97 in detection phase */
+ unsigned int in_suspend: 1; /* 1 during suspend/resume */
struct _snd_4dwave synth; /* synth specific variables */
@@ -452,7 +453,6 @@ struct _snd_trident {
snd_trident_pcm_mixer_t pcm_mixer[32];
spinlock_t reg_lock;
- snd_info_entry_t *proc_entry;
struct snd_trident_gameport *gameport;
};
@@ -479,7 +479,7 @@ void snd_trident_write_voice_regs(trident_t * trident, snd_trident_voice_t *voic
void snd_trident_clear_voices(trident_t * trident, unsigned short v_min, unsigned short v_max);
/* TLB memory allocation */
-snd_util_memblk_t *snd_trident_alloc_pages(trident_t *trident, void *pages, dma_addr_t addr, unsigned long size);
+snd_util_memblk_t *snd_trident_alloc_pages(trident_t *trident, snd_pcm_substream_t *substream);
int snd_trident_free_pages(trident_t *trident, snd_util_memblk_t *blk);
snd_util_memblk_t *snd_trident_synth_alloc(trident_t *trident, unsigned int size);
int snd_trident_synth_free(trident_t *trident, snd_util_memblk_t *blk);
diff --git a/include/sound/version.h b/include/sound/version.h
index a4c7d9a7cd3a..323f4e184ed5 100644
--- a/include/sound/version.h
+++ b/include/sound/version.h
@@ -1,3 +1,3 @@
/* include/version.h. Generated automatically by configure. */
#define CONFIG_SND_VERSION "0.9.0rc6"
-#define CONFIG_SND_DATE " (Thu Dec 26 11:57:42 2002 UTC)"
+#define CONFIG_SND_DATE " (Wed Jan 08 17:04:59 2003 UTC)"
diff --git a/sound/core/control.c b/sound/core/control.c
index 31173c043e10..da7471dca9a4 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -169,6 +169,15 @@ void snd_ctl_notify(snd_card_t *card, unsigned int mask, snd_ctl_elem_id_t *id)
read_unlock(&card->ctl_files_rwlock);
}
+/**
+ * snd_ctl_new - create a control instance from the template
+ * @control: the control template
+ *
+ * Allocates a new snd_kcontrol_t instance and copies the given template
+ * to the new instance.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control)
{
snd_kcontrol_t *kctl;
@@ -181,6 +190,17 @@ snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control)
return kctl;
}
+/**
+ * snd_ctl_new1 - create a control instance from the template
+ * @ncontrol: the initialization record
+ * @private_data: the private data to set
+ *
+ * Allocates a new snd_kcontrol_t instance and initialize from the given
+ * template. When the access field of ncontrol is 0, it's assumed as
+ * READWRITE access.
+ *
+ * Returns the pointer of the newly generated instance, or NULL on failure.
+ */
snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data)
{
snd_kcontrol_t kctl;
@@ -203,6 +223,14 @@ snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data)
return snd_ctl_new(&kctl);
}
+/**
+ * snd_ctl_free_one - release the control instance
+ * @kcontrol: the control instance
+ *
+ * Releases the control instance created via snd_ctl_new()
+ * or snd_ctl_new1().
+ * Don't call this after the control was added to the card.
+ */
void snd_ctl_free_one(snd_kcontrol_t * kcontrol)
{
if (kcontrol) {
@@ -212,6 +240,16 @@ void snd_ctl_free_one(snd_kcontrol_t * kcontrol)
}
}
+/**
+ * snd_ctl_add - add the control instance to the card
+ * @card: the card instance
+ * @kcontrol: the control instance to add
+ *
+ * Adds the control instance created via snd_ctl_new() or
+ * snd_ctl_new1() to the given card.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
{
snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
@@ -227,6 +265,16 @@ int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
return 0;
}
+/**
+ * snd_ctl_remove - remove the control from the card and release it
+ * @card: the card instance
+ * @kcontrol: the control instance to remove
+ *
+ * Removes the control from the card and then releases the instance.
+ * You don't need to call snd_ctl_free_one().
+ *
+ * Returns 0 if successful, or a negative error code on failure.
+ */
int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
{
snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
@@ -239,6 +287,16 @@ int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
return 0;
}
+/**
+ * snd_ctl_remove_id - remove the control of the given id and release it
+ * @card: the card instance
+ * @id: the control id to remove
+ *
+ * Finds the control instance with the given id, removes it from the
+ * card list and releases it.
+ *
+ * Returns 0 if successful, or a negative error code on failure.
+ */
int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id)
{
snd_kcontrol_t *kctl;
@@ -251,6 +309,17 @@ int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id)
static snd_kcontrol_t *_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id); /* w/o lock */
+/**
+ * snd_ctl_rename_id - replace the id of a control on the card
+ * @card: the card instance
+ * @src_id: the old id
+ * @dst_id: the new id
+ *
+ * Finds the control with the old id from the card, and replaces the
+ * id with the new one.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_ctl_rename_id(snd_card_t * card, snd_ctl_elem_id_t *src_id, snd_ctl_elem_id_t *dst_id)
{
snd_kcontrol_t *kctl;
@@ -306,7 +375,15 @@ static snd_kcontrol_t *_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id)
return NULL;
}
-/* exported: with read lock */
+/**
+ * snd_ctl_find_id - find the control instance with the given id
+ * @card: the card instance
+ * @id: the id to search
+ *
+ * Finds the control instance with the given id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ */
snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id)
{
snd_kcontrol_t *kctl;
@@ -316,7 +393,15 @@ snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id)
return kctl;
}
-/* exported: with read lock */
+/**
+ * snd_ctl_find_numid - find the control instance with the given number-id
+ * @card: the card instance
+ * @numid: the number-id to search
+ *
+ * Finds the control instance with the given number-id from the card.
+ *
+ * Returns the pointer of the instance if found, or NULL if not.
+ */
snd_kcontrol_t *snd_ctl_find_numid(snd_card_t * card, unsigned int numid)
{
snd_kcontrol_t *kctl;
@@ -628,13 +713,15 @@ static int snd_ctl_ioctl(struct inode *inode, struct file *file,
return -EFAULT;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
+ err = -ENOPROTOOPT;
#ifdef CONFIG_PM
- if (card->set_power_state == NULL)
- return -ENOPROTOOPT;
- return card->set_power_state(card, err);
-#else
- return -ENOPROTOOPT;
+ if (card->set_power_state) {
+ snd_power_lock(card);
+ err = card->set_power_state(card, err);
+ snd_power_unlock(card);
+ }
#endif
+ return err;
case SNDRV_CTL_IOCTL_POWER_STATE:
#ifdef CONFIG_PM
return put_user(card->power_state, (int *)arg) ? -EFAULT : 0;
@@ -727,6 +814,10 @@ static unsigned int snd_ctl_poll(struct file *file, poll_table * wait)
return mask;
}
+/*
+ * register the device-specific control-ioctls.
+ * called from each device manager like pcm.c, hwdep.c, etc.
+ */
int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
{
snd_kctl_ioctl_t *pn;
@@ -742,6 +833,9 @@ int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
return 0;
}
+/*
+ * de-register the device-specific control-ioctls.
+ */
int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn)
{
struct list_head *list;
@@ -797,6 +891,10 @@ static snd_minor_t snd_ctl_reg =
.f_ops = &snd_ctl_f_ops,
};
+/*
+ * registration of the control device:
+ * called from init.c
+ */
int snd_ctl_register(snd_card_t *card)
{
int err, cardnum;
@@ -812,6 +910,10 @@ int snd_ctl_register(snd_card_t *card)
return 0;
}
+/*
+ * disconnection of the control device:
+ * called from init.c
+ */
int snd_ctl_disconnect(snd_card_t *card)
{
struct list_head *flist;
@@ -827,6 +929,10 @@ int snd_ctl_disconnect(snd_card_t *card)
return 0;
}
+/*
+ * de-registration of the control device:
+ * called from init.c
+ */
int snd_ctl_unregister(snd_card_t *card)
{
int err, cardnum;
diff --git a/sound/core/device.c b/sound/core/device.c
index cb23f8a3eb7d..2665fb1e4974 100644
--- a/sound/core/device.c
+++ b/sound/core/device.c
@@ -25,6 +25,22 @@
#include <linux/errno.h>
#include <sound/core.h>
+/**
+ * snd_device_new - create an ALSA device component
+ * @card: the card instance
+ * @type: the device type, SNDRV_DEV_TYPE_XXX
+ * @device_data: the data pointer of this device
+ * @ops: the operator table
+ *
+ * Creates a new device component for the given data pointer.
+ * The device will be assigned to the card and managed together
+ * by the card.
+ *
+ * The data pointer plays a role as the identifier, too, so the
+ * pointer address must be unique and unchanged.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_device_new(snd_card_t *card, snd_device_type_t type,
void *device_data, snd_device_ops_t *ops)
{
@@ -43,6 +59,18 @@ int snd_device_new(snd_card_t *card, snd_device_type_t type,
return 0;
}
+/**
+ * snd_device_free - release the device from the card
+ * @card: the card instance
+ * @device_data: the data pointer to release
+ *
+ * Removes the device from the list on the card and invokes the
+ * callback, dev_unregister or dev_free, corresponding to the state.
+ * Then release the device.
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
int snd_device_free(snd_card_t *card, void *device_data)
{
struct list_head *list;
@@ -73,6 +101,19 @@ int snd_device_free(snd_card_t *card, void *device_data)
return -ENXIO;
}
+/**
+ * snd_device_free - disconnect the device
+ * @card: the card instance
+ * @device_data: the data pointer to disconnect
+ *
+ * Turns the device into the disconnection state, invoking
+ * dev_disconnect callback, if the device was already registered.
+ *
+ * Usually called from snd_card_disconnect().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
int snd_device_disconnect(snd_card_t *card, void *device_data)
{
struct list_head *list;
@@ -95,6 +136,19 @@ int snd_device_disconnect(snd_card_t *card, void *device_data)
return -ENXIO;
}
+/**
+ * snd_device_register - register the device
+ * @card: the card instance
+ * @device_data: the data pointer to register
+ *
+ * Registers the device which was already created via
+ * snd_device_new(). Usually this is called from snd_card_register(),
+ * but it can be called later if any new devices are created after
+ * invokation of snd_card_register().
+ *
+ * Returns zero if successful, or a negative error code on failure or if the
+ * device not found.
+ */
int snd_device_register(snd_card_t *card, void *device_data)
{
struct list_head *list;
@@ -118,6 +172,10 @@ int snd_device_register(snd_card_t *card, void *device_data)
return -ENXIO;
}
+/*
+ * register all the devices on the card.
+ * called from init.c
+ */
int snd_device_register_all(snd_card_t *card)
{
struct list_head *list;
@@ -136,6 +194,10 @@ int snd_device_register_all(snd_card_t *card)
return 0;
}
+/*
+ * disconnect all the devices on the card.
+ * called from init.c
+ */
int snd_device_disconnect_all(snd_card_t *card)
{
snd_device_t *dev;
@@ -151,6 +213,10 @@ int snd_device_disconnect_all(snd_card_t *card)
return err;
}
+/*
+ * release all the devices on the card.
+ * called from init.c
+ */
int snd_device_free_all(snd_card_t *card, snd_device_cmd_t cmd)
{
snd_device_t *dev;
diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c
index 36a217c48e2c..5826ea7d5419 100644
--- a/sound/core/hwdep.c
+++ b/sound/core/hwdep.c
@@ -256,6 +256,19 @@ static snd_minor_t snd_hwdep_reg =
.f_ops = &snd_hwdep_f_ops,
};
+/**
+ * snd_hwdep_new - create a new hwdep instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero-based)
+ * @rhwdep: the pointer to store the new hwdep instance
+ *
+ * Creates a new hwdep instance with the given index on the card.
+ * The callbacks (hwdep->ops) must be set on the returned instance
+ * after this call manually by the caller.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_hwdep_new(snd_card_t * card, char *id, int device, snd_hwdep_t ** rhwdep)
{
snd_hwdep_t *hwdep;
diff --git a/sound/core/info.c b/sound/core/info.c
index b4e85fb69c21..b37093a9df78 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -83,10 +83,16 @@ typedef struct _snd_info_private_data {
static int snd_info_version_init(void);
static int snd_info_version_done(void);
-/*
+/**
+ * snd_iprintf - printf on the procfs buffer
+ * @buffer: the procfs buffer
+ * @fmt: the printf format
+ *
+ * Outputs the string on the procfs buffer just like printf().
+ *
+ * Returns the size of output string.
*/
-
int snd_iprintf(snd_info_buffer_t * buffer, char *fmt,...)
{
va_list args;
@@ -591,6 +597,17 @@ struct inode_operations snd_info_card_link_inode_operations =
.follow_link = snd_info_card_followlink,
};
+/**
+ * snd_create_proc_entry - create a procfs entry
+ * @name: the name of the proc file
+ * @mode: the file permission bits, S_Ixxx
+ * @parent: the parent proc-directory entry
+ *
+ * Creates a new proc file entry with the given name and permission
+ * on the given directory.
+ *
+ * Returns the pointer of new instance or NULL on failure.
+ */
struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode,
struct proc_dir_entry *parent)
{
@@ -682,6 +699,10 @@ int __exit snd_info_done(void)
*/
+/*
+ * create a card proc file
+ * called from init.c
+ */
int snd_info_card_create(snd_card_t * card)
{
char str[8];
@@ -701,6 +722,10 @@ int snd_info_card_create(snd_card_t * card)
return 0;
}
+/*
+ * register the card proc file
+ * called from init.c
+ */
int snd_info_card_register(snd_card_t * card)
{
char *s;
@@ -728,6 +753,10 @@ int snd_info_card_register(snd_card_t * card)
return 0;
}
+/*
+ * de-register the card proc file
+ * called from init.c
+ */
int snd_info_card_free(snd_card_t * card)
{
void *data;
@@ -747,10 +776,17 @@ int snd_info_card_free(snd_card_t * card)
return 0;
}
-/*
+/**
+ * snd_info_get_line - read one line from the procfs buffer
+ * @buffer: the procfs buffer
+ * @line: the buffer to store
+ * @len: the max. buffer size - 1
+ *
+ * Reads one line from the buffer and stores the string.
+ *
+ * Returns zero if successful, or 1 if error or EOF.
*/
-
int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len)
{
int c = -1;
@@ -781,6 +817,18 @@ int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len)
return 0;
}
+/**
+ * snd_info_get_line - parse a string token
+ * @dest: the buffer to store the string token
+ * @src: the original string
+ * @len: the max. length of token - 1
+ *
+ * Parses the original string and copy a token to the given
+ * string buffer.
+ *
+ * Returns the updated pointer of the original string so that
+ * it can be used for the next call.
+ */
char *snd_info_get_str(char *dest, char *src, int len)
{
int c;
@@ -805,15 +853,27 @@ char *snd_info_get_str(char *dest, char *src, int len)
return src;
}
+/**
+ * snd_info_create_entry - create an info entry
+ * @name: the proc file name
+ *
+ * Creates an info entry with the given file name and initializes as
+ * the default state.
+ *
+ * Usually called from other functions such as
+ * snd_info_create_card_entry().
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
static snd_info_entry_t *snd_info_create_entry(const char *name)
{
snd_info_entry_t *entry;
- entry = (snd_info_entry_t *) snd_kcalloc(sizeof(snd_info_entry_t), GFP_KERNEL);
+ entry = snd_magic_kcalloc(snd_info_entry_t, 0, GFP_KERNEL);
if (entry == NULL)
return NULL;
entry->name = snd_kmalloc_strdup(name, GFP_KERNEL);
if (entry->name == NULL) {
- kfree(entry);
+ snd_magic_kfree(entry);
return NULL;
}
entry->mode = S_IFREG | S_IRUGO;
@@ -822,6 +882,16 @@ static snd_info_entry_t *snd_info_create_entry(const char *name)
return entry;
}
+/**
+ * snd_info_create_module_entry - create an info entry for the given module
+ * @module: the module pointer
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given module.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
snd_info_entry_t *snd_info_create_module_entry(struct module * module,
const char *name,
snd_info_entry_t *parent)
@@ -834,6 +904,16 @@ snd_info_entry_t *snd_info_create_module_entry(struct module * module,
return entry;
}
+/**
+ * snd_info_create_card_entry - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @parent: the parent directory
+ *
+ * Creates a new info entry and assigns it to the given card.
+ *
+ * Returns the pointer of the new instance, or NULL on failure.
+ */
snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card,
const char *name,
snd_info_entry_t * parent)
@@ -847,6 +927,75 @@ snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card,
return entry;
}
+static int snd_info_dev_free_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = snd_magic_cast(snd_info_entry_t, device->device_data, return -ENXIO);
+ snd_info_free_entry(entry);
+ return 0;
+}
+
+static int snd_info_dev_register_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = snd_magic_cast(snd_info_entry_t, device->device_data, return -ENXIO);
+ return snd_info_register(entry);
+}
+
+static int snd_info_dev_unregister_entry(snd_device_t *device)
+{
+ snd_info_entry_t *entry = snd_magic_cast(snd_info_entry_t, device->device_data, return -ENXIO);
+ return snd_info_unregister(entry);
+}
+
+/**
+ * snd_card_proc_new - create an info entry for the given card
+ * @card: the card instance
+ * @name: the file name
+ * @entryp: the pointer to store the new info entry
+ *
+ * Creates a new info entry and assigns it to the given card.
+ * Unlike snd_info_create_card_entry(), this function registers the
+ * info entry as an ALSA device component, so that it can be
+ * unregistered/released without explicit call.
+ * Also, you don't have to register this entry via snd_info_register(),
+ * since this will be registered by snd_card_register() automatically.
+ *
+ * The parent is assumed as card->proc_root.
+ *
+ * For releasing this entry, use snd_device_free() instead of
+ * snd_info_free_entry().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_card_proc_new(snd_card_t *card, const char *name,
+ snd_info_entry_t **entryp)
+{
+ static snd_device_ops_t ops = {
+ .dev_free = snd_info_dev_free_entry,
+ .dev_register = snd_info_dev_register_entry,
+ // .dev_disconnect = snd_info_dev_disconnect_entry,
+ .dev_unregister = snd_info_dev_unregister_entry
+ };
+ snd_info_entry_t *entry;
+ int err;
+
+ entry = snd_info_create_card_entry(card, name, card->proc_root);
+ if (! entry)
+ return -ENOMEM;
+ if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) {
+ snd_info_free_entry(entry);
+ return err;
+ }
+ if (entryp)
+ *entryp = entry;
+ return 0;
+}
+
+/**
+ * snd_info_free_entry - release the info entry
+ * @entry: the info entry
+ *
+ * Releases the info entry. Don't call this after registered.
+ */
void snd_info_free_entry(snd_info_entry_t * entry)
{
if (entry == NULL)
@@ -855,7 +1004,7 @@ void snd_info_free_entry(snd_info_entry_t * entry)
kfree((char *)entry->name);
if (entry->private_free)
entry->private_free(entry);
- kfree(entry);
+ snd_magic_kfree(entry);
}
#ifdef LINUX_2_2
@@ -892,11 +1041,11 @@ static inline void snd_info_device_entry_prepare(struct proc_dir_entry *de, snd_
}
#endif /* LINUX_2_2 */
+/*
+ * create a procfs device file
+ */
snd_info_entry_t *snd_info_create_device(const char *name, unsigned int number, unsigned int mode)
{
-#ifdef CONFIG_DEVFS_FS
- char dname[32];
-#endif
unsigned short _major = number >> 16;
unsigned short minor = (unsigned short) number;
snd_info_entry_t *entry;
@@ -933,6 +1082,7 @@ snd_info_entry_t *snd_info_create_device(const char *name, unsigned int number,
up(&info_mutex);
#ifdef CONFIG_DEVFS_FS
if (strncmp(name, "controlC", 8)) { /* created in sound.c */
+ char dname[32];
sprintf(dname, "snd/%s", name);
devfs_register(NULL, dname, DEVFS_FL_DEFAULT,
major, minor, mode,
@@ -942,6 +1092,9 @@ snd_info_entry_t *snd_info_create_device(const char *name, unsigned int number,
return entry;
}
+/*
+ * release a procfs device file
+ */
void snd_info_free_device(snd_info_entry_t * entry)
{
snd_runtime_check(entry, return);
@@ -953,6 +1106,14 @@ void snd_info_free_device(snd_info_entry_t * entry)
snd_info_free_entry(entry);
}
+/**
+ * snd_info_register - register the info entry
+ * @entry: the info entry
+ *
+ * Registers the proc info entry.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_info_register(snd_info_entry_t * entry)
{
struct proc_dir_entry *root, *p = NULL;
@@ -982,6 +1143,14 @@ int snd_info_register(snd_info_entry_t * entry)
return 0;
}
+/**
+ * snd_info_unregister - de-register the info entry
+ * @entry: the info entry
+ *
+ * De-registers the info entry and releases the instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_info_unregister(snd_info_entry_t * entry)
{
struct proc_dir_entry *root;
diff --git a/sound/core/init.c b/sound/core/init.c
index 26aa3d323c12..30e217be051d 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -51,14 +51,16 @@ static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer
}
/**
- * snd_card_new: create and initialize a soundcard structure
+ * snd_card_new - create and initialize a soundcard structure
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
* @xid: card identification (ASCII string)
* @module: top level module for locking
* @extra_size: allocate this extra size after the main soundcard structure
*
+ * Creates and initializes a soundcard structure.
+ *
* Returns kmallocated snd_card_t structure. Creates the ALSA control interface
- * (which is blocked until #snd_card_register function is called).
+ * (which is blocked until snd_card_register function is called).
*/
snd_card_t *snd_card_new(int idx, const char *xid,
struct module *module, int extra_size)
@@ -136,10 +138,12 @@ static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
}
/**
- * snd_card_disconnect: disconnect all APIs from the file-operations (user space)
+ * snd_card_disconnect - disconnect all APIs from the file-operations (user space)
* @card: soundcard structure
*
- * Returns - zero, otherwise a negative error code.
+ * Disconnects all APIs from the file-operations (user space).
+ *
+ * Returns zero, otherwise a negative error code.
*
* Note: The current implementation replaces all active file->f_op with special
* dummy file operations (they do nothing except release).
@@ -219,19 +223,18 @@ int snd_card_disconnect(snd_card_t * card)
}
/**
- * snd_card_free: frees given soundcard structure
+ * snd_card_free - frees given soundcard structure
* @card: soundcard structure
*
* This function releases the soundcard structure and the all assigned
* devices automatically. That is, you don't have to release the devices
* by yourself.
*
- * Returns - zero. Frees all associated devices and frees the control
+ * Returns zero. Frees all associated devices and frees the control
* interface associated to given soundcard.
*/
int snd_card_free(snd_card_t * card)
{
- wait_queue_t wait;
struct snd_shutdown_f_ops *s_f_ops;
if (card == NULL)
@@ -242,13 +245,7 @@ int snd_card_free(snd_card_t * card)
write_unlock(&snd_card_rwlock);
/* wait, until all devices are ready for the free operation */
- init_waitqueue_entry(&wait, current);
- add_wait_queue(&card->shutdown_sleep, &wait);
- while (card->files) {
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule_timeout(30 * HZ);
- }
- remove_wait_queue(&card->shutdown_sleep, &wait);
+ wait_event(card->shutdown_sleep, card->files == NULL);
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
if (snd_mixer_oss_notify_callback)
@@ -299,24 +296,22 @@ static void snd_card_free_thread(void * __card)
module = NULL;
}
- wait_event(card->shutdown_sleep, card->files == NULL);
-
snd_card_free(card);
module_put(module);
}
/**
- * snd_card_free_in_thread: call snd_card_free() in thread
+ * snd_card_free_in_thread - call snd_card_free() in thread
* @card: soundcard structure
*
- * This function schedules the call of #snd_card_free function in a
+ * This function schedules the call of snd_card_free() function in a
* work queue. When all devices are released (non-busy), the work
- * is woken up and calls #snd_card_free.
+ * is woken up and calls snd_card_free().
*
* When a card can be disconnected at any time by hotplug service,
* this function should be used in disconnect (or detach) callback
- * instead of calling #snd_card_free directly.
+ * instead of calling snd_card_free() directly.
*
* Returns - zero otherwise a negative error code if the start of thread failed.
*/
@@ -392,7 +387,7 @@ static void choose_default_id(snd_card_t * card)
}
/**
- * snd_card_register: register the soundcard
+ * snd_card_register - register the soundcard
* @card: soundcard structure
*
* This function registers all the devices assigned to the soundcard.
@@ -400,7 +395,7 @@ static void choose_default_id(snd_card_t * card)
* external accesses. Thus, you should call this function at the end
* of the initialization of the card.
*
- * Returns - zero otherwise a negative error code if the registrain failed.
+ * Returns zero otherwise a negative error code if the registrain failed.
*/
int snd_card_register(snd_card_t * card)
{
@@ -517,14 +512,14 @@ int __exit snd_card_info_done(void)
}
/**
- * snd_component_add: add a component string
+ * snd_component_add - add a component string
* @card: soundcard structure
* @component: the component id string
*
* This function adds the component id string to the supported list.
* The component can be referred from the alsa-lib.
*
- * Returns - zero otherwise a negative error code.
+ * Returns zero otherwise a negative error code.
*/
int snd_component_add(snd_card_t *card, const char *component)
@@ -548,7 +543,7 @@ int snd_component_add(snd_card_t *card, const char *component)
}
/**
- * snd_card_file_add: add the file to the file list of the card
+ * snd_card_file_add - add the file to the file list of the card
* @card: soundcard structure
* @file: file pointer
*
@@ -580,15 +575,15 @@ int snd_card_file_add(snd_card_t *card, struct file *file)
}
/**
- * snd_card_file_remove: remove the file from the file list
+ * snd_card_file_remove - remove the file from the file list
* @card: soundcard structure
* @file: file pointer
*
* This function removes the file formerly added to the card via
- * #snd_card_file_add function.
+ * snd_card_file_add() function.
* If all files are removed and the release of the card is
- * scheduled, it will wake up the the thread to call #snd_card_free
- * (see #snd_card_free_in_thread function).
+ * scheduled, it will wake up the the thread to call snd_card_free()
+ * (see snd_card_free_in_thread() function).
*
* Returns zero or a negative error code.
*/
@@ -623,9 +618,11 @@ int snd_card_file_remove(snd_card_t *card, struct file *file)
#ifdef CONFIG_PM
/**
- * snd_power_wait: wait until the power-state is changed.
+ * snd_power_wait - wait until the power-state is changed.
* @card: soundcard structure
*
+ * Waits until the power-state is changed.
+ *
* Note: the power lock must be active before call.
*/
void snd_power_wait(snd_card_t *card)
diff --git a/sound/core/isadma.c b/sound/core/isadma.c
index 561c1111962f..48f355397df2 100644
--- a/sound/core/isadma.c
+++ b/sound/core/isadma.c
@@ -30,10 +30,15 @@
#include <sound/core.h>
#include <asm/dma.h>
-/*
+/**
+ * snd_dma_program - program an ISA DMA transfer
+ * @dma: the dma number
+ * @addr: the physical address of the buffer
+ * @size: the DMA transfer size
+ * @mode: the DMA transfer mode, DMA_MODE_XXX
*
+ * Programs an ISA DMA transfer for the given buffer.
*/
-
void snd_dma_program(unsigned long dma,
unsigned long addr, unsigned int size,
unsigned short mode)
@@ -51,6 +56,12 @@ void snd_dma_program(unsigned long dma,
release_dma_lock(flags);
}
+/**
+ * snd_dma_disable - stop the ISA DMA transfer
+ * @dma: the dma number
+ *
+ * Stops the ISA DMA transfer.
+ */
void snd_dma_disable(unsigned long dma)
{
unsigned long flags;
@@ -61,6 +72,12 @@ void snd_dma_disable(unsigned long dma)
release_dma_lock(flags);
}
+/**
+ * snd_dma_residue - return the residue count of the given DMA
+ * @dma: the dma number
+ *
+ * Returns the residue count of the given DMA transfer.
+ */
unsigned int snd_dma_residue(unsigned long dma)
{
unsigned long flags;
diff --git a/sound/core/memory.c b/sound/core/memory.c
index c12a28ab2ecd..5a45ec66893d 100644
--- a/sound/core/memory.c
+++ b/sound/core/memory.c
@@ -264,7 +264,15 @@ int __exit snd_memory_info_done(void)
#endif /* CONFIG_SND_DEBUG_MEMORY */
-
+/**
+ * snd_malloc_pages - allocate pages with the given size
+ * @size: the size to allocate in bytes
+ * @dma_flags: the allocation conditions, GFP_XXX
+ *
+ * Allocates the physically contiguous pages with the given size.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_pages(unsigned long size, unsigned int dma_flags)
{
int pg;
@@ -285,6 +293,19 @@ void *snd_malloc_pages(unsigned long size, unsigned int dma_flags)
return res;
}
+/**
+ * snd_malloc_pages_fallback - allocate pages with the given size with fallback
+ * @size: the requested size to allocate in bytes
+ * @dma_flags: the allocation conditions, GFP_XXX
+ * @res_size: the pointer to store the size of buffer actually allocated
+ *
+ * Allocates the physically contiguous pages with the given request
+ * size. When no space is left, this function reduces the size and
+ * tries to allocate again. The size actually allocated is stored in
+ * res_size argument.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_pages_fallback(unsigned long size, unsigned int dma_flags, unsigned long *res_size)
{
void *res;
@@ -301,6 +322,13 @@ void *snd_malloc_pages_fallback(unsigned long size, unsigned int dma_flags, unsi
return NULL;
}
+/**
+ * snd_free_pages - release the pages
+ * @ptr: the buffer pointer to release
+ * @size: the allocated buffer size
+ *
+ * Releases the buffer allocated via snd_malloc_pages().
+ */
void snd_free_pages(void *ptr, unsigned long size)
{
int pg;
@@ -321,6 +349,16 @@ void snd_free_pages(void *ptr, unsigned long size)
#if defined(CONFIG_ISA) && ! defined(CONFIG_PCI)
+/**
+ * snd_malloc_isa_pages - allocate pages for ISA bus with the given size
+ * @size: the size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ *
+ * Allocates the physically contiguous pages with the given size for
+ * ISA bus.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_isa_pages(unsigned long size, dma_addr_t *dma_addr)
{
void *dma_area;
@@ -329,6 +367,19 @@ void *snd_malloc_isa_pages(unsigned long size, dma_addr_t *dma_addr)
return dma_area;
}
+/**
+ * snd_malloc_isa_pages_fallback - allocate pages with the given size with fallback for ISA bus
+ * @size: the requested size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ * @res_size: the pointer to store the size of buffer actually allocated
+ *
+ * Allocates the physically contiguous pages with the given request
+ * size for PCI bus. When no space is left, this function reduces the size and
+ * tries to allocate again. The size actually allocated is stored in
+ * res_size argument.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_isa_pages_fallback(unsigned long size,
dma_addr_t *dma_addr,
unsigned long *res_size)
@@ -343,6 +394,17 @@ void *snd_malloc_isa_pages_fallback(unsigned long size,
#ifdef CONFIG_PCI
+/**
+ * snd_malloc_pci_pages - allocate pages for PCI bus with the given size
+ * @pci: the pci device pointer
+ * @size: the size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ *
+ * Allocates the physically contiguous pages with the given size for
+ * PCI bus.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_pci_pages(struct pci_dev *pci,
unsigned long size,
dma_addr_t *dma_addr)
@@ -366,6 +428,20 @@ void *snd_malloc_pci_pages(struct pci_dev *pci,
return res;
}
+/**
+ * snd_malloc_pci_pages_fallback - allocate pages with the given size with fallback for PCI bus
+ * @pci: pci device pointer
+ * @size: the requested size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ * @res_size: the pointer to store the size of buffer actually allocated
+ *
+ * Allocates the physically contiguous pages with the given request
+ * size for PCI bus. When no space is left, this function reduces the size and
+ * tries to allocate again. The size actually allocated is stored in
+ * res_size argument.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_pci_pages_fallback(struct pci_dev *pci,
unsigned long size,
dma_addr_t *dma_addr,
@@ -384,6 +460,15 @@ void *snd_malloc_pci_pages_fallback(struct pci_dev *pci,
return NULL;
}
+/**
+ * snd_free_pci_pages - release the pages
+ * @pci: pci device pointer
+ * @size: the allocated buffer size
+ * @ptr: the buffer pointer to release
+ * @dma_addr: the physical address of the buffer
+ *
+ * Releases the buffer allocated via snd_malloc_pci_pages().
+ */
void snd_free_pci_pages(struct pci_dev *pci,
unsigned long size,
void *ptr,
@@ -409,6 +494,17 @@ void snd_free_pci_pages(struct pci_dev *pci,
#ifdef CONFIG_SBUS
+/**
+ * snd_malloc_sbus_pages - allocate pages for SBUS with the given size
+ * @sdev: sbus device pointer
+ * @size: the size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ *
+ * Allocates the physically contiguous pages with the given size for
+ * SBUS.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_sbus_pages(struct sbus_dev *sdev,
unsigned long size,
dma_addr_t *dma_addr)
@@ -432,6 +528,20 @@ void *snd_malloc_sbus_pages(struct sbus_dev *sdev,
return res;
}
+/**
+ * snd_malloc_pci_pages_fallback - allocate pages with the given size with fallback for SBUS
+ * @sdev: sbus device pointer
+ * @size: the requested size to allocate in bytes
+ * @dma_addr: the pointer to store the physical address of the buffer
+ * @res_size: the pointer to store the size of buffer actually allocated
+ *
+ * Allocates the physically contiguous pages with the given request
+ * size for SBUS. When no space is left, this function reduces the size and
+ * tries to allocate again. The size actually allocated is stored in
+ * res_size argument.
+ *
+ * Returns the pointer of the buffer, or NULL if no enoguh memory.
+ */
void *snd_malloc_sbus_pages_fallback(struct sbus_dev *sdev,
unsigned long size,
dma_addr_t *dma_addr,
@@ -450,6 +560,15 @@ void *snd_malloc_sbus_pages_fallback(struct sbus_dev *sdev,
return NULL;
}
+/**
+ * snd_free_sbus_pages - release the pages
+ * @sdev: sbus device pointer
+ * @size: the allocated buffer size
+ * @ptr: the buffer pointer to release
+ * @dma_addr: the physical address of the buffer
+ *
+ * Releases the buffer allocated via snd_malloc_pci_pages().
+ */
void snd_free_sbus_pages(struct sbus_dev *sdev,
unsigned long size,
void *ptr,
@@ -473,6 +592,15 @@ void snd_free_sbus_pages(struct sbus_dev *sdev,
#endif /* CONFIG_SBUS */
+/**
+ * snd_kcalloc - memory allocation and zero-clear
+ * @size: the size to allocate in bytes
+ * @flags: allocation conditions, GFP_XXX
+ *
+ * Allocates a memory chunk via kmalloc() and initializes it to zero.
+ *
+ * Returns the pointer, or NULL if no enoguh memory.
+ */
void *snd_kcalloc(size_t size, int flags)
{
void *ptr;
@@ -483,6 +611,15 @@ void *snd_kcalloc(size_t size, int flags)
return ptr;
}
+/**
+ * snd_kmalloc_strdup - copy the string
+ * @string: the original string
+ * @flags: allocation conditions, GFP_XXX
+ *
+ * Allocates a memory chunk via kmalloc() and copies the string to it.
+ *
+ * Returns the pointer, or NULL if no enoguh memory.
+ */
char *snd_kmalloc_strdup(const char *string, int flags)
{
size_t len;
@@ -497,6 +634,16 @@ char *snd_kmalloc_strdup(const char *string, int flags)
return ptr;
}
+/**
+ * copy_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 bytes
+ *
+ * Copies the data from mmio-space to user-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
int copy_to_user_fromio(void *dst, unsigned long src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
@@ -518,6 +665,16 @@ int copy_to_user_fromio(void *dst, unsigned long src, size_t count)
#endif
}
+/**
+ * copy_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 bytes
+ *
+ * Copies the data from user-space to mmio-space.
+ *
+ * Returns zero if successful, or non-zero on failure.
+ */
int copy_from_user_toio(unsigned long dst, const void *src, size_t count)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
@@ -538,3 +695,81 @@ int copy_from_user_toio(unsigned long dst, const void *src, size_t count)
return 0;
#endif
}
+
+
+#ifdef CONFIG_PCI
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0) && defined(__i386__)
+/*
+ * on ix86, we allocate a page with GFP_KERNEL to assure the
+ * allocation. the code is almost same with kernel/i386/pci-dma.c but
+ * it allocates only a single page and checks the validity of the
+ * page address with the given pci dma mask.
+ */
+
+/**
+ * snd_malloc_pci_page - allocate a page in the valid pci dma mask
+ * @pci: pci device pointer
+ * @addrp: the pointer to store the physical address of the buffer
+ *
+ * Allocates a single page for the given PCI device and returns
+ * the virtual address and stores the physical address on addrp.
+ *
+ * This function cannot be called from interrupt handlers or
+ * within spinlocks.
+ */
+void *snd_malloc_pci_page(struct pci_dev *pci, dma_addr_t *addrp)
+{
+ void *ptr;
+ dma_addr_t addr;
+ unsigned long rmask;
+
+ rmask = ~(unsigned long)(pci ? pci->dma_mask : 0x00ffffff);
+ ptr = (void *)__get_free_page(GFP_KERNEL);
+ if (ptr) {
+ addr = virt_to_phys(ptr);
+ if (((unsigned long)addr + PAGE_SIZE - 1) & rmask) {
+ /* try to reallocate with the GFP_DMA */
+ free_page((unsigned long)ptr);
+ ptr = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+ if (ptr) /* ok, the address must be within lower 16MB... */
+ addr = virt_to_phys(ptr);
+ else
+ addr = 0;
+ }
+ } else
+ addr = 0;
+ if (ptr) {
+ struct page *page = virt_to_page(ptr);
+ memset(ptr, 0, PAGE_SIZE);
+ SetPageReserved(page);
+#ifdef CONFIG_SND_DEBUG_MEMORY
+ snd_alloc_pages++;
+#endif
+ }
+ *addrp = addr;
+ return ptr;
+}
+#else
+/* on other architectures, call snd_malloc_pci_pages() helper function
+ * which uses pci_alloc_consistent().
+ */
+void *snd_malloc_pci_page(struct pci_dev *pci, dma_addr_t *addrp)
+{
+ return snd_malloc_pci_pages(pci, PAGE_SIZE, addrp);
+}
+#endif /* 2.4 && i386 */
+
+#if 0 /* for kernel-doc */
+/**
+ * snd_free_pci_page - release a page
+ * @pci: pci device pointer
+ * @ptr: the buffer pointer to release
+ * @dma_addr: the physical address of the buffer
+ *
+ * Releases the buffer allocated via snd_malloc_pci_page().
+ */
+void snd_free_pci_page(struct pci_dev *pci, void *ptr, dma_addr_t dma_addr);
+#endif /* for kernel-doc */
+
+#endif /* CONFIG_PCI */
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 562f4f4ca6a2..d9383e69f19c 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -542,6 +542,19 @@ static int snd_pcm_substream_proc_done(snd_pcm_substream_t *substream)
return 0;
}
+/**
+ * snd_pcm_new_stream - create a new PCM stream
+ * @pcm: the pcm instance
+ * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
+ * @substream_count: the number of substreams
+ *
+ * Creates a new stream for the pcm.
+ * The corresponding stream on the pcm must have been empty before
+ * calling this, i.e. zero must be given to the argument of
+ * snd_pcm_new().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count)
{
int idx, err;
@@ -582,7 +595,7 @@ int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count)
snd_magic_kfree(substream);
return err;
}
- substream->dma_type = SNDRV_PCM_DMA_TYPE_ISA;
+ substream->dma_type = SNDRV_PCM_DMA_TYPE_UNKNOWN;
substream->dma_private = NULL;
spin_lock_init(&substream->timer_lock);
prev = substream;
@@ -590,6 +603,22 @@ int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count)
return 0;
}
+/**
+ * snd_pcm_new - create a new PCM instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index (zero based)
+ * @playback_count: the number of substreams for playback
+ * @capture_count: the number of substreams for capture
+ * @rpcm: the pointer to store the new pcm instance
+ *
+ * Creates a new PCM instance.
+ *
+ * The pcm operators have to be set afterwards to the new instance
+ * via snd_pcm_set_ops().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_new(snd_card_t * card, char *id, int device,
int playback_count, int capture_count,
snd_pcm_t ** rpcm)
@@ -660,6 +689,7 @@ static int snd_pcm_free(snd_pcm_t *pcm)
snd_assert(pcm != NULL, return -ENXIO);
if (pcm->private_free)
pcm->private_free(pcm);
+ snd_pcm_lib_preallocate_free_for_all(pcm);
snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]);
snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]);
snd_magic_kfree(pcm);
@@ -774,6 +804,7 @@ int snd_pcm_open_substream(snd_pcm_t *pcm, int stream,
runtime->status->state = SNDRV_PCM_STATE_OPEN;
substream->runtime = runtime;
+ substream->private_data = pcm->private_data;
pstr->substream_opened++;
*rsubstream = substream;
return 0;
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index e5c4d15e55c9..1c3a6ec1dc50 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -214,10 +214,14 @@ int snd_pcm_update_hw_ptr(snd_pcm_substream_t *substream)
return 0;
}
-/*
- * Operations
+/**
+ * snd_pcm_set_ops - set the PCM operators
+ * @pcm: the pcm instance
+ * @direction: stream direction, SNDRV_PCM_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the given PCM operators to the pcm instance.
*/
-
void snd_pcm_set_ops(snd_pcm_t *pcm, int direction, snd_pcm_ops_t *ops)
{
snd_pcm_str_t *stream = &pcm->streams[direction];
@@ -227,10 +231,13 @@ void snd_pcm_set_ops(snd_pcm_t *pcm, int direction, snd_pcm_ops_t *ops)
substream->ops = ops;
}
-/*
- * Sync
+
+/**
+ * snd_pcm_sync - set the PCM sync id
+ * @substream: the pcm substream
+ *
+ * Sets the PCM sync identifier for the card.
*/
-
void snd_pcm_set_sync(snd_pcm_substream_t * substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -354,7 +361,17 @@ int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax)
return changed;
}
-/* r <- v */
+/**
+ * snd_interval_refine - refine the interval value of configurator
+ * @i: the interval value to refine
+ * @v: the interval value to refer to
+ *
+ * Refines the interval value with the reference value.
+ * The interval is changed to the range satisfying both intervals.
+ * The interval status (min, max, integer, etc.) are evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v)
{
int changed = 0;
@@ -445,6 +462,13 @@ void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_inte
c->integer = (a->integer && b->integer);
}
+/**
+ * snd_interval_div - refine the interval value with division
+ *
+ * c = a / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
{
unsigned int r;
@@ -469,7 +493,13 @@ void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_inte
c->integer = 0;
}
-/* a * b / k */
+/**
+ * snd_interval_muldivk - refine the interval value
+ *
+ * c = a * b / k
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b,
unsigned int k, snd_interval_t *c)
{
@@ -490,7 +520,13 @@ void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b,
c->integer = 0;
}
-/* a * k / b */
+/**
+ * snd_interval_mulkdiv - refine the interval value
+ *
+ * c = a * k / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
const snd_interval_t *b, snd_interval_t *c)
{
@@ -520,6 +556,11 @@ void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
/* ---- */
+/**
+ * snd_interval_ratnum - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
int snd_interval_ratnum(snd_interval_t *i,
unsigned int rats_count, ratnum_t *rats,
unsigned int *nump, unsigned int *denp)
@@ -612,6 +653,11 @@ int snd_interval_ratnum(snd_interval_t *i,
return err;
}
+/**
+ * snd_interval_ratden - refine the interval value
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
int snd_interval_ratden(snd_interval_t *i,
unsigned int rats_count, ratden_t *rats,
unsigned int *nump, unsigned int *denp)
@@ -698,6 +744,19 @@ int snd_interval_ratden(snd_interval_t *i,
return err;
}
+/**
+ * snd_interval_list - refine the interval value from the list
+ * @i: the interval value to refine
+ * @count: the number of elements in the list
+ * @list: the value list
+ * @mask: the bit-mask to evaluate
+ *
+ * Refines the interval value from the list.
+ * When mask is non-zero, only the elements corresponding to bit 1 are
+ * evaluated.
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
int snd_interval_list(snd_interval_t *i, unsigned int count, unsigned int *list, unsigned int mask)
{
unsigned int k;
@@ -762,6 +821,17 @@ int snd_interval_step(snd_interval_t *i, unsigned int min, unsigned int step)
/* Info constraints helpers */
+/**
+ * snd_pcm_hw_rule_add - add the hw-constraint rule
+ * @runtime: the pcm runtime instance
+ * @cond: condition bits
+ * @var: the variable to evaluate
+ * @func: the evaluation function
+ * @private: the private data pointer passed to function
+ * @dep: the dependent variables
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_hw_rule_add(snd_pcm_runtime_t *runtime, unsigned int cond,
int var,
snd_pcm_hw_rule_func_t func, void *private,
@@ -808,6 +878,9 @@ int snd_pcm_hw_rule_add(snd_pcm_runtime_t *runtime, unsigned int cond,
return 0;
}
+/**
+ * snd_pcm_hw_constraint_mask
+ */
int snd_pcm_hw_constraint_mask(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
u_int32_t mask)
{
@@ -820,6 +893,9 @@ int snd_pcm_hw_constraint_mask(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t va
return 0;
}
+/**
+ * snd_pcm_hw_constraint_mask64
+ */
int snd_pcm_hw_constraint_mask64(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
u_int64_t mask)
{
@@ -833,12 +909,18 @@ int snd_pcm_hw_constraint_mask64(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t
return 0;
}
+/**
+ * snd_pcm_hw_constraint_integer
+ */
int snd_pcm_hw_constraint_integer(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var)
{
snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints;
return snd_interval_setinteger(constrs_interval(constrs, var));
}
+/**
+ * snd_pcm_hw_constraint_minmax
+ */
int snd_pcm_hw_constraint_minmax(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var,
unsigned int min, unsigned int max)
{
@@ -859,6 +941,9 @@ static int snd_pcm_hw_rule_list(snd_pcm_hw_params_t *params,
}
+/**
+ * snd_pcm_hw_constraint_list
+ */
int snd_pcm_hw_constraint_list(snd_pcm_runtime_t *runtime,
unsigned int cond,
snd_pcm_hw_param_t var,
@@ -884,6 +969,9 @@ static int snd_pcm_hw_rule_ratnums(snd_pcm_hw_params_t *params,
return err;
}
+/**
+ * snd_pcm_hw_constraint_ratnums
+ */
int snd_pcm_hw_constraint_ratnums(snd_pcm_runtime_t *runtime,
unsigned int cond,
snd_pcm_hw_param_t var,
@@ -908,6 +996,9 @@ static int snd_pcm_hw_rule_ratdens(snd_pcm_hw_params_t *params,
return err;
}
+/**
+ * snd_pcm_hw_constraint_ratdens
+ */
int snd_pcm_hw_constraint_ratdens(snd_pcm_runtime_t *runtime,
unsigned int cond,
snd_pcm_hw_param_t var,
@@ -930,6 +1021,9 @@ static int snd_pcm_hw_rule_msbits(snd_pcm_hw_params_t *params,
return 0;
}
+/**
+ * snd_pcm_hw_constraint_msbits
+ */
int snd_pcm_hw_constraint_msbits(snd_pcm_runtime_t *runtime,
unsigned int cond,
unsigned int width,
@@ -949,6 +1043,9 @@ static int snd_pcm_hw_rule_step(snd_pcm_hw_params_t *params,
return snd_interval_step(hw_param_interval(params, rule->var), 0, step);
}
+/**
+ * snd_pcm_hw_constraint_step
+ */
int snd_pcm_hw_constraint_step(snd_pcm_runtime_t *runtime,
unsigned int cond,
snd_pcm_hw_param_t var,
@@ -971,6 +1068,9 @@ static int snd_pcm_hw_rule_pow2(snd_pcm_hw_params_t *params, snd_pcm_hw_rule_t *
sizeof(pow2_sizes)/sizeof(int), pow2_sizes, 0);
}
+/**
+ * snd_pcm_hw_constraint_pow2
+ */
int snd_pcm_hw_constraint_pow2(snd_pcm_runtime_t *runtime,
unsigned int cond,
snd_pcm_hw_param_t var)
@@ -1004,6 +1104,9 @@ void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var)
snd_BUG();
}
+/**
+ * snd_pcm_hw_param_any
+ */
int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var)
{
@@ -1022,16 +1125,23 @@ void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params)
params->info = ~0U;
}
-/* Fill PARAMS with full configuration space boundaries */
+/**
+ * snd_pcm_hw_params_any
+ *
+ * Fill PARAMS with full configuration space boundaries
+ */
int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
_snd_pcm_hw_params_any(params);
return snd_pcm_hw_refine(pcm, params);
}
-/* Return the value for field PAR if it's fixed in configuration space
- defined by PARAMS. Return -EINVAL otherwise
-*/
+/**
+ * snd_pcm_hw_param_value
+ *
+ * Return the value for field PAR if it's fixed in configuration space
+ * defined by PARAMS. Return -EINVAL otherwise
+ */
int snd_pcm_hw_param_value(const snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, int *dir)
{
@@ -1055,7 +1165,11 @@ int snd_pcm_hw_param_value(const snd_pcm_hw_params_t *params,
return -EINVAL;
}
-/* Return the minimum value for field PAR. */
+/**
+ * snd_pcm_hw_param_value_min
+ *
+ * Return the minimum value for field PAR.
+ */
unsigned int snd_pcm_hw_param_value_min(const snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, int *dir)
{
@@ -1074,7 +1188,11 @@ unsigned int snd_pcm_hw_param_value_min(const snd_pcm_hw_params_t *params,
return -EINVAL;
}
-/* Return the maximum value for field PAR. */
+/**
+ * snd_pcm_hw_param_value_max
+ *
+ * Return the maximum value for field PAR.
+ */
unsigned int snd_pcm_hw_param_value_max(const snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, int *dir)
{
@@ -1122,10 +1240,13 @@ int _snd_pcm_hw_param_setinteger(snd_pcm_hw_params_t *params,
return changed;
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- non integer values. Reduce configuration space accordingly.
- Return -EINVAL if the configuration space is empty
-*/
+/**
+ * snd_pcm_hw_param_setinteger
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * non integer values. Reduce configuration space accordingly.
+ * Return -EINVAL if the configuration space is empty
+ */
int snd_pcm_hw_param_setinteger(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var)
@@ -1161,10 +1282,13 @@ int _snd_pcm_hw_param_first(snd_pcm_hw_params_t *params,
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- values > minimum. Reduce configuration space accordingly.
- Return the minimum.
-*/
+/**
+ * snd_pcm_hw_param_first
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values > minimum. Reduce configuration space accordingly.
+ * Return the minimum.
+ */
int snd_pcm_hw_param_first(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, int *dir)
@@ -1199,10 +1323,13 @@ int _snd_pcm_hw_param_last(snd_pcm_hw_params_t *params,
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- values < maximum. Reduce configuration space accordingly.
- Return the maximum.
-*/
+/**
+ * snd_pcm_hw_param_last
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values < maximum. Reduce configuration space accordingly.
+ * Return the maximum.
+ */
int snd_pcm_hw_param_last(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, int *dir)
@@ -1247,10 +1374,13 @@ int _snd_pcm_hw_param_min(snd_pcm_hw_params_t *params,
return changed;
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- values < VAL. Reduce configuration space accordingly.
- Return new minimum or -EINVAL if the configuration space is empty
-*/
+/**
+ * snd_pcm_hw_param_min
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values < VAL. Reduce configuration space accordingly.
+ * Return new minimum or -EINVAL if the configuration space is empty
+ */
int snd_pcm_hw_param_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, unsigned int val, int *dir)
{
@@ -1297,10 +1427,13 @@ int _snd_pcm_hw_param_max(snd_pcm_hw_params_t *params,
return changed;
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- values >= VAL + 1. Reduce configuration space accordingly.
- Return new maximum or -EINVAL if the configuration space is empty
-*/
+/**
+ * snd_pcm_hw_param_max
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values >= VAL + 1. Reduce configuration space accordingly.
+ * Return new maximum or -EINVAL if the configuration space is empty
+ */
int snd_pcm_hw_param_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, unsigned int val, int *dir)
{
@@ -1364,10 +1497,13 @@ int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params,
return changed;
}
-/* Inside configuration space defined by PARAMS remove from PAR all
- values != VAL. Reduce configuration space accordingly.
- Return VAL or -EINVAL if the configuration space is empty
-*/
+/**
+ * snd_pcm_hw_param_set
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all
+ * values != VAL. Reduce configuration space accordingly.
+ * Return VAL or -EINVAL if the configuration space is empty
+ */
int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, unsigned int val, int dir)
{
@@ -1395,13 +1531,16 @@ int _snd_pcm_hw_param_mask(snd_pcm_hw_params_t *params,
return changed;
}
-/* Inside configuration space defined by PARAMS remove from PAR all values
- not contained in MASK. Reduce configuration space accordingly.
- This function can be called only for SNDRV_PCM_HW_PARAM_ACCESS,
- SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
- Return 0 on success or -EINVAL
- if the configuration space is empty
-*/
+/**
+ * snd_pcm_hw_param_mask
+ *
+ * Inside configuration space defined by PARAMS remove from PAR all values
+ * not contained in MASK. Reduce configuration space accordingly.
+ * This function can be called only for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return 0 on success or -EINVAL
+ * if the configuration space is empty
+ */
int snd_pcm_hw_param_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, const snd_mask_t *val)
{
@@ -1464,12 +1603,15 @@ static int boundary_nearer(int min, int mindir,
return boundary_lt(dmin, dmindir, dmax, dmaxdir);
}
-/* Inside configuration space defined by PARAMS set PAR to the available value
- nearest to VAL. Reduce configuration space accordingly.
- This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS,
- SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
- Return the value found.
- */
+/**
+ * snd_pcm_hw_param_near
+ *
+ * Inside configuration space defined by PARAMS set PAR to the available value
+ * nearest to VAL. Reduce configuration space accordingly.
+ * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS,
+ * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT.
+ * Return the value found.
+ */
int snd_pcm_hw_param_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_param_t var, unsigned int best, int *dir)
{
@@ -1537,17 +1679,14 @@ int snd_pcm_hw_param_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
return v;
}
-/* Choose one configuration from configuration space defined by PARAMS
- The configuration choosen is that obtained fixing in this order:
- first access
- first format
- first subformat
- min channels
- min rate
- min period time
- max buffer size
- min tick time
-*/
+/**
+ * snd_pcm_hw_param_choose
+ *
+ * Choose one configuration from configuration space defined by PARAMS
+ * The configuration choosen is that obtained fixing in this order:
+ * first access, first format, first subformat, min channels,
+ * min rate, min period time, max buffer size, min tick time
+ */
int snd_pcm_hw_params_choose(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
int err;
@@ -1630,6 +1769,17 @@ static int snd_pcm_lib_ioctl_channel_info(snd_pcm_substream_t *substream,
return 0;
}
+/**
+ * snd_pcm_lib_ioctl - a generic PCM ioctl callback
+ * @substream: the pcm substream instance
+ * @cmd: ioctl command
+ * @arg: ioctl argument
+ *
+ * Processes the generic ioctl commands for PCM.
+ * Can be passed as the ioctl callback for PCM ops.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_ioctl(snd_pcm_substream_t *substream,
unsigned int cmd, void *arg)
{
@@ -1648,30 +1798,70 @@ int snd_pcm_lib_ioctl(snd_pcm_substream_t *substream,
* Conditions
*/
+/**
+ * snd_pcm_playback_ready - check whether the playback buffer is available
+ * @substream: the pcm substream instance
+ *
+ * Checks whether enough free space is available on the playback buffer.
+ *
+ * Returns non-zero if available, or zero if not.
+ */
int snd_pcm_playback_ready(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
return snd_pcm_playback_avail(runtime) >= runtime->control->avail_min;
}
+/**
+ * snd_pcm_capture_ready - check whether the capture buffer is available
+ * @substream: the pcm substream instance
+ *
+ * Checks whether enough capture data is available on the capture buffer.
+ *
+ * Returns non-zero if available, or zero if not.
+ */
int snd_pcm_capture_ready(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
return snd_pcm_capture_avail(runtime) >= runtime->control->avail_min;
}
+/**
+ * snd_pcm_playback_data - check whether any data exists on the playback buffer
+ * @substream: the pcm substream instance
+ *
+ * Checks whether any data exists on the playback buffer.
+ *
+ * Returns non-zero if exists, or zero if not.
+ */
int snd_pcm_playback_data(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
return snd_pcm_playback_avail(runtime) < runtime->buffer_size;
}
+/**
+ * snd_pcm_playback_empty - check whether the playback buffer is empty
+ * @substream: the pcm substream instance
+ *
+ * Checks whether the playback buffer is empty.
+ *
+ * Returns non-zero if empty, or zero if not.
+ */
int snd_pcm_playback_empty(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
return snd_pcm_playback_avail(runtime) >= runtime->buffer_size;
}
+/**
+ * snd_pcm_capture_empty - check whether the capture buffer is empty
+ * @substream: the pcm substream instance
+ *
+ * Checks whether the capture buffer is empty.
+ *
+ * Returns non-zero if empty, or zero if not.
+ */
int snd_pcm_capture_empty(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
@@ -1766,6 +1956,17 @@ void snd_pcm_tick_elapsed(snd_pcm_substream_t *substream)
spin_unlock_irq(&runtime->lock);
}
+/**
+ * snd_pcm_period_elapsed - update the pcm status for the next period
+ * @substream: the pcm substream instance
+ *
+ * This function is called from the interrupt handler when the
+ * PCM has processed the period size. It will update the current
+ * pointer, set up the tick, wake up sleepers, etc.
+ *
+ * Even if more than one periods have elapsed since the last call, you
+ * have to call this only once.
+ */
void snd_pcm_period_elapsed(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime;
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
index 4cc1750197dd..b2da702f0d7e 100644
--- a/sound/core/pcm_memory.c
+++ b/sound/core/pcm_memory.c
@@ -37,36 +37,120 @@ MODULE_PARM(maximum_substreams, "i");
MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");
MODULE_PARM_SYNTAX(maximum_substreams, SNDRV_BOOLEAN_TRUE_DESC);
-static int snd_minimum_buffer = 16384;
+const static int snd_minimum_buffer = 16384;
-static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
+/*
+ * allocate pages on the specified bus
+ */
+static int alloc_pcm_pages(snd_pcm_substream_t *substream, size_t size,
+ void **dma_area, dma_addr_t *dma_addr)
+{
+ switch (substream->dma_type) {
+ case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
+ *dma_area = snd_malloc_pages(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff));
+ *dma_addr = 0UL; /* not valid */
+ break;
+#ifdef CONFIG_ISA
+ case SNDRV_PCM_DMA_TYPE_ISA:
+ *dma_area = snd_malloc_isa_pages(size, dma_addr);
+ break;
+#endif
+#ifdef CONFIG_PCI
+ case SNDRV_PCM_DMA_TYPE_PCI:
+ *dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, dma_addr);
+ break;
+#endif
+#ifdef CONFIG_SBUS
+ case SNDRV_PCM_DMA_TYPE_SBUS:
+ *dma_area = snd_malloc_sbus_pages((struct sbus_dev *)substream->dma_private, size, dma_addr);
+ break;
+#endif
+ default:
+ *dma_area = NULL;
+ *dma_addr = 0;
+ return -ENXIO;
+ }
+ return 0;
+}
+
+/*
+ * try to allocate as the large pages as possible.
+ * stores the resultant memory size in *res_size.
+ *
+ * the minimum size is snd_minimum_buffer. it should be power of 2.
+ */
+static void *alloc_pcm_pages_fallback(snd_pcm_substream_t *substream,
+ size_t size, dma_addr_t *addrp,
+ size_t *res_size)
+{
+ void *res;
+
+ snd_assert(size > 0, return NULL);
+ snd_assert(res_size != NULL, return NULL);
+ do {
+ if (alloc_pcm_pages(substream, size, &res, addrp) < 0)
+ return NULL;
+ if (res) {
+ *res_size = size;
+ return res;
+ }
+ size >>= 1;
+ } while (size >= snd_minimum_buffer);
+ *res_size = 0; /* tell error */
+ return NULL;
+}
+
+/*
+ * release the pages on the specified bus
+ */
+static void free_pcm_pages(snd_pcm_substream_t *substream, size_t size,
+ void *dma_area, dma_addr_t dma_addr)
{
- if (substream->dma_area == NULL)
- return;
switch (substream->dma_type) {
case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
- snd_free_pages(substream->dma_area, substream->dma_bytes);
+ snd_free_pages(dma_area, size);
break;
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
- snd_free_isa_pages(substream->dma_bytes, substream->dma_area, substream->dma_addr);
+ snd_free_isa_pages(size, dma_area, dma_addr);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
- snd_free_pci_pages((struct pci_dev *)substream->dma_private, substream->dma_bytes, substream->dma_area, substream->dma_addr);
+ snd_free_pci_pages((struct pci_dev *)substream->dma_private,
+ size, dma_area, dma_addr);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
- snd_free_sbus_pages((struct sbus_dev *)substream->dma_private, substream->dma_bytes, substream->dma_area, substream->dma_addr);
+ snd_free_sbus_pages((struct sbus_dev *)substream->dma_private,
+ size, dma_area, dma_addr);
break;
#endif
}
+}
+
+/*
+ * release the preallocated buffer if not yet done.
+ */
+static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
+{
+ if (substream->dma_area == NULL)
+ return;
+ free_pcm_pages(substream, substream->dma_bytes,
+ substream->dma_area, substream->dma_addr);
substream->dma_area = NULL;
}
+/**
+ * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream.
+ * @substream: the pcm substream instance
+ *
+ * Releases the pre-allocated buffer of the given substream.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
{
snd_pcm_lib_preallocate_dma_free(substream);
@@ -77,6 +161,14 @@ int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
return 0;
}
+/**
+ * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
+ * @pcm: the pcm instance
+ *
+ * Releases all the pre-allocated buffers on the given pcm.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
{
snd_pcm_substream_t *substream;
@@ -88,6 +180,11 @@ int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
return 0;
}
+/*
+ * read callback for prealloc proc file
+ *
+ * prints the current allocated size in kB.
+ */
static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
@@ -95,6 +192,11 @@ static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_bytes / 1024);
}
+/*
+ * write callback for prealloc proc file
+ *
+ * accepts the preallocation size in kB.
+ */
static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
@@ -118,27 +220,7 @@ static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
if (substream->dma_bytes == size)
return;
if (size > 0) {
- switch (substream->dma_type) {
- case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
- dma_area = snd_malloc_pages(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff));
- dma_addr = 0UL; /* not valid */
- break;
-#ifdef CONFIG_ISA
- case SNDRV_PCM_DMA_TYPE_ISA:
- dma_area = snd_malloc_isa_pages(size, &dma_addr);
- break;
-#endif
-#ifdef CONFIG_PCI
- case SNDRV_PCM_DMA_TYPE_PCI:
- dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
- break;
-#endif
-#ifdef CONFIG_SBUS
- case SNDRV_PCM_DMA_TYPE_SBUS:
- dma_area = snd_malloc_sbus_pages((struct sbus_dev *)substream->dma_private, size, &dma_addr);
- break;
-#endif
- default:
+ if (alloc_pcm_pages(substream, size, &dma_area, &dma_addr) < 0) {
dma_area = NULL;
dma_addr = 0UL;
}
@@ -160,45 +242,19 @@ static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
}
}
+/*
+ * pre-allocate the buffer and create a proc file for the substream
+ */
static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
size_t size, size_t max)
{
- unsigned long rsize = 0;
+ size_t rsize = 0;
void *dma_area = NULL;
dma_addr_t dma_addr = 0UL;
snd_info_entry_t *entry;
- if (!size || !preallocate_dma || substream->number >= maximum_substreams) {
- size = 0;
- } else {
- switch (substream->dma_type) {
- case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
- dma_area = snd_malloc_pages_fallback(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff), &rsize);
- dma_addr = 0UL; /* not valid */
- break;
-#ifdef CONFIG_ISA
- case SNDRV_PCM_DMA_TYPE_ISA:
- dma_area = snd_malloc_isa_pages_fallback(size, &dma_addr, &rsize);
- break;
-#endif
-#ifdef CONFIG_PCI
- case SNDRV_PCM_DMA_TYPE_PCI:
- dma_area = snd_malloc_pci_pages_fallback((struct pci_dev *)substream->dma_private, size, &dma_addr, &rsize);
- break;
-#endif
-#ifdef CONFIG_SBUS
- case SNDRV_PCM_DMA_TYPE_SBUS:
- dma_area = snd_malloc_sbus_pages_fallback((struct sbus_dev *)substream->dma_private, size, &dma_addr, &rsize);
- break;
-#endif
- default:
- size = 0;
- }
- if (rsize < snd_minimum_buffer) {
- snd_pcm_lib_preallocate_dma_free(substream);
- size = 0;
- }
- }
+ if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
+ dma_area = alloc_pcm_pages_fallback(substream, size, &dma_addr, &rsize);
substream->dma_area = dma_area;
substream->dma_addr = dma_addr;
substream->dma_bytes = rsize;
@@ -220,6 +276,17 @@ static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
return 0;
}
+/**
+ * snd_pcm_lib_preallocate_pages - pre-allocation for the continuous memory type
+ * @substream: the pcm substream instance
+ * @size: the requested pre-allocation size in bytes
+ * @max: the max. allowed pre-allocation size
+ * @flags: allocation condition, GFP_XXX
+ *
+ * Do pre-allocation for the continuous memory type.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
size_t size, size_t max,
unsigned int flags)
@@ -229,6 +296,18 @@ int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
+/**
+ * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
+ * @pcm: pcm to assign the buffer
+ * @size: the requested pre-allocation size in bytes
+ * @max: max. buffer size acceptable for the changes via proc file
+ * @flags: allocation condition, GFP_XXX
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * continuous memory type.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
size_t size, size_t max,
unsigned int flags)
@@ -244,6 +323,16 @@ int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
}
#ifdef CONFIG_ISA
+/**
+ * snd_pcm_lib_preallocate_isa_pages - pre-allocation for the ISA bus
+ * @substream: substream to assign the buffer
+ * @size: the requested pre-allocation size in bytes
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation for the ISA bus.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_isa_pages(snd_pcm_substream_t *substream,
size_t size, size_t max)
{
@@ -252,6 +341,18 @@ int snd_pcm_lib_preallocate_isa_pages(snd_pcm_substream_t *substream,
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
+/*
+ * FIXME: the function name is too long for docbook!
+ *
+ * snd_pcm_lib_preallocate_isa_pages_for_all - pre-allocation for the ISA bus (all substreams)
+ * @pcm: pcm to assign the buffer
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * ISA bus.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_isa_pages_for_all(snd_pcm_t *pcm,
size_t size, size_t max)
{
@@ -266,12 +367,24 @@ int snd_pcm_lib_preallocate_isa_pages_for_all(snd_pcm_t *pcm,
}
#endif /* CONFIG_ISA */
+/**
+ * snd_pcm_lib_malloc_pages - allocate the DMA buffer
+ * @substream: the substream to allocate the DMA buffer to
+ * @size: the requested buffer size in bytes
+ *
+ * Allocates the DMA buffer on the BUS type given by
+ * snd_pcm_lib_preallocate_xxx_pages().
+ *
+ * Returns 1 if the buffer is changed, 0 if not changed, or a negative
+ * code on failure.
+ */
int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
{
snd_pcm_runtime_t *runtime;
void *dma_area = NULL;
dma_addr_t dma_addr = 0UL;
+ snd_assert(substream->dma_type != SNDRV_PCM_DMA_TYPE_UNKNOWN, return -EINVAL);
snd_assert(substream != NULL, return -EINVAL);
runtime = substream->runtime;
snd_assert(runtime != NULL, return -EINVAL);
@@ -287,29 +400,7 @@ int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
dma_area = substream->dma_area;
dma_addr = substream->dma_addr;
} else {
- switch (substream->dma_type) {
- case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
- dma_area = snd_malloc_pages(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff));
- dma_addr = 0UL; /* not valid */
- break;
-#ifdef CONFIG_ISA
- case SNDRV_PCM_DMA_TYPE_ISA:
- dma_area = snd_malloc_isa_pages(size, &dma_addr);
- break;
-#endif
-#ifdef CONFIG_PCI
- case SNDRV_PCM_DMA_TYPE_PCI:
- dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
- break;
-#endif
-#ifdef CONFIG_SBUS
- case SNDRV_PCM_DMA_TYPE_SBUS:
- dma_area = snd_malloc_sbus_pages((struct sbus_dev *)substream->dma_private, size, &dma_addr);
- break;
-#endif
- default:
- return -ENXIO;
- }
+ alloc_pcm_pages(substream, size, &dma_area, &dma_addr);
}
if (! dma_area)
return -ENOMEM;
@@ -319,6 +410,14 @@ int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
return 1; /* area was changed */
}
+/**
+ * snd_pcm_lib_free_pages - release the allocated DMA buffer.
+ * @substream: the substream to release the DMA buffer
+ *
+ * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages().
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime;
@@ -328,25 +427,9 @@ int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
snd_assert(runtime != NULL, return -EINVAL);
if (runtime->dma_area == NULL)
return 0;
- if (runtime->dma_area != substream->dma_area) {
- switch (substream->dma_type) {
-#ifdef CONFIG_ISA
- case SNDRV_PCM_DMA_TYPE_ISA:
- snd_free_isa_pages(runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
- break;
-#endif
-#ifdef CONFIG_PCI
- case SNDRV_PCM_DMA_TYPE_PCI:
- snd_free_pci_pages((struct pci_dev *)substream->dma_private, runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
- break;
-#endif
-#ifdef CONFIG_SBUS
- case SNDRV_PCM_DMA_TYPE_SBUS:
- snd_free_sbus_pages((struct sbus_dev *)substream->dma_private, runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
- break;
-#endif
- }
- }
+ if (runtime->dma_area != substream->dma_area)
+ free_pcm_pages(substream, runtime->dma_bytes,
+ runtime->dma_area, runtime->dma_addr);
runtime->dma_area = NULL;
runtime->dma_addr = 0UL;
runtime->dma_bytes = 0;
@@ -354,7 +437,18 @@ int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
}
#ifdef CONFIG_PCI
-
+/**
+ * snd_pcm_lib_preallocate_pci_pages - pre-allocation for the PCI bus
+ *
+ * @pci: pci device
+ * @substream: substream to assign the buffer
+ * @size: the requested pre-allocation size in bytes
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation for the PCI bus.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_pci_pages(struct pci_dev *pci,
snd_pcm_substream_t *substream,
size_t size, size_t max)
@@ -364,6 +458,20 @@ int snd_pcm_lib_preallocate_pci_pages(struct pci_dev *pci,
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
+/*
+ * FIXME: the function name is too long for docbook!
+ *
+ * snd_pcm_lib_preallocate_pci_pages_for_all - pre-allocation for the PCI bus (all substreams)
+ * @pci: pci device
+ * @pcm: pcm to assign the buffer
+ * @size: the requested pre-allocation size in bytes
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * PCI bus.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_pci_pages_for_all(struct pci_dev *pci,
snd_pcm_t *pcm,
size_t size, size_t max)
@@ -381,7 +489,17 @@ int snd_pcm_lib_preallocate_pci_pages_for_all(struct pci_dev *pci,
#endif /* CONFIG_PCI */
#ifdef CONFIG_SBUS
-
+/**
+ * snd_pcm_lib_preallocate_sbus_pages - pre-allocation for the SBUS bus
+ *
+ * @sbus: SBUS device
+ * @substream: substream to assign the buffer
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation for the SBUS.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_sbus_pages(struct sbus_dev *sdev,
snd_pcm_substream_t *substream,
size_t size, size_t max)
@@ -391,6 +509,20 @@ int snd_pcm_lib_preallocate_sbus_pages(struct sbus_dev *sdev,
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
+/*
+ * FIXME: the function name is too long for docbook!
+ *
+ * snd_pcm_lib_preallocate_sbus_pages_for_all - pre-allocation for the SBUS bus (all substreams)
+ * @sbus: SBUS device
+ * @pcm: pcm to assign the buffer
+ * @size: the requested pre-allocation size in bytes
+ * @max: max. buffer size acceptable for the changes via proc file
+ *
+ * Do pre-allocation to all substreams of the given pcm for the
+ * SUBS.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_pcm_lib_preallocate_sbus_pages_for_all(struct sbus_dev *sdev,
snd_pcm_t *pcm,
size_t size, size_t max)
diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c
index 38a87b6d69eb..310f1f1c4006 100644
--- a/sound/core/pcm_misc.c
+++ b/sound/core/pcm_misc.c
@@ -30,6 +30,13 @@
#define snd_enum_to_int(v) (v)
#define snd_int_to_enum(v) (v)
+/**
+ * snd_pcm_format_signed - Check the PCM format is signed linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is signed linear, 0 if unsigned
+ * linear, and a negative error code for non-linear formats.
+ */
int snd_pcm_format_signed(snd_pcm_format_t format)
{
switch (snd_enum_to_int(format)) {
@@ -66,6 +73,13 @@ int snd_pcm_format_signed(snd_pcm_format_t format)
}
}
+/**
+ * snd_pcm_format_unsigned - Check the PCM format is unsigned linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is unsigned linear, 0 if signed
+ * linear, and a negative error code for non-linear formats.
+ */
int snd_pcm_format_unsigned(snd_pcm_format_t format)
{
int val;
@@ -76,11 +90,24 @@ int snd_pcm_format_unsigned(snd_pcm_format_t format)
return !val;
}
+/**
+ * snd_pcm_format_linear - Check the PCM format is linear
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is linear, 0 if not.
+ */
int snd_pcm_format_linear(snd_pcm_format_t format)
{
return snd_pcm_format_signed(format) >= 0;
}
+/**
+ * snd_pcm_format_little_endian - Check the PCM format is little-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is little-endian, 0 if
+ * big-endian, or a negative error code if endian not specified.
+ */
int snd_pcm_format_little_endian(snd_pcm_format_t format)
{
switch (snd_enum_to_int(format)) {
@@ -121,6 +148,13 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format)
}
}
+/**
+ * snd_pcm_format_big_endian - Check the PCM format is big-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is big-endian, 0 if
+ * little-endian, or a negative error code if endian not specified.
+ */
int snd_pcm_format_big_endian(snd_pcm_format_t format)
{
int val;
@@ -131,6 +165,13 @@ int snd_pcm_format_big_endian(snd_pcm_format_t format)
return !val;
}
+/**
+ * snd_pcm_format_cpu_endian - Check the PCM format is CPU-endian
+ * @format: the format to check
+ *
+ * Returns 1 if the given PCM format is CPU-endian, 0 if
+ * opposite, or a negative error code if endian not specified.
+ */
int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
{
#ifdef SNDRV_LITTLE_ENDIAN
@@ -140,6 +181,13 @@ int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
#endif
}
+/**
+ * snd_pcm_format_width - return the bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the bit-width of the format, or a negative error code
+ * if unknown format.
+ */
int snd_pcm_format_width(snd_pcm_format_t format)
{
switch (snd_enum_to_int(format)) {
@@ -193,6 +241,13 @@ int snd_pcm_format_width(snd_pcm_format_t format)
}
}
+/**
+ * snd_pcm_format_physical_width - return the physical bit-width of the format
+ * @format: the format to check
+ *
+ * Returns the physical bit-width of the format, or a negative error code
+ * if unknown format.
+ */
int snd_pcm_format_physical_width(snd_pcm_format_t format)
{
switch (snd_enum_to_int(format)) {
@@ -243,6 +298,13 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format)
}
}
+/**
+ * snd_pcm_format_size - return the byte size of samples on the given format
+ * @format: the format to check
+ *
+ * Returns the byte size of the given samples for the format, or a
+ * negative error code if unknown format.
+ */
ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
{
switch (snd_enum_to_int(format)) {
@@ -296,6 +358,12 @@ ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
}
}
+/**
+ * snd_pcm_format_silence_64 - return the silent data in 64bit integer
+ * @format: the format to check
+ *
+ * Returns the silent data in 64bit integer for the given format.
+ */
u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
{
switch (snd_enum_to_int(format)) {
@@ -451,6 +519,16 @@ u_int8_t snd_pcm_format_silence(snd_pcm_format_t format)
return (u_int8_t)snd_pcm_format_silence_64(format);
}
+/**
+ * snd_pcm_format_set_silence - set the silence data on the buffer
+ * @format: the PCM format
+ * @data: the buffer pointer
+ * @samples: the number of samples to set silence
+ *
+ * Sets the silence data on the buffer for the given samples.
+ *
+ * Returns zero if sucessful, or a negative error code on failure.
+ */
int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
{
if (samples == 0)
@@ -544,6 +622,14 @@ static int linear_formats[4*2*2] = {
SNDRV_PCM_FORMAT_U32_BE
};
+/**
+ * snd_pcm_build_linear_format - return the suitable linear format for the given condition
+ * @width: the bit-width
+ * @unsignd: 1 if unsigned, 0 if signed.
+ * @big_endian: 1 if big-endian, 0 if little-endian
+ *
+ * Returns the suitable linear format for the given condition.
+ */
snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian)
{
switch (width) {
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index ce21615f6c92..c12bad5d92b4 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -676,6 +676,9 @@ static inline void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
snd_pcm_tick_prepare(substream);
}
+/**
+ * snd_pcm_sart
+ */
int snd_pcm_start(snd_pcm_substream_t *substream)
{
SND_PCM_ACTION(start, substream, 0);
@@ -705,6 +708,9 @@ static inline void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
wake_up(&runtime->sleep);
}
+/**
+ * snd_pcm_stop
+ */
int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
{
SND_PCM_ACTION(stop, substream, state);
@@ -781,11 +787,17 @@ static inline void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int stat
wake_up(&runtime->sleep);
}
+/**
+ * snd_pcm_suspend
+ */
int snd_pcm_suspend(snd_pcm_substream_t *substream)
{
SND_PCM_ACTION(suspend, substream, 0);
}
+/**
+ * snd_pcm_suspend_all
+ */
int snd_pcm_suspend_all(snd_pcm_t *pcm)
{
snd_pcm_substream_t *substream;
@@ -978,6 +990,9 @@ static inline void snd_pcm_post_prepare(snd_pcm_substream_t * substream, int sta
runtime->status->state = SNDRV_PCM_STATE_PREPARED;
}
+/**
+ * snd_pcm_prepare
+ */
int snd_pcm_prepare(snd_pcm_substream_t *substream)
{
int res;
diff --git a/sound/core/pcm_sgbuf.c b/sound/core/pcm_sgbuf.c
index 2511b99b3ede..914373a2a7e9 100644
--- a/sound/core/pcm_sgbuf.c
+++ b/sound/core/pcm_sgbuf.c
@@ -41,16 +41,24 @@ static void sgbuf_shrink(struct snd_sg_buf *sgbuf, int pages)
return;
while (sgbuf->pages > pages) {
sgbuf->pages--;
- snd_free_pci_pages(sgbuf->pci, PAGE_SIZE,
- sgbuf->table[sgbuf->pages].buf,
+ snd_free_pci_page(sgbuf->pci, sgbuf->table[sgbuf->pages].buf,
sgbuf->table[sgbuf->pages].addr);
}
}
-/*
- * initialize the sg buffer
- * assigned to substream->dma_private.
- * initialize the table with the given size.
+/**
+ * snd_pcm_sgbuf_init - initialize the sg buffer
+ * @substream: the pcm substream instance
+ * @pci: pci device pointer
+ * @tblsize: the default table size
+ *
+ * Initializes the SG-buffer instance and assigns it to
+ * substream->dma_private. The SG-table is initialized with the
+ * given size.
+ *
+ * Call this function in the open callback.
+ *
+ * Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_sgbuf_init(snd_pcm_substream_t *substream, struct pci_dev *pci, int tblsize)
{
@@ -73,8 +81,15 @@ int snd_pcm_sgbuf_init(snd_pcm_substream_t *substream, struct pci_dev *pci, int
return 0;
}
-/*
- * release all pages and free the sgbuf instance
+/**
+ * snd_pcm_sgbuf_delete - release all pages and free the sgbuf instance
+ * @substream: the pcm substream instance
+ *
+ * Releaes all pages and free the sgbuf instance.
+ *
+ * Call this function in the close callback.
+ *
+ * Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_sgbuf_delete(snd_pcm_substream_t *substream)
{
@@ -92,60 +107,19 @@ int snd_pcm_sgbuf_delete(snd_pcm_substream_t *substream)
return 0;
}
-/*
- * snd_pci_alloc_page - allocate a page in the valid pci dma mask
+/**
+ * snd_pcm_sgbuf_alloc - allocate the pages for the SG buffer
+ * @substream: the pcm substream instance
+ * @size: the requested buffer size in bytes
*
- * returns the virtual address and stores the physical address on
- * addrp. this function cannot be called from interrupt handlers or
- * within spinlocks.
- */
-#ifdef __i386__
-/*
- * on ix86, we allocate a page with GFP_KERNEL to assure the
- * allocation. the code is almost same with kernel/i386/pci-dma.c but
- * it allocates only a single page and checkes the validity of the
- * page address with the given pci dma mask.
- */
-inline static void *snd_pci_alloc_page(struct pci_dev *pci, dma_addr_t *addrp)
-{
- void *ptr;
- dma_addr_t addr;
- unsigned long rmask;
-
- if (pci)
- rmask = ~(unsigned long)pci->dma_mask;
- else
- rmask = 0;
- ptr = (void *)__get_free_page(GFP_KERNEL);
- if (ptr) {
- addr = virt_to_phys(ptr);
- if (((unsigned long)addr + PAGE_SIZE - 1) & rmask) {
- /* try to reallocate with the GFP_DMA */
- free_page((unsigned long)ptr);
- ptr = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
- if (ptr) /* ok, the address must be within lower 16MB... */
- addr = virt_to_phys(ptr);
- else
- addr = 0;
- }
- } else
- addr = 0;
- if (ptr)
- memset(ptr, 0, PAGE_SIZE);
- *addrp = addr;
- return ptr;
-}
-#else
-/* on other architectures, call snd_malloc_pci_pages() helper function
- * which uses pci_alloc_consistent().
- */
-#define snd_pci_alloc_page(pci, addrp) snd_malloc_pci_pages(pci, PAGE_SIZE, addrp)
-#endif
-
-/*
- * allocate sg buffer table with the given byte size.
- * if the buffer table already exists, try to resize it.
- * call this from hw_params callback.
+ * Allocates the buffer pages for the given size and updates the
+ * sg buffer table. If the buffer table already exists, try to resize
+ * it.
+ *
+ * Call this function from hw_params callback.
+ *
+ * Returns 1 if the buffer is changed, 0 if not changed, or a negative
+ * code on failure.
*/
int snd_pcm_sgbuf_alloc(snd_pcm_substream_t *substream, size_t size)
{
@@ -178,7 +152,7 @@ int snd_pcm_sgbuf_alloc(snd_pcm_substream_t *substream, size_t size)
while (sgbuf->pages < pages) {
void *ptr;
dma_addr_t addr;
- ptr = snd_pci_alloc_page(sgbuf->pci, &addr);
+ ptr = snd_malloc_pci_page(sgbuf->pci, &addr);
if (! ptr)
return -ENOMEM;
sgbuf->table[sgbuf->pages].buf = ptr;
@@ -192,10 +166,15 @@ int snd_pcm_sgbuf_alloc(snd_pcm_substream_t *substream, size_t size)
return changed;
}
-/*
- * free the sg buffer
- * the table is kept.
- * call this from hw_free callback.
+/**
+ * snd_pcm_sgbuf_free - free the sg buffer
+ * @substream: the pcm substream instance
+ *
+ * Releases the pages. The SG-table itself is still kept.
+ *
+ * Call this function from hw_free callback.
+ *
+ * Returns zero if successful, or a negative error code on failure.
*/
int snd_pcm_sgbuf_free(snd_pcm_substream_t *substream)
{
@@ -221,9 +200,13 @@ static void *sgbuf_get_addr(snd_pcm_substream_t *substream, unsigned long offset
return sgbuf->table[idx].buf;
}
-/*
- * get the page struct at the given offset
- * used as the page callback of pcm ops
+/**
+ * snd_pcm_sgbuf_ops_page - get the page struct at the given offset
+ * @substream: the pcm substream instance
+ * @offset: the buffer offset
+ *
+ * Returns the page struct at the given buffer offset.
+ * Used as the page callback of PCM ops.
*/
struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset)
{
@@ -324,7 +307,9 @@ static int set_silence_sg_buf(snd_pcm_substream_t *substream,
return 0;
}
-/*
+/**
+ * snd_pcm_sgbuf_ops_copy_playback - copy callback for playback pcm ops
+ *
* copy callback for playback pcm ops
*/
int snd_pcm_sgbuf_ops_copy_playback(snd_pcm_substream_t *substream, int channel,
@@ -340,7 +325,9 @@ int snd_pcm_sgbuf_ops_copy_playback(snd_pcm_substream_t *substream, int channel,
}
}
-/*
+/**
+ * snd_pcm_sgbuf_ops_copy_capture - copy callback for capture pcm ops
+ *
* copy callback for capture pcm ops
*/
int snd_pcm_sgbuf_ops_copy_capture(snd_pcm_substream_t *substream, int channel,
@@ -356,7 +343,9 @@ int snd_pcm_sgbuf_ops_copy_capture(snd_pcm_substream_t *substream, int channel,
}
}
-/*
+/**
+ * snd_pcm_sgbuf_ops_silence - silence callback for pcm ops
+ *
* silence callback for pcm ops
*/
int snd_pcm_sgbuf_ops_silence(snd_pcm_substream_t *substream, int channel,
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index 061cb55fa1e1..17376907b20f 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -815,6 +815,16 @@ int snd_rawmidi_control_ioctl(snd_card_t * card, snd_ctl_file_t * control,
return -ENOIOCTLCMD;
}
+/**
+ * snd_rawmidi_receive - receive the input data from the device
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: the data size to read
+ *
+ * Reads the data from the internal buffer.
+ *
+ * Returns the size of read data, or a negative error code on failure.
+ */
int snd_rawmidi_receive(snd_rawmidi_substream_t * substream, const unsigned char *buffer, int count)
{
unsigned long flags;
@@ -959,6 +969,12 @@ static ssize_t snd_rawmidi_read(struct file *file, char *buf, size_t count, loff
return result;
}
+/**
+ * snd_rawmidi_transmit_empty - check whether the output buffer is empty
+ * @substream: the rawmidi substream
+ *
+ * Returns 1 if the internal output buffer is empty, 0 if not.
+ */
int snd_rawmidi_transmit_empty(snd_rawmidi_substream_t * substream)
{
snd_rawmidi_runtime_t *runtime = substream->runtime;
@@ -977,6 +993,20 @@ int snd_rawmidi_transmit_empty(snd_rawmidi_substream_t * substream)
return result;
}
+/**
+ * snd_rawmidi_transmit_peek - copy data from the internal buffer
+ * @substream: the rawmidi substream
+ * @buffer: the buffer pointer
+ * @count: data size to transfer
+ *
+ * Copies data from the internal output buffer to the given buffer.
+ *
+ * Call this in the interrupt handler when the midi output is ready,
+ * and call snd_rawmidi_transmit_ack() after the transmission is
+ * finished.
+ *
+ * Returns the size of copied data, or a negative error code on failure.
+ */
int snd_rawmidi_transmit_peek(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
{
unsigned long flags;
@@ -1014,6 +1044,17 @@ int snd_rawmidi_transmit_peek(snd_rawmidi_substream_t * substream, unsigned char
return result;
}
+/**
+ * snd_rawmidi_transmit_ack - acknowledge the transmission
+ * @substream: the rawmidi substream
+ * @count: the tranferred count
+ *
+ * Advances the hardware pointer for the internal output buffer with
+ * the given size and updates the condition.
+ * Call after the transmission is finished.
+ *
+ * Returns the advanced size if successful, or a negative error code on failure.
+ */
int snd_rawmidi_transmit_ack(snd_rawmidi_substream_t * substream, int count)
{
unsigned long flags;
@@ -1041,6 +1082,16 @@ int snd_rawmidi_transmit_ack(snd_rawmidi_substream_t * substream, int count)
return count;
}
+/**
+ * snd_rawmidi_transmit - copy from the buffer to the device
+ * @substream: the rawmidi substream
+ * @buf: the buffer pointer
+ * @count: the data size to transfer
+ *
+ * Copies data from the buffer to the device and advances the pointer.
+ *
+ * Returns the copied size if successful, or a negative error code on failure.
+ */
int snd_rawmidi_transmit(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count)
{
count = snd_rawmidi_transmit_peek(substream, buffer, count);
@@ -1304,6 +1355,20 @@ static int snd_rawmidi_alloc_substreams(snd_rawmidi_t *rmidi,
return 0;
}
+/**
+ * snd_rawmidi_new - create a rawmidi instance
+ * @card: the card instance
+ * @id: the id string
+ * @device: the device index
+ * @output_count: the number of output streams
+ * @input_count: the number of input streams
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new rawmidi instance.
+ * Use snd_rawmidi_set_ops() to set the operators to the new instance.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_rawmidi_new(snd_card_t * card, char *id, int device,
int output_count, int input_count,
snd_rawmidi_t ** rrawmidi)
@@ -1513,6 +1578,14 @@ static int snd_rawmidi_dev_unregister(snd_device_t *device)
return snd_rawmidi_free(rmidi);
}
+/**
+ * snd_rawmidi_set_ops - set the rawmidi operators
+ * @rmidi: the rawmidi instance
+ * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX
+ * @ops: the operator table
+ *
+ * Sets the rawmidi operators for the given stream direction.
+ */
void snd_rawmidi_set_ops(snd_rawmidi_t *rmidi, int stream, snd_rawmidi_ops_t *ops)
{
struct list_head *list;
diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile
index 59b386b1f74a..1a8b6335f5cf 100644
--- a/sound/core/seq/Makefile
+++ b/sound/core/seq/Makefile
@@ -77,5 +77,6 @@ obj-$(CONFIG_SND_EMU10K1) += $(RAWMIDI_OBJS) snd-seq-midi-emul.o snd-seq-virmidi
obj-$(CONFIG_SND_TRIDENT) += $(RAWMIDI_OBJS) snd-seq-midi-emul.o snd-seq-instr.o
obj-$(CONFIG_SND_YMFPCI) += $(RAWMIDI_OBJS) $(OPL3_OBJS)
obj-$(CONFIG_SND_USB_AUDIO) += $(RAWMIDI_OBJS)
+obj-$(CONFIG_SND_HDSP) += $(RAWMIDI_OBJS)
obj-m := $(sort $(obj-m))
diff --git a/sound/core/sound.c b/sound/core/sound.c
index 0a18ccae9fd1..3f6a4b68cce4 100644
--- a/sound/core/sound.c
+++ b/sound/core/sound.c
@@ -73,6 +73,13 @@ static devfs_handle_t devfs_handle = NULL;
#ifdef CONFIG_KMOD
+/**
+ * snd_request_card - try to load the card module
+ * @card: the card number
+ *
+ * Tries to load the module "snd-card-X" for the given card number
+ * via KMOD. Returns immediately if already loaded.
+ */
void snd_request_card(int card)
{
char str[32];
@@ -188,6 +195,19 @@ static int snd_kernel_minor(int type, snd_card_t * card, int dev)
return minor;
}
+/**
+ * snd_register_device - Register the ALSA device file for the card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ * @reg: the snd_minor_t record
+ * @name: the device file name
+ *
+ * Registers an ALSA device file for the given card.
+ * The operators have to be set in reg parameter.
+ *
+ * Retrurns zero if successful, or a negative error code on failure.
+ */
int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
{
int minor = snd_kernel_minor(type, card, dev);
@@ -215,6 +235,17 @@ int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg,
return 0;
}
+/**
+ * snd_unregister_device - unregister the device on the given card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ *
+ * Unregisters the device file already registered via
+ * snd_register_device().
+ *
+ * Returns zero if sucecessful, or a negative error code on failure
+ */
int snd_unregister_device(int type, snd_card_t * card, int dev)
{
int minor = snd_kernel_minor(type, card, dev);
@@ -411,6 +442,7 @@ EXPORT_SYMBOL(snd_malloc_isa_pages_fallback);
#ifdef CONFIG_PCI
EXPORT_SYMBOL(snd_malloc_pci_pages);
EXPORT_SYMBOL(snd_malloc_pci_pages_fallback);
+EXPORT_SYMBOL(snd_malloc_pci_page);
EXPORT_SYMBOL(snd_free_pci_pages);
#endif
#ifdef CONFIG_SBUS
@@ -463,6 +495,7 @@ EXPORT_SYMBOL(snd_info_create_device);
EXPORT_SYMBOL(snd_info_free_device);
EXPORT_SYMBOL(snd_info_register);
EXPORT_SYMBOL(snd_info_unregister);
+EXPORT_SYMBOL(snd_card_proc_new);
#endif
/* info_oss.c */
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c
index 88e3f72c89be..33aab157bf99 100644
--- a/sound/drivers/mpu401/mpu401_uart.c
+++ b/sound/drivers/mpu401/mpu401_uart.c
@@ -74,6 +74,14 @@ static void _snd_mpu401_uart_interrupt(mpu401_t *mpu)
snd_mpu401_uart_output_write(mpu);
}
+/**
+ * snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler
+ * @irq: the irq number
+ * @dev_id: mpu401 instance
+ * @regs: the reigster
+ *
+ * Processes the interrupt for MPU401-UART i/o.
+ */
void snd_mpu401_uart_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
mpu401_t *mpu = snd_magic_cast(mpu401_t, dev_id, return);
@@ -375,6 +383,25 @@ static void snd_mpu401_uart_free(snd_rawmidi_t *rmidi)
snd_magic_kfree(mpu);
}
+/**
+ * snd_mpu401_uart_new - create an MPU401-UART instance
+ * @card: the card instance
+ * @device: the device index, zero-based
+ * @hardware: the hardware type, MPU401_HW_XXXX
+ * @port: the base address of MPU401 port
+ * @integrated: non-zero if the port was already reserved by the chip
+ * @irq: the irq number, -1 if no interrupt for mpu
+ * @irq_flags: the irq request flags (SA_XXX), 0 if irq was already reserved.
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new MPU-401 instance.
+ *
+ * Note that the rawmidi instance is returned on the rrawmidi argument,
+ * not the mpu401 instance itself. To access to the mpu401 instance,
+ * cast from rawmidi->private_data (with mpu401_t magic-cast).
+ *
+ * Returns zero if successful, or a negative error code.
+ */
int snd_mpu401_uart_new(snd_card_t * card, int device,
unsigned short hardware,
unsigned long port, int integrated,
@@ -418,9 +445,9 @@ int snd_mpu401_uart_new(snd_card_t * card, int device,
snd_device_free(card, rmidi);
return -EBUSY;
}
- mpu->irq = irq;
- mpu->irq_flags = irq_flags;
}
+ mpu->irq = irq;
+ mpu->irq_flags = irq_flags;
strcpy(rmidi->name, "MPU-401 (UART)");
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_mpu401_uart_output);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_mpu401_uart_input);
diff --git a/sound/i2c/l3/uda1341.c b/sound/i2c/l3/uda1341.c
index 51ae6acf0b85..caa103d9d33a 100644
--- a/sound/i2c/l3/uda1341.c
+++ b/sound/i2c/l3/uda1341.c
@@ -16,7 +16,7 @@
* 2002-04-12 Tomas Kasparek Proc interface update, code cleanup
*/
-/* $Id: uda1341.c,v 1.5 2002/11/09 13:12:19 perex Exp $ */
+/* $Id: uda1341.c,v 1.6 2003/01/07 10:36:28 tiwai Exp $ */
#include <sound/driver.h>
#include <linux/module.h>
@@ -128,9 +128,6 @@ struct uda1341{
snd_card_t *card;
- snd_info_entry_t *proc_entry;
- snd_info_entry_t *proc_regs_entry;
-
uda1341_cfg cfg;
};
@@ -410,46 +407,10 @@ static void __devinit snd_uda1341_proc_init(snd_card_t *card, struct l3_client *
DEBUG_NAME(KERN_DEBUG "proc_init\n");
- if ((entry = snd_info_create_card_entry(card, "uda1341", card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = clnt;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 512;
- entry->c.text.read = snd_uda1341_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- uda->proc_entry = entry;
- if ((entry = snd_info_create_card_entry(card, "uda1341-regs", card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = clnt;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 1024;
- entry->c.text.read = snd_uda1341_proc_regs_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- uda->proc_regs_entry = entry;
-}
-
-static void snd_uda1341_proc_done(struct l3_client *clnt)
-{
- struct uda1341 *uda = clnt->driver_data;
-
- DEBUG_NAME(KERN_DEBUG "proc_done\n");
-
- if (uda->proc_regs_entry) {
- snd_info_unregister(uda->proc_regs_entry);
- uda->proc_regs_entry = NULL;
- }
- if (uda->proc_entry) {
- snd_info_unregister(uda->proc_entry);
- uda->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(card, "uda1341", &entry))
+ snd_info_set_text_ops(entry, clnt, snd_uda1341_proc_read);
+ if (! snd_card_proc_new(card, "uda1341-regs", &entry)) {
+ snd_info_set_text_ops(entry, clnt, snd_uda1341_proc_regs_read);
}
/* }}} */
@@ -731,7 +692,6 @@ void __init snd_chip_uda1341_mixer_del(snd_card_t *card)
{
DEBUG_NAME(KERN_DEBUG "uda1341 mixer_del\n");
- snd_uda1341_proc_done(uda1341);
l3_detach_client(uda1341);
snd_magic_kfree(uda1341);
diff --git a/sound/isa/ad1848/ad1848_lib.c b/sound/isa/ad1848/ad1848_lib.c
index d36d57a2efa9..a46d5a4613ad 100644
--- a/sound/isa/ad1848/ad1848_lib.c
+++ b/sound/isa/ad1848/ad1848_lib.c
@@ -936,6 +936,12 @@ int snd_ad1848_pcm(ad1848_t *chip, int device, snd_pcm_t **rpcm)
return 0;
}
+const snd_pcm_ops_t *snd_ad1848_get_pcm_ops(int direction)
+{
+ return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_ad1848_playback_ops : &snd_ad1848_capture_ops;
+}
+
/*
* MIXER part
*/
@@ -1160,6 +1166,7 @@ EXPORT_SYMBOL(snd_ad1848_mce_down);
EXPORT_SYMBOL(snd_ad1848_interrupt);
EXPORT_SYMBOL(snd_ad1848_create);
EXPORT_SYMBOL(snd_ad1848_pcm);
+EXPORT_SYMBOL(snd_ad1848_get_pcm_ops);
EXPORT_SYMBOL(snd_ad1848_mixer);
EXPORT_SYMBOL(snd_ad1848_info_single);
EXPORT_SYMBOL(snd_ad1848_get_single);
diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c
index 093420d84783..cf41048fbf65 100644
--- a/sound/isa/cmi8330.c
+++ b/sound/isa/cmi8330.c
@@ -147,6 +147,15 @@ struct snd_cmi8330 {
struct isapnp_dev *cap;
struct isapnp_dev *play;
#endif
+ snd_card_t *card;
+ ad1848_t *wss;
+ sb_t *sb;
+
+ snd_pcm_t *pcm;
+ snd_pcm_ops_t playback_ops;
+ int (*playback_open)(snd_pcm_substream_t *);
+ snd_pcm_ops_t capture_ops;
+ int (*capture_open)(snd_pcm_substream_t *);
};
static snd_card_t *snd_cmi8330_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
@@ -294,6 +303,79 @@ static void snd_cmi8330_deactivate(struct snd_cmi8330 *acard)
}
#endif
+/*
+ * PCM interface
+ *
+ * since we call the different chip interfaces for playback and capture
+ * directions, we need a trick.
+ *
+ * - copy the ops for each direction into a local record.
+ * - replace the open callback with the new one, which replaces the
+ * substream->private_data with the corresponding chip instance
+ * and calls again the original open callback of the chip.
+ *
+ */
+
+static int snd_cmi8330_playback_open(snd_pcm_substream_t * substream)
+{
+ struct snd_cmi8330 *chip = (struct snd_cmi8330 *)_snd_pcm_substream_chip(substream);
+
+ /* replace the private_data and call the original open callback */
+ substream->private_data = chip->sb;
+ return chip->playback_open(substream);
+}
+
+static int snd_cmi8330_capture_open(snd_pcm_substream_t * substream)
+{
+ struct snd_cmi8330 *chip = (struct snd_cmi8330 *)_snd_pcm_substream_chip(substream);
+
+ /* replace the private_data and call the original open callback */
+ substream->private_data = chip->wss;
+ return chip->capture_open(substream);
+}
+
+static void snd_cmi8330_pcm_free(snd_pcm_t *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int __init snd_cmi8330_pcm(snd_card_t *card, struct snd_cmi8330 *chip)
+{
+ snd_pcm_t *pcm;
+ const snd_pcm_ops_t *ops;
+ int err;
+
+ if ((err = snd_pcm_new(card, "CMI8330", 0, 1, 1, &pcm)) < 0)
+ return err;
+ strcpy(pcm->name, "CMI8330");
+ pcm->private_data = chip;
+ pcm->private_free = snd_cmi8330_pcm_free;
+
+ /* playback - SB16 */
+ ops = snd_sb16dsp_get_pcm_ops(SNDRV_PCM_STREAM_PLAYBACK);
+ chip->playback_ops = *ops;
+ chip->playback_open = ops->open;
+ chip->playback_ops.open = snd_cmi8330_playback_open;
+
+ /* capture - AD1848 */
+ ops = snd_ad1848_get_pcm_ops(SNDRV_PCM_STREAM_CAPTURE);
+ chip->capture_ops = *ops;
+ chip->capture_open = ops->open;
+ chip->capture_ops.open = snd_cmi8330_capture_open;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->capture_ops);
+
+ snd_pcm_lib_preallocate_isa_pages_for_all(pcm, 64*1024, 128*1024);
+ chip->pcm = pcm;
+
+ return 0;
+}
+
+
+/*
+ */
+
static void snd_cmi8330_free(snd_card_t *card)
{
struct snd_cmi8330 *acard = (struct snd_cmi8330 *)card->private_data;
@@ -309,12 +391,8 @@ static int __init snd_cmi8330_probe(int dev)
{
snd_card_t *card;
struct snd_cmi8330 *acard;
- ad1848_t *chip_wss;
- sb_t *chip_sb;
unsigned long flags;
int i, err;
- snd_pcm_t *pcm, *wss_pcm, *sb_pcm;
- snd_pcm_str_t *pstr;
#ifdef __ISAPNP__
if (!isapnp[dev]) {
@@ -337,6 +415,7 @@ static int __init snd_cmi8330_probe(int dev)
return -ENOMEM;
}
acard = (struct snd_cmi8330 *)card->private_data;
+ acard->card = card;
card->private_free = snd_cmi8330_free;
#ifdef __ISAPNP__
@@ -352,83 +431,60 @@ static int __init snd_cmi8330_probe(int dev)
wssirq[dev],
wssdma[dev],
AD1848_HW_DETECT,
- &chip_wss)) < 0) {
+ &acard->wss)) < 0) {
snd_printk("(AD1848) device busy??\n");
snd_card_free(card);
return err;
}
- if (chip_wss->hardware != AD1848_HW_CMI8330) {
+ if (acard->wss->hardware != AD1848_HW_CMI8330) {
snd_printk("(AD1848) not found during probe\n");
snd_card_free(card);
return -ENODEV;
}
- if ((err = snd_ad1848_pcm(chip_wss, 0, &wss_pcm)) < 0) {
- snd_printk("(AD1848) no enough memory??\n");
- snd_card_free(card);
- return err;
- }
if ((err = snd_sbdsp_create(card, sbport[dev],
sbirq[dev],
snd_sb16dsp_interrupt,
sbdma8[dev],
sbdma16[dev],
- SB_HW_AUTO, &chip_sb)) < 0) {
+ SB_HW_AUTO, &acard->sb)) < 0) {
snd_printk("(SB16) device busy??\n");
snd_card_free(card);
return err;
}
- if ((err = snd_sb16dsp_pcm(chip_sb, 1, &sb_pcm)) < 0) {
- snd_printk("(SB16) no enough memory??\n");
- snd_card_free(card);
- return err;
- }
-
- if (chip_sb->hardware != SB_HW_16) {
+ if (acard->sb->hardware != SB_HW_16) {
snd_printk("(SB16) not found during probe\n");
snd_card_free(card);
return -ENODEV;
}
+ memcpy(&acard->wss->image[16], &snd_cmi8330_image, sizeof(snd_cmi8330_image));
- memcpy(&chip_wss->image[16], &snd_cmi8330_image, sizeof(snd_cmi8330_image));
+ spin_lock_irqsave(&acard->wss->reg_lock, flags);
+ snd_ad1848_out(acard->wss, AD1848_MISC_INFO, /* switch on MODE2 */
+ acard->wss->image[AD1848_MISC_INFO] |= 0x40);
+ spin_unlock_irqrestore(&acard->wss->reg_lock, flags);
- spin_lock_irqsave(&chip_wss->reg_lock, flags);
- snd_ad1848_out(chip_wss, AD1848_MISC_INFO, /* switch on MODE2 */
- chip_wss->image[AD1848_MISC_INFO] |= 0x40);
- spin_unlock_irqrestore(&chip_wss->reg_lock, flags);
-
- if ((err = snd_cmi8330_mixer(card, chip_wss)) < 0) {
+ if ((err = snd_cmi8330_mixer(card, acard->wss)) < 0) {
snd_printk("failed to create mixers\n");
snd_card_free(card);
return err;
}
- spin_lock_irqsave(&chip_wss->reg_lock, flags);
+ spin_lock_irqsave(&acard->wss->reg_lock, flags);
for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++)
- snd_ad1848_out(chip_wss, i, chip_wss->image[i]);
- spin_unlock_irqrestore(&chip_wss->reg_lock, flags);
-
- /*
- * KLUDGE ALERT
- * disable AD1848 playback
- * disable SB16 capture
- */
- pcm = wss_pcm;
- pstr = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
- snd_magic_kfree(pstr->substream);
- pstr->substream = 0;
- pstr->substream_count = 0;
-
- pcm = sb_pcm;
- pstr = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
- snd_magic_kfree(pstr->substream);
- pstr->substream = 0;
- pstr->substream_count = 0;
+ snd_ad1848_out(acard->wss, i, acard->wss->image[i]);
+ spin_unlock_irqrestore(&acard->wss->reg_lock, flags);
+
+ if ((err = snd_cmi8330_pcm(card, acard)) < 0) {
+ snd_printk("failed to create pcms\n");
+ snd_card_free(card);
+ return err;
+ }
strcpy(card->driver, "CMI8330/C3D");
strcpy(card->shortname, "C-Media CMI8330/C3D");
sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
- wss_pcm->name,
- chip_wss->port,
+ card->shortname,
+ acard->wss->port,
wssirq[dev],
wssdma[dev]);
diff --git a/sound/isa/cs423x/cs4231_lib.c b/sound/isa/cs423x/cs4231_lib.c
index 80ceb4581ba3..7aeaf531df97 100644
--- a/sound/isa/cs423x/cs4231_lib.c
+++ b/sound/isa/cs423x/cs4231_lib.c
@@ -158,7 +158,7 @@ static __CS4231_INLINE__ u8 cs4231_inb(cs4231_t *chip, u8 offset)
} else {
#endif
#ifdef SBUS_SUPPORT
- return sbus_writeb(chip->port + (offset << 2));
+ return sbus_readb(chip->port + (offset << 2));
#endif
#ifdef EBUS_SUPPORT
}
@@ -338,13 +338,13 @@ void snd_cs4231_mce_up(cs4231_t *chip)
unsigned long flags;
int timeout;
- spin_lock_irqsave(&chip->reg_lock, flags);
for (timeout = 250; timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); timeout--)
udelay(100);
#ifdef CONFIG_SND_DEBUG
if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
snd_printk("mce_up - auto calibration time out (0)\n");
#endif
+ spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit |= CS4231_MCE;
timeout = cs4231_inb(chip, CS4231P(REGSEL));
if (timeout == 0x80)
@@ -360,7 +360,6 @@ void snd_cs4231_mce_down(cs4231_t *chip)
int timeout;
signed long time;
- spin_lock_irqsave(&chip->reg_lock, flags);
snd_cs4231_busy_wait(chip);
#if 0
printk("(1) timeout = %i\n", timeout);
@@ -369,14 +368,15 @@ void snd_cs4231_mce_down(cs4231_t *chip)
if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", (long)CS4231P(REGSEL));
#endif
+ spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit &= ~CS4231_MCE;
timeout = cs4231_inb(chip, CS4231P(REGSEL));
cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
if (timeout == 0x80)
snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
if ((timeout & CS4231_MCE) == 0 ||
!(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
- spin_unlock_irqrestore(&chip->reg_lock, flags);
return;
}
snd_cs4231_busy_wait(chip);
@@ -387,7 +387,6 @@ void snd_cs4231_mce_down(cs4231_t *chip)
udelay(10);
if ((snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0) {
snd_printd("cs4231_mce_down - auto calibration time out (1)\n");
- spin_unlock_irqrestore(&chip->reg_lock, flags);
return;
}
#if 0
@@ -395,30 +394,25 @@ void snd_cs4231_mce_down(cs4231_t *chip)
#endif
time = HZ / 4;
while (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) {
- spin_unlock_irqrestore(&chip->reg_lock, flags);
if (time <= 0) {
snd_printk("mce_down - auto calibration time out (2)\n");
return;
}
set_current_state(TASK_INTERRUPTIBLE);
time = schedule_timeout(time);
- spin_lock_irqsave(&chip->reg_lock, flags);
}
#if 0
printk("(3) jiffies = %li\n", jiffies);
#endif
time = HZ / 10;
while (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
- spin_unlock_irqrestore(&chip->reg_lock, flags);
if (time <= 0) {
- snd_printk("mce_down - auto calibration time out (3)\n");
+ snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
return;
}
set_current_state(TASK_INTERRUPTIBLE);
time = schedule_timeout(time);
- spin_lock_irqsave(&chip->reg_lock, flags);
}
- spin_unlock_irqrestore(&chip->reg_lock, flags);
#if 0
printk("(4) jiffies = %li\n", jiffies);
snd_printk("mce_down - exit = 0x%x\n", cs4231_inb(chip, CS4231P(REGSEL)));
@@ -1375,20 +1369,19 @@ static void snd_cs4231_resume(cs4231_t *chip)
This is the first half of copy of snd_cs4231_mce_down(), but doesn't
include rescheduling. -- iwai
*/
- spin_lock_irqsave(&chip->reg_lock, flags);
snd_cs4231_busy_wait(chip);
+ spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit &= ~CS4231_MCE;
timeout = cs4231_inb(chip, CS4231P(REGSEL));
cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
if (timeout == 0x80)
snd_printk("down [0x%lx]: serious init problem - codec still busy\n", chip->port);
if ((timeout & CS4231_MCE) == 0 ||
!(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
- spin_unlock_irqrestore(&chip->reg_lock, flags);
return;
}
snd_cs4231_busy_wait(chip);
- spin_unlock_irqrestore(&chip->reg_lock, flags);
#endif
}
diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c
index f62c466009fe..31442b641067 100644
--- a/sound/isa/es18xx.c
+++ b/sound/isa/es18xx.c
@@ -460,6 +460,11 @@ static int snd_es18xx_playback_hw_params(snd_pcm_substream_t * substream,
return 0;
}
+static int snd_es18xx_pcm_hw_free(snd_pcm_substream_t * substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
static int snd_es18xx_playback1_prepare(es18xx_t *chip,
snd_pcm_substream_t *substream)
{
@@ -1540,6 +1545,7 @@ static snd_pcm_ops_t snd_es18xx_playback_ops = {
.close = snd_es18xx_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_es18xx_playback_hw_params,
+ .hw_free = snd_es18xx_pcm_hw_free,
.prepare = snd_es18xx_playback_prepare,
.trigger = snd_es18xx_playback_trigger,
.pointer = snd_es18xx_playback_pointer,
@@ -1550,6 +1556,7 @@ static snd_pcm_ops_t snd_es18xx_capture_ops = {
.close = snd_es18xx_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_es18xx_capture_hw_params,
+ .hw_free = snd_es18xx_pcm_hw_free,
.prepare = snd_es18xx_capture_prepare,
.trigger = snd_es18xx_capture_trigger,
.pointer = snd_es18xx_capture_pointer,
@@ -1606,9 +1613,8 @@ static void snd_es18xx_suspend(es18xx_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
@@ -1619,24 +1625,19 @@ static void snd_es18xx_suspend(es18xx_t *chip)
snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void snd_es18xx_resume(es18xx_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
/* restore PM register, we won't wake till (not 0x07) i/o activity though */
snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
/* callback for control API */
diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c
index 55fd6f5f6d6b..3ea4d76ca101 100644
--- a/sound/isa/gus/gus_irq.c
+++ b/sound/isa/gus/gus_irq.c
@@ -132,26 +132,8 @@ void snd_gus_irq_profile_init(snd_gus_card_t *gus)
{
snd_info_entry_t *entry;
- gus->irq_entry = NULL;
- entry = snd_info_create_card_entry(gus->card, "gusirq", gus->card->proc_root);
- if (entry) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->c.text.read_size = 512;
- entry->c.text.read = snd_gus_irq_info_read;
- entry->private_data = gus;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- gus->irq_entry = entry;
+ if (! snd_card_proc_new(gus->card, "gusirq", &entry))
+ snd_info_set_text_ops(entry, gus, snd_gus_irq_info_read);
}
-void snd_gus_irq_profile_done(snd_gus_card_t *gus)
-{
- if (gus->irq_entry) {
- snd_info_unregister(gus->irq_entry);
- gus->irq_entry = NULL;
- }
-}
#endif
diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c
index 9cedfa7e83da..b20cac1f6e6a 100644
--- a/sound/isa/gus/gus_mem.c
+++ b/sound/isa/gus/gus_mem.c
@@ -264,19 +264,10 @@ int snd_gf1_mem_init(snd_gus_card_t * gus)
if (snd_gf1_mem_xalloc(alloc, &block) == NULL)
return -ENOMEM;
#ifdef CONFIG_SND_DEBUG
- alloc->info_entry = NULL;
- entry = snd_info_create_card_entry(gus->card, "gusmem", gus->card->proc_root);
- if (entry) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
+ if (! snd_card_proc_new(gus->card, "gusmem", &entry)) {
+ snd_info_set_text_ops(entry, gus, snd_gf1_mem_info_read);
entry->c.text.read_size = 256 * 1024;
- entry->c.text.read = snd_gf1_mem_info_read;
- entry->private_data = gus;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- alloc->info_entry = entry;
#endif
return 0;
}
@@ -293,10 +284,6 @@ int snd_gf1_mem_done(snd_gus_card_t * gus)
snd_gf1_mem_xfree(alloc, block);
block = nblock;
}
-#ifdef CONFIG_SND_DEBUG
- if (alloc->info_entry)
- snd_info_unregister(alloc->info_entry);
-#endif
return 0;
}
diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c
index 58284186c88c..32f02ad501da 100644
--- a/sound/isa/gus/gus_mem_proc.c
+++ b/sound/isa/gus/gus_mem_proc.c
@@ -96,73 +96,40 @@ int snd_gf1_mem_proc_init(snd_gus_card_t * gus)
gus_proc_private_t *priv;
snd_info_entry_t *entry;
- memset(&gus->gf1.rom_entries, 0, sizeof(gus->gf1.rom_entries));
- memset(&gus->gf1.ram_entries, 0, sizeof(gus->gf1.ram_entries));
for (idx = 0; idx < 4; idx++) {
if (gus->gf1.mem_alloc.banks_8[idx].size > 0) {
priv = snd_magic_kcalloc(gus_proc_private_t, 0, GFP_KERNEL);
- if (priv == NULL) {
- snd_gf1_mem_proc_done(gus);
+ if (priv == NULL)
return -ENOMEM;
- }
priv->gus = gus;
sprintf(name, "gus-ram-%i", idx);
- entry = snd_info_create_card_entry(gus->card, name, gus->card->proc_root);
- if (entry) {
+ if (! snd_card_proc_new(gus->card, name, &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = priv;
entry->private_free = snd_gf1_mem_proc_free;
entry->c.ops = &snd_gf1_mem_proc_ops;
priv->address = gus->gf1.mem_alloc.banks_8[idx].address;
priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- gus->gf1.ram_entries[idx] = entry;
}
}
for (idx = 0; idx < 4; idx++) {
if (gus->gf1.rom_present & (1 << idx)) {
priv = snd_magic_kcalloc(gus_proc_private_t, 0, GFP_KERNEL);
- if (priv == NULL) {
- snd_gf1_mem_proc_done(gus);
+ if (priv == NULL)
return -ENOMEM;
- }
priv->rom = 1;
priv->gus = gus;
sprintf(name, "gus-rom-%i", idx);
- entry = snd_info_create_card_entry(gus->card, name, gus->card->proc_root);
- if (entry) {
+ if (! snd_card_proc_new(gus->card, name, &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = priv;
entry->private_free = snd_gf1_mem_proc_free;
entry->c.ops = &snd_gf1_mem_proc_ops;
priv->address = idx * 4096 * 1024;
priv->size = entry->size = gus->gf1.rom_memory;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- gus->gf1.rom_entries[idx] = entry;
}
}
return 0;
}
-
-int snd_gf1_mem_proc_done(snd_gus_card_t * gus)
-{
- int idx;
-
- for (idx = 0; idx < 4; idx++) {
- if (gus->gf1.ram_entries[idx])
- snd_info_unregister(gus->gf1.ram_entries[idx]);
- }
- for (idx = 0; idx < 4; idx++) {
- if (gus->gf1.rom_entries[idx])
- snd_info_unregister(gus->gf1.rom_entries[idx]);
- }
- return 0;
-}
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
index a6f086781f0b..10182383a7a1 100644
--- a/sound/isa/gus/gus_reset.c
+++ b/sound/isa/gus/gus_reset.c
@@ -410,10 +410,6 @@ int snd_gf1_stop(snd_gus_card_t * gus)
snd_gf1_stop_voices(gus, 0, 31); /* stop all voices */
snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */
snd_gf1_timers_done(gus);
-#ifdef CONFIG_SND_DEBUG
- snd_gus_irq_profile_done(gus);
-#endif
- snd_gf1_mem_proc_done(gus);
snd_gf1_mem_done(gus);
#if 0
snd_gf1_lfo_done(gus);
diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c
index 0b8edb576638..57c7116a1472 100644
--- a/sound/isa/sb/sb16_csp.c
+++ b/sound/isa/sb/sb16_csp.c
@@ -107,7 +107,6 @@ static void snd_sb_qsound_destroy(snd_sb_csp_t * p);
static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p);
static int init_proc_entry(snd_sb_csp_t * p, int device);
-static void delete_proc_entry(snd_sb_csp_t * p);
static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer);
/*
@@ -170,7 +169,6 @@ static void snd_sb_csp_free(snd_hwdep_t *hwdep)
if (p) {
if (p->running & SNDRV_SB_CSP_ST_RUNNING)
snd_sb_csp_stop(p);
- delete_proc_entry(p);
snd_magic_kfree(p);
}
}
@@ -1104,28 +1102,11 @@ static int init_proc_entry(snd_sb_csp_t * p, int device)
char name[16];
snd_info_entry_t *entry;
sprintf(name, "cspD%d", device);
- entry = p->proc = snd_info_create_card_entry(p->chip->card, name, p->chip->card->proc_root);
- if (entry) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->c.text.read_size = 256;
- entry->c.text.read = info_read;
- entry->private_data = p;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- p->proc = NULL;
- }
- }
+ if (! snd_card_proc_new(p->chip->card, name, &entry))
+ snd_info_set_text_ops(entry, p, info_read);
return 0;
}
-static void delete_proc_entry(snd_sb_csp_t * p)
-{
- if (p->proc) {
- snd_info_unregister(p->proc);
- p->proc = NULL;
- }
-}
-
static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
{
snd_sb_csp_t *p = snd_magic_cast(snd_sb_csp_t, entry->private_data, return);
diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c
index 30a1a67249ce..4b4ce71593e8 100644
--- a/sound/isa/sb/sb16_main.c
+++ b/sound/isa/sb/sb16_main.c
@@ -887,7 +887,14 @@ int snd_sb16dsp_pcm(sb_t * chip, int device, snd_pcm_t ** rpcm)
return 0;
}
+const snd_pcm_ops_t *snd_sb16dsp_get_pcm_ops(int direction)
+{
+ return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_sb16_playback_ops : &snd_sb16_capture_ops;
+}
+
EXPORT_SYMBOL(snd_sb16dsp_pcm);
+EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
EXPORT_SYMBOL(snd_sb16dsp_configure);
EXPORT_SYMBOL(snd_sb16dsp_interrupt);
diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c
index e63a3052def7..f555de1de90c 100644
--- a/sound/isa/sb/sb_mixer.c
+++ b/sound/isa/sb/sb_mixer.c
@@ -64,7 +64,8 @@ unsigned char snd_sbmixer_read(sb_t *chip, unsigned char reg)
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_sbmixer_info_single, \
- .get = snd_sbmixer_get_single, put: snd_sbmixer_put_single, \
+ .get = snd_sbmixer_get_single, \
+ .put = snd_sbmixer_put_single, \
.private_value = reg | (shift << 16) | (mask << 24) }
static int snd_sbmixer_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
@@ -123,7 +124,8 @@ static int snd_sbmixer_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_sbmixer_info_double, \
- .get = snd_sbmixer_get_double, put: snd_sbmixer_put_double, \
+ .get = snd_sbmixer_get_double, \
+ .put = snd_sbmixer_put_double, \
.private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) }
static int snd_sbmixer_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
@@ -366,7 +368,8 @@ static int snd_sb8mixer_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_sb16mixer_info_input_sw, \
- .get = snd_sb16mixer_get_input_sw, put: snd_sb16mixer_put_input_sw, \
+ .get = snd_sb16mixer_get_input_sw, \
+ .put = snd_sb16mixer_put_input_sw, \
.private_value = reg1 | (reg2 << 8) | (left_shift << 16) | (right_shift << 24) }
static int snd_sb16mixer_info_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo)
diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index a02b6e8ef8f5..1b6dbaf42cce 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -51,7 +51,6 @@ MODULE_PARM_SYNTAX(enable_loopback, SNDRV_BOOLEAN_FALSE_DESC);
*/
static void snd_ac97_proc_init(snd_card_t * card, ac97_t * ac97);
-static void snd_ac97_proc_done(ac97_t * ac97);
typedef struct {
unsigned int id;
@@ -240,6 +239,18 @@ static int snd_ac97_valid_reg(ac97_t *ac97, unsigned short reg)
return 1;
}
+/**
+ * snd_ac97_write - write a value on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Writes a value on the given register. This will invoke the write
+ * callback directly after the register check.
+ * This function doesn't change the register cache unlike
+ * #snd_ca97_write_cache(), so use this only when you don't want to
+ * reflect the change to the suspend/resume state.
+ */
void snd_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short value)
{
if (!snd_ac97_valid_reg(ac97, reg))
@@ -247,6 +258,17 @@ void snd_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short value)
ac97->write(ac97, reg, value);
}
+/**
+ * snd_ac97_read - read a value from the given register
+ *
+ * @ac97: the ac97 instance
+ * @reg: the register to read
+ *
+ * Reads a value from the given register. This will invoke the read
+ * callback directly after the register check.
+ *
+ * Returns the read value.
+ */
unsigned short snd_ac97_read(ac97_t *ac97, unsigned short reg)
{
if (!snd_ac97_valid_reg(ac97, reg))
@@ -254,6 +276,16 @@ unsigned short snd_ac97_read(ac97_t *ac97, unsigned short reg)
return ac97->read(ac97, reg);
}
+/**
+ * snd_ac97_write_cache - write a value on the given register and update the cache
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Writes a value on the given register and updates the register
+ * cache. The cached values are used for the cached-read and the
+ * suspend/resume.
+ */
void snd_ac97_write_cache(ac97_t *ac97, unsigned short reg, unsigned short value)
{
if (!snd_ac97_valid_reg(ac97, reg))
@@ -279,6 +311,18 @@ static void snd_ac97_write_cache_test(ac97_t *ac97, unsigned short reg, unsigned
snd_ac97_write_cache(ac97, reg, value);
}
+/**
+ * snd_ac97_update - update the value on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Compares the value with the register cache and updates the value
+ * only when the value is changed.
+ *
+ * Retruns 1 if the value is changed, 0 if no change, or a negative
+ * code on failure.
+ */
int snd_ac97_update(ac97_t *ac97, unsigned short reg, unsigned short value)
{
int change;
@@ -295,7 +339,7 @@ int snd_ac97_update(ac97_t *ac97, unsigned short reg, unsigned short value)
return change;
}
-int snd_ac97_update_bits_nolock(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value)
+static int snd_ac97_update_bits_nolock(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value)
{
int change;
unsigned short old, new;
@@ -312,6 +356,19 @@ int snd_ac97_update_bits_nolock(ac97_t *ac97, unsigned short reg, unsigned short
return change;
}
+/**
+ * snd_ac97_update_bits - update the bits on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @mask: the bit-mask to change
+ * @value: the value to set
+ *
+ * Updates the masked-bits on the given register onle when the value
+ * is changed.
+ *
+ * Returns 1 if the bits are changed, 0 if no change, or a negative
+ * code on failure.
+ */
int snd_ac97_update_bits(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value)
{
int change;
@@ -321,7 +378,7 @@ int snd_ac97_update_bits(ac97_t *ac97, unsigned short reg, unsigned short mask,
return change;
}
-int snd_ac97_ad18xx_update_pcm_bits(ac97_t *ac97, int codec, unsigned short mask, unsigned short value)
+static int snd_ac97_ad18xx_update_pcm_bits(ac97_t *ac97, int codec, unsigned short mask, unsigned short value)
{
int change;
unsigned short old, new;
@@ -935,6 +992,11 @@ static const snd_kcontrol_new_t snd_ac97_controls_alc650[] = {
AC97_SINGLE("Surround Down Mix", AC97_ALC650_MULTICH, 1, 1, 0),
AC97_SINGLE("Center/LFE Down Mix", AC97_ALC650_MULTICH, 2, 1, 0),
AC97_SINGLE("Exchange Center/LFE", AC97_ALC650_MULTICH, 3, 1, 0),
+ /* 4: Analog Input To Surround */
+ /* 5: Analog Input To Center/LFE */
+ /* 6: Indepedent Master Volume Right */
+ /* 7: Indepedent Master Volume Left */
+ /* 8: reserved */
AC97_SINGLE("Line-In As Surround", AC97_ALC650_MULTICH, 9, 1, 0),
AC97_SINGLE("Mic As Center/LFE", AC97_ALC650_MULTICH, 10, 1, 0),
AC97_SINGLE("IEC958 Capture Switch", AC97_ALC650_MULTICH, 11, 1, 0),
@@ -944,6 +1006,10 @@ static const snd_kcontrol_new_t snd_ac97_controls_alc650[] = {
AC97_SINGLE("IEC958 Input Clock Enable", AC97_ALC650_CLOCK, 0, 1, 0),
AC97_SINGLE("IEC958 Input Pin Enable", AC97_ALC650_CLOCK, 1, 1, 0),
#endif
+ AC97_SINGLE("Surround DAC Switch", AC97_ALC650_SURR_DAC_VOL, 15, 1, 1),
+ AC97_DOUBLE("Surround DAC Volume", AC97_ALC650_SURR_DAC_VOL, 8, 0, 31, 1),
+ AC97_SINGLE("Center/LFE DAC Switch", AC97_ALC650_LFE_DAC_VOL, 15, 1, 1),
+ AC97_DOUBLE("Center/LFE DAC Volume", AC97_ALC650_LFE_DAC_VOL, 8, 0, 31, 1),
};
/* The following snd_ac97_ymf753_... items added by David Shust (dshust@shustring.com) */
@@ -1098,7 +1164,6 @@ static const snd_kcontrol_new_t snd_ac97_ymf753_controls_spdif[3] = {
static int snd_ac97_free(ac97_t *ac97)
{
if (ac97) {
- snd_ac97_proc_done(ac97);
if (ac97->private_free)
ac97->private_free(ac97);
snd_magic_kfree(ac97);
@@ -1686,6 +1751,32 @@ static void snd_ac97_get_name(ac97_t *ac97, unsigned int id, char *name)
sprintf(name + strlen(name), " (%x)", id & 0xff);
}
+
+/**
+ * snd_ac97_mixer - create an AC97 codec component
+ * @card: the card instance
+ * @_ac97: the template of ac97, including index, callbacks and
+ * the private data.
+ * @rac97: the pointer to store the new ac97 instance.
+ *
+ * Creates an AC97 codec component. An ac97_t instance is newly
+ * allocated and initialized from the template (_ac97). The codec
+ * is then initialized by the standard procedure.
+ *
+ * The template must include the valid callbacks (at least read and
+ * write), the codec number (num) and address (addr), and the private
+ * data (private_data). The other callbacks, wait and reset, are not
+ * mandantory.
+ *
+ * The clock is set to 48000. If another clock is needed, reset
+ * ac97->clock manually afterwards.
+ *
+ * The ac97 instance is registered as a low-level device, so you don't
+ * have to release it manually.
+ *
+ * Returns zero if sucessful, or a negative error code on failure.
+ */
+
int snd_ac97_mixer(snd_card_t * card, ac97_t * _ac97, ac97_t ** rac97)
{
int err;
@@ -1866,7 +1957,7 @@ int snd_ac97_mixer(snd_card_t * card, ac97_t * _ac97, ac97_t ** rac97)
}
/*
-
+ * proc interface
*/
static void snd_ac97_proc_read_main(ac97_t *ac97, snd_info_buffer_t * buffer, int subidx)
@@ -2089,46 +2180,14 @@ static void snd_ac97_proc_init(snd_card_t * card, ac97_t * ac97)
sprintf(name, "ac97#%d-%d", ac97->addr, ac97->num);
else
sprintf(name, "ac97#%d", ac97->addr);
- if ((entry = snd_info_create_card_entry(card, name, card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = ac97;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 512;
- entry->c.text.read = snd_ac97_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- ac97->proc_entry = entry;
+ if (! snd_card_proc_new(card, name, &entry))
+ snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read);
if (ac97->num)
sprintf(name, "ac97#%d-%dregs", ac97->addr, ac97->num);
else
sprintf(name, "ac97#%dregs", ac97->addr);
- if ((entry = snd_info_create_card_entry(card, name, card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = ac97;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 1024;
- entry->c.text.read = snd_ac97_proc_regs_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- ac97->proc_regs_entry = entry;
-}
-
-static void snd_ac97_proc_done(ac97_t * ac97)
-{
- if (ac97->proc_regs_entry) {
- snd_info_unregister(ac97->proc_regs_entry);
- ac97->proc_regs_entry = NULL;
- }
- if (ac97->proc_entry) {
- snd_info_unregister(ac97->proc_entry);
- ac97->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(card, name, &entry))
+ snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read);
}
/*
@@ -2174,6 +2233,23 @@ static int set_spdif_rate(ac97_t *ac97, unsigned short rate)
return 0;
}
+/**
+ * snd_ac97_set_rate - change the rate of the given input/output.
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @rate: the sample rate to set
+ *
+ * Changes the rate of the given input/output on the codec.
+ * If the codec doesn't support VAR, the rate must be 48000 (except
+ * for SPDIF).
+ *
+ * The valid registers are AC97_PMC_MIC_ADC_RATE,
+ * AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE and AC97_SPDIF.
+ * The SPDIF register is a pseudo-register to change the rate of SPDIF
+ * (only if supported).
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
int snd_ac97_set_rate(ac97_t *ac97, int reg, unsigned short rate)
{
unsigned short mask;
@@ -2213,8 +2289,11 @@ int snd_ac97_set_rate(ac97_t *ac97, int reg, unsigned short rate)
#ifdef CONFIG_PM
-/*
- * general suspend procedure
+/**
+ * snd_ac97_suspend - General suspend function for AC97 codec
+ * @ac97: the ac97 instance
+ *
+ * Suspends the codec, power down the chip.
*/
void snd_ac97_suspend(ac97_t *ac97)
{
@@ -2231,8 +2310,12 @@ void snd_ac97_suspend(ac97_t *ac97)
snd_ac97_write(ac97, AC97_POWERDOWN, power);
}
-/*
- * general resume procedure
+/**
+ * snd_ac97_resume - General resume function for AC97 codec
+ * @ac97: the ac97 instance
+ *
+ * Do the standard resume procedure, power up and restoring the
+ * old register values.
*/
void snd_ac97_resume(ac97_t *ac97)
{
diff --git a/sound/pci/ac97/ak4531_codec.c b/sound/pci/ac97/ak4531_codec.c
index 22afe9f7d82b..18e1731d9bcf 100644
--- a/sound/pci/ac97/ak4531_codec.c
+++ b/sound/pci/ac97/ak4531_codec.c
@@ -33,7 +33,6 @@ MODULE_LICENSE("GPL");
#define chip_t ak4531_t
static void snd_ak4531_proc_init(snd_card_t * card, ak4531_t * ak4531);
-static void snd_ak4531_proc_done(ak4531_t * ak4531);
/*
*
@@ -314,7 +313,6 @@ AK4531_SINGLE("Mic Boost (+30dB)", 0, AK4531_MIC_GAIN, 0, 1, 0)
static int snd_ak4531_free(ak4531_t *ak4531)
{
if (ak4531) {
- snd_ak4531_proc_done(ak4531);
if (ak4531->private_free)
ak4531->private_free(ak4531);
snd_magic_kfree(ak4531);
@@ -425,26 +423,8 @@ static void snd_ak4531_proc_init(snd_card_t * card, ak4531_t * ak4531)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(card, "ak4531", card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = ak4531;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_ak4531_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- ak4531->proc_entry = entry;
-}
-
-static void snd_ak4531_proc_done(ak4531_t * ak4531)
-{
- if (ak4531->proc_entry) {
- snd_info_unregister(ak4531->proc_entry);
- ak4531->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(card, "ak4531", &entry))
+ snd_info_set_text_ops(entry, ak4531, snd_ak4531_proc_read);
}
EXPORT_SYMBOL(snd_ak4531_mixer);
diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c
index ce0a20f9f7e5..b3a02850d812 100644
--- a/sound/pci/cmipci.c
+++ b/sound/pci/cmipci.c
@@ -472,7 +472,6 @@ struct snd_stru_cmipci {
snd_rawmidi_t *rmidi;
spinlock_t reg_lock;
- snd_info_entry_t *proc_entry;
};
@@ -2746,26 +2745,8 @@ static void __devinit snd_cmipci_proc_init(cmipci_t *cm)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(cm->card, "cmipci", cm->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = cm;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_cmipci_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- cm->proc_entry = entry;
-}
-
-static void snd_cmipci_proc_done(cmipci_t *cm)
-{
- if (cm->proc_entry) {
- snd_info_unregister(cm->proc_entry);
- cm->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(cm->card, "cmipci", &entry))
+ snd_info_set_text_ops(entry, cm, snd_cmipci_proc_read);
}
@@ -2832,8 +2813,6 @@ static void __devinit query_chip(cmipci_t *cm)
static int snd_cmipci_free(cmipci_t *cm)
{
- snd_cmipci_proc_done(cm);
-
if (cm->irq >= 0) {
snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN);
snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT);
@@ -2949,20 +2928,10 @@ static int __devinit snd_cmipci_create(snd_card_t *card,
/* Assume TX and compatible chip set (Autodetection required for VX chip sets) */
switch (pci->device) {
- struct list_head *pos;
- int txvx;
case PCI_DEVICE_ID_CMEDIA_CM8738:
case PCI_DEVICE_ID_CMEDIA_CM8738B:
- txvx = 1;
- list_for_each(pos, &(pci->global_list)) {
- struct pci_dev * cur = list_entry(pos, struct pci_dev, global_list);
- if (cur->vendor != 0x8086) /* PCI_VENDOR_ID_INTEL */
- continue;
- if (cur->device != 0x7030) /* PCI_DEVICE_ID_INTEL_82437VX */
- continue;
- txvx = 0;
- }
- if (txvx)
+ /* PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437VX */
+ if (! pci_find_device(0x8086, 0x7030, NULL))
snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_TXVX);
break;
default:
diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c
index a9468e9e127c..11207a67b87e 100644
--- a/sound/pci/cs4281.c
+++ b/sound/pci/cs4281.c
@@ -499,13 +499,9 @@ struct snd_cs4281 {
unsigned int spurious_dhtc_irq;
unsigned int spurious_dtc_irq;
- void *proc_entry_BA0;
- void *proc_entry_BA1;
-
spinlock_t reg_lock;
unsigned int midcr;
unsigned int uartm;
- snd_info_entry_t *proc_entry;
struct snd_cs4281_gameport *gameport;
@@ -1251,55 +1247,19 @@ static void __devinit snd_cs4281_proc_init(cs4281_t * chip)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(chip->card, "cs4281", chip->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = chip;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_cs4281_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- chip->proc_entry = entry;
- if ((entry = snd_info_create_card_entry(chip->card, "cs4281_BA0", chip->card->proc_root)) != NULL) {
+ if (! snd_card_proc_new(chip->card, "cs4281", &entry))
+ snd_info_set_text_ops(entry, chip, snd_cs4281_proc_read);
+ if (! snd_card_proc_new(chip->card, "cs4281_BA0", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = chip;
entry->c.ops = &snd_cs4281_proc_ops_BA0;
entry->size = CS4281_BA0_SIZE;
- if (snd_info_register(entry) < 0) {
- snd_info_unregister(entry);
- entry = NULL;
- }
}
- chip->proc_entry_BA0 = entry;
- if ((entry = snd_info_create_card_entry(chip->card, "cs4281_BA1", chip->card->proc_root)) != NULL) {
+ if (! snd_card_proc_new(chip->card, "cs4281_BA1", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = chip;
entry->c.ops = &snd_cs4281_proc_ops_BA1;
entry->size = CS4281_BA1_SIZE;
- if (snd_info_register(entry) < 0) {
- snd_info_unregister(entry);
- entry = NULL;
- }
- }
- chip->proc_entry_BA1 = entry;
-}
-
-static void snd_cs4281_proc_done(cs4281_t * chip)
-{
- if (chip->proc_entry_BA1) {
- snd_info_unregister(chip->proc_entry_BA1);
- chip->proc_entry_BA1 = NULL;
- }
- if (chip->proc_entry_BA0) {
- snd_info_unregister(chip->proc_entry_BA0);
- chip->proc_entry_BA0 = NULL;
- }
- if (chip->proc_entry) {
- snd_info_unregister(chip->proc_entry);
- chip->proc_entry = NULL;
}
}
@@ -1414,7 +1374,6 @@ static int snd_cs4281_free(cs4281_t *chip)
kfree(chip->gameport);
}
#endif
- snd_cs4281_proc_done(chip);
if (chip->irq >= 0)
synchronize_irq(chip->irq);
@@ -2097,9 +2056,8 @@ static void cs4281_suspend(cs4281_t *chip)
u32 ulCLK;
int i;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
@@ -2132,8 +2090,6 @@ static void cs4281_suspend(cs4281_t *chip)
snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void cs4281_resume(cs4281_t *chip)
@@ -2142,9 +2098,8 @@ static void cs4281_resume(cs4281_t *chip)
int i;
u32 ulCLK;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
pci_enable_device(chip->pci);
@@ -2169,8 +2124,6 @@ static void cs4281_resume(cs4281_t *chip)
snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
#ifndef PCI_OLD_SUSPEND
diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c
index d4a97f58c4dd..f8546a2979f1 100644
--- a/sound/pci/cs46xx/cs46xx_lib.c
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -26,7 +26,7 @@
* at Cirrus for have helping me out with the DSP, however we
* still dont have sufficient documentation and technical
* references to be able to implement all fancy feutures
- * supported by the cs46xx DPS's.
+ * supported by the cs46xx DSP's.
* Benny <benny@hostmobility.com>
*
* This program is free software; you can redistribute it and/or modify
@@ -57,6 +57,8 @@
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include <sound/cs46xx.h>
#include <asm/io.h>
@@ -730,10 +732,12 @@ static int snd_cs46xx_playback_transfer(snd_pcm_substream_t *substream,
snd_pcm_runtime_t *runtime = substream->runtime;
snd_pcm_sframes_t diff;
cs46xx_pcm_t * cpcm;
- int buffer_size = runtime->period_size * CS46XX_FRAGS * 4;
+ int buffer_size;
cpcm = snd_magic_cast(cs46xx_pcm_t, substream->runtime->private_data, return -ENXIO);
+ buffer_size = runtime->period_size * CS46XX_FRAGS << cpcm->shift;
+
diff = runtime->control->appl_ptr - cpcm->appl_ptr;
if (diff) {
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
@@ -774,7 +778,7 @@ static int snd_cs46xx_capture_transfer(snd_pcm_substream_t *substream,
cs46xx_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_pcm_sframes_t diff = runtime->control->appl_ptr - chip->capt.appl_ptr;
- int buffer_size = runtime->period_size * CS46XX_FRAGS * 4;
+ int buffer_size = runtime->period_size * CS46XX_FRAGS << chip->capt.shift;
if (diff) {
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
diff += runtime->boundary;
@@ -830,7 +834,7 @@ static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(snd_pcm_substream_
size_t ptr;
cs46xx_pcm_t *cpcm = snd_magic_cast(cs46xx_pcm_t, substream->runtime->private_data, return -ENXIO);
ssize_t bytes;
- int buffer_size = substream->runtime->period_size * CS46XX_FRAGS * 4;
+ int buffer_size = substream->runtime->period_size * CS46XX_FRAGS << cpcm->shift;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
snd_assert (cpcm->pcm_channel,return -ENXIO);
@@ -865,7 +869,7 @@ static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(snd_pcm_substream_t
cs46xx_t *chip = snd_pcm_substream_chip(substream);
size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_addr;
ssize_t bytes = ptr - chip->capt.hw_io;
- int buffer_size = substream->runtime->period_size * CS46XX_FRAGS * 4;
+ int buffer_size = substream->runtime->period_size * CS46XX_FRAGS << chip->capt.shift;
if (bytes < 0)
bytes += buffer_size;
@@ -1071,7 +1075,7 @@ static int snd_cs46xx_playback_hw_params(snd_pcm_substream_t * substream,
int err;
cs46xx_t *chip = snd_pcm_substream_chip(substream);
int sample_rate = params_rate(hw_params);
- int period_size = params_period_size(hw_params);
+ int period_size = params_period_bytes(hw_params);
cpcm = snd_magic_cast(cs46xx_pcm_t, runtime->private_data, return -ENXIO);
#ifdef CONFIG_SND_CS46XX_NEW_DSP
@@ -1090,12 +1094,15 @@ static int snd_cs46xx_playback_hw_params(snd_pcm_substream_t * substream,
return -ENXIO;
}
- if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size * 4)) {
+
+ if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size)) {
up (&chip->spos_mutex);
return -EINVAL;
}
- snd_printdd ("period_size (%d), periods (%d)\n",
- period_size, params_periods(hw_params));
+
+ snd_printdd ("period_size (%d), periods (%d) buffer_size(%d)\n",
+ period_size, params_periods(hw_params),
+ params_buffer_bytes(hw_params));
#endif
if (params_periods(hw_params) == CS46XX_FRAGS) {
@@ -1255,12 +1262,10 @@ static int snd_cs46xx_capture_hw_params(snd_pcm_substream_t * substream,
cs46xx_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int err;
- int period_size = params_period_size(hw_params);
+ int period_size = params_period_bytes(hw_params);
#ifdef CONFIG_SND_CS46XX_NEW_DSP
- snd_printdd ("capture period size (%d)\n",period_size);
-
- cs46xx_dsp_pcm_ostream_set_period (chip,period_size * 4);
+ cs46xx_dsp_pcm_ostream_set_period (chip,period_size);
#endif
if (runtime->periods == CS46XX_FRAGS) {
if (runtime->dma_area != chip->capt.hw_area)
@@ -1445,7 +1450,7 @@ static snd_pcm_hardware_t snd_cs46xx_capture =
.fifo_size = 0,
};
-static unsigned int period_sizes[] = { 8, 16, 32, 64, 128, 256, 512 };
+static unsigned int period_sizes[] = { 32, 64, 128, 256, 512, 1024, 2048 };
#define PERIOD_SIZES sizeof(period_sizes) / sizeof(period_sizes[0])
@@ -1488,8 +1493,11 @@ static int _cs46xx_playback_open_channel (snd_pcm_substream_t * substream,int pc
cpcm->pcm_channel = NULL;
cpcm->pcm_channel_id = pcm_channel_id;
- snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
&hw_constraints_period_sizes);
+
up (&chip->spos_mutex);
#else
chip->playback_pcm = cpcm; /* HACK */
@@ -1565,7 +1573,8 @@ static int snd_cs46xx_capture_open(snd_pcm_substream_t * substream)
chip->amplifier_ctrl(chip, 1);
#ifdef CONFIG_SND_CS46XX_NEW_DSP
- snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
&hw_constraints_period_sizes);
#endif
return 0;
@@ -2874,19 +2883,13 @@ static int __devinit snd_cs46xx_proc_init(snd_card_t * card, cs46xx_t *chip)
for (idx = 0; idx < 5; idx++) {
snd_cs46xx_region_t *region = &chip->region.idx[idx];
- entry = snd_info_create_card_entry(card, region->name, card->proc_root);
- if (entry) {
+ if (! snd_card_proc_new(card, region->name, &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = chip;
entry->c.ops = &snd_cs46xx_proc_io_ops;
entry->size = region->size;
entry->mode = S_IFREG | S_IRUSR;
- if (snd_info_register(entry) < 0) {
- snd_info_unregister(entry);
- entry = NULL;
- }
}
- region->proc_entry = entry;
}
#ifdef CONFIG_SND_CS46XX_NEW_DSP
cs46xx_dsp_proc_init(card, chip);
@@ -2896,15 +2899,6 @@ static int __devinit snd_cs46xx_proc_init(snd_card_t * card, cs46xx_t *chip)
static int snd_cs46xx_proc_done(cs46xx_t *chip)
{
- int idx;
-
- for (idx = 0; idx < 5; idx++) {
- snd_cs46xx_region_t *region = &chip->region.idx[idx];
- if (region->proc_entry) {
- snd_info_unregister((snd_info_entry_t *) region->proc_entry);
- region->proc_entry = NULL;
- }
- }
#ifdef CONFIG_SND_CS46XX_NEW_DSP
cs46xx_dsp_proc_done(chip);
#endif
@@ -3716,16 +3710,13 @@ void snd_cs46xx_suspend(cs46xx_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
// chip->ac97_powerdown = snd_cs46xx_codec_read(chip, AC97_POWER_CONTROL);
// chip->ac97_general_purpose = snd_cs46xx_codec_read(chip, BA0_AC97_GENERAL_PURPOSE);
snd_cs46xx_hw_stop(chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
void snd_cs46xx_resume(cs46xx_t *chip)
@@ -3733,9 +3724,8 @@ void snd_cs46xx_resume(cs46xx_t *chip)
snd_card_t *card = chip->card;
int amp_saved;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
pci_enable_device(chip->pci);
amp_saved = chip->amplifier;
@@ -3764,8 +3754,6 @@ void snd_cs46xx_resume(cs46xx_t *chip)
chip->active_ctrl(chip, -1);
}
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
static int snd_cs46xx_set_power_state(snd_card_t *card, unsigned int power_state)
diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c
index fe5c5d0b3698..0e9a18fb235c 100644
--- a/sound/pci/emu10k1/emu10k1.c
+++ b/sound/pci/emu10k1/emu10k1.c
@@ -80,6 +80,7 @@ MODULE_PARM_SYNTAX(enable_ir, SNDRV_ENABLE_DESC);
static struct pci_device_id snd_emu10k1_ids[] __devinitdata = {
{ 0x1102, 0x0002, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* EMU10K1 */
+ { 0x1102, 0x0006, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* Dell OEM version (EMU10K1) */
{ 0x1102, 0x0004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 }, /* Audigy */
{ 0, }
};
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
index 48b558a76194..2575010a28ed 100644
--- a/sound/pci/emu10k1/emu10k1_main.c
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -494,7 +494,6 @@ static int __devinit snd_emu10k1_ecard_init(emu10k1_t * emu)
static int snd_emu10k1_free(emu10k1_t *emu)
{
- snd_emu10k1_proc_done(emu);
if (emu->res_port != NULL) { /* avoid access to already used hardware */
snd_emu10k1_fx8010_tram_setup(emu, 0);
snd_emu10k1_done(emu);
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index 1885bebc908c..1c3d91f84169 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -365,7 +365,7 @@ static int snd_emu10k1_playback_hw_params(snd_pcm_substream_t * substream,
snd_util_memblk_t *memblk;
if (epcm->memblk != NULL)
snd_emu10k1_free_pages(emu, epcm->memblk);
- memblk = snd_emu10k1_alloc_pages(emu, (struct snd_sg_buf *)substream->dma_private);
+ memblk = snd_emu10k1_alloc_pages(emu, substream);
if ((epcm->memblk = memblk) == NULL || ((emu10k1_memblk_t *)memblk)->mapped_page < 0) {
epcm->start_addr = 0;
return -ENOMEM;
diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c
index 1fcade671328..fa0e42af4f6d 100644
--- a/sound/pci/emu10k1/emuproc.c
+++ b/sound/pci/emu10k1/emuproc.c
@@ -239,111 +239,43 @@ int __devinit snd_emu10k1_proc_init(emu10k1_t * emu)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(emu->card, "emu10k1", emu->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 4096;
- entry->c.text.read = snd_emu10k1_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- emu->proc_entry = entry;
- entry = NULL;
- if ((entry = snd_info_create_card_entry(emu->card, "fx8010_gpr", emu->card->proc_root)) != NULL) {
+ if (! snd_card_proc_new(emu->card, "emu10k1", &entry))
+ snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_read);
+
+ if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = TOTAL_SIZE_GPR;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- emu->proc_entry_fx8010_gpr = entry;
- entry = NULL;
- if (!emu->audigy && (entry = snd_info_create_card_entry(emu->card, "fx8010_tram_data", emu->card->proc_root)) != NULL) {
+ if (!emu->audigy && ! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = TOTAL_SIZE_TANKMEM_DATA;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- emu->proc_entry_fx8010_tram_data = entry;
- entry = NULL;
- if (!emu->audigy && (entry = snd_info_create_card_entry(emu->card, "fx8010_tram_addr", emu->card->proc_root)) != NULL) {
+ if (!emu->audigy && ! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = TOTAL_SIZE_TANKMEM_ADDR;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- emu->proc_entry_fx8010_tram_addr = entry;
- entry = NULL;
- if ((entry = snd_info_create_card_entry(emu->card, "fx8010_code", emu->card->proc_root)) != NULL) {
+ if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = TOTAL_SIZE_CODE;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
}
- emu->proc_entry_fx8010_code = entry;
- entry = NULL;
- if ((entry = snd_info_create_card_entry(emu->card, "fx8010_acode", emu->card->proc_root)) != NULL) {
+ if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) {
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->private_data = emu;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+ entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->c.text.read_size = 64*1024;
entry->c.text.read = snd_emu10k1_proc_acode_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- emu->proc_entry_fx8010_iblocks = entry;
- return 0;
-}
-
-int snd_emu10k1_proc_done(emu10k1_t * emu)
-{
- if (emu->proc_entry) {
- snd_info_unregister(emu->proc_entry);
- emu->proc_entry = NULL;
- }
- if (emu->proc_entry_fx8010_gpr) {
- snd_info_unregister(emu->proc_entry_fx8010_gpr);
- emu->proc_entry_fx8010_gpr = NULL;
- }
- if (emu->proc_entry_fx8010_tram_data) {
- snd_info_unregister(emu->proc_entry_fx8010_tram_data);
- emu->proc_entry_fx8010_tram_data = NULL;
- }
- if (emu->proc_entry_fx8010_tram_addr) {
- snd_info_unregister(emu->proc_entry_fx8010_tram_addr);
- emu->proc_entry_fx8010_tram_addr = NULL;
- }
- if (emu->proc_entry_fx8010_code) {
- snd_info_unregister(emu->proc_entry_fx8010_code);
- emu->proc_entry_fx8010_code = NULL;
- }
- if (emu->proc_entry_fx8010_iblocks) {
- snd_info_unregister(emu->proc_entry_fx8010_iblocks);
- emu->proc_entry_fx8010_iblocks = NULL;
}
return 0;
}
diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c
index dc1f597eb70c..5037fb5fdea4 100644
--- a/sound/pci/emu10k1/memory.c
+++ b/sound/pci/emu10k1/memory.c
@@ -25,6 +25,7 @@
#include <linux/time.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
+#include <sound/pcm_sgbuf.h>
/* page arguments of these two macros are Emu page (4096 bytes), not like
* aligned pages in others
@@ -288,8 +289,9 @@ int snd_emu10k1_memblk_map(emu10k1_t *emu, emu10k1_memblk_t *blk)
* page allocation for DMA
*/
snd_util_memblk_t *
-snd_emu10k1_alloc_pages(emu10k1_t *emu, struct snd_sg_buf *sgbuf)
+snd_emu10k1_alloc_pages(emu10k1_t *emu, snd_pcm_substream_t *substream)
{
+ struct snd_sg_buf *sgbuf = snd_magic_cast(snd_pcm_sgbuf_t, _snd_pcm_substream_sgbuf(substream), return NULL);
snd_util_memhdr_t *hdr;
emu10k1_memblk_t *blk;
int page, err, idx;
@@ -306,7 +308,7 @@ snd_emu10k1_alloc_pages(emu10k1_t *emu, struct snd_sg_buf *sgbuf)
return NULL;
}
/* fill buffer addresses but pointers are not stored so that
- * snd_free_pci_pages() is not called in in synth_free()
+ * snd_free_pci_page() is not called in in synth_free()
*/
idx = 0;
for (page = blk->first_page; page <= blk->last_page; page++, idx++) {
@@ -430,11 +432,11 @@ static int synth_alloc_pages(emu10k1_t *emu, emu10k1_memblk_t *blk)
get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
/* allocate kernel pages */
for (page = first_page; page <= last_page; page++) {
- ptr = snd_malloc_pci_pages(emu->pci, PAGE_SIZE, &addr);
+ ptr = snd_malloc_pci_page(emu->pci, &addr);
if (ptr == NULL)
goto __fail;
if (! is_valid_page(addr)) {
- snd_free_pci_pages(emu->pci, PAGE_SIZE, ptr, addr);
+ snd_free_pci_page(emu->pci, ptr, addr);
goto __fail;
}
emu->page_addr_table[page] = addr;
@@ -446,7 +448,7 @@ __fail:
/* release allocated pages */
last_page = page - 1;
for (page = first_page; page <= last_page; page++) {
- snd_free_pci_pages(emu->pci, PAGE_SIZE, emu->page_ptr_table[page], emu->page_addr_table[page]);
+ snd_free_pci_page(emu->pci, emu->page_ptr_table[page], emu->page_addr_table[page]);
emu->page_addr_table[page] = 0;
emu->page_ptr_table[page] = NULL;
}
@@ -464,7 +466,7 @@ static int synth_free_pages(emu10k1_t *emu, emu10k1_memblk_t *blk)
get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
for (page = first_page; page <= last_page; page++) {
if (emu->page_ptr_table[page])
- snd_free_pci_pages(emu->pci, PAGE_SIZE, emu->page_ptr_table[page], emu->page_addr_table[page]);
+ snd_free_pci_page(emu->pci, emu->page_ptr_table[page], emu->page_addr_table[page]);
emu->page_addr_table[page] = 0;
emu->page_ptr_table[page] = NULL;
}
diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c
index 1b3c1f7b3036..d0e112a04a15 100644
--- a/sound/pci/ens1370.c
+++ b/sound/pci/ens1370.c
@@ -398,8 +398,6 @@ struct _snd_ensoniq {
unsigned int spdif_default;
unsigned int spdif_stream;
- snd_info_entry_t *proc_entry;
-
#ifdef CHIP1370
unsigned char *bugbuf;
dma_addr_t bugbuf_addr;
@@ -1264,9 +1262,10 @@ static int __devinit snd_ensoniq_pcm2(ensoniq_t * ensoniq, int device, snd_pcm_t
* Mixer section
*/
+/*
+ * ENS1371 mixer (including SPDIF interface)
+ */
#ifdef CHIP1371
-
-
static int snd_ens1373_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
@@ -1490,6 +1489,8 @@ static int snd_ensoniq_1371_mixer(ensoniq_t * ensoniq)
#endif /* CHIP1371 */
+/* generic control callbacks for ens1370 and for joystick */
+#if defined(CHIP1370) || defined(CONFIG_GAMEPORT) || defined(CONFIG_GAMEPORT_MODULE)
#define ENSONIQ_CONTROL(xname, mask) \
{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, .info = snd_ensoniq_control_info, \
.get = snd_ensoniq_control_get, .put = snd_ensoniq_control_put, \
@@ -1517,7 +1518,6 @@ static int snd_ensoniq_control_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value
}
#ifdef CHIP1370
-
static int snd_ensoniq_control_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
ensoniq_t *ensoniq = snd_kcontrol_chip(kcontrol);
@@ -1535,14 +1535,21 @@ static int snd_ensoniq_control_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value
spin_unlock_irqrestore(&ensoniq->reg_lock, flags);
return change;
}
+#endif /* CHIP1370 */
+#endif /* CHIP1370 || GAMEPORT */
-#define ES1370_CONTROLS 2
+/*
+ * ENS1370 mixer
+ */
+#ifdef CHIP1370
static snd_kcontrol_new_t snd_es1370_controls[2] __devinitdata = {
ENSONIQ_CONTROL("PCM 0 Output also on Line-In Jack", ES_1370_XCTL0),
ENSONIQ_CONTROL("Mic +5V bias", ES_1370_XCTL1)
};
+#define ES1370_CONTROLS ARRAY_SIZE(snd_es1370_controls)
+
static void snd_ensoniq_mixer_free_ak4531(ak4531_t *ak4531)
{
ensoniq_t *ensoniq = snd_magic_cast(ensoniq_t, ak4531->private_data, return);
@@ -1725,26 +1732,8 @@ static void __devinit snd_ensoniq_proc_init(ensoniq_t * ensoniq)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(ensoniq->card, "audiopci", ensoniq->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = ensoniq;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_ensoniq_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- ensoniq->proc_entry = entry;
-}
-
-static void snd_ensoniq_proc_done(ensoniq_t * ensoniq)
-{
- if (ensoniq->proc_entry) {
- snd_info_unregister(ensoniq->proc_entry);
- ensoniq->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(ensoniq->card, "audiopci", &entry))
+ snd_info_set_text_ops(entry, ensoniq, snd_ensoniq_proc_read);
}
/*
@@ -1757,7 +1746,6 @@ static int snd_ensoniq_free(ensoniq_t *ensoniq)
if (ensoniq->ctrl & ES_JYSTK_EN)
snd_ensoniq_joy_disable(ensoniq);
#endif
- snd_ensoniq_proc_done(ensoniq);
if (ensoniq->irq < 0)
goto __hw_end;
#ifdef CHIP1370
diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c
index 0b91028e2768..4de0e08b524c 100644
--- a/sound/pci/es1938.c
+++ b/sound/pci/es1938.c
@@ -202,8 +202,6 @@ MODULE_PARM_SYNTAX(enable, SNDRV_ENABLE_DESC);
typedef struct _snd_es1938 es1938_t;
struct _snd_es1938 {
- unsigned long dma1size;
- unsigned long dma2size;
int irq;
unsigned long io_port;
@@ -840,6 +838,25 @@ static int snd_es1938_capture_copy(snd_pcm_substream_t *substream,
return 0;
}
+/*
+ * buffer management
+ */
+static int snd_es1938_pcm_hw_params(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t * hw_params)
+
+{
+ int err;
+
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+ return err;
+ return 0;
+}
+
+static int snd_es1938_pcm_hw_free(snd_pcm_substream_t *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
/* ----------------------------------------------------------------------
* Audio1 Capture (ADC)
* ----------------------------------------------------------------------*/
@@ -890,8 +907,6 @@ static int snd_es1938_capture_open(snd_pcm_substream_t * substream)
if (chip->playback2_substream)
return -EAGAIN;
- if ((runtime->dma_area = snd_malloc_pci_pages_fallback(chip->pci, chip->dma2size, &runtime->dma_addr, &runtime->dma_bytes)) == NULL)
- return -ENOMEM;
chip->capture_substream = substream;
runtime->hw = snd_es1938_capture;
snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
@@ -907,15 +922,11 @@ static int snd_es1938_playback_open(snd_pcm_substream_t * substream)
switch (substream->number) {
case 0:
- if ((runtime->dma_area = snd_malloc_pci_pages_fallback(chip->pci, chip->dma1size, &runtime->dma_addr, &runtime->dma_bytes)) == NULL)
- return -ENOMEM;
chip->playback1_substream = substream;
break;
case 1:
if (chip->capture_substream)
return -EAGAIN;
- if ((runtime->dma_area = snd_malloc_pci_pages_fallback(chip->pci, chip->dma1size, &runtime->dma_addr, &runtime->dma_bytes)) == NULL)
- return -ENOMEM;
chip->playback2_substream = substream;
break;
default:
@@ -963,6 +974,8 @@ static snd_pcm_ops_t snd_es1938_playback_ops = {
.open = snd_es1938_playback_open,
.close = snd_es1938_playback_close,
.ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_es1938_pcm_hw_params,
+ .hw_free = snd_es1938_pcm_hw_free,
.prepare = snd_es1938_playback_prepare,
.trigger = snd_es1938_playback_trigger,
.pointer = snd_es1938_playback_pointer,
@@ -972,6 +985,8 @@ static snd_pcm_ops_t snd_es1938_capture_ops = {
.open = snd_es1938_capture_open,
.close = snd_es1938_capture_close,
.ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_es1938_pcm_hw_params,
+ .hw_free = snd_es1938_pcm_hw_free,
.prepare = snd_es1938_capture_prepare,
.trigger = snd_es1938_capture_trigger,
.pointer = snd_es1938_capture_pointer,
@@ -1364,8 +1379,6 @@ static int snd_es1938_dev_free(snd_device_t *device)
static int __devinit snd_es1938_create(snd_card_t * card,
struct pci_dev * pci,
- unsigned long dma1size,
- unsigned long dma2size,
es1938_t ** rchip)
{
es1938_t *chip;
@@ -1393,8 +1406,6 @@ static int __devinit snd_es1938_create(snd_card_t * card,
spin_lock_init(&chip->mixer_lock);
chip->card = card;
chip->pci = pci;
- chip->dma1size = dma1size;
- chip->dma2size = dma2size;
chip->io_port = pci_resource_start(pci, 0);
if ((chip->res_io_port = request_region(chip->io_port, 8, "ESS Solo-1")) == NULL) {
snd_es1938_free(chip);
@@ -1608,10 +1619,7 @@ static int __devinit snd_es1938_probe(struct pci_dev *pci,
return -ENODEV;
}
}
- if ((err = snd_es1938_create(card, pci,
- 64 * 1024,
- 64 * 1024,
- &chip)) < 0) {
+ if ((err = snd_es1938_create(card, pci, &chip)) < 0) {
snd_card_free(card);
return err;
}
diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c
index 29c94cbb229c..d27a8d2a73e4 100644
--- a/sound/pci/es1968.c
+++ b/sound/pci/es1968.c
@@ -2420,15 +2420,12 @@ static void es1968_suspend(es1968_t *chip)
if (! chip->do_pm)
return;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
snd_es1968_bob_stop(chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void es1968_resume(es1968_t *chip)
@@ -2438,9 +2435,8 @@ static void es1968_resume(es1968_t *chip)
if (! chip->do_pm)
return;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
/* restore all our config */
pci_enable_device(chip->pci);
@@ -2459,8 +2455,6 @@ static void es1968_resume(es1968_t *chip)
if (atomic_read(&chip->bobclient))
snd_es1968_bob_start(chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
#ifndef PCI_OLD_SUSPEND
diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c
index 8133aa9796c3..5c5120904532 100644
--- a/sound/pci/ice1712/ice1712.c
+++ b/sound/pci/ice1712/ice1712.c
@@ -1681,12 +1681,11 @@ static void snd_ice1712_proc_read(snd_info_entry_t *entry,
snd_iprintf(buffer, " CCS%02x : 0x%02x\n", idx, inb(ice->port+idx));
for (idx = 0x0; idx < 0x30 ; idx++)
snd_iprintf(buffer, " MT%02x : 0x%02x\n", idx, inb(ice->profi_port+idx));
-
- }
- else {
+ } else {
snd_iprintf(buffer, " PSDOUT03 : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_PSDOUT03)));
snd_iprintf(buffer, " CAPTURE : 0x%08x\n", inl(ICEMT(ice, ROUTE_CAPTURE)));
snd_iprintf(buffer, " SPDOUT : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_SPDOUT)));
+ snd_iprintf(buffer, " RATE : 0x%02x\n", (unsigned)inb(ICEMT(ice, RATE)));
}
}
@@ -1694,26 +1693,8 @@ static void __devinit snd_ice1712_proc_init(ice1712_t * ice)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(ice->card, "ice1712", ice->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = ice;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 2048;
- entry->c.text.read = snd_ice1712_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- ice->proc_entry = entry;
-}
-
-static void snd_ice1712_proc_done(ice1712_t * ice)
-{
- if (ice->proc_entry) {
- snd_info_unregister(ice->proc_entry);
- ice->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(ice->card, "ice1712", &entry))
+ snd_info_set_text_ops(entry, ice, snd_ice1712_proc_read);
}
/*
@@ -2689,7 +2670,6 @@ static int snd_ice1712_free(ice1712_t *ice)
outb(0xff, ICEREG(ice, IRQMASK));
/* --- */
__hw_end:
- snd_ice1712_proc_done(ice);
if (ice->irq >= 0) {
synchronize_irq(ice->irq);
free_irq(ice->irq, (void *) ice);
diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c
index 392178381d5e..bbfa3c2435b2 100644
--- a/sound/pci/intel8x0.c
+++ b/sound/pci/intel8x0.c
@@ -322,7 +322,6 @@ typedef struct {
unsigned char piv_saved;
unsigned short picb_saved;
#endif
- snd_info_entry_t *proc_entry;
} ichdev_t;
typedef struct _snd_intel8x0 intel8x0_t;
@@ -368,7 +367,6 @@ struct _snd_intel8x0 {
spinlock_t reg_lock;
spinlock_t ac97_lock;
- snd_info_entry_t *proc_entry;
u32 bdbars_count;
u32 *bdbars;
@@ -1854,8 +1852,6 @@ static int snd_intel8x0_chip_init(intel8x0_t *chip)
return 0;
}
-static void snd_intel8x0_proc_done(intel8x0_t * chip);
-
static int snd_intel8x0_free(intel8x0_t *chip)
{
int i;
@@ -1871,7 +1867,6 @@ static int snd_intel8x0_free(intel8x0_t *chip)
/* --- */
synchronize_irq(chip->irq);
__hw_end:
- snd_intel8x0_proc_done(chip);
if (chip->bdbars)
snd_free_pci_pages(chip->pci, chip->bdbars_count * sizeof(u32) * ICH_MAX_FRAGS * 2, chip->bdbars, chip->bdbars_addr);
if (chip->remap_addr)
@@ -1900,17 +1895,15 @@ static void intel8x0_suspend(intel8x0_t *chip)
{
snd_card_t *card = chip->card;
- chip->in_suspend = 1;
- snd_power_lock(card);
- if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ if (chip->in_suspend ||
+ card->power_state == SNDRV_CTL_POWER_D3hot)
+ return;
+ chip->in_suspend = 1;
snd_pcm_suspend_all(chip->pcm);
if (chip->pcm_mic)
snd_pcm_suspend_all(chip->pcm_mic);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void intel8x0_resume(intel8x0_t *chip)
@@ -1918,9 +1911,9 @@ static void intel8x0_resume(intel8x0_t *chip)
snd_card_t *card = chip->card;
int i;
- snd_power_lock(card);
- if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ if (! chip->in_suspend ||
+ card->power_state == SNDRV_CTL_POWER_D0)
+ return;
pci_enable_device(chip->pci);
snd_intel8x0_chip_init(chip);
@@ -1930,8 +1923,6 @@ static void intel8x0_resume(intel8x0_t *chip)
chip->in_suspend = 0;
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
#ifndef PCI_OLD_SUSPEND
@@ -2100,26 +2091,8 @@ static void __devinit snd_intel8x0_proc_init(intel8x0_t * chip)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(chip->card, "intel8x0", chip->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = chip;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 2048;
- entry->c.text.read = snd_intel8x0_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- chip->proc_entry = entry;
-}
-
-static void snd_intel8x0_proc_done(intel8x0_t * chip)
-{
- if (chip->proc_entry) {
- snd_info_unregister(chip->proc_entry);
- chip->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(chip->card, "intel8x0", &entry))
+ snd_info_set_text_ops(entry, chip, snd_intel8x0_proc_read);
}
static int snd_intel8x0_dev_free(snd_device_t *device)
@@ -2493,6 +2466,8 @@ static struct pci_device_id snd_intel8x0_joystick_ids[] __devinitdata = {
// { 0x8086, 0x7195, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* 440MX */
// { 0x1039, 0x7012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SI7012 */
{ 0x10de, 0x01b2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* NFORCE */
+ { 0x10de, 0x006b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* NFORCE2 */
+ { 0x10de, 0x00db, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* NFORCE3 */
{ 0, }
};
diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c
index 75f26090ed67..1e6ef1efaf3b 100644
--- a/sound/pci/korg1212/korg1212.c
+++ b/sound/pci/korg1212/korg1212.c
@@ -368,7 +368,6 @@ struct _snd_korg1212 {
snd_pcm_substream_t *playback_substream;
snd_pcm_substream_t *capture_substream;
- snd_info_entry_t * proc_entry;
CardState cardState;
int running;
@@ -1898,26 +1897,8 @@ static void __devinit snd_korg1212_proc_init(korg1212_t *korg1212)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(korg1212->card, "korg1212", korg1212->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = korg1212;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_korg1212_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- korg1212->proc_entry = entry;
-}
-
-static void snd_korg1212_proc_done(korg1212_t * korg1212)
-{
- if (korg1212->proc_entry) {
- snd_info_unregister(korg1212->proc_entry);
- korg1212->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(korg1212->card, "korg1212", &entry))
+ snd_info_set_text_ops(entry, korg1212, snd_korg1212_proc_read);
}
static int __devinit snd_korg1212_create(korg1212_t *korg1212)
@@ -2174,8 +2155,6 @@ snd_korg1212_free(void *private_data)
return;
}
- snd_korg1212_proc_done(korg1212);
-
snd_korg1212_TurnOffIdleMonitor(korg1212);
snd_korg1212_DisableCardInterrupts(korg1212);
diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c
index 18749650cb71..003f7de25dff 100644
--- a/sound/pci/maestro3.c
+++ b/sound/pci/maestro3.c
@@ -2377,9 +2377,8 @@ static void m3_suspend(m3_t *chip)
snd_card_t *card = chip->card;
int i, index;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
@@ -2400,8 +2399,6 @@ static void m3_suspend(m3_t *chip)
snd_m3_outw(chip, 0xffff, 0x54);
snd_m3_outw(chip, 0xffff, 0x56);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void m3_resume(m3_t *chip)
@@ -2409,9 +2406,8 @@ static void m3_resume(m3_t *chip)
snd_card_t *card = chip->card;
int i, index;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
/* first lets just bring everything back. .*/
snd_m3_outw(chip, 0, 0x54);
@@ -2442,8 +2438,6 @@ static void m3_resume(m3_t *chip)
snd_m3_amp_enable(chip, 1);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
#ifndef PCI_OLD_SUSPEND
diff --git a/sound/pci/nm256/nm256.c b/sound/pci/nm256/nm256.c
index 7fedaab022c6..2336a659f89a 100644
--- a/sound/pci/nm256/nm256.c
+++ b/sound/pci/nm256/nm256.c
@@ -1270,24 +1270,20 @@ static void nm256_suspend(nm256_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
chip->coeffs_current = 0;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void nm256_resume(nm256_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
/* Perform a full reset on the hardware */
pci_enable_device(chip->pci);
@@ -1297,8 +1293,6 @@ static void nm256_resume(nm256_t *chip)
snd_ac97_resume(chip->ac97);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
#ifndef PCI_OLD_SUSPEND
diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c
index b1f061459a3d..bbe85ce76cf4 100644
--- a/sound/pci/rme32.c
+++ b/sound/pci/rme32.c
@@ -220,7 +220,6 @@ typedef struct snd_rme32 {
snd_pcm_t *spdif_pcm;
snd_pcm_t *adat_pcm;
struct pci_dev *pci;
- snd_info_entry_t *proc_entry;
snd_kcontrol_t *spdif_ctl;
} rme32_t;
@@ -561,18 +560,22 @@ static int snd_rme32_setclockmode(rme32_t * rme32, int mode)
{
switch (mode) {
case RME32_CLOCKMODE_SLAVE:
+ /* AutoSync */
rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) &
~RME32_WCR_FREQ_1;
break;
case RME32_CLOCKMODE_MASTER_32:
+ /* Internal 32.0kHz */
rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) &
~RME32_WCR_FREQ_1;
break;
case RME32_CLOCKMODE_MASTER_44:
+ /* Internal 44.1kHz */
rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) |
RME32_WCR_FREQ_1;
break;
case RME32_CLOCKMODE_MASTER_48:
+ /* Internal 48.0kHz */
rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) |
RME32_WCR_FREQ_1;
break;
@@ -1240,7 +1243,6 @@ static void snd_rme32_free(void *private_data)
if (rme32->irq >= 0) {
snd_rme32_playback_stop(rme32);
snd_rme32_capture_stop(rme32);
- snd_rme32_proc_done(rme32);
free_irq(rme32->irq, (void *) rme32);
rme32->irq = -1;
}
@@ -1464,9 +1466,9 @@ snd_rme32_proc_read(snd_info_entry_t * entry, snd_info_buffer_t * buffer)
snd_rme32_playback_getrate(rme32));
}
if (rme32->wcreg & RME32_RCR_KMODE) {
- snd_iprintf(buffer, " clock mode: slave\n");
+ snd_iprintf(buffer, " sample clock source: AutoSync\n");
} else {
- snd_iprintf(buffer, " clock mode: master\n");
+ snd_iprintf(buffer, " sample clock source: Internal\n");
}
if (rme32->wcreg & RME32_WCR_PRO) {
snd_iprintf(buffer, " format: AES/EBU (professional)\n");
@@ -1484,26 +1486,8 @@ static void __devinit snd_rme32_proc_init(rme32_t * rme32)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(rme32->card, "rme32", rme32->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = rme32;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_rme32_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- rme32->proc_entry = entry;
-}
-
-static void snd_rme32_proc_done(rme32_t * rme32)
-{
- if (rme32->proc_entry) {
- snd_info_unregister(rme32->proc_entry);
- rme32->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(rme32->card, "rme32", &entry))
+ snd_info_set_text_ops(entry, rme32, snd_rme32_proc_read);
}
/*
@@ -1646,7 +1630,10 @@ static int
snd_rme32_info_clockmode_control(snd_kcontrol_t * kcontrol,
snd_ctl_elem_info_t * uinfo)
{
- static char *texts[4] = { "Slave", "Master (32.0 kHz)", "Master (44.1 kHz)", "Master (48.0 kHz)" };
+ static char *texts[4] = { "AutoSync",
+ "Internal 32.0kHz",
+ "Internal 44.1kHz",
+ "Internal 48.0kHz" };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c
index 65279749c7bf..5986e8e9e015 100644
--- a/sound/pci/rme96.c
+++ b/sound/pci/rme96.c
@@ -257,7 +257,6 @@ typedef struct snd_rme96 {
snd_pcm_t *spdif_pcm;
snd_pcm_t *adat_pcm;
struct pci_dev *pci;
- snd_info_entry_t *proc_entry;
snd_kcontrol_t *spdif_ctl;
} rme96_t;
@@ -308,9 +307,6 @@ snd_rme96_capture_pointer(snd_pcm_substream_t *substream);
static void __devinit
snd_rme96_proc_init(rme96_t *rme96);
-static void
-snd_rme96_proc_done(rme96_t *rme96);
-
static int
snd_rme96_create_switches(snd_card_t *card,
rme96_t *rme96);
@@ -330,6 +326,20 @@ snd_rme96_capture_ptr(rme96_t *rme96)
}
static int
+snd_rme96_ratecode(int rate)
+{
+ switch (rate) {
+ case 32000: return SNDRV_PCM_RATE_32000;
+ case 44100: return SNDRV_PCM_RATE_44100;
+ case 48000: return SNDRV_PCM_RATE_48000;
+ case 64000: return SNDRV_PCM_RATE_64000;
+ case 88200: return SNDRV_PCM_RATE_88200;
+ case 96000: return SNDRV_PCM_RATE_96000;
+ }
+ return 0;
+}
+
+static int
snd_rme96_playback_silence(snd_pcm_substream_t *substream,
int channel, /* not used (interleaved data) */
snd_pcm_uframes_t pos,
@@ -671,8 +681,8 @@ snd_rme96_playback_getrate(rme96_t *rme96)
if (!(rme96->wcreg & RME96_WCR_MASTER) &&
(rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
{
- /* slave clock */
- return rate;
+ /* slave clock */
+ return rate;
}
rate = ((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_0) & 1) +
(((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_1) & 1) << 1);
@@ -974,14 +984,22 @@ static int
snd_rme96_playback_hw_params(snd_pcm_substream_t *substream,
snd_pcm_hw_params_t *params)
{
- unsigned long flags;
+ unsigned long flags;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
- int err;
+ int err, rate, dummy;
if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params))) < 0)
return err;
spin_lock_irqsave(&rme96->lock, flags);
- if ((err = snd_rme96_playback_setrate(rme96, params_rate(params))) < 0) {
+ if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+ (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+ {
+ /* slave clock */
+ if (params_rate(params) != rate) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EIO;
+ }
+ } else if ((err = snd_rme96_playback_setrate(rme96, params_rate(params))) < 0) {
spin_unlock_irqrestore(&rme96->lock, flags);
return err;
}
@@ -1024,7 +1042,8 @@ snd_rme96_capture_hw_params(snd_pcm_substream_t *substream,
{
unsigned long flags;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
- int err, isadat;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int err, isadat, rate;
if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params))) < 0)
return err;
@@ -1040,9 +1059,6 @@ snd_rme96_capture_hw_params(snd_pcm_substream_t *substream,
spin_unlock_irqrestore(&rme96->lock, flags);
return err;
}
- } else if (params_rate(params) != snd_rme96_capture_getrate(rme96, &isadat)) {
- spin_unlock_irqrestore(&rme96->lock, flags);
- return -EBUSY;
}
snd_rme96_setframelog(rme96, params_channels(params), 0);
if (rme96->playback_periodsize != 0) {
@@ -1052,7 +1068,18 @@ snd_rme96_capture_hw_params(snd_pcm_substream_t *substream,
spin_unlock_irqrestore(&rme96->lock, flags);
return -EBUSY;
}
- }
+ } else if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+ if (params_rate(params) != rate) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EIO;
+ }
+ if ((isadat && runtime->hw.channels_min == 2) ||
+ (!isadat && runtime->hw.channels_min == 8))
+ {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EIO;
+ }
+ }
rme96->capture_periodsize =
params_period_size(params) << rme96->capture_frlog;
snd_rme96_set_period_properties(rme96, rme96->capture_periodsize);
@@ -1138,7 +1165,7 @@ snd_rme96_interrupt(int irq,
if (rme96->rcreg & RME96_RCR_IRQ) {
/* playback */
- snd_pcm_period_elapsed(rme96->playback_substream);
+ snd_pcm_period_elapsed(rme96->playback_substream);
writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ);
}
if (rme96->rcreg & RME96_RCR_IRQ_2) {
@@ -1162,12 +1189,17 @@ static int
snd_rme96_playback_spdif_open(snd_pcm_substream_t *substream)
{
unsigned long flags;
+ int rate, dummy;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_pcm_set_sync(substream);
spin_lock_irqsave(&rme96->lock, flags);
+ if (rme96->playback_substream != NULL) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EBUSY;
+ }
rme96->wcreg &= ~RME96_WCR_ADAT;
writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
rme96->playback_substream = substream;
@@ -1175,7 +1207,15 @@ snd_rme96_playback_spdif_open(snd_pcm_substream_t *substream)
rme96->playback_ptr = 0;
spin_unlock_irqrestore(&rme96->lock, flags);
- runtime->hw = snd_rme96_playback_spdif_info;
+ runtime->hw = snd_rme96_playback_spdif_info;
+ if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+ (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+ {
+ /* slave clock */
+ runtime->hw.rates = snd_rme96_ratecode(rate);
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, RME96_BUFFER_SIZE, RME96_BUFFER_SIZE);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_period_bytes);
@@ -1190,28 +1230,31 @@ static int
snd_rme96_capture_spdif_open(snd_pcm_substream_t *substream)
{
unsigned long flags;
- int isadat;
+ int isadat, rate;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
- rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
- if (snd_rme96_capture_getrate(rme96, &isadat) < 0) {
- /* no input */
- return -EIO;
- }
- if (isadat) {
- /* ADAT input */
- return -EBUSY;
- }
snd_pcm_set_sync(substream);
- spin_lock_irqsave(&rme96->lock, flags);
+ runtime->hw = snd_rme96_capture_spdif_info;
+ if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+ if (isadat) {
+ return -EIO;
+ }
+ runtime->hw.rates = snd_rme96_ratecode(rate);
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
+
+ spin_lock_irqsave(&rme96->lock, flags);
+ if (rme96->capture_substream != NULL) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EBUSY;
+ }
rme96->capture_substream = substream;
rme96->capture_ptr = 0;
spin_unlock_irqrestore(&rme96->lock, flags);
- runtime->hw = snd_rme96_capture_spdif_info;
-
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, RME96_BUFFER_SIZE, RME96_BUFFER_SIZE);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_period_bytes);
@@ -1222,12 +1265,17 @@ static int
snd_rme96_playback_adat_open(snd_pcm_substream_t *substream)
{
unsigned long flags;
+ int rate, dummy;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
- snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_runtime_t *runtime = substream->runtime;
snd_pcm_set_sync(substream);
spin_lock_irqsave(&rme96->lock, flags);
+ if (rme96->playback_substream != NULL) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EBUSY;
+ }
rme96->wcreg |= RME96_WCR_ADAT;
writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
rme96->playback_substream = substream;
@@ -1236,6 +1284,14 @@ snd_rme96_playback_adat_open(snd_pcm_substream_t *substream)
spin_unlock_irqrestore(&rme96->lock, flags);
runtime->hw = snd_rme96_playback_adat_info;
+ if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+ (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+ {
+ /* slave clock */
+ runtime->hw.rates = snd_rme96_ratecode(rate);
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, RME96_BUFFER_SIZE, RME96_BUFFER_SIZE);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_period_bytes);
return 0;
@@ -1245,27 +1301,31 @@ static int
snd_rme96_capture_adat_open(snd_pcm_substream_t *substream)
{
unsigned long flags;
- int isadat;
+ int isadat, rate;
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
- rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
- if (snd_rme96_capture_getrate(rme96, &isadat) < 0) {
- /* no input */
- return -EIO;
- }
- if (!isadat) {
- /* S/PDIF input */
- return -EBUSY;
- }
snd_pcm_set_sync(substream);
+ runtime->hw = snd_rme96_capture_adat_info;
+ if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+ if (!isadat) {
+ return -EIO;
+ }
+ runtime->hw.rates = snd_rme96_ratecode(rate);
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
+
spin_lock_irqsave(&rme96->lock, flags);
+ if (rme96->capture_substream != NULL) {
+ spin_unlock_irqrestore(&rme96->lock, flags);
+ return -EBUSY;
+ }
rme96->capture_substream = substream;
rme96->capture_ptr = 0;
spin_unlock_irqrestore(&rme96->lock, flags);
- runtime->hw = snd_rme96_capture_adat_info;
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, RME96_BUFFER_SIZE, RME96_BUFFER_SIZE);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_period_bytes);
return 0;
@@ -1279,6 +1339,9 @@ snd_rme96_playback_close(snd_pcm_substream_t *substream)
int spdif = 0;
spin_lock_irqsave(&rme96->lock, flags);
+ if (RME96_ISPLAYING(rme96)) {
+ snd_rme96_playback_stop(rme96);
+ }
rme96->playback_substream = NULL;
rme96->playback_periodsize = 0;
spdif = (rme96->wcreg & RME96_WCR_ADAT) == 0;
@@ -1298,6 +1361,9 @@ snd_rme96_capture_close(snd_pcm_substream_t *substream)
rme96_t *rme96 = _snd_pcm_substream_chip(substream);
spin_lock_irqsave(&rme96->lock, flags);
+ if (RME96_ISRECORDING(rme96)) {
+ snd_rme96_capture_stop(rme96);
+ }
rme96->capture_substream = NULL;
rme96->capture_periodsize = 0;
spin_unlock_irqrestore(&rme96->lock, flags);
@@ -1558,7 +1624,6 @@ snd_rme96_free(void *private_data)
snd_rme96_capture_stop(rme96);
rme96->areg &= ~RME96_AR_DAC_EN;
writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
- snd_rme96_proc_done(rme96);
free_irq(rme96->irq, (void *)rme96);
rme96->irq = -1;
}
@@ -1843,27 +1908,8 @@ snd_rme96_proc_init(rme96_t *rme96)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(rme96->card, "rme96", rme96->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = rme96;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_rme96_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- rme96->proc_entry = entry;
-}
-
-static void
-snd_rme96_proc_done(rme96_t * rme96)
-{
- if (rme96->proc_entry) {
- snd_info_unregister(rme96->proc_entry);
- rme96->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(rme96->card, "rme96", &entry))
+ snd_info_set_text_ops(entry, rme96, snd_rme96_proc_read);
}
/*
diff --git a/sound/pci/rme9652/hammerfall_mem.c b/sound/pci/rme9652/hammerfall_mem.c
index 067ce2c18fb0..6981baaa43c5 100644
--- a/sound/pci/rme9652/hammerfall_mem.c
+++ b/sound/pci/rme9652/hammerfall_mem.c
@@ -25,7 +25,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- $Id: hammerfall_mem.c,v 1.6 2002/12/19 15:59:18 tiwai Exp $
+ $Id: hammerfall_mem.c,v 1.7 2003/01/06 14:21:28 perex Exp $
Tue Oct 17 2000 Jaroslav Kysela <perex@suse.cz>
@@ -179,7 +179,7 @@ void snd_hammerfall_free_buffer (struct pci_dev *pcidev, void *addr)
printk ("Hammerfall memory allocator: unknown buffer address or PCI device ID");
}
-static void __exit hammerfall_free_buffers (void)
+static void hammerfall_free_buffers (void)
{
int i;
diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c
index 320a057bdfe2..8ce7169d419a 100644
--- a/sound/pci/rme9652/hdsp.c
+++ b/sound/pci/rme9652/hdsp.c
@@ -368,7 +368,6 @@ struct _hdsp {
snd_card_t *card;
snd_pcm_t *pcm;
struct pci_dev *pci;
- snd_info_entry_t *proc_entry;
snd_kcontrol_t *spdif_ctl;
unsigned short mixer_matrix[HDSP_MATRIX_MIXER_SIZE];
};
@@ -2076,27 +2075,8 @@ static void __devinit snd_hdsp_proc_init(hdsp_t *hdsp)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(hdsp->card, "hdsp", hdsp->card->proc_root)) !=
- NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = hdsp;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_hdsp_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- hdsp->proc_entry = entry;
-}
-
-static void snd_hdsp_proc_done(hdsp_t *hdsp)
-{
- if (hdsp->proc_entry) {
- snd_info_unregister(hdsp->proc_entry);
- hdsp->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(hdsp->card, "hdsp", &entry))
+ snd_info_set_text_ops(entry, hdsp, snd_hdsp_proc_read);
}
static void snd_hdsp_free_buffers(hdsp_t *hdsp)
@@ -3078,7 +3058,6 @@ static int snd_hdsp_free(hdsp_t *hdsp)
if (hdsp->irq >= 0)
free_irq(hdsp->irq, (void *)hdsp);
- snd_hdsp_proc_done(hdsp);
snd_hdsp_free_buffers(hdsp);
if (hdsp->iobase)
diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c
index 3c86c0d418f9..646b925100a2 100644
--- a/sound/pci/rme9652/rme9652.c
+++ b/sound/pci/rme9652/rme9652.c
@@ -263,7 +263,6 @@ typedef struct snd_rme9652 {
snd_card_t *card;
snd_pcm_t *pcm;
struct pci_dev *pci;
- snd_info_entry_t *proc_entry;
snd_kcontrol_t *spdif_ctl;
} rme9652_t;
@@ -1803,27 +1802,8 @@ static void __devinit snd_rme9652_proc_init(rme9652_t *rme9652)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(rme9652->card, "rme9652", rme9652->card->proc_root)) !=
- NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = rme9652;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_rme9652_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- rme9652->proc_entry = entry;
-}
-
-static void snd_rme9652_proc_done(rme9652_t *rme9652)
-{
- if (rme9652->proc_entry) {
- snd_info_unregister(rme9652->proc_entry);
- rme9652->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(rme9652->card, "rme9652", &entry))
+ snd_info_set_text_ops(entry, rme9652, snd_rme9652_proc_read);
}
static void snd_rme9652_free_buffers(rme9652_t *rme9652)
@@ -1855,7 +1835,6 @@ static int snd_rme9652_free(rme9652_t *rme9652)
{
if (rme9652->irq >= 0)
rme9652_stop(rme9652);
- snd_rme9652_proc_done(rme9652);
snd_rme9652_free_buffers(rme9652);
if (rme9652->iobase)
diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c
index 5f1767c27cf0..ecc1be3df90f 100644
--- a/sound/pci/sonicvibes.c
+++ b/sound/pci/sonicvibes.c
@@ -247,7 +247,6 @@ struct _snd_sonicvibes {
snd_hwdep_t *fmsynth; /* S3FM */
spinlock_t reg_lock;
- snd_info_entry_t *proc_entry;
unsigned int p_dma_size;
unsigned int c_dma_size;
@@ -1175,26 +1174,8 @@ static void __devinit snd_sonicvibes_proc_init(sonicvibes_t * sonic)
{
snd_info_entry_t *entry;
- if ((entry = snd_info_create_card_entry(sonic->card, "sonicvibes", sonic->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = sonic;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_sonicvibes_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- sonic->proc_entry = entry;
-}
-
-static void snd_sonicvibes_proc_done(sonicvibes_t * sonic)
-{
- if (sonic->proc_entry) {
- snd_info_unregister(sonic->proc_entry);
- sonic->proc_entry = NULL;
- }
+ if (! snd_card_proc_new(sonic->card, "sonicvibes", &entry))
+ snd_info_set_text_ops(entry, sonic, snd_sonicvibes_proc_read);
}
/*
@@ -1210,7 +1191,6 @@ static int snd_sonicvibes_free(sonicvibes_t *sonic)
if (sonic->gameport.io)
gameport_unregister_port(&sonic->gameport);
#endif
- snd_sonicvibes_proc_done(sonic);
pci_write_config_dword(sonic->pci, 0x40, sonic->dmaa_port);
pci_write_config_dword(sonic->pci, 0x48, sonic->dmac_port);
if (sonic->res_sb_port) {
diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c
index 0e3496c3c7e0..43b2d84b959f 100644
--- a/sound/pci/trident/trident_main.c
+++ b/sound/pci/trident/trident_main.c
@@ -41,6 +41,7 @@
#include <sound/control.h>
#include <sound/trident.h>
#include <sound/asoundef.h>
+#include <sound/pcm_sgbuf.h>
#include <asm/io.h>
@@ -756,16 +757,19 @@ int snd_trident_allocate_pcm_mem(snd_pcm_substream_t * substream,
snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
int err;
- if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
- return err;
- if (err > 0 && trident->tlb.entries) {
- if (voice->memblk)
- snd_trident_free_pages(trident, voice->memblk);
- spin_lock_irq(&trident->reg_lock);
- voice->memblk = snd_trident_alloc_pages(trident, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
- spin_unlock_irq(&trident->reg_lock);
- if (voice->memblk == NULL)
- return -ENOMEM;
+ if (trident->tlb.entries) {
+ if ((err = snd_pcm_sgbuf_alloc(substream, params_buffer_bytes(hw_params))) < 0)
+ return err;
+ if (err > 0) { /* change */
+ if (voice->memblk)
+ snd_trident_free_pages(trident, voice->memblk);
+ voice->memblk = snd_trident_alloc_pages(trident, substream);
+ if (voice->memblk == NULL)
+ return -ENOMEM;
+ }
+ } else {
+ if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+ return err;
}
return 0;
}
@@ -792,7 +796,7 @@ int snd_trident_allocate_evoice(snd_pcm_substream_t * substream,
/* voice management */
- if (params_buffer_size(hw_params) / 2 != params_buffer_size(hw_params)) {
+ if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) {
if (evoice == NULL) {
evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (evoice == NULL)
@@ -851,11 +855,14 @@ static int snd_trident_hw_free(snd_pcm_substream_t * substream)
snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
snd_trident_voice_t *evoice = voice ? voice->extra : NULL;
- if (trident->tlb.entries && voice && voice->memblk) {
- snd_trident_free_pages(trident, voice->memblk);
- voice->memblk = NULL;
- }
- snd_pcm_lib_free_pages(substream);
+ if (trident->tlb.entries) {
+ if (voice && voice->memblk) {
+ snd_trident_free_pages(trident, voice->memblk);
+ voice->memblk = NULL;
+ }
+ snd_pcm_sgbuf_free(substream);
+ } else
+ snd_pcm_lib_free_pages(substream);
if (evoice != NULL) {
snd_trident_free_voice(trident, evoice);
voice->extra = NULL;
@@ -990,10 +997,12 @@ static int snd_trident_capture_prepare(snd_pcm_substream_t * substream)
outb(0x54, TRID_REG(trident, LEGACY_DMAR11));
// Set channel buffer Address
- voice->LBA = runtime->dma_addr;
- outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0));
+ /* FIXME: LEGACY_DMAR0 correctly set? */
if (voice->memblk)
voice->LBA = voice->memblk->offset;
+ else
+ voice->LBA = runtime->dma_addr;
+ outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0));
// set ESO
ESO_bytes = snd_pcm_lib_buffer_bytes(substream) - 1;
@@ -1343,6 +1352,7 @@ static int snd_trident_spdif_prepare(snd_pcm_substream_t * substream)
voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
/* set Loop Back Address */
+ /* FIXME: LBAO?? */
LBAO = runtime->dma_addr;
if (voice->memblk)
voice->LBA = voice->memblk->offset;
@@ -1755,17 +1765,27 @@ static snd_pcm_hardware_t snd_trident_spdif_7018 =
.fifo_size = 0,
};
+static int snd_trident_sgbuf_init(trident_t *trident, snd_pcm_substream_t *substream)
+{
+ if (trident->tlb.entries)
+ return snd_pcm_sgbuf_init(substream, trident->pci, 32);
+ return 0;
+}
+
+static void snd_trident_sgbuf_delete(trident_t *trident, snd_pcm_substream_t *substream)
+{
+ if (trident->tlb.entries)
+ snd_pcm_sgbuf_delete(substream);
+}
+
static void snd_trident_pcm_free_substream(snd_pcm_runtime_t *runtime)
{
- unsigned long flags;
snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
trident_t *trident;
if (voice) {
trident = voice->trident;
- spin_lock_irqsave(&trident->reg_lock, flags);
snd_trident_free_voice(trident, voice);
- spin_unlock_irqrestore(&trident->reg_lock, flags);
}
}
@@ -1774,14 +1794,15 @@ static int snd_trident_playback_open(snd_pcm_substream_t * substream)
trident_t *trident = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_trident_voice_t *voice;
+ int err;
- spin_lock_irq(&trident->reg_lock);
+ if ((err = snd_trident_sgbuf_init(trident, substream)) < 0)
+ return err;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL) {
- spin_unlock_irq(&trident->reg_lock);
+ snd_trident_sgbuf_delete(trident, substream);
return -EAGAIN;
}
- spin_unlock_irq(&trident->reg_lock);
snd_trident_pcm_mixer_build(trident, voice, substream);
voice->substream = substream;
runtime->private_data = voice;
@@ -1808,6 +1829,7 @@ static int snd_trident_playback_close(snd_pcm_substream_t * substream)
snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data;
snd_trident_pcm_mixer_free(trident, voice, substream);
+ snd_trident_sgbuf_delete(trident, substream);
return 0;
}
@@ -1827,11 +1849,13 @@ static int snd_trident_spdif_open(snd_pcm_substream_t * substream)
trident_t *trident = snd_pcm_substream_chip(substream);
snd_trident_voice_t *voice;
snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
- spin_lock_irq(&trident->reg_lock);
+ if ((err = snd_trident_sgbuf_init(trident, substream)) < 0)
+ return err;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL) {
- spin_unlock_irq(&trident->reg_lock);
+ snd_trident_sgbuf_delete(trident, substream);
return -EAGAIN;
}
voice->spdif = 1;
@@ -1889,6 +1913,7 @@ static int snd_trident_spdif_close(snd_pcm_substream_t * substream)
trident->spdif_pcm_ctl->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+ snd_trident_sgbuf_delete(trident, substream);
return 0;
}
@@ -1908,15 +1933,16 @@ static int snd_trident_capture_open(snd_pcm_substream_t * substream)
trident_t *trident = snd_pcm_substream_chip(substream);
snd_trident_voice_t *voice;
snd_pcm_runtime_t *runtime = substream->runtime;
+ int err;
- spin_lock_irq(&trident->reg_lock);
+ if ((err = snd_trident_sgbuf_init(trident, substream)) < 0)
+ return err;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL) {
- spin_unlock_irq(&trident->reg_lock);
+ snd_trident_sgbuf_delete(trident, substream);
return -EAGAIN;
}
voice->capture = 1;
- spin_unlock_irq(&trident->reg_lock);
voice->substream = substream;
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
@@ -1937,6 +1963,8 @@ static int snd_trident_capture_open(snd_pcm_substream_t * substream)
---------------------------------------------------------------------------*/
static int snd_trident_capture_close(snd_pcm_substream_t * substream)
{
+ trident_t *trident = snd_pcm_substream_chip(substream);
+ snd_trident_sgbuf_delete(trident, substream);
return 0;
}
@@ -1954,26 +1982,19 @@ static int snd_trident_capture_close(snd_pcm_substream_t * substream)
static int snd_trident_foldback_open(snd_pcm_substream_t * substream)
{
trident_t *trident = snd_pcm_substream_chip(substream);
+ int err;
snd_trident_voice_t *voice;
snd_pcm_runtime_t *runtime = substream->runtime;
- spin_lock_irq(&trident->reg_lock);
+ if ((err = snd_trident_sgbuf_init(trident, substream)) < 0)
+ return err;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL) {
- spin_unlock_irq(&trident->reg_lock);
+ snd_trident_sgbuf_delete(trident, substream);
return -EAGAIN;
}
- if (trident->tlb.entries) {
- voice->memblk = snd_trident_alloc_pages(trident, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
- if (voice->memblk == NULL) {
- snd_trident_free_voice(trident, voice);
- spin_unlock_irq(&trident->reg_lock);
- return -ENOMEM;
- }
- }
- voice->substream = substream;
voice->foldback_chan = substream->number;
- spin_unlock_irq(&trident->reg_lock);
+ voice->substream = substream;
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
runtime->hw = snd_trident_foldback;
@@ -2001,6 +2022,7 @@ static int snd_trident_foldback_close(snd_pcm_substream_t * substream)
spin_lock_irq(&trident->reg_lock);
outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
spin_unlock_irq(&trident->reg_lock);
+ snd_trident_sgbuf_delete(trident, substream);
return 0;
}
@@ -2019,6 +2041,20 @@ static snd_pcm_ops_t snd_trident_playback_ops = {
.pointer = snd_trident_playback_pointer,
};
+static snd_pcm_ops_t snd_trident_nx_playback_ops = {
+ .open = snd_trident_playback_open,
+ .close = snd_trident_playback_close,
+ .ioctl = snd_trident_ioctl,
+ .hw_params = snd_trident_hw_params,
+ .hw_free = snd_trident_hw_free,
+ .prepare = snd_trident_playback_prepare,
+ .trigger = snd_trident_trigger,
+ .pointer = snd_trident_playback_pointer,
+ .copy = snd_pcm_sgbuf_ops_copy_playback,
+ .silence = snd_pcm_sgbuf_ops_silence,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
static snd_pcm_ops_t snd_trident_capture_ops = {
.open = snd_trident_capture_open,
.close = snd_trident_capture_close,
@@ -2030,6 +2066,20 @@ static snd_pcm_ops_t snd_trident_capture_ops = {
.pointer = snd_trident_capture_pointer,
};
+static snd_pcm_ops_t snd_trident_nx_capture_ops = {
+ .open = snd_trident_capture_open,
+ .close = snd_trident_capture_close,
+ .ioctl = snd_trident_ioctl,
+ .hw_params = snd_trident_capture_hw_params,
+ .hw_free = snd_trident_hw_free,
+ .prepare = snd_trident_capture_prepare,
+ .trigger = snd_trident_trigger,
+ .pointer = snd_trident_capture_pointer,
+ .copy = snd_pcm_sgbuf_ops_copy_capture,
+ .silence = snd_pcm_sgbuf_ops_silence,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
static snd_pcm_ops_t snd_trident_si7018_capture_ops = {
.open = snd_trident_capture_open,
.close = snd_trident_capture_close,
@@ -2052,6 +2102,20 @@ static snd_pcm_ops_t snd_trident_foldback_ops = {
.pointer = snd_trident_playback_pointer,
};
+static snd_pcm_ops_t snd_trident_nx_foldback_ops = {
+ .open = snd_trident_foldback_open,
+ .close = snd_trident_foldback_close,
+ .ioctl = snd_trident_ioctl,
+ .hw_params = snd_trident_hw_params,
+ .hw_free = snd_trident_hw_free,
+ .prepare = snd_trident_foldback_prepare,
+ .trigger = snd_trident_trigger,
+ .pointer = snd_trident_playback_pointer,
+ .copy = snd_pcm_sgbuf_ops_copy_capture,
+ .silence = snd_pcm_sgbuf_ops_silence,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
static snd_pcm_ops_t snd_trident_spdif_ops = {
.open = snd_trident_spdif_open,
.close = snd_trident_spdif_close,
@@ -2063,6 +2127,20 @@ static snd_pcm_ops_t snd_trident_spdif_ops = {
.pointer = snd_trident_spdif_pointer,
};
+static snd_pcm_ops_t snd_trident_nx_spdif_ops = {
+ .open = snd_trident_spdif_open,
+ .close = snd_trident_spdif_close,
+ .ioctl = snd_trident_ioctl,
+ .hw_params = snd_trident_spdif_hw_params,
+ .hw_free = snd_trident_hw_free,
+ .prepare = snd_trident_spdif_prepare,
+ .trigger = snd_trident_trigger,
+ .pointer = snd_trident_spdif_pointer,
+ .copy = snd_pcm_sgbuf_ops_copy_playback,
+ .silence = snd_pcm_sgbuf_ops_silence,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
static snd_pcm_ops_t snd_trident_spdif_7018_ops = {
.open = snd_trident_spdif_open,
.close = snd_trident_spdif_close,
@@ -2088,21 +2166,24 @@ static void snd_trident_pcm_free(snd_pcm_t *pcm)
{
trident_t *trident = snd_magic_cast(trident_t, pcm->private_data, return);
trident->pcm = NULL;
- snd_pcm_lib_preallocate_free_for_all(pcm);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_free_for_all(pcm);
}
static void snd_trident_foldback_pcm_free(snd_pcm_t *pcm)
{
trident_t *trident = snd_magic_cast(trident_t, pcm->private_data, return);
trident->foldback = NULL;
- snd_pcm_lib_preallocate_free_for_all(pcm);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_free_for_all(pcm);
}
static void snd_trident_spdif_pcm_free(snd_pcm_t *pcm)
{
trident_t *trident = snd_magic_cast(trident_t, pcm->private_data, return);
trident->spdif = NULL;
- snd_pcm_lib_preallocate_free_for_all(pcm);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_free_for_all(pcm);
}
/*---------------------------------------------------------------------------
@@ -2129,18 +2210,24 @@ int __devinit snd_trident_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm
pcm->private_data = trident;
pcm->private_free = snd_trident_pcm_free;
- snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
- snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ if (trident->tlb.entries) {
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_capture_ops);
+ } else {
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
trident->device != TRIDENT_DEVICE_ID_SI7018 ?
- &snd_trident_capture_ops :
- &snd_trident_si7018_capture_ops);
+ &snd_trident_capture_ops :
+ &snd_trident_si7018_capture_ops);
+ }
pcm->info_flags = 0;
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strcpy(pcm->name, "Trident 4DWave");
trident->pcm = pcm;
- snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, pcm, 64*1024, 256*1024);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, pcm, 64*1024, 128*1024);
if (rpcm)
*rpcm = pcm;
@@ -2174,7 +2261,10 @@ int __devinit snd_trident_foldback_pcm(trident_t * trident, int device, snd_pcm_
foldback->private_data = trident;
foldback->private_free = snd_trident_foldback_pcm_free;
- snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
+ if (trident->tlb.entries)
+ snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops);
+ else
+ snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
foldback->info_flags = 0;
strcpy(foldback->name, "Trident 4DWave");
substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
@@ -2189,7 +2279,8 @@ int __devinit snd_trident_foldback_pcm(trident_t * trident, int device, snd_pcm_
}
trident->foldback = foldback;
- snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, foldback, 64*1024, 128*1024);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, foldback, 64*1024, 128*1024);
if (rpcm)
*rpcm = foldback;
@@ -2219,7 +2310,9 @@ int __devinit snd_trident_spdif_pcm(trident_t * trident, int device, snd_pcm_t *
spdif->private_data = trident;
spdif->private_free = snd_trident_spdif_pcm_free;
- if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ if (trident->tlb.entries) {
+ snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_spdif_ops);
+ } else if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops);
} else {
snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops);
@@ -2228,7 +2321,8 @@ int __devinit snd_trident_spdif_pcm(trident_t * trident, int device, snd_pcm_t *
strcpy(spdif->name, "Trident 4DWave IEC958");
trident->spdif = spdif;
- snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, spdif, 64*1024, 128*1024);
+ if (! trident->tlb.entries)
+ snd_pcm_lib_preallocate_pci_pages_for_all(trident->pci, spdif, 64*1024, 128*1024);
if (rpcm)
*rpcm = spdif;
@@ -2886,7 +2980,7 @@ static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t *v
static int __devinit snd_trident_mixer(trident_t * trident, int pcm_spdif_device)
{
- ac97_t _ac97, *ac97;
+ ac97_t _ac97;
snd_card_t * card = trident->card;
snd_kcontrol_t *kctl;
snd_ctl_elem_value_t uctl;
@@ -2900,7 +2994,7 @@ static int __devinit snd_trident_mixer(trident_t * trident, int pcm_spdif_device
_ac97.private_data = trident;
trident->ac97_detect = 1;
__again:
- if ((err = snd_ac97_mixer(trident->card, &_ac97, &ac97)) < 0) {
+ if ((err = snd_ac97_mixer(trident->card, &_ac97, &trident->ac97)) < 0) {
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
if ((err = snd_trident_sis_reset(trident)) < 0)
return err;
@@ -3077,6 +3171,21 @@ void __devinit snd_trident_gameport(trident_t *chip)
#endif /* CONFIG_GAMEPORT */
/*
+ * delay for 1 tick
+ */
+inline static void do_delay(trident_t *chip)
+{
+#ifdef CONFIG_PM
+ if (chip->in_suspend) {
+ mdelay((1000 + HZ - 1) / HZ);
+ return;
+ }
+#endif
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+}
+
+/*
* SiS reset routine
*/
@@ -3086,7 +3195,7 @@ static int snd_trident_sis_reset(trident_t *trident)
unsigned int i;
int r;
- r = 2; /* count of retries */
+ r = trident->in_suspend ? 0 : 2; /* count of retries */
__si7018_retry:
pci_write_config_byte(trident->pci, 0x46, 0x04); /* SOFTWARE RESET */
udelay(100);
@@ -3107,15 +3216,13 @@ static int snd_trident_sis_reset(trident_t *trident)
do {
if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0)
goto __si7018_ok;
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule_timeout(1);
+ do_delay(trident);
} while (time_after_eq(end_time, jiffies));
snd_printk("AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)));
if (r-- > 0) {
end_time = jiffies + HZ;
do {
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule_timeout(1);
+ do_delay(trident);
} while (time_after_eq(end_time, jiffies));
goto __si7018_retry;
}
@@ -3173,36 +3280,195 @@ static void snd_trident_proc_read(snd_info_entry_t *entry,
static void __devinit snd_trident_proc_init(trident_t * trident)
{
snd_info_entry_t *entry;
- char *s = "trident";
+ const char *s = "trident";
if (trident->device == TRIDENT_DEVICE_ID_SI7018)
s = "sis7018";
- if ((entry = snd_info_create_card_entry(trident->card, s, trident->card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = trident;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 256;
- entry->c.text.read = snd_trident_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
+ if (! snd_card_proc_new(trident->card, s, &entry))
+ snd_info_set_text_ops(entry, trident, snd_trident_proc_read);
+}
+
+static int snd_trident_dev_free(snd_device_t *device)
+{
+ trident_t *trident = snd_magic_cast(trident_t, device->device_data, return -ENXIO);
+ return snd_trident_free(trident);
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_tlb_alloc
+
+ Description: Allocate and set up the TLB page table on 4D NX.
+ Each entry has 4 bytes (physical PCI address).
+
+ Paramters: trident - pointer to target device class for 4DWave.
+
+ Returns: 0 or negative error code
+
+ ---------------------------------------------------------------------------*/
+
+static int __devinit snd_trident_tlb_alloc(trident_t *trident)
+{
+ int i;
+
+ /* TLB array must be aligned to 16kB !!! so we allocate
+ 32kB region and correct offset when necessary */
+
+ trident->tlb.buffer = snd_malloc_pci_pages(trident->pci, 2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer_dmaaddr);
+ if (trident->tlb.buffer == NULL) {
+ snd_printk(KERN_ERR "trident: unable to allocate TLB buffer\n");
+ return -ENOMEM;
+ }
+ trident->tlb.entries = (unsigned int*)(((unsigned long)trident->tlb.buffer + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1));
+ trident->tlb.entries_dmaaddr = (trident->tlb.buffer_dmaaddr + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1);
+ /* allocate shadow TLB page table (virtual addresses) */
+ trident->tlb.shadow_entries = (unsigned long *)vmalloc(SNDRV_TRIDENT_MAX_PAGES*sizeof(unsigned long));
+ if (trident->tlb.shadow_entries == NULL) {
+ snd_printk(KERN_ERR "trident: unable to allocate shadow TLB entries\n");
+ return -ENOMEM;
+ }
+ /* allocate and setup silent page and initialise TLB entries */
+ trident->tlb.silent_page = snd_malloc_pci_pages(trident->pci, SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page_dmaaddr);
+ if (trident->tlb.silent_page == 0UL) {
+ snd_printk(KERN_ERR "trident: unable to allocate silent page\n");
+ return -ENOMEM;
}
- trident->proc_entry = entry;
+ memset(trident->tlb.silent_page, 0, SNDRV_TRIDENT_PAGE_SIZE);
+ for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) {
+ trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page_dmaaddr & ~(SNDRV_TRIDENT_PAGE_SIZE-1));
+ trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page;
+ }
+
+ /* use emu memory block manager code to manage tlb page allocation */
+ trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
+ if (trident->tlb.memhdr == NULL)
+ return -ENOMEM;
+
+ trident->tlb.memhdr->block_extra_size = sizeof(snd_trident_memblk_arg_t);
+ return 0;
+}
+
+/*
+ * initialize 4D DX chip
+ */
+
+static void snd_trident_stop_all_voices(trident_t *trident)
+{
+ outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
+ outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
+ outl(0, TRID_REG(trident, T4D_AINTEN_A));
+ outl(0, TRID_REG(trident, T4D_AINTEN_B));
+}
+
+static int snd_trident_4d_dx_init(trident_t *trident)
+{
+ struct pci_dev *pci = trident->pci;
+ signed long end_time;
+
+ /* reset the legacy configuration and whole audio/wavetable block */
+ pci_write_config_dword(pci, 0x40, 0); /* DDMA */
+ pci_write_config_byte(pci, 0x44, 0); /* ports */
+ pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
+ pci_write_config_byte(pci, 0x46, 4); /* reset */
+ udelay(100);
+ pci_write_config_byte(pci, 0x46, 0); /* release reset */
+ udelay(100);
+
+ /* warm reset of the AC'97 codec */
+ outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ udelay(100);
+ outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ /* DAC on, disable SB IRQ and try to force ADC valid signal */
+ trident->ac97_ctrl = 0x0000004a;
+ outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ /* wait, until the codec is ready */
+ end_time = (jiffies + (HZ * 3) / 4) + 1;
+ do {
+ if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
+ goto __dx_ok;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ snd_printk(KERN_ERR "AC'97 codec ready error\n");
+ return -EIO;
+
+ __dx_ok:
+ snd_trident_stop_all_voices(trident);
+
+ return 0;
}
-static void snd_trident_proc_done(trident_t * trident)
+/*
+ * initialize 4D NX chip
+ */
+static int snd_trident_4d_nx_init(trident_t *trident)
{
- if (trident->proc_entry) {
- snd_info_unregister(trident->proc_entry);
- trident->proc_entry = NULL;
+ struct pci_dev *pci = trident->pci;
+ signed long end_time;
+
+ /* reset the legacy configuration and whole audio/wavetable block */
+ pci_write_config_dword(pci, 0x40, 0); /* DDMA */
+ pci_write_config_byte(pci, 0x44, 0); /* ports */
+ pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
+
+ pci_write_config_byte(pci, 0x46, 1); /* reset */
+ udelay(100);
+ pci_write_config_byte(pci, 0x46, 0); /* release reset */
+ udelay(100);
+
+ /* warm reset of the AC'97 codec */
+ outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ udelay(100);
+ outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ /* wait, until the codec is ready */
+ end_time = (jiffies + (HZ * 3) / 4) + 1;
+ do {
+ if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
+ goto __nx_ok;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
+ return -EIO;
+
+ __nx_ok:
+ /* DAC on */
+ trident->ac97_ctrl = 0x00000002;
+ outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ /* disable SB IRQ */
+ outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
+
+ snd_trident_stop_all_voices(trident);
+
+ if (trident->tlb.entries != NULL) {
+ unsigned int i;
+ /* enable virtual addressing via TLB */
+ i = trident->tlb.entries_dmaaddr;
+ i |= 0x00000001;
+ outl(i, TRID_REG(trident, NX_TLBC));
+ } else {
+ outl(0, TRID_REG(trident, NX_TLBC));
}
+ /* initialize S/PDIF */
+ outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+ outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+
+ return 0;
}
-static int snd_trident_dev_free(snd_device_t *device)
+/*
+ * initialize sis7018 chip
+ */
+static int snd_trident_sis_init(trident_t *trident)
{
- trident_t *trident = snd_magic_cast(trident_t, device->device_data, return -ENXIO);
- return snd_trident_free(trident);
+ int err;
+
+ if ((err = snd_trident_sis_reset(trident)) < 0)
+ return err;
+
+ snd_trident_stop_all_voices(trident);
+
+ /* initialize S/PDIF */
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+
+ return 0;
}
/*---------------------------------------------------------------------------
@@ -3229,9 +3495,7 @@ int __devinit snd_trident_create(snd_card_t * card,
trident_t ** rtrident)
{
trident_t *trident;
- unsigned int i;
- int err;
- signed long end_time;
+ int i, err;
snd_trident_voice_t *voice;
snd_trident_pcm_mixer_t *tmix;
static snd_device_ops_t ops = {
@@ -3290,151 +3554,39 @@ int __devinit snd_trident_create(snd_card_t * card,
trident->tlb.entries = NULL;
trident->tlb.buffer = NULL;
if (trident->device == TRIDENT_DEVICE_ID_NX) {
- /* allocate and setup TLB page table */
- /* each entry has 4 bytes (physical PCI address) */
- /* TLB array must be aligned to 16kB !!! so we allocate
- 32kB region and correct offset when necessary */
- trident->tlb.buffer = snd_malloc_pci_pages(trident->pci, 2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer_dmaaddr);
- if (trident->tlb.buffer == NULL) {
- snd_trident_free(trident);
- snd_printk("unable to allocate TLB buffer\n");
- return -ENOMEM;
- }
- trident->tlb.entries = (unsigned int*)(((unsigned long)trident->tlb.buffer + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1));
- trident->tlb.entries_dmaaddr = (trident->tlb.buffer_dmaaddr + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1);
- /* allocate shadow TLB page table (virtual addresses) */
- trident->tlb.shadow_entries = (unsigned long *)vmalloc(SNDRV_TRIDENT_MAX_PAGES*sizeof(unsigned long));
- if (trident->tlb.shadow_entries == NULL) {
+ if ((err = snd_trident_tlb_alloc(trident)) < 0) {
snd_trident_free(trident);
- snd_printk("unable to allocate shadow TLB entries\n");
- return -ENOMEM;
- }
- /* allocate and setup silent page and initialise TLB entries */
- trident->tlb.silent_page = snd_malloc_pci_pages(trident->pci, SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page_dmaaddr);
- if (trident->tlb.silent_page == 0UL) {
- snd_trident_free(trident);
- snd_printk("unable to allocate silent page\n");
- return -ENOMEM;
- }
- memset(trident->tlb.silent_page, 0, SNDRV_TRIDENT_PAGE_SIZE);
- for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) {
- trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page_dmaaddr & ~(SNDRV_TRIDENT_PAGE_SIZE-1));
- trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page;
- }
-
- /* use emu memory block manager code to manage tlb page allocation */
- trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
- if (trident->tlb.memhdr == NULL) {
- snd_trident_free(trident);
- return -ENOMEM;
+ return err;
}
- trident->tlb.memhdr->block_extra_size = sizeof(snd_trident_memblk_arg_t);
}
- /* reset the legacy configuration and whole audio/wavetable block */
- if (trident->device == TRIDENT_DEVICE_ID_DX ||
- trident->device == TRIDENT_DEVICE_ID_NX) {
- pci_write_config_dword(pci, 0x40, 0); /* DDMA */
- pci_write_config_byte(pci, 0x44, 0); /* ports */
- pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
- if (trident->device == TRIDENT_DEVICE_ID_DX) {
- pci_write_config_byte(pci, 0x46, 4); /* reset */
- udelay(100);
- pci_write_config_byte(pci, 0x46, 0); /* release reset */
- udelay(100);
- } else /* NX */ {
- pci_write_config_byte(pci, 0x46, 1); /* reset */
- udelay(100);
- pci_write_config_byte(pci, 0x46, 0); /* release reset */
- udelay(100);
- }
- }
-
- /* initialize chip */
+ trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+ /* initialize chip */
switch (trident->device) {
case TRIDENT_DEVICE_ID_DX:
- /* warm reset of the AC'97 codec */
- outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
- udelay(100);
- outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
- /* DAC on, disable SB IRQ and try to force ADC valid signal */
- trident->ac97_ctrl = 0x0000004a;
- outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
- /* wait, until the codec is ready */
- end_time = (jiffies + (HZ * 3) / 4) + 1;
- do {
- if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
- goto __dx_ok;
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule_timeout(1);
- } while (end_time - (signed long)jiffies >= 0);
- snd_printk("AC'97 codec ready error\n");
- snd_trident_free(trident);
- return -EIO;
- __dx_ok:
+ err = snd_trident_4d_dx_init(trident);
break;
case TRIDENT_DEVICE_ID_NX:
- /* warm reset of the AC'97 codec */
- outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
- udelay(100);
- outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
- /* wait, until the codec is ready */
- end_time = (jiffies + (HZ * 3) / 4) + 1;
- do {
- if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
- goto __nx_ok;
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule_timeout(1);
- } while (end_time - (signed long)jiffies >= 0);
- snd_printk("AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
- snd_trident_free(trident);
- return -EIO;
- __nx_ok:
- /* DAC on */
- trident->ac97_ctrl = 0x00000002;
- outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
- /* disable SB IRQ */
- outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
+ err = snd_trident_4d_nx_init(trident);
break;
case TRIDENT_DEVICE_ID_SI7018:
- if ((err = snd_trident_sis_reset(trident)) < 0) {
- snd_trident_free(trident);
- return err;
- }
+ err = snd_trident_sis_init(trident);
+ break;
+ default:
+ snd_BUG();
+ break;
+ }
+ if (err < 0) {
+ snd_trident_free(trident);
+ return err;
}
-
- outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
- outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
- outl(0, TRID_REG(trident, T4D_AINTEN_A));
- outl(0, TRID_REG(trident, T4D_AINTEN_B));
if ((err = snd_trident_mixer(trident, pcm_spdif_device)) < 0) {
snd_trident_free(trident);
return err;
}
- if (trident->device == TRIDENT_DEVICE_ID_NX) {
- if (trident->tlb.entries != NULL) {
- /* enable virtual addressing via TLB */
- i = trident->tlb.entries_dmaaddr;
- i |= 0x00000001;
- outl(i, TRID_REG(trident, NX_TLBC));
- } else {
- outl(0, TRID_REG(trident, NX_TLBC));
- }
- /* initialize S/PDIF */
- trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
- outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
- outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
- }
-
- if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
- /* initialize S/PDIF */
- trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
- outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
- }
-
/* initialise synth voices */
for (i = 0; i < 64; i++) {
voice = &trident->synth.voices[i];
@@ -3493,7 +3645,6 @@ int snd_trident_free(trident_t *trident)
else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
outl(0, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
- snd_trident_proc_done(trident);
if (trident->tlb.buffer) {
outl(0, TRID_REG(trident, NX_TLBC));
if (trident->tlb.memhdr)
@@ -3654,6 +3805,7 @@ snd_trident_voice_t *snd_trident_alloc_voice(trident_t * trident, int type, int
pvoice->capture = 0;
pvoice->spdif = 0;
pvoice->memblk = NULL;
+ pvoice->substream = NULL;
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return pvoice;
}
@@ -3732,9 +3884,9 @@ void snd_trident_suspend(trident_t *trident)
{
snd_card_t *card = trident->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
+ trident->in_suspend = 1;
snd_pcm_suspend_all(trident->pcm);
if (trident->foldback)
snd_pcm_suspend_all(trident->foldback);
@@ -3748,27 +3900,40 @@ void snd_trident_suspend(trident_t *trident)
break;
}
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
void snd_trident_resume(trident_t *trident)
{
snd_card_t *card = trident->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
+
+ pci_enable_device(trident->pci);
+ pci_set_dma_mask(trident->pci, 0x3fffffff); /* to be sure */
+ pci_set_master(trident->pci); /* to be sure */
+
switch (trident->device) {
case TRIDENT_DEVICE_ID_DX:
+ snd_trident_4d_dx_init(trident);
+ break;
case TRIDENT_DEVICE_ID_NX:
- break; /* TODO */
+ snd_trident_4d_nx_init(trident);
+ break;
case TRIDENT_DEVICE_ID_SI7018:
+ snd_trident_sis_init(trident);
break;
}
+
+ snd_ac97_resume(trident->ac97);
+
+ /* restore some registers */
+ outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+
+ snd_trident_enable_eso(trident);
+
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
+ trident->in_suspend = 0;
}
static int snd_trident_set_power_state(snd_card_t *card, unsigned int power_state)
diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c
index d7701b999dd9..fb8a44f283a7 100644
--- a/sound/pci/trident/trident_memory.c
+++ b/sound/pci/trident/trident_memory.c
@@ -28,6 +28,7 @@
#include <linux/time.h>
#include <sound/core.h>
#include <sound/trident.h>
+#include <sound/pcm_sgbuf.h>
/* page arguments of these two macros are Trident page (4096 bytes), not like
* aligned pages in others
@@ -166,9 +167,8 @@ __found_pages:
/*
* check if the given pointer is valid for pages
*/
-static int is_valid_page(void *pages)
+static int is_valid_page(unsigned long ptr)
{
- unsigned long ptr = (unsigned long)virt_to_phys(pages);
if (ptr & ~0x3fffffffUL) {
snd_printk("max memory size is 1GB!!\n");
return 0;
@@ -184,33 +184,42 @@ static int is_valid_page(void *pages)
* page allocation for DMA
*/
snd_util_memblk_t *
-snd_trident_alloc_pages(trident_t *trident, void *pages, dma_addr_t addr, unsigned long size)
+snd_trident_alloc_pages(trident_t *trident, snd_pcm_substream_t *substream)
{
- unsigned long ptr;
snd_util_memhdr_t *hdr;
snd_util_memblk_t *blk;
- int page;
+ int idx, page;
+ struct snd_sg_buf *sgbuf = snd_magic_cast(snd_pcm_sgbuf_t, _snd_pcm_substream_sgbuf(substream), return NULL);
snd_assert(trident != NULL, return NULL);
- snd_assert(size > 0 && size < SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL);
+ snd_assert(sgbuf->size > 0 && sgbuf->size < SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL);
hdr = trident->tlb.memhdr;
snd_assert(hdr != NULL, return NULL);
- if (! is_valid_page(pages))
- return NULL;
-
down(&hdr->block_mutex);
- blk = search_empty(hdr, size);
+ blk = search_empty(hdr, sgbuf->size);
if (blk == NULL) {
up(&hdr->block_mutex);
return NULL;
}
+ if (lastpg(blk) - firstpg(blk) >= sgbuf->pages) {
+ snd_printk(KERN_ERR "page calculation doesn't match: allocated pages = %d, trident = %d/%d\n", sgbuf->pages, firstpg(blk), lastpg(blk));
+ __snd_util_mem_free(hdr, blk);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
+
/* set TLB entries */
- ptr = (unsigned long)pages;
- for (page = firstpg(blk); page <= lastpg(blk); page++) {
+ idx = 0;
+ for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) {
+ dma_addr_t addr = sgbuf->table[idx].addr;
+ unsigned long ptr = (unsigned long)sgbuf->table[idx].buf;
+ if (! is_valid_page(addr)) {
+ __snd_util_mem_free(hdr, blk);
+ up(&hdr->block_mutex);
+ return NULL;
+ }
set_tlb_bus(trident, page, ptr, addr);
- ptr += ALIGN_PAGE_SIZE;
- addr += ALIGN_PAGE_SIZE;
}
up(&hdr->block_mutex);
return blk;
@@ -301,7 +310,8 @@ static void clear_tlb(trident_t *trident, int page)
void *ptr = page_to_ptr(trident, page);
dma_addr_t addr = page_to_addr(trident, page);
set_silent_tlb(trident, page);
- snd_free_pci_pages(trident->pci, ALIGN_PAGE_SIZE, ptr, addr);
+ if (ptr)
+ snd_free_pci_pages(trident->pci, ALIGN_PAGE_SIZE, ptr, addr);
}
/* check new allocation range */
@@ -346,7 +356,7 @@ static int synth_alloc_pages(trident_t *hw, snd_util_memblk_t *blk)
ptr = snd_malloc_pci_pages(hw->pci, ALIGN_PAGE_SIZE, &addr);
if (ptr == NULL)
goto __fail;
- if (! is_valid_page(ptr)) {
+ if (! is_valid_page(addr)) {
snd_free_pci_pages(hw->pci, ALIGN_PAGE_SIZE, ptr, addr);
goto __fail;
}
diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c
index b5ce828f1aa2..6f7d49d2fdf0 100644
--- a/sound/pci/ymfpci/ymfpci_main.c
+++ b/sound/pci/ymfpci/ymfpci_main.c
@@ -1827,26 +1827,8 @@ static int __devinit snd_ymfpci_proc_init(snd_card_t * card, ymfpci_t *chip)
{
snd_info_entry_t *entry;
- entry = snd_info_create_card_entry(card, "ymfpci", card->proc_root);
- if (entry) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = chip;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 4096;
- entry->c.text.read = snd_ymfpci_proc_read;
- if (snd_info_register(entry) < 0) {
- snd_info_unregister(entry);
- entry = NULL;
- }
- }
- chip->proc_entry = entry;
- return 0;
-}
-
-static int snd_ymfpci_proc_done(ymfpci_t *chip)
-{
- if (chip->proc_entry)
- snd_info_unregister((snd_info_entry_t *) chip->proc_entry);
+ if (! snd_card_proc_new(card, "ymfpci", &entry))
+ snd_info_set_text_ops(entry, chip, snd_ymfpci_proc_read);
return 0;
}
@@ -2037,7 +2019,6 @@ static int snd_ymfpci_free(ymfpci_t *chip)
u16 ctrl;
snd_assert(chip != NULL, return -EINVAL);
- snd_ymfpci_proc_done(chip);
if (chip->res_reg_area) { /* don't touch busy hardware */
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
@@ -2127,9 +2108,8 @@ void snd_ymfpci_suspend(ymfpci_t *chip)
snd_card_t *card = chip->card;
int i;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
snd_pcm_suspend_all(chip->pcm);
snd_pcm_suspend_all(chip->pcm2);
snd_pcm_suspend_all(chip->pcm_spdif);
@@ -2140,8 +2120,6 @@ void snd_ymfpci_suspend(ymfpci_t *chip)
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
snd_ymfpci_disable_dsp(chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
void snd_ymfpci_resume(ymfpci_t *chip)
@@ -2149,10 +2127,8 @@ void snd_ymfpci_resume(ymfpci_t *chip)
snd_card_t *card = chip->card;
int i;
- snd_power_lock(card);
-
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
pci_enable_device(chip->pci);
pci_set_master(chip->pci);
@@ -2174,8 +2150,6 @@ void snd_ymfpci_resume(ymfpci_t *chip)
spin_unlock(&chip->reg_lock);
}
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
static int snd_ymfpci_set_power_state(snd_card_t *card, unsigned int power_state)
diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c
index 190984c2e287..3d443de8f3a5 100644
--- a/sound/ppc/pmac.c
+++ b/sound/ppc/pmac.c
@@ -1460,9 +1460,8 @@ static void snd_pmac_suspend(pmac_t *chip)
unsigned long flags;
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D3hot)
- goto __skip;
+ return;
if (chip->suspend)
chip->suspend(chip);
@@ -1476,17 +1475,14 @@ static void snd_pmac_suspend(pmac_t *chip)
disable_irq(chip->rx_irq);
snd_pmac_sound_feature(chip, 0);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- __skip:
- snd_power_unlock(card);
}
static void snd_pmac_resume(pmac_t *chip)
{
snd_card_t *card = chip->card;
- snd_power_lock(card);
if (card->power_state == SNDRV_CTL_POWER_D0)
- goto __skip;
+ return;
snd_pmac_sound_feature(chip, 1);
if (chip->resume)
@@ -1505,8 +1501,6 @@ static void snd_pmac_resume(pmac_t *chip)
enable_irq(chip->rx_irq);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- __skip:
- snd_power_unlock(card);
}
/* the chip is stored statically by snd_pmac_register_sleep_notifier
diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c
index 66e2c95adb32..1564ca401970 100644
--- a/sound/usb/usbaudio.c
+++ b/sound/usb/usbaudio.c
@@ -184,7 +184,6 @@ struct snd_usb_stream {
int pcm_index;
snd_usb_substream_t substream[2];
struct list_head list;
- snd_info_entry_t *proc_entry;
};
#define chip_t snd_usb_stream_t
@@ -931,6 +930,70 @@ static struct audioformat *find_format(snd_usb_substream_t *subs, snd_pcm_runtim
/*
+ * initialize the picth control and sample rate
+ */
+static int init_usb_pitch(struct usb_device *dev, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt)
+{
+ unsigned int ep;
+ unsigned char data[1];
+ int err;
+
+ ep = get_endpoint(alts, 0)->bEndpointAddress;
+ /* if endpoint has pitch control, enable it */
+ if (fmt->attributes & EP_CS_ATTR_PITCH_CONTROL) {
+ data[0] = 1;
+ if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
+ USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+ PITCH_CONTROL << 8, ep, data, 1, HZ)) < 0) {
+ snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
+ dev->devnum, iface, ep);
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int init_usb_sample_rate(struct usb_device *dev, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt, int rate)
+{
+ unsigned int ep;
+ unsigned char data[3];
+ int err;
+
+ ep = get_endpoint(alts, 0)->bEndpointAddress;
+ /* if endpoint has sampling rate control, set it */
+ if (fmt->attributes & EP_CS_ATTR_SAMPLE_RATE) {
+ int crate;
+ data[0] = rate;
+ data[1] = rate >> 8;
+ data[2] = rate >> 16;
+ if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
+ USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+ SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
+ snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep 0x%x\n",
+ dev->devnum, iface, fmt->altsetting, rate, ep);
+ return err;
+ }
+ if ((err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR,
+ USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN,
+ SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
+ snd_printk(KERN_ERR "%d:%d:%d: cannot get freq at ep 0x%x\n",
+ dev->devnum, iface, fmt->altsetting, ep);
+ return err;
+ }
+ crate = data[0] | (data[1] << 8) | (data[2] << 16);
+ if (crate != rate) {
+ snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate);
+ // runtime->rate = crate;
+ }
+ }
+ return 0;
+}
+
+/*
* find a matching format and set up the interface
*/
static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
@@ -942,7 +1005,6 @@ static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
struct usb_interface *iface;
struct audioformat *fmt;
unsigned int ep, attr;
- unsigned char data[3];
int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
int err;
@@ -1015,44 +1077,11 @@ static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
subs->syncinterval = get_endpoint(alts, 1)->bRefresh;
}
- ep = get_endpoint(alts, 0)->bEndpointAddress;
- /* if endpoint has pitch control, enable it */
- if (fmt->attributes & EP_CS_ATTR_PITCH_CONTROL) {
- data[0] = 1;
- if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
- USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
- PITCH_CONTROL << 8, ep, data, 1, HZ)) < 0) {
- snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
- dev->devnum, subs->interface, ep);
- return err;
- }
- }
- /* if endpoint has sampling rate control, set it */
- if (fmt->attributes & EP_CS_ATTR_SAMPLE_RATE) {
- int crate;
- data[0] = runtime->rate;
- data[1] = runtime->rate >> 8;
- data[2] = runtime->rate >> 16;
- if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
- USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
- SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
- snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep 0x%x\n",
- dev->devnum, subs->interface, fmt->altsetting, runtime->rate, ep);
- return err;
- }
- if ((err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR,
- USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN,
- SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
- snd_printk(KERN_ERR "%d:%d:%d: cannot get freq at ep 0x%x\n",
- dev->devnum, subs->interface, fmt->altsetting, ep);
- return err;
- }
- crate = data[0] | (data[1] << 8) | (data[2] << 16);
- if (crate != runtime->rate) {
- snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, runtime->rate);
- // runtime->rate = crate;
- }
- }
+ if ((err = init_usb_pitch(dev, subs->interface, alts, fmt)) < 0 ||
+ (err = init_usb_sample_rate(dev, subs->interface, alts, fmt,
+ runtime->rate)) < 0)
+ return err;
+
/* always fill max packet size */
if (fmt->attributes & EP_CS_ATTR_FILL_MAX)
subs->fill_max = 1;
@@ -1426,18 +1455,8 @@ static void proc_pcm_format_add(snd_usb_stream_t *stream)
snd_card_t *card = stream->chip->card;
sprintf(name, "stream%d", stream->pcm_index);
- if ((entry = snd_info_create_card_entry(card, name, card->proc_root)) != NULL) {
- entry->content = SNDRV_INFO_CONTENT_TEXT;
- entry->private_data = stream;
- entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
- entry->c.text.read_size = 4096;
- entry->c.text.read = proc_pcm_format_read;
- if (snd_info_register(entry) < 0) {
- snd_info_free_entry(entry);
- entry = NULL;
- }
- }
- stream->proc_entry = entry;
+ if (! snd_card_proc_new(card, name, &entry))
+ snd_info_set_text_ops(entry, stream, proc_pcm_format_read);
}
@@ -1492,10 +1511,6 @@ static void free_substream(snd_usb_substream_t *subs)
*/
static void snd_usb_audio_stream_free(snd_usb_stream_t *stream)
{
- if (stream->proc_entry) {
- snd_info_unregister(stream->proc_entry);
- stream->proc_entry = NULL;
- }
free_substream(&stream->substream[0]);
free_substream(&stream->substream[1]);
list_del(&stream->list);
@@ -1828,6 +1843,10 @@ static int parse_audio_endpoints(snd_usb_audio_t *chip, unsigned char *buffer, i
kfree(fp);
return err;
}
+ /* try to set the interface... */
+ usb_set_interface(chip->dev, iface_no, i);
+ init_usb_pitch(chip->dev, iface_no, alts, fp);
+ init_usb_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max);
}
return 0;
}
@@ -1890,9 +1909,10 @@ static int snd_usb_create_streams(snd_usb_audio_t *chip, int ctrlif,
/* skip non-supported classes */
continue;
}
- parse_audio_endpoints(chip, buffer, buflen, j);
- usb_set_interface(dev, j, 0); /* reset the current interface */
- usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1);
+ if (! parse_audio_endpoints(chip, buffer, buflen, j)) {
+ usb_set_interface(dev, j, 0); /* reset the current interface */
+ usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1);
+ }
}
return 0;