world leader in high performance signal processing
Trace: » writing-an-alsa-driver

Writing an ALSA Driver

File Tree Structure

Basic Flow for PCI Drivers

Management of Cards and Components

PCI Resource Management

PCM Interface

Control Interface

API for AC97 Codec

MIDI (MPU401-UART) Interface

RawMIDI Interface

Miscellaneous Devices

Buffer and Memory Management

Proc Interface

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 <sound/info.h>.

To create a proc file, call snd_card_proc_new(). struct snd_info_entry *entry; int err = snd_card_proc_new(card, “my-file”, &entry); where the second argument specifies the name of the proc file to be created. The above example will create a file my-file under the card directory, e.g. /proc/asound/card0/my-file.

Like other components, the proc entry created via snd_card_proc_new() will be registered and released automatically in the card registration and release functions.

When the creation is successful, the function stores a new instance in the pointer given in the third argument. It is initialized as a text proc file for read only. To use this proc file as a read-only text file as it is, set the read callback with a private data via snd_info_set_text_ops(). snd_info_set_text_ops(entry, chip, my_proc_read); where the second argument (chip) is the private data to be used in the callbacks. The third parameter specifies the read buffer size and the fourth (my_proc_read) is the callback function, which is defined like static void my_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);

In the read callback, use snd_iprintf() for output strings, which works just like normal printf(). For example, static void my_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct my_chip *chip = entry→private_data; snd_iprintf(buffer, “This is my chip!\n”); snd_iprintf(buffer, “Port = %ld\n”, chip→port); }

The file permissions can be changed afterwards. As default, it's set as read only for all users. If you want to add write permission for the user (root as default), do as follows: entry→mode = S_IFREG | S_IRUGO | S_IWUSR; and set the write buffer size and the callback entry→c.text.write = my_proc_write;

For the write callback, you can use snd_info_get_line() to get a text line, and snd_info_get_str() to retrieve a string from the line. Some examples are found in core/oss/mixer_oss.c, core/oss/and pcm_oss.c.

For a raw-data proc-file, set the attributes as follows: 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;

The callback is much more complicated than the text-file version. You need to use a low-level I/O functions such as copy_from/to_user() to transfer the data. static long my_file_io_read(struct snd_info_entry *entry, void *file_private_data, struct file *file, char *buf, unsigned long count, unsigned long pos) { long size = count; if (pos + size > local_max_size) size = local_max_size - pos; if (copy_to_user(buf, local_data + pos, size)) return -EFAULT; return size; }

Power Management

If the chip is supposed to work with suspend/resume functions, you need to add power-management code to the driver. The additional code for power-management should be ifdef'ed with CONFIG_PM.

If the driver fully supports suspend/resume that is, the device can be properly resumed to its state when suspend was called, you can set the SNDRV_PCM_INFO_RESUME flag in the pcm info field. Usually, this is possible when the registers of the chip can be safely saved and restored to RAM. If this is set, the trigger callback is called with SNDRV_PCM_TRIGGER_RESUME after the resume callback completes.

Even if the driver doesn't support PM fully but partial suspend/resume is still possible, it's still worthy to implement suspend/resume callbacks. In such a case, applications would reset the status by calling snd_pcm_prepare() and restart the stream appropriately. Hence, you can define suspend/resume callbacks below but don't set SNDRV_PCM_INFO_RESUME info flag to the PCM.

Note that the trigger with SUSPEND can always be called when snd_pcm_suspend_all is called, regardless of the SNDRV_PCM_INFO_RESUME flag. The RESUME flag affects only the behavior of snd_pcm_resume(). (Thus, in theory, SNDRV_PCM_TRIGGER_RESUME isn't needed to be handled in the trigger callback when no SNDRV_PCM_INFO_RESUME flag is set. But, it's better to keep it for compatibility reasons.)

In the earlier version of ALSA drivers, a common power-management layer was provided, but it has been removed. The driver needs to define the suspend/resume hooks according to the bus the device is connected to. In the case of PCI drivers, the callbacks look like below: #ifdef CONFIG_PM static int snd_my_suspend(struct pci_dev *pci, pm_message_t state) { …. /* do things for suspend */ return 0; } static int snd_my_resume(struct pci_dev *pci) { …. /* do things for suspend */ return 0; } #endif

The scheme of the real suspend job is as follows. Retrieve the card and the chip data.Call snd_power_change_state() with SNDRV_CTL_POWER_D3hot to change the power status.Call snd_pcm_suspend_all() to suspend the running PCM streams.If AC97 codecs are used, call snd_ac97_suspend() for each codec.Save the register values if necessary.Stop the hardware if necessary.Disable the PCI device by calling pci_disable_device(). Then, call pci_save_state() at last.

A typical code would be like: static int mychip_suspend(struct pci_dev *pci, pm_message_t state) { /* (1) */ struct snd_card *card = pci_get_drvdata(pci); struct mychip *chip = card→private_data; /* (2) */ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); /* (3) */ snd_pcm_suspend_all(chip→pcm); /* (4) */ snd_ac97_suspend(chip→ac97); /* (5) */ snd_mychip_save_registers(chip); /* (6) */ snd_mychip_stop_hardware(chip); /* (7) */ pci_disable_device(pci); pci_save_state(pci); return 0; }

The scheme of the real resume job is as follows. Retrieve the card and the chip data.Set up PCI. First, call pci_restore_state(). Then enable the pci device again by calling pci_enable_device(). Call pci_set_master() if necessary, too.Re-initialize the chip.Restore the saved registers if necessary.Resume the mixer, e.g. calling snd_ac97_resume().Restart the hardware (if any).Call snd_power_change_state() with SNDRV_CTL_POWER_D0 to notify the processes.

A typical code would be like: static int mychip_resume(struct pci_dev *pci) { /* (1) */ struct snd_card *card = pci_get_drvdata(pci); struct mychip *chip = card→private_data; /* (2) */ pci_restore_state(pci); pci_enable_device(pci); pci_set_master(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); return 0; }

As shown in the above, it's better to save registers after suspending the PCM operations via snd_pcm_suspend_all() or snd_pcm_suspend(). It means that the PCM streams are already stoppped when the register snapshot is taken. But, remember that you don't have to restart the PCM stream in the resume callback. It'll be restarted via trigger call with SNDRV_PCM_TRIGGER_RESUME when necessary.

OK, we have all callbacks now. Let's set them up. In the initialization of the card, make sure that you can get the chip data from the card instance, typically via private_data field, in case you created the chip data individually. static int __devinit snd_mychip_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { …. struct snd_card *card; struct mychip *chip; int err; …. err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); …. chip = kzalloc(sizeof(*chip), GFP_KERNEL); …. card→private_data = chip; …. } When you created the chip data with snd_card_create(), it's anyway accessible via private_data field. static int __devinit snd_mychip_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { …. struct snd_card *card; struct mychip *chip; int err; …. err = snd_card_create(index[dev], id[dev], THIS_MODULE, sizeof(struct mychip), &card); …. chip = card→private_data; …. }

If you need a space to save the registers, allocate the buffer for it here, too, since it would be fatal if you cannot allocate a memory in the suspend phase. The allocated buffer should be released in the corresponding destructor.

And next, set suspend/resume callbacks to the pci_driver. static struct pci_driver driver = { .name = “My Chip”, .id_table = snd_my_ids, .probe = snd_my_probe, .remove = __devexit_p(snd_my_remove), #ifdef CONFIG_PM .suspend = snd_my_suspend, .resume = snd_my_resume, #endif };

Module Parameters

There are standard module options for ALSA. At least, each module should have the index, id and enable options.

If the module supports multiple cards (usually up to 8 = SNDRV_CARDS cards), they should be arrays. The default initial values are defined already as constants for easier programming: 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;

If the module supports only a single card, they could be single variables, instead. enable option is not always necessary in this case, but it would be better to have a dummy option for compatibility.

The module parameters must be declared with the standard module_param()(), module_param_array()() and MODULE_PARM_DESC() macros.

The typical coding would be like below: #define CARD_NAME “My Chip” module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, “Index value for ” CARD_NAME ” soundcard.”); module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, “ID string for ” CARD_NAME ” soundcard.”); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, “Enable ” CARD_NAME ” soundcard.”);

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 tainted. MODULE_DESCRIPTION(“My Chip”); MODULE_LICENSE(“GPL”); MODULE_SUPPORTED_DEVICE(”vendor_my_chip_name”);

How To Put Your Driver Into ALSA Tree

Useful Functions


I would like to thank Phil Kerr for his help for improvement and corrections of this document.

Kevin Conder reformatted the original plain-text to the DocBook format.

Giuliano Pochini corrected typos and contributed the example codes in the hardware constraints section.

About This Book

Last updated on Oct 15, 2007.


  • Takashi Iwai

Legal Notice

Copyright © 2002-2005 Takashi Iwai

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.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA