ALSA: opti-miro: add PnP detection
Krzysztof Helt [Mon, 30 Nov 2009 06:46:56 +0000 (07:46 +0100)]
The PCM12 and PCM20 can be set into the ISA PnP mode. The PCM12 PnP
was sold as the PnP device.
Add code to handle detection of these cards using ISA PnP framework.

Tested on the PCM20 in PnP mode. The PCM12 PnP has the same MS Windows
INF file except for a card name displayed for user.

Signed-off-by: Krzysztof Helt <krzysztof.h1@wp.pl>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

sound/isa/opti9xx/miro.c

index c67bc3c..6123c75 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/init.h>
 #include <linux/err.h>
 #include <linux/isa.h>
+#include <linux/pnp.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
 #include <linux/ioport.h>
@@ -60,6 +61,9 @@ static int dma1 = SNDRV_DEFAULT_DMA1;         /* 0,1,3 */
 static int dma2 = SNDRV_DEFAULT_DMA1;          /* 0,1,3 */
 static int wss;
 static int ide;
+#ifdef CONFIG_PNP
+static int isapnp = 1;                         /* Enable ISA PnP detection */
+#endif
 
 module_param(index, int, 0444);
 MODULE_PARM_DESC(index, "Index value for miro soundcard.");
@@ -83,6 +87,10 @@ module_param(wss, int, 0444);
 MODULE_PARM_DESC(wss, "wss mode");
 module_param(ide, int, 0444);
 MODULE_PARM_DESC(ide, "enable ide port");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
 
 #define OPTi9XX_HW_DETECT      0
 #define OPTi9XX_HW_82C928      1
@@ -131,6 +139,21 @@ static char * snd_opti9xx_names[] = {
        "82C930", "82C931", "82C933"
 };
 
+static int snd_miro_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_miro_pnpids[] = {
+       /* PCM20 and PCM12 in PnP mode */
+       { .id = "MIR0924",
+         .devs = { { "MIR0000" }, { "MIR0002" }, { "MIR0005" } }, },
+       { .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_miro_pnpids);
+
+#endif /* CONFIG_PNP */
+
 /* 
  *  ACI control
  */
@@ -781,17 +804,23 @@ static int __devinit snd_miro_init(struct snd_miro *chip,
        chip->mpu_port = -1;
        chip->mpu_irq = -1;
 
+       chip->pwd_reg = 3;
+
+#ifdef CONFIG_PNP
+       if (isapnp && chip->mc_base)
+               /* PnP resource gives the least 10 bits */
+               chip->mc_base |= 0xc00;
+       else
+#endif
+               chip->mc_base = 0xf8c;
+
        switch (hardware) {
        case OPTi9XX_HW_82C929:
-               chip->mc_base = 0xf8c;
                chip->password = 0xe3;
-               chip->pwd_reg = 3;
                break;
 
        case OPTi9XX_HW_82C924:
-               chip->mc_base = 0xf8c;
                chip->password = 0xe5;
-               chip->pwd_reg = 3;
                break;
 
        default:
@@ -1014,17 +1043,22 @@ static int __devinit snd_miro_configure(struct snd_miro *chip)
                return -EINVAL;
        }
 
-       switch (chip->wss_base) {
-       case 0x530:
+       /* PnP resource says it decodes only 10 bits of address */
+       switch (chip->wss_base & 0x3ff) {
+       case 0x130:
+               chip->wss_base = 0x530;
                wss_base_bits = 0x00;
                break;
-       case 0x604:
+       case 0x204:
+               chip->wss_base = 0x604;
                wss_base_bits = 0x03;
                break;
-       case 0xe80:
+       case 0x280:
+               chip->wss_base = 0xe80;
                wss_base_bits = 0x01;
                break;
-       case 0xf40:
+       case 0x340:
+               chip->wss_base = 0xf40;
                wss_base_bits = 0x02;
                break;
        default:
@@ -1238,7 +1272,7 @@ static int __devinit snd_card_miro_aci_detect(struct snd_card *card,
 static void snd_card_miro_free(struct snd_card *card)
 {
        struct snd_miro *miro = card->private_data;
-        
+
        release_and_free_resource(miro->res_aci_port);
        if (miro->aci)
                miro->aci->aci_port = 0;
@@ -1370,6 +1404,12 @@ static int __devinit snd_miro_probe(struct snd_card *card)
 
 static int __devinit snd_miro_isa_match(struct device *devptr, unsigned int n)
 {
+#ifdef CONFIG_PNP
+       if (snd_miro_pnp_is_probed)
+               return 0;
+       if (isapnp)
+               return 0;
+#endif
        return 1;
 }
 
@@ -1487,14 +1527,155 @@ static struct isa_driver snd_miro_driver = {
        },
 };
 
+#ifdef CONFIG_PNP
+
+static int __devinit snd_card_miro_pnp(struct snd_miro *chip,
+                                       struct pnp_card_link *card,
+                                       const struct pnp_card_device_id *pid)
+{
+       struct pnp_dev *pdev;
+       int err;
+       struct pnp_dev *devmpu;
+       struct pnp_dev *devmc;
+
+       pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+       if (pdev == NULL)
+               return -EBUSY;
+
+       devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+       if (devmpu == NULL)
+               return -EBUSY;
+
+       devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+       if (devmc == NULL)
+               return -EBUSY;
+
+       err = pnp_activate_dev(pdev);
+       if (err < 0) {
+               snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+               return err;
+       }
+
+       err = pnp_activate_dev(devmc);
+       if (err < 0) {
+               snd_printk(KERN_ERR "OPL syntg pnp configure failure: %d\n",
+                                   err);
+               return err;
+       }
+
+       port = pnp_port_start(pdev, 1);
+       fm_port = pnp_port_start(pdev, 2) + 8;
+
+       /*
+        * The MC(0) is never accessed and the miroSOUND PCM20 card does not
+        * include it in the PnP resource range. OPTI93x include it.
+        */
+       chip->mc_base = pnp_port_start(devmc, 0) - 1;
+       chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+
+       irq = pnp_irq(pdev, 0);
+       dma1 = pnp_dma(pdev, 0);
+       dma2 = pnp_dma(pdev, 1);
+
+       if (mpu_port > 0) {
+               err = pnp_activate_dev(devmpu);
+               if (err < 0) {
+                       snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+                       mpu_port = -1;
+                       return err;
+               }
+               mpu_port = pnp_port_start(devmpu, 0);
+               mpu_irq = pnp_irq(devmpu, 0);
+       }
+       return 0;
+}
+
+static int __devinit snd_miro_pnp_probe(struct pnp_card_link *pcard,
+                                       const struct pnp_card_device_id *pid)
+{
+       struct snd_card *card;
+       int err;
+       struct snd_miro *miro;
+
+       if (snd_miro_pnp_is_probed)
+               return -EBUSY;
+       if (!isapnp)
+               return -ENODEV;
+       err = snd_card_create(index, id, THIS_MODULE,
+                               sizeof(struct snd_miro), &card);
+       if (err < 0)
+               return err;
+
+       card->private_free = snd_card_miro_free;
+       miro = card->private_data;
+
+       err = snd_card_miro_pnp(miro, pcard, pid);
+       if (err) {
+               snd_card_free(card);
+               return err;
+       }
+
+       /* only miroSOUND PCM20 and PCM12 == OPTi924 */
+       err = snd_miro_init(miro, OPTi9XX_HW_82C924);
+       if (err) {
+               snd_card_free(card);
+               return err;
+       }
+
+       err = snd_miro_opti_check(miro);
+       if (err) {
+               snd_printk(KERN_ERR "OPTI chip not found\n");
+               snd_card_free(card);
+               return err;
+       }
+
+       snd_card_set_dev(card, &pcard->card->dev);
+       err = snd_miro_probe(card);
+       if (err < 0) {
+               snd_card_free(card);
+               return err;
+       }
+       pnp_set_card_drvdata(pcard, card);
+       snd_miro_pnp_is_probed = 1;
+       return 0;
+}
+
+static void __devexit snd_miro_pnp_remove(struct pnp_card_link * pcard)
+{
+       snd_card_free(pnp_get_card_drvdata(pcard));
+       pnp_set_card_drvdata(pcard, NULL);
+       snd_miro_pnp_is_probed = 0;
+}
+
+static struct pnp_card_driver miro_pnpc_driver = {
+       .flags          = PNP_DRIVER_RES_DISABLE,
+       .name           = "miro",
+       .id_table       = snd_miro_pnpids,
+       .probe          = snd_miro_pnp_probe,
+       .remove         = __devexit_p(snd_miro_pnp_remove),
+};
+#endif
+
 static int __init alsa_card_miro_init(void)
 {
+#ifdef CONFIG_PNP
+       pnp_register_card_driver(&miro_pnpc_driver);
+       if (snd_miro_pnp_is_probed)
+               return 0;
+       pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
        return isa_register_driver(&snd_miro_driver, 1);
 }
 
 static void __exit alsa_card_miro_exit(void)
 {
-       isa_unregister_driver(&snd_miro_driver);
+       if (!snd_miro_pnp_is_probed) {
+               isa_unregister_driver(&snd_miro_driver);
+               return;
+       }
+#ifdef CONFIG_PNP
+       pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
 }
 
 module_init(alsa_card_miro_init)