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).
The first case works fine if the external hardware buffer is large
enough. This method doesn't need any extra buffers and thus is
more effective. You need to define the
copy
and
silence
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.
The second case allows for mmap on the buffer, although you have to handle an interrupt or a tasklet to transfer the data from the intermediate buffer to the hardware buffer. You can find an example in the vxpocket driver.
Another case is when 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 the Intel one.
In non-mmap mode, the data cannot be transferred as in the normal
way. Thus you need to define the copy
and
silence
callbacks as well,
as in the cases above. The examples are found in
rme32.c
and rme96.c
.
The implementation of the copy
and
silence
callbacks depends upon
whether the hardware supports interleaved or non-interleaved
samples. The copy
callback is
defined like below, a bit
differently depending whether the direction is playback or
capture:
static int playback_copy(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void *src, snd_pcm_uframes_t count); static int capture_copy(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void *dst, snd_pcm_uframes_t count);
In the case of interleaved samples, the second argument
(channel
) is not used. The third argument
(pos
) points the
current position offset in frames.
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.
The last argument is the number of frames to be copied.
What you have to do in this callback is again different
between playback and capture directions. In the
playback case, you copy the given amount of data
(count
) at the specified pointer
(src
) to the specified offset
(pos
) on the hardware buffer. When
coded like memcpy-like way, the copy would be like:
my_memcpy(my_buffer + frames_to_bytes(runtime, pos), src, frames_to_bytes(runtime, count));
For the capture direction, you copy the given amount of
data (count
) at the specified offset
(pos
) on the hardware buffer to the
specified pointer (dst
).
my_memcpy(dst, my_buffer + frames_to_bytes(runtime, pos), frames_to_bytes(runtime, count));
Note that both the position and the amount of data are given in frames.
In the case of non-interleaved samples, the implementation will be a bit more complicated.
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
isa/gus/gus_pcm.c
as an example.
The silence
callback is also
implemented in a similar way.
static int silence(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
The meanings of arguments are the same as in the
copy
callback, although there is no src/dst
argument. In the case of interleaved samples, the channel
argument has no meaning, as well as on
copy
callback.
The role of silence
callback is to
set the given amount
(count
) of silence data at the
specified offset (pos
) 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:
my_memcpy(my_buffer + frames_to_bytes(runtime, pos), 0, frames_to_bytes(runtime, count));
In the case of non-interleaved samples, again, the
implementation becomes a bit more complicated. See, for example,
isa/gus/gus_pcm.c
.