[alsa-devel] [PATCH] PnP: Protocol for Media Vision Jazz16 sound cards
From: Rask Ingemann Lambertsen rask@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.
Signed-off-by: Rask Ingemann Lambertsen rask@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 | 555 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 568 insertions(+)
--- linux-2.6.20/drivers/pnp/Makefile-orig 2007-02-19 23:24:22.000000000 +0100 +++ linux-2.6.20/drivers/pnp/Makefile 2007-02-19 23:24:22.000000000 +0100 @@ -8,3 +8,4 @@ obj-$(CONFIG_PNPACPI) += pnpacpi/ obj-$(CONFIG_PNPACPI) += pnpacpi/ obj-$(CONFIG_PNPBIOS) += pnpbios/ obj-$(CONFIG_ISAPNP) += isapnp/ +obj-$(CONFIG_JAZZ16PNP) += jazz16pnp.o --- linux-2.6.20/drivers/pnp/jazz16pnp.c-orig 2007-03-11 22:11:58.000000000 +0100 +++ linux-2.6.20/drivers/pnp/jazz16pnp.c 2007-03-25 21:27:18.000000000 +0200 @@ -0,0 +1,555 @@ +/* + * Media Vision Jazz16 Plug & Play support. + * + * Copyright (c) 2007 Rask Ingemann Lambertsen rask@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@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[1] __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_ports[2] __initdata = { + { .min = 0x220, .max = 0x260, .align = 0x20, .size = 0x10 }, + { .min = 0x300, .max = 0x330, .align = 0x10, .size = 0x02 }, +}; + +static struct pnp_irq jazz16pnp_irqs[2] __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_dmas[2] __initdata = { + { .map = (1 << 1) | (1 << 3), .flags = IORESOURCE_DMA_8BIT, }, + { .map = (1 << 5) | (1 << 7), .flags = IORESOURCE_DMA_16BIT, }, +}; + +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_ports, 2, + jazz16pnp_irqs, 2, + jazz16pnp_dmas, 2)) + goto out_no_sboption; + if (!jazz16pnp_easy_option(sbdev, PNP_RES_PRIORITY_ACCEPTABLE, + jazz16pnp_ports, 2, + jazz16pnp_irqs, 1, + jazz16pnp_dmas, 2)) + 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 0x220: + case 0x240: + 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 = 0x%x, MPU port = 0x%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 = res->irq_resource[0].end = jazz16_sb_irq; + res->irq_resource[0].flags = IORESOURCE_IRQ; + + res->dma_resource[0].start = res->dma_resource[0].end = jazz16_sb_dma1; + res->dma_resource[0].flags = IORESOURCE_DMA; + res->dma_resource[1].start = 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; + + if (!jazz16_mpu401_irq) + return 0; + res->irq_resource[1].start = res->irq_resource[0].end = jazz16_mpu401_irq; + res->irq_resource[1].flags = IORESOURCE_IRQ; + } + else + return -EINVAL; + return 0; +} + +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 ((res->port_resource[0].flags & (IORESOURCE_IO | IORESOURCE_UNSET)) == IORESOURCE_IO) + jazz16_port_setup(res->port_resource[0].start, jazz16_mpu401_port); + if ((res->port_resource[1].flags & (IORESOURCE_IO | IORESOURCE_UNSET)) == IORESOURCE_IO) + jazz16_port_setup(jazz16_sb_port, res->port_resource[1].start); + if ((res->dma_resource[0].flags & (IORESOURCE_DMA | IORESOURCE_UNSET)) == IORESOURCE_DMA) + dma1 = res->dma_resource[0].start; + else + dma1 = jazz16_sb_dma1; + if ((res->dma_resource[1].flags & (IORESOURCE_DMA | IORESOURCE_UNSET)) == IORESOURCE_DMA) + dma2 = res->dma_resource[1].start; + else + dma2 = jazz16_sb_dma2; + if ((res->irq_resource[0].flags & (IORESOURCE_IRQ | IORESOURCE_UNSET)) == IORESOURCE_IRQ) + sbirq = res->irq_resource[0].start; + else + sbirq = jazz16_sb_irq; + if ((res->irq_resource[1].flags & (IORESOURCE_IRQ | IORESOURCE_UNSET)) == IORESOURCE_IRQ) + mpuirq = res->irq_resource[1].start; + else + mpuirq = jazz16_mpu401_irq; + if (jazz16_sb_port) { + /* Block the OPL3 alias to avoid conflicts. */ + jazz16pnp_fm_res = request_region(0x388, 4, "SoundBlaster FM"); + jazz16_irq_dma_setup(jazz16_sb_port, sbirq, mpuirq, dma1, dma2); + } else + return -EBUSY; + dev->active = true; + return 0; + } + else + return -EINVAL; +} + +static int jazz16pnp_disable_resources(struct pnp_dev *dev) +{ + if (dev == jazz16_gameport_dev) { + return -ENOSYS; + } + else if (dev == jazz16_sb_dev) { + 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; + } +} + +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 = 0x330; mpubase >= 0x300; mpubase -= 0x10) + if (request_region(mpubase, 2, "Jazz16 PnP probe")) + break; + if (mpubase < 0x300) + goto out_no_mpubase; + + if (jazz16pnp_verbose) + printk(PFX_DEBUG "Scanning for Jazz16 cards...\n"); + + for (base = 0x260; base >= 0x220; base -= 0x20) { + if (!request_region(base, 16, "Jazz16 PnP probe")) + continue; + if (jazz16pnp_verbose) + printk(PFX_DEBUG "Probing at 0x%x.\n", base); + 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 < 0x220) + 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); --- linux-2.6.20/drivers/pnp/Kconfig-orig 2007-02-19 23:23:26.000000000 +0100 +++ linux-2.6.20/drivers/pnp/Kconfig 2007-03-25 21:44:48.000000000 +0200 @@ -39,5 +39,17 @@ source "drivers/pnp/pnpacpi/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
participants (1)
-
Rask Ingemann Lambertsen