At Wed, 11 Apr 2007 18:18:54 -0700 (PDT), Tim Harvey wrote:
Greetings,
I'm setting out to write an ALSA driver for a very limited device thats behind a PLD. The actual codec is a uda1380 and does 16bit MSB stereo encode/decode. The way the device is driven the sampling rate is limited to 8KHz and data transfer to/from the device I have to handle manually.
I've read over the 'writing an ALSA driver' tutorial and have started the driver but have run into a few questions:
- for the specs I've given above: 8000Hz sampling rate, 16bit stereo MSB
samples does the following snd_pcm_hardware_t look right?:
static snd_pcm_hardware_t my_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_U16_BE, // is this 16bit MSB? .rates = SNDRV_PCM_RATE_8000, // seems redundant? .rate_min = 8000, .rate_max = 8000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 32768, .period_bytes_min = 4096, .period_bytes_max = 32768, .periods_min = 1, .periods_max = 1024, };
Looks fine to me. rates field shouldn't be empty, so it's not redundant (although it might work without it).
- I'm a little confused at how to deal with copying data from/to the device.
It looks like I would implement the copy callback and copy the passed buffer to the device in the device specific manner I need to (playback), but I'm not clear where to put data that I receive from the device (capture). I notice that when the hw_params callback is called a dma buffer has been created by ALSA in the substream->runtime struct. I assume this is created based on hte buffer_bytes_max field above and I'm to copy data to this buffer? How do I know where to copy it within the buffer and what data ALSA has already consumed from the buffer?
It's a little bit complicated right now. If you don't need mmap mode transfer, the thing gets easier, but it restricts application usage.
Without mmap mode, you can implement copy and silence callbacks to copy the user-space data directly to the hardware buffer. In this case, the period and buffer sizes in snd_pcm_hardware have to be identical with what the hardware can accept. ISA GUS and emu8000_pcm are examples.
Otherwise, you'd need an intermediate buffer. In this case, the transfer to the hardware is done either in the irq handler (via ack callback) or through a dedicated workqueue in background.
- I might need to create an intermediate buffer between ALSA's rather large
buffers and my hardware buffer. Is there any driver that I could look at that does this? The tutorial talks about the vxpocket driver but that code looks very foriegn from the rest of the ALSA drivers.
The vx driver implementation is complex and not well suitable as a reference. If the manual copy can be done relatively fast, you can use helper functions in pcm_indirect.h, as in emu10k1/emupcm.c, rme32 and cs46xx. They transfer the data via ack callback, which is invoked from snd_pcm_period_elapsed().
Actually, this is a missing piece in the current ALSA design, and now becoming a frequently asked thing. Maybe it's good to think of a standard framework for non-DMA type driver implementation.
- I'm not sure I understand what the 'period' is about. Its clear to me that
I need to call snd_pcm_period_elapsed() periodically from either an IRQ or other timed callback, but I'm not clear what the hardware pointer is or how that converts to 'frames' and 'periods'.
The hardware pointer (hw_ptr) is the current position of the transfer. Note that this is in frames unit. Meanwhile, the application pointer (appl_ptr) is the current position of the filled data by the app. Thus, appl_ptr - hw_ptr gives you the number of the rest data to be processed.
Both pointers are linear and _not_ the offset in a ring-buffer. It can be between 0 and runtime->boundary-1 (ca. LONG_MAX, aligned to period_size).
However, the pointer callback returns the offset in a ring-buffer, i.e. between 0 and (buffer_size-1). Then the pcm core layer recomputes the linear position.
The "frame" represents the unit, 1 frame = # channels x sample_bytes. In your case, 1 frame corresponds to 2 channels x 16 bits = 4 bytes.
The periods is the number of periods in a ring-buffer. In OSS, called as "fragments".
So, - buffer_size = period_size * periods - period_bytes = period_size * bytes_per_frame - bytes_per_frame = channels * bytes_per_sample
- in general, if my hardware device only allows 16bit MSB 8KHz, does ALSA
provides a mechanism for converting data from other formats?
Yes, it's done in alsa-lib on user-space.
Takashi