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 snd_pcm_period_elapsed()
function.
There are several types of sound chips to generate the interrupts.
This is the most frequently found type: the hardware
generates an interrupt at each period boundary.
In this case, you can call
snd_pcm_period_elapsed() at each
interrupt.
snd_pcm_period_elapsed() 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).
If you acquire 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
snd_pcm_period_elapsed(), because
snd_pcm_period_elapsed() calls other pcm
callbacks inside.
A typical coding would be like:
Example 5-3. Interrupt Handler Case #1
static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
struct mychip *chip = dev_id;
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);
// acknowledge the interrupt if necessary
}
....
spin_unlock(&chip->lock);
return IRQ_HANDLED;
}
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
snd_pcm_period_elapsed() and reset the
accumulator.
A typical coding would be like the following.
Example 5-4. Interrupt Handler Case #2
static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
struct mychip *chip = dev_id;
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);
}
// acknowledge the interrupt if necessary
}
....
spin_unlock(&chip->lock);
return IRQ_HANDLED;
}
snd_pcm_period_elapsed() In both cases, even if more than one period are elapsed, you
don't have to call
snd_pcm_period_elapsed() many times. Call
only once. And the pcm layer will check the current hardware
pointer and update to the latest status.