Hi,

let's start the second part of the alsa-driver-writing howto.

so far, i explained the basic flow of constructor and destructor of
the module.  i forgot to mention that the resource is most likely a
memory region, which is allocated via request_mem_region().

        typedef struct _snd_geode geode_t;
        struct _snd_geode {
                ...
                unsigned long iobase_phys;
                unsigned long iobase_virt;
                struct resource *res_iobase;
        };

        chip->iobase_phys = pci_resource_start(pci, 0);
        if ((chip->res_iobase = request_mem_region(chip->iobase_phys, 512,
                                                "Geode")) == NULL) {
                snd_geode_free(chip);
                snd_printk(KERN_ERR "ERROR!\n");
                return -EBUSY;
        }
        chip->iobase_virt = (unsigned long)ioremap_nocache(chip->iobase_phys, 512);
        ...

and it seems that geode uses a fixed irq 5.  i'm not sure whether it's
true.  anyway, the code will be like:

#define GEODE_IRQ       5
        if (request_irq(GEODE_IRQ, snd_geode_interrupt, SA_INTERRUPT|SA_SHIRQ, 
"Geode", (void *) chip)) {
                snd_geode_free(chip);
                snd_printk("unable to grab IRQ %d\n", GEODE_IRQ);
                return -EBUSY;
        }
        chip->irq = GEODE_IRQ;

and you need to write an interrupt handler,

        static void snd_geode_interrupt(int irq, void *dev_id, struct pt_regs *regs)
        {
                geode_t *chip = snd_magic_cast(geode_t, dev_id, return);
                ...
        }

the destructor will be

        static int snd_geode_free(es1938_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);
                }
                if (chip->irq >= 0)
                        free_irq(chip->irq, (void *)chip);
                snd_magic_kfree(chip);
                return 0;
        }


***

OK, let's rock to PCM stuff.

the pcm is allocated snd_pcm_new() function.
it would be better to create a constructor for pcm, namely,

        static int __devinit snd_geode_new_pcm(geode_t *chip)
        {
                snd_pcm_t *pcm;
                int err;

                if ((err = snd_pcm_new(chip->card, "Geode", 0, 1, 1, &pcm)) < 0) 
                        return err;
                pcm->private_data = chip;
                strcpy(pcm->name, "Geode");
                chip->pcm = pcm;
                /* more to come here.. */
                return 0;
        }

the third argument (0) of snd_pcm_new is the index of this new pcm.
it begins from zero.
the fourth and fifth arguments are the number of substreams for
playback and capture.  here both 1 are given.
if a chip supports multiple playbacks or captures, you can specify
more numbers, but they must be handled properly in open/close, etc.

after the pcm is created, you need to set operators for the pcm
streams.

        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_geode_playback_ops);
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_geode_capture_ops);

the operators are defined typically like this:

        static snd_pcm_ops_t snd_geode_playback_ops = {
                .open =         snd_geode_playback_open,
                .close =        snd_geode_playback_close,
                .ioctl =        snd_pcm_lib_ioctl,
                .hw_params =    snd_geode_pcm_hw_params,
                .hw_free =      snd_geode_pcm_hw_free,
                .prepare =      snd_geode_pcm_prepare,
                .trigger =      snd_geode_pcm_trigger,
                .pointer =      snd_geode_pcm_pointer,
        };

each of callbacks is explained in the next section.

after setting the operators, pre-allocate the buffer.
simply call the following,

        snd_pcm_lib_preallocate_pci_pages_for_all(chip->pci, pcm, 64*1024, 64*1024);

it will allocate up to 64kB buffer as default.

... and the destructor?
not always necessary.  since the pcm device will be released by the
middle layer code automatically, you don't have to call destructor
explicitly.


***

ok, let me explain the detail of each pcm callback (ops).
all callbacks must return 0 if successful, or a negative number at any
error.
for retrieving the chip record from the given substream instance, you
can use the following macro.

        #define chip_t geode_t

        int xxx() {
                geode_t *chip = snd_pcm_substream_chip(substream);
                ...
        }

it's expanded with a magic-cast, so the cast-error is automatically
checked.


* open callback

        static int snd_xxx_open(snd_pcm_substream_t *subs);

  this is called when a pcm substream is opened.
  at least, here you have to init the runtime hardware record.
  typically, this is done by like this:

        static int snd_xxx_open(snd_pcm_substream_t *substream)
        {
                geode_t *chip = snd_pcm_substream_chip(substream);
                snd_pcm_runtime_t *runtime = substream->runtime;

                runtime->hw = snd_geode_playback;
                return 0;
        }

  where snd_geode_playback is the pre-defined hardware record.

        static snd_pcm_hardware_t snd_geode_playback = {
                .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,
        };

  the similar struct exists on ALSA 0.5.x driver, so you can guess the
  values.

  the info field contains the type and capabilities of this pcm.
  here you have to specify the MMAP and interleaved format.
  MMAP_VALID and BLOCK_TRANSFER are specified for OSS mmap mode.
  usually both are set.

  formats field contains the bit-flags of supported formats.
  rates field contains the bit-flags of supported rates.
  the rate bits are defined only for typical rates.  if your chip
  supports unconventional rates, you need to set up the constraint
  manually (explained later).
  the other fields are self-explanatory.
  please note that here, both min/max buffer and period sizes are
  specifed in bytes.

  some drivers allocate the private instace for each pcm substream.
  it can be stored in runtime->private_data.  since it's a void
  pointer, you should use magic-kmalloc and magic-cast for such an
  object.  the allocated object must be released in the close
  callback below.


* close callback

        static int snd_xxx_close(snd_pcm_substream_t *subs);

  obviously, this is called at close.
  any private instance for a pcm substream will be released here.


* ioctl callback

  this is used for any special action to pcm ioctls.
  but usually you can pass a generic ioctl callback,
  snd_pcm_lib_ioctl.


* hw_params callback

  static int snd_xxx_hw_params(snd_pcm_substream_t * substream,
                               snd_pcm_hw_params_t * hw_params);

  (this and hw_free callbacks exist only on ALSA 0.9.0.)

  this is called when hw_params is set up by the application.
  many hardware set-up should be done in this callback, including the
  allocation of buffers.
  parameters to be initialized are retrieved by params_xxx() macros.
  for allocating a buffer, you can call a helper function,

        snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));

  note that this callback may be called multiple times.
  thus, you need to take care not to allocate many times!

  this callback is schedulable.  this is important, because the
  prepare callback is non-schedulable.  that is, mutex or any
  schedule-related functions are available only in hw_params
  callback.


* hw_free callback

  static int snd_xxx_hw_free(snd_pcm_substream_t * substream);

  this is called to release the resources allocated via hw_params.
  for example, releasing the buffer is done like

        snd_pcm_lib_free_pages(substream);

  note that this function may be called multiple times, too.
  keep track whether the resource was alreay released.


* prepare callback

  static int snd_xxx_prepare(snd_pcm_substream_t * substream);

  this callback is called when the pcm is "prepared".
  you can set the format type, sample rate, etc. here.

  as mentioned above, this callback is non-schedulable.
  i.e. you cannot use mutex in this callback.

  in this and the follwoing callbacks, you can refer to the values via
  runtime record.  for example, to get the current rate or format,
  access to runtime->rate or runtime->format, respectively.

  note that the period and the buffer sizes are stored in "frames".
  on ALSA world, 1 frame = channels * samples-size.
  for conversion between frames and bytes, you can use the helper
  functions, frames_to_bytes() and bytes_to_frames().

  be careful that this callback will be called many times at each set
  up, too.


* trigger callback

  static int snd_xxx_trigger(snd_pcm_substream_t * substream, int cmd);

  this is called when the pcm is started, stopped or paused.
  which action is specifed in the second argument:
    SNDRV_PCM_TRIGGER_START, SNDRV_PCM_TRIGGER_STOP.

  this callback is also non-schedulable.


* pointer callback

  static snd_pcm_uframes_t snd_xxx_pointer(snd_pcm_substream_t * substream)

  this callback is called when the PCM middle layer inquires the
  current h/w position on the buffer.
  the position must be returned in frames, from 0 to buffer_size - 1.
  (note that it's not in bytes like ALSA 0.5.x!)

  this is called usually when snd_pcm_period_elapsed() 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.

  this callback is also non-schedulable.


***

the major rest of pcm stuff is the interrupt handler.
the role of 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.

1) generates an interrupt at the period (fragment) boundary

  this is the most frequently found type.
  in this case, you can call snd_pcm_period_elapsed() at each
  interrupt.

2) high-frequent timer interrupts
   (e.g. es1968 or ymfpci drivers)

  check the current h/w position and accumulates the processed sample
  length.  when the accumulated size overcomes the period size,
  call snd_pcm_period_elapsed() and reset the accumulator.

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 h/w pointer
and update to the latest status.


***

i thought to explain about the hw_constraint in this section, but i'm
tired and hungry now - please allow me to postpone this and the
ac97-mixer stuff tomorrow :)


ciao,

Takashi


-------------------------------------------------------
This SF.net email is sponsored by: Get the new Palm Tungsten T 
handheld. Power & Color in a compact size! 
http://ads.sourceforge.net/cgi-bin/redirect.pl?palm0002en
_______________________________________________
Alsa-devel mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/alsa-devel

Reply via email to