From: Rask Ingemann Lambertsen <rask(a)sygehus.dk>
- Add code to detect and PnP protocol to manage Media Vision Jazz16 sound
cards.
Tested with a Jazz16 based sound card and Linux 2.6.20.6.
Signed-off-by: Rask Ingemann Lambertsen <rask(a)sygehus.dk>
---
This patch depends on the patch "ALSA: Support Media Vision Jazz16 chips"
because the patched include/sound/sb.h is needed.
drivers/pnp/Kconfig | 12
drivers/pnp/Makefile | 1
drivers/pnp/jazz16pnp.c | 591 ++++++++++++++++++++++++++++++++++++++
3 files changed, 604 insertions(+)
Main changes since the last patch:
Request just one DMA channel (1, 3, 5 or 7).
Request port 0x388-0x38b for the OPL3 alias to avoid conflicts.
Use the recommended resources from the manual for the preferred option.
More SB port bases: 0x210, 0x230 and 0x250.
diff -rup linux-2.6.20.6-clean/drivers/pnp/Kconfig linux-2.6.20.6-ril/drivers/pnp/Kconfig
--- linux-2.6.20.6-clean/drivers/pnp/Kconfig 2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20.6-ril/drivers/pnp/Kconfig 2007-04-07 20:10:35.000000000 +0200
@@ -37,5 +37,17 @@ source "drivers/pnp/pnpbios/Kconfig"
source "drivers/pnp/pnpacpi/Kconfig"
+#
+# Jazz16 Plug and Play configuration
+#
+config JAZZ16PNP
+ bool "Jazz16 sound card Plug and Play support"
+ depends on PNP && ISA
+ help
+ Say Y here if you would like support for automatic configuration
+ of I/O ports, DMA channels and IRQ lines of Jazz16 sound cards.
+
+ Say Y if you have such a card. Otherwise, say N.
+
endmenu
diff -rup linux-2.6.20.6-clean/drivers/pnp/Makefile linux-2.6.20.6-ril/drivers/pnp/Makefile
--- linux-2.6.20.6-clean/drivers/pnp/Makefile 2007-02-04 19:44:54.000000000 +0100
+++ linux-2.6.20.6-ril/drivers/pnp/Makefile 2007-04-07 20:10:35.000000000 +0200
@@ -7,3 +7,4 @@ obj-y := core.o card.o driver.o resourc
obj-$(CONFIG_PNPACPI) += pnpacpi/
obj-$(CONFIG_PNPBIOS) += pnpbios/
obj-$(CONFIG_ISAPNP) += isapnp/
+obj-$(CONFIG_JAZZ16PNP) += jazz16pnp.o
--- /dev/null 2007-04-11 19:30:17.320748640 +0200
+++ linux-2.6.20.6-ril/drivers/pnp/jazz16pnp.c 2007-04-11 21:41:39.000000000 +0200
@@ -0,0 +1,591 @@
+/*
+ * Media Vision Jazz16 Plug & Play support.
+ *
+ * Copyright (c) 2007 Rask Ingemann Lambertsen <rask(a)sygehus.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver manages two devices:
+ *
+ * Device 0: The game port.
+ * Device 1: The SoundBlaster look-alike part.
+ *
+ * Sources:
+ * sound/oss/sb_common.c
+ * sys/dev/isa/sbdsp.c (OpenBSD)
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Rask Ingemann Lambertsen <rask(a)sygehus.dk>");
+MODULE_DESCRIPTION("Jazz16 Plug & Play support");
+MODULE_LICENSE("GPL");
+
+int jazz16pnp_disable = false; /* Disable Jazz16 PnP. */
+module_param_named(disable, jazz16pnp_disable, bool, false);
+MODULE_PARM_DESC(disable, "Jazz16 Plug & Play disable switch");
+
+int jazz16pnp_verbose = false; /* Verbosity level. */
+module_param_named(verbose, jazz16pnp_verbose, bool, false);
+MODULE_PARM_DESC(verbose, "Jazz16 Plug & Play verbosity level");
+
+#define PFX "jazz16pnp: "
+#define PFX_DEBUG KERN_DEBUG PFX
+#define PFX_INFO KERN_INFO PFX
+#define PFX_WARN KERN_WARNING PFX
+
+/* Jazz16 configuration. */
+#define SB_JAZZ16_CONFIG_PORT 0x201
+#define SB_JAZZ16_WAKEUP 0xaf
+#define SB_JAZZ16_SET_PORTS 0x50
+#define SB_JAZZ16_SET_DMAINTR 0xFB
+
+extern void *pnp_alloc(long size);
+extern void pnp_free_option(struct pnp_option *option);
+
+static struct pnp_dev *jazz16_gameport_dev = NULL;
+static struct pnp_dev *jazz16_sb_dev = NULL;
+static struct resource *jazz16pnp_fm_res = NULL;
+
+static uint jazz16_sb_port = 0, jazz16_sb_irq = 0;
+static uint jazz16_sb_dma1 = 0, jazz16_sb_dma2 = 0;
+static uint jazz16_mpu401_port = 0, jazz16_mpu401_irq = 0;
+
+static uint jazz16pnp_count = 0;
+
+static struct pnp_protocol jazz16pnp_protocol;
+
+static bool __init jazz16pnp_easy_option(struct pnp_dev *dev, uint pri,
+ struct pnp_port *port, uint nports,
+ struct pnp_irq *irq, uint nirqs,
+ struct pnp_dma *dma, uint ndmas)
+{
+ struct pnp_option *opt;
+ struct pnp_port *new_port;
+ struct pnp_irq *new_irq;
+ struct pnp_dma *new_dma;
+ uint i;
+
+ switch (pri) {
+ case PNP_RES_PRIORITY_PREFERRED:
+ case PNP_RES_PRIORITY_ACCEPTABLE:
+ case PNP_RES_PRIORITY_FUNCTIONAL:
+ opt = pnp_register_dependent_option(dev, pri);
+ break;
+
+ default:
+ opt = pnp_register_independent_option(dev);
+ break;
+ }
+ if (!opt)
+ return false;
+
+ for (i = 0; i < nports; i ++) {
+ new_port = pnp_alloc(sizeof(*new_port));
+ if (!new_port)
+ goto out_no_mem;
+ memcpy(new_port, &port[i], sizeof(*new_port));
+ pnp_register_port_resource(opt, new_port);
+ }
+ for (i = 0; i < nirqs; i ++) {
+ new_irq = pnp_alloc(sizeof(*new_irq));
+ if (!new_irq)
+ goto out_no_mem;
+ memcpy(new_irq, &irq[i], sizeof(*new_irq));
+ pnp_register_irq_resource(opt, new_irq);
+ }
+ for (i = 0; i < ndmas; i ++) {
+ new_dma = pnp_alloc(sizeof(*new_dma));
+ if (!new_dma)
+ goto out_no_mem;
+ memcpy(new_dma, &dma[i], sizeof(*new_dma));
+ pnp_register_dma_resource(opt, new_dma);
+ }
+ return true;
+
+out_no_mem:
+ return false;
+}
+
+static struct pnp_port jazz16pnp_game_ports[] __initdata = {
+ { .min = SB_JAZZ16_CONFIG_PORT,
+ .max = SB_JAZZ16_CONFIG_PORT,
+ .size = 1, .align = 0,
+ .flags = PNP_PORT_FLAG_FIXED, },
+};
+
+static int __init jazz16pnp_add_gameport(void)
+{
+ struct pnp_dev *dev;
+ struct pnp_id *dev_id;
+
+ if (!(dev = pnp_alloc(sizeof(*dev))))
+ goto out_no_dev;
+ if (!(dev_id = pnp_alloc(sizeof(*dev_id))))
+ goto out_no_id;
+ if (!jazz16pnp_easy_option(dev, PNP_RES_PRIORITY_INVALID,
+ jazz16pnp_game_ports, 1,
+ NULL, 0, NULL, 0))
+ goto out_no_option;
+
+ dev->protocol = &jazz16pnp_protocol;
+ dev->capabilities = PNP_READ;
+ dev->active = 1;
+ dev->number = 0;
+
+ pnp_init_resource_table(&dev->res);
+ dev->res.port_resource[0].start = SB_JAZZ16_CONFIG_PORT;
+ dev->res.port_resource[0].end = SB_JAZZ16_CONFIG_PORT;
+ dev->res.port_resource[0].flags = IORESOURCE_IO;
+
+ memcpy(dev_id->id, "PNPb02f", PNP_ID_LEN);
+ pnp_add_id(dev_id, dev);
+
+ jazz16_gameport_dev = dev;
+ pnp_add_device(dev);
+ return 0;
+
+out_no_option:
+ if (dev->independent)
+ pnp_free_option(dev->independent);
+ kfree(dev_id);
+out_no_id:
+ kfree(dev);
+out_no_dev:
+ return -ENOMEM;
+};
+
+static struct pnp_port jazz16pnp_pref_ports[] __initdata = {
+ { .min = 0x220, .max = 0x220, .align = 0x00, .size = 0x10 },
+ { .min = 0x330, .max = 0x330, .align = 0x00, .size = 0x02 },
+ { .min = 0x388, .max = 0x388, .align = 0x00, .size = 0x04 },
+};
+
+static struct pnp_port jazz16pnp_ports[] __initdata = {
+ { .min = 0x210, .max = 0x260, .align = 0x10, .size = 0x10 },
+ { .min = 0x300, .max = 0x330, .align = 0x10, .size = 0x02 },
+ { .min = 0x388, .max = 0x388, .align = 0x00, .size = 0x04 },
+};
+
+static struct pnp_irq jazz16pnp_pref_irqs[] __initdata = {
+ { .map[0] = (1 << 5),
+ .flags = IORESOURCE_IRQ_HIGHEDGE },
+ { .map[0] = (1 << 9),
+ .flags = IORESOURCE_IRQ_HIGHEDGE },
+};
+
+static struct pnp_irq jazz16pnp_irqs[] __initdata = {
+ { .map[0] = (1 << 3) | (1 << 5) | (1 << 7) |
+ (1 << 9) | (1 << 10) | (1 << 15),
+ .flags = IORESOURCE_IRQ_HIGHEDGE },
+ { .map[0] = (1 << 3) | (1 << 5) | (1 << 7) |
+ (1 << 9) | (1 << 10) | (1 << 15),
+ .flags = IORESOURCE_IRQ_HIGHEDGE },
+};
+
+static struct pnp_dma jazz16pnp_pref_dmas[] __initdata = {
+ { .map = (1 << 1), .flags = IORESOURCE_DMA_8BIT, },
+};
+
+static struct pnp_dma jazz16pnp_dmas[] __initdata = {
+ { .map = (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7),
+ .flags = IORESOURCE_DMA_8AND16BIT, },
+};
+
+static int __init jazz16pnp_add_sb(void)
+{
+ struct pnp_dev *sbdev;
+ struct pnp_id *sbid;
+
+ if (!(sbdev = pnp_alloc(sizeof(*sbdev))))
+ goto out_no_sbdev;
+ if (!(sbid = pnp_alloc(sizeof(*sbid))))
+ goto out_no_sbid;
+
+ if (!jazz16pnp_easy_option(sbdev, PNP_RES_PRIORITY_PREFERRED,
+ jazz16pnp_pref_ports, 3,
+ jazz16pnp_pref_irqs, 2,
+ jazz16pnp_pref_dmas, 1))
+ goto out_no_sboption;
+ if (!jazz16pnp_easy_option(sbdev, PNP_RES_PRIORITY_ACCEPTABLE,
+ jazz16pnp_ports, 3,
+ jazz16pnp_irqs, 2,
+ jazz16pnp_dmas, 1))
+ goto out_no_sboption;
+ if (!jazz16pnp_easy_option(sbdev, PNP_RES_PRIORITY_ACCEPTABLE,
+ jazz16pnp_ports, 3,
+ jazz16pnp_irqs, 1,
+ jazz16pnp_dmas, 1))
+ goto out_no_sboption;
+ memcpy(sbid->id, "PNPb00f", PNP_ID_LEN);
+ pnp_add_id(sbid, sbdev);
+
+ pnp_init_resource_table(&sbdev->res);
+ sbdev->capabilities = PNP_READ | PNP_WRITE | PNP_CONFIGURABLE | PNP_DISABLE;
+ sbdev->protocol = &jazz16pnp_protocol;
+ sbdev->number = 1;
+
+ jazz16_sb_dev = sbdev;
+ pnp_add_device(sbdev);
+ return 0;
+
+out_no_sboption:
+ if (sbdev->dependent)
+ pnp_free_option(sbdev->dependent);
+ kfree(sbid);
+out_no_sbid:
+ kfree(sbdev);
+out_no_sbdev:
+ return -ENOMEM;
+}
+
+/* Configure the SB and MPU ports. Passing 0 for sbport disables the Jazz16. */
+static void jazz16_port_setup(const uint sbport, const uint mpuport)
+{
+ u8 config;
+
+ switch (sbport) {
+ case 0x210:
+ case 0x220:
+ case 0x230:
+ case 0x240:
+ case 0x250:
+ case 0x260:
+ config = sbport & 0x70;
+ jazz16_sb_port = sbport;
+ break;
+
+ default:
+ config = 0;
+ jazz16_sb_port = 0;
+ break;
+ }
+
+ switch (mpuport) {
+ case 0x300:
+ case 0x310:
+ case 0x320:
+ case 0x330:
+ config |= (mpuport >> 4) & 0x07;
+ jazz16_mpu401_port = mpuport;
+ break;
+
+ default:
+ if (jazz16_sb_port)
+ BUG();
+ }
+ if (jazz16pnp_verbose)
+ printk(PFX_DEBUG "Setting SB port = %#x, MPU port = %#x.\n",
+ jazz16_sb_port, jazz16_mpu401_port);
+ outb(SB_JAZZ16_WAKEUP, SB_JAZZ16_CONFIG_PORT);
+ outb(SB_JAZZ16_SET_PORTS, SB_JAZZ16_CONFIG_PORT);
+ outb(config, SB_JAZZ16_CONFIG_PORT);
+}
+
+static u8 jazz16_irq_config(uint irq)
+{
+ switch (irq) {
+ case 3: return 3;
+ case 5: return 1;
+ case 7: return 4;
+ case 9: return 2;
+ case 10: return 5;
+ case 15: return 6;
+ default: return 0;
+ }
+}
+
+static u8 jazz16_dma_config(uint dma)
+{
+ switch (dma) {
+ case 1: return 1;
+ case 3: return 2;
+ case 5: return 3;
+ case 7: return 4;
+ default: return 0;
+ }
+}
+
+/* The my_sbdsp_xxx functions were ripped from sound/isa/sb/sb_common.c. */
+#define BUSY_LOOPS 100000
+
+static int my_sbdsp_command(unsigned int base, unsigned char val)
+{
+ int i;
+ for (i = BUSY_LOOPS; i; i--)
+ if ((inb(SBP1(base, STATUS)) & 0x80) == 0) {
+ outb(val, SBP1(base, COMMAND));
+ return 1;
+ }
+ return 0;
+}
+
+static int my_sbdsp_get_byte(unsigned int base)
+{
+ int val;
+ int i;
+ for (i = BUSY_LOOPS; i; i--) {
+ if (inb(SBP1(base, DATA_AVAIL)) & 0x80) {
+ val = inb(SBP1(base, READ));
+ return val;
+ }
+ }
+ return -ENODEV;
+}
+
+static int my_sbdsp_reset(unsigned int base)
+{
+ int i;
+
+ outb(1, SBP1(base, RESET));
+ udelay(10);
+ outb(0, SBP1(base, RESET));
+ udelay(30);
+ for (i = BUSY_LOOPS; i; i--)
+ if (inb(SBP1(base, DATA_AVAIL)) & 0x80) {
+ if (inb(SBP1(base, READ)) == 0xaa)
+ return 0;
+ else
+ break;
+ }
+ return -ENODEV;
+}
+
+static int jazz16_irq_dma_setup(unsigned int base, uint sbirq, uint mpuirq, uint dma8, uint dma16)
+{
+ u8 dma_config, irq_config;
+
+ if (jazz16pnp_verbose)
+ printk(PFX_DEBUG "Setting SB irq %u, dma %u&%u, MPU irq %u.\n",
+ sbirq, dma8, dma16, mpuirq);
+ irq_config = jazz16_irq_config(sbirq) | jazz16_irq_config(mpuirq) << 4;
+ dma_config = jazz16_dma_config(dma8) | jazz16_dma_config(dma16) << 4;
+ if (!my_sbdsp_command(base, SB_JAZZ16_SET_DMAINTR))
+ return 0;
+
+ if (!my_sbdsp_command(base, dma_config))
+ return 0;
+ jazz16_sb_dma1 = dma8;
+ jazz16_sb_dma2 = dma16;
+
+ if (!my_sbdsp_command(base, irq_config))
+ return 0;
+ jazz16_sb_irq = sbirq;
+ jazz16_mpu401_irq = mpuirq;
+ return 1;
+}
+
+static int jazz16pnp_get_resources(struct pnp_dev *dev, struct pnp_resource_table *res)
+{
+ pnp_init_resource_table(res);
+ if (dev == jazz16_gameport_dev) {
+ res->port_resource[0].start = res->port_resource[0].end = SB_JAZZ16_CONFIG_PORT;
+ res->port_resource[0].flags = IORESOURCE_IO;
+ } else if (dev == jazz16_sb_dev) {
+ if (!jazz16_sb_port)
+ return 0;
+ res->port_resource[0].start = jazz16_sb_port;
+ res->port_resource[0].end = jazz16_sb_port + 0x10 - 1;
+ res->port_resource[0].flags = IORESOURCE_IO;
+
+ res->irq_resource[0].start = jazz16_sb_irq;
+ res->irq_resource[0].end = jazz16_sb_irq;
+ res->irq_resource[0].flags = IORESOURCE_IRQ;
+ if (jazz16_mpu401_irq) {
+ res->irq_resource[1].start = jazz16_mpu401_irq;
+ res->irq_resource[1].end = jazz16_mpu401_irq;
+ res->irq_resource[1].flags = IORESOURCE_IRQ;
+ }
+
+ res->dma_resource[0].start = jazz16_sb_dma1;
+ res->dma_resource[0].end = jazz16_sb_dma1;
+ res->dma_resource[0].flags = IORESOURCE_DMA;
+ if (jazz16_sb_dma2 != jazz16_sb_dma1) {
+ res->dma_resource[1].start = jazz16_sb_dma2;
+ res->dma_resource[1].end = jazz16_sb_dma2;
+ res->dma_resource[1].flags = IORESOURCE_DMA;
+ }
+ res->port_resource[1].start = jazz16_mpu401_port;
+ res->port_resource[1].end = jazz16_mpu401_port + 2 - 1;
+ res->port_resource[1].flags = IORESOURCE_IO;
+ } else
+ return -EINVAL;
+ return 0;
+}
+
+/* Disables the device if supported. */
+static int jazz16pnp_disable_resources(struct pnp_dev *dev)
+{
+ if (dev == jazz16_gameport_dev) {
+ return -ENOSYS;
+ } else if (dev == jazz16_sb_dev) {
+ if (jazz16_sb_port)
+ jazz16_irq_dma_setup(jazz16_sb_port, 0, 0, 0, 0);
+ jazz16_port_setup(0, 0);
+ if (jazz16pnp_fm_res) {
+ release_resource(jazz16pnp_fm_res);
+ jazz16pnp_fm_res = NULL;
+ }
+ dev->active = false;
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+}
+
+#define PNP_RES_IS_SET(res,type,TYPE,i) \
+ (((res)->type##_resource[i].flags & (IORESOURCE_##TYPE | IORESOURCE_UNSET)) \
+ == IORESOURCE_##TYPE)
+
+static int jazz16pnp_set_resources(struct pnp_dev *dev, struct pnp_resource_table *res)
+{
+ unsigned int sbirq, mpuirq, dma1, dma2;
+
+ if (dev == jazz16_gameport_dev)
+ return -ENOSYS;
+
+ else if (dev == jazz16_sb_dev) {
+ if (PNP_RES_IS_SET(res, dma, DMA, 0))
+ dma1 = res->dma_resource[0].start;
+ else
+ return -EINVAL;
+ if (PNP_RES_IS_SET(res, dma, DMA, 1))
+ dma2 = res->dma_resource[1].start;
+ else
+ dma2 = dma1; /* "half duplex" operation. */
+ if (PNP_RES_IS_SET(res, irq, IRQ, 0))
+ sbirq = res->irq_resource[0].start;
+ else
+ return -EINVAL;
+ if (PNP_RES_IS_SET(res, irq, IRQ, 1))
+ mpuirq = res->irq_resource[1].start;
+ else
+ mpuirq = 0; /* MPU-401 driver supports this. */
+ if (! PNP_RES_IS_SET(res, port, IO, 0))
+ return -EINVAL;
+ if (! PNP_RES_IS_SET(res, port, IO, 1))
+ return -EINVAL;
+
+ /* Block the OPL3 alias to avoid conflicts. */
+ jazz16pnp_fm_res = request_region(0x388, 4, "SoundBlaster FM");
+ /* Avoid setting the mpu port after setting the sb port
+ because doing so will glitch the mpu port from the
+ previous one to the new one. */
+ jazz16_port_setup(res->port_resource[0].start,
+ res->port_resource[1].start);
+ jazz16_irq_dma_setup(jazz16_sb_port, sbirq, mpuirq, dma1, dma2);
+ dev->active = true;
+ return 0;
+ } else
+ return -EINVAL;
+}
+#undef PNP_RES_IS_SET
+
+static struct pnp_protocol jazz16pnp_protocol = {
+ .name = "Jazz16 PnP",
+ .get = jazz16pnp_get_resources,
+ .set = jazz16pnp_set_resources,
+ .disable = jazz16pnp_disable_resources,
+};
+
+static uint __init jazz16_check(unsigned int base)
+{
+ if (my_sbdsp_reset(base))
+ return 0;
+ if (!(my_sbdsp_command(base, SB_DSP_GET_JAZZ_VERSION)))
+ return 0;
+ return SB_VERSION_IS_JAZZ16(my_sbdsp_get_byte(base));
+}
+
+static int __init jazz16pnp_init(void)
+{
+ int err = 0;
+ unsigned int base, mpubase;
+
+ if (jazz16pnp_disable == 1) {
+ printk(PFX_INFO "Jazz16 Plug & Play support disabled.\n");
+ return 0;
+ }
+ if (!request_region(SB_JAZZ16_CONFIG_PORT, 1, "Jazz16 PnP probe"))
+ return -EBUSY;
+
+ for (mpubase = 0x300; mpubase <= 0x330; mpubase += 0x10)
+ if (request_region(mpubase, 2, "Jazz16 PnP probe"))
+ break;
+ if (mpubase > 0x330)
+ goto out_no_mpubase;
+
+ if (jazz16pnp_verbose)
+ printk(PFX_DEBUG "Scanning for Jazz16 cards...\n");
+
+ for (base = 0x260; base >= 0x210; base -= 0x10) {
+ if (!request_region(base, 16, "Jazz16 PnP probe"))
+ continue;
+ if (jazz16pnp_verbose)
+ printk(PFX_DEBUG "Probing at %#x (with MPU at %#x).\n",
+ base, mpubase);
+ jazz16_port_setup(base, mpubase);
+ if (jazz16_check(base))
+ jazz16pnp_count++;
+ jazz16_irq_dma_setup(base, 0, 0, 0, 0);
+ jazz16_port_setup(0, 0);
+ release_region(base, 16);
+ if (jazz16pnp_count)
+ break;
+ }
+ if (base < 0x210)
+ goto out_no_sbbase;
+ if (!jazz16pnp_count) {
+ err = -ENODEV;
+ goto out_no_devices;
+ }
+ if (pnp_register_protocol(&jazz16pnp_protocol) < 0)
+ err = -ENOMEM;
+ else {
+ if (jazz16pnp_add_gameport())
+ printk(PFX_WARN "Failed to add game port.\n");
+ if (jazz16pnp_add_sb())
+ printk(PFX_WARN "Failed to add SB port.\n");
+ err = 0;
+ }
+out_no_devices:
+ printk(PFX_INFO "%u Jazz16 card%s detected.\n", jazz16pnp_count,
+ jazz16pnp_count == 1 ? "" : "s");
+out_no_sbbase:
+ release_region(mpubase, 2);
+out_no_mpubase:
+ release_region(SB_JAZZ16_CONFIG_PORT, 1);
+ return err;
+}
+module_init(jazz16pnp_init);
+
+static void __exit jazz16pnp_exit(void)
+{
+ pnp_unregister_protocol(&jazz16pnp_protocol);
+}
+module_exit(jazz16pnp_exit);
--
Rask Ingemann Lambertsen