}
// (7)
- pci_set_drvdata(pci, chip);
+ pci_set_drvdata(pci, card);
dev++;
return 0;
}
// destructor -- see "Destructor" sub-section
static void __devexit snd_mychip_remove(struct pci_dev *pci)
{
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(pci), return);
- if (chip)
- snd_card_free(chip->card);
+ snd_card_free(pci_get_drvdata(pci));
pci_set_drvdata(pci, NULL);
}
]]>
<informalexample>
<programlisting>
<![CDATA[
- pci_set_drvdata(pci, chip);
+ pci_set_drvdata(pci, card);
dev++;
return 0;
]]>
</programlisting>
</informalexample>
- In the above, the chip record is stored. This pointer is
+ In the above, the card record is stored. This pointer is
referred in the remove callback and power-management
callbacks, too.
- If the card doesn't support the suspend/resume, you can store
- the card pointer instead of the chip pointer, so that
- <function>snd_card_free</function> can be called directly
- without cast in the remove callback. But anyway, be sure
- which pointer is used.
</para>
</section>
</section>
<![CDATA[
static void __devexit snd_mychip_remove(struct pci_dev *pci)
{
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(pci), return);
- if (chip)
- snd_card_free(chip->card);
+ snd_card_free(pci_get_drvdata(pci));
pci_set_drvdata(pci, NULL);
}
]]>
</programlisting>
</informalexample>
- The above code assumes that the chip is allocated
- with snd_magic stuff and
- has the field to hold the card pointer (see <link
- linkend="card-management"><citetitle>the next
- section</citetitle></link>).
+ The above code assumes that the card pointer is set to the PCI
+ driver data.
</para>
</section>
// initialization of the module
static int __init alsa_card_mychip_init(void)
{
- int err;
-
- if ((err = pci_module_init(&driver)) < 0) {
- #ifdef MODULE
- printk(KERN_ERR "My chip soundcard not found "
- "or device busy\n");
- #endif
- return err;
- }
- return 0;
+ return pci_module_init(&driver);
}
// clean up the module
<![CDATA[
static int __init alsa_card_mychip_init(void)
{
- int err;
-
- if ((err = pci_module_init(&driver)) < 0) {
- #ifdef MODULE
- printk(KERN_ERR "My chip soundcard not found"
- " or device busy\n");
- #endif
- return err;
- }
- return 0;
+ return pci_module_init(&driver);
}
static void __exit alsa_card_mychip_exit(void)
</para>
<para>
- Basic jobs of suspend/resume are done in
- <structfield>suspend</structfield> and
- <structfield>resume</structfield> callbacks of
- <structname>pci_driver</structname> struct. Unfortunately, the
- API of these callbacks was changed at the middle time of Linux
- 2.4.x, if you want to keep the support for older kernels, you
- have to write two different callbacks. The example below is the
- skeleton callbacks which just call the real suspend and resume
- functions.
+ ALSA provides the common power-management layer. Each card driver
+ needs to have only low-level suspend and resume callbacks.
<informalexample>
<programlisting>
<![CDATA[
- #ifndef PCI_OLD_SUSPEND
- static int snd_my_suspend(struct pci_dev *dev, u32 state)
+ #ifdef CONFIG_PM
+ static int snd_my_suspend(snd_card_t *card, unsigned int state)
{
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(dev), return -ENXIO);
- mychip_suspend(chip);
+ .... // do things for suspsend
return 0;
}
- static int snd_my_resume(struct pci_dev *dev)
+ static int snd_my_resume(snd_card_t *card, unsigned int state)
{
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(dev), return -ENXIO);
- mychip_resume(chip);
+ .... // do things for suspsend
return 0;
}
- #else
- static void snd_my_suspend(struct pci_dev *dev)
- {
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(dev), return);
- mychip_suspend(chip);
- }
- static void snd_mychip_resume(struct pci_dev *dev)
- {
- mychip_t *chip = snd_magic_cast(mychip_t,
- pci_get_drvdata(dev), return);
- mychip_resume(chip);
- }
#endif
]]>
</programlisting>
</informalexample>
</para>
- <para>
- For keeping the readability of 2.6 source code, it's recommended to
- separate the above ifdef condition as the patch file in alsa-driver
- directory.
- See <filename>alsa-driver/pci/ali5451.c</filename> for example.
- </para>
-
<para>
The scheme of the real suspend job is as following.
<orderedlist>
- <listitem><para>Check whether the power-state is already D3hot. If yes, skip the job.</para></listitem>
+ <listitem><para>Retrieve the chip data from pm_private_data field.</para></listitem>
<listitem><para>Call <function>snd_pcm_suspend_all()</function> to suspend the running PCM streams.</para></listitem>
<listitem><para>Save the register values if necessary.</para></listitem>
<listitem><para>Stop the hardware if necessary.</para></listitem>
<informalexample>
<programlisting>
<![CDATA[
- static void mychip_suspend(mychip_t *chip)
+ static int mychip_suspend(snd_card_t *card, unsigned int state)
{
- snd_card_t *card = chip->card;
// (1)
- if (card->power_state == SNDRV_CTL_POWER_D3hot)
- return;
+ mychip_t *chip = snd_magic_cast(mychip_t, card->pm_private_data,
+ return -ENXIO);
// (2)
snd_pcm_suspend_all(chip->pcm);
// (3)
snd_mychip_stop_hardware(chip);
// (5)
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return 0;
}
]]>
</programlisting>
The scheme of the real resume job is as following.
<orderedlist>
- <listitem><para>Check whether the power-state is already D0.
- If yes, skip the job.</para></listitem>
+ <listitem><para>Retrieve the chip data from pm_private_data field.</para></listitem>
<listitem><para>Enable the pci device again by calling
<function>pci_enable_device()</function>.</para></listitem>
<listitem><para>Re-initialize the chip.</para></listitem>
<![CDATA[
static void mychip_resume(mychip_t *chip)
{
- snd_card_t *card = chip->card;
// (1)
- if (card->power_state == SNDRV_CTL_POWER_D0)
- return;
+ mychip_t *chip = snd_magic_cast(mychip_t, card->pm_private_data,
+ return -ENXIO);
// (2)
pci_enable_device(chip->pci);
// (3)
snd_mychip_restart_chip(chip);
// (7)
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- }
-]]>
- </programlisting>
- </informalexample>
- </para>
-
- <para>
- In addition to the callbacks above, you should define a callback
- for the changes via the ALSA control interface. It's defined
- like below:
-
- <informalexample>
- <programlisting>
-<![CDATA[
- static int snd_mychip_set_power_state(snd_card_t *card,
- unsigned int power_state)
- {
- mychip_t *chip = snd_magic_cast(mychip_t,
- card->power_state_private_data, return -ENXIO);
- switch (power_state) {
- case SNDRV_CTL_POWER_D0:
- case SNDRV_CTL_POWER_D1:
- case SNDRV_CTL_POWER_D2:
- mychip_resume(chip);
- break;
- case SNDRV_CTL_POWER_D3hot:
- case SNDRV_CTL_POWER_D3cold:
- mychip_suspend(chip);
- break;
- default:
- return -EINVAL;
- }
return 0;
}
]]>
{
....
snd_card_t *card;
+ mychip_t *chip;
....
- #ifdef CONFIG_PM
- card->set_power_state = snd_mychip_set_power_state;
- card->power_state_private_data = chip;
- #endif
+ snd_card_set_pm_callback(card, snd_my_suspend, snd_my_resume, chip);
....
}
]]>
</programlisting>
</informalexample>
+
+ Here you don't have to put ifdef CONFIG_PM around, since it's already
+ checked in the header and expanded to empty if not needed.
</para>
<para>
If you need a space for saving the registers, you'll need to
- allocate the buffer for it here, too, since you cannot call
- <function>kmalloc()</function> with
- <constant>GFP_KERNEL</constant> flag or
- <function>vmalloc()</function> in the suspend callback.
+ 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.
</para>
<para>
And next, set suspend/resume callbacks to the pci_driver,
+ This can be done by passing a macro SND_PCI_PM_CALLBACKS
+ in the pci_driver struct. This macro is expanded to the correct
+ (global) callbacks if CONFIG_PM is set.
<informalexample>
<programlisting>
<![CDATA[
static struct pci_driver driver = {
.name = "My Chip",
- ....
- #ifdef CONFIG_PM
- .suspend = snd_mychip_suspend,
- .resume = snd_mychip_resume,
- #endif
+ .id_table = snd_my_ids,
+ .probe = snd_my_probe,
+ .remove = __devexit_p(snd_my_remove),
+ SND_PCI_PM_CALLBACKS
};
]]>
</programlisting>
<para>
The module parameters must be declared with the standard
- <function>MODULE_PARM()</function> and
+ <function>module_param()()</function>,
+ <function>module_param_array()()</function> and
<function>MODULE_PARM_DESC()</function> macros. The ALSA provides
an additional macro, <function>MODULE_PARM_SYNTAX()</function>,
for describing its syntax. The strings will be written to
<![CDATA[
#define CARD_NAME "My Chip"
- MODULE_PARM(index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
+ static int boot_devs;
+ module_param_array(index, int, boot_devs, 0444);
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
MODULE_PARM_SYNTAX(index, SNDRV_INDEX_DESC);
- MODULE_PARM(id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
+ module_param_array(id, charp, boot_devs, 0444);
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
MODULE_PARM_SYNTAX(id, SNDRV_ID_DESC);
- MODULE_PARM(enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
+ module_param_array(enable, bool, boot_devs, 0444);
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
MODULE_PARM_SYNTAX(enable, SNDRV_ENABLE_DESC);
]]>
</programlisting>
</informalexample>
+
+ Here boot_devs is passed but simply ignored since we don't care
+ the number of parsed parameters.
</para>
<para>
</informalexample>
</para>
- <para>
- For building the driver into kernel, you should define the
- <function>setup()</function> function in addition, too.
- ALSA provides <function>get_id()</function> function to retrieve
- a string argument from the kernel boot parameters.
-
- <informalexample>
- <programlisting>
-<![CDATA[
- #ifndef MODULE
-
- /* format is: snd-mychip=enable,index,id */
-
- static int __init alsa_card_mychip_setup(char *str)
- {
- static unsigned __initdata nr_dev = 0;
-
- if (nr_dev >= SNDRV_CARDS)
- return 0;
- (void)(get_option(&str,&enable[nr_dev]) == 2 &&
- get_option(&str,&index[nr_dev]) == 2 &&
- get_id(&str,&id[nr_dev]) == 2);
- nr_dev++;
- return 1;
- }
-
- __setup("snd-mychip=", alsa_card_mychip_setup);
-
- #endif /* ifndef MODULE */
-]]>
- </programlisting>
- </informalexample>
- </para>
</chapter>
Suppose that you'll create a new PCI driver for the card
<quote>xyz</quote>. The card module name would be
snd-xyz. The new driver is usually put into alsa-driver
- tree. Then the driver is evaluated, audited and tested
+ tree, <filename>alsa-driver/pci</filename> directory in
+ the case of PCI cards.
+ Then the driver is evaluated, audited and tested
by developers and users. After a certain time, the driver
- will go to alsa-kernel tree and eventually integrated into
- Linux 2.6 tree.
+ will go to alsa-kernel tree (to the corresponding directory,
+ such as <filename>alsa-kernel/pci</filename>) and eventually
+ integrated into Linux 2.6 tree (the directory would be
+ <filename>linux/sound/pci</filename>).
</para>
<para>
<programlisting>
<![CDATA[
snd-xyz-objs := xyz.o
- extra-obj-$(CONFIG_SND_XYZ) += snd-xyz.o
+ obj-$(CONFIG_SND_XYZ) += snd-xyz.o
]]>
</programlisting>
</informalexample>
<informalexample>
<programlisting>
<![CDATA[
- config SND_BT87X
- tristate "Foobar XYX"
+ config SND_XYZ
+ tristate "Foobar XYZ"
depends on SND
select SND_PCM
help
<orderedlist>
<listitem>
<para>
- Add a new directory (xyz) to extra-subdir-y list in alsa-driver/pci/Makefile
+ Add a new directory (<filename>xyz</filename>) in
+ <filename>alsa-driver/pci/Makefile</filename> like below
<informalexample>
<programlisting>
<listitem>
<para>
- Under the directory xyz, create a Makefile
+ Under the directory <filename>xyz</filename>, create a Makefile
<example>
<title>Sample Makefile for a driver xyz</title>