| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Aztech AZT1605/AZT2316 Driver |
| * Copyright (C) 2007,2010 Rene Herman |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/isa.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <asm/processor.h> |
| #include <sound/core.h> |
| #include <sound/initval.h> |
| #include <sound/wss.h> |
| #include <sound/mpu401.h> |
| #include <sound/opl3.h> |
| |
| MODULE_DESCRIPTION(CRD_NAME); |
| MODULE_AUTHOR("Rene Herman"); |
| MODULE_LICENSE("GPL"); |
| |
| static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
| static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; |
| static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; |
| |
| module_param_array(index, int, NULL, 0444); |
| MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); |
| module_param_array(id, charp, NULL, 0444); |
| MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); |
| module_param_array(enable, bool, NULL, 0444); |
| MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); |
| |
| static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
| static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
| static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
| static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; |
| static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; |
| static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; |
| static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; |
| static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; |
| |
| module_param_hw_array(port, long, ioport, NULL, 0444); |
| MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); |
| module_param_hw_array(wss_port, long, ioport, NULL, 0444); |
| MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver."); |
| module_param_hw_array(mpu_port, long, ioport, NULL, 0444); |
| MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); |
| module_param_hw_array(fm_port, long, ioport, NULL, 0444); |
| MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver."); |
| module_param_hw_array(irq, int, irq, NULL, 0444); |
| MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); |
| module_param_hw_array(mpu_irq, int, irq, NULL, 0444); |
| MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); |
| module_param_hw_array(dma1, int, dma, NULL, 0444); |
| MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver."); |
| module_param_hw_array(dma2, int, dma, NULL, 0444); |
| MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver."); |
| |
| /* |
| * Generic SB DSP support routines |
| */ |
| |
| #define DSP_PORT_RESET 0x6 |
| #define DSP_PORT_READ 0xa |
| #define DSP_PORT_COMMAND 0xc |
| #define DSP_PORT_STATUS 0xc |
| #define DSP_PORT_DATA_AVAIL 0xe |
| |
| #define DSP_SIGNATURE 0xaa |
| |
| #define DSP_COMMAND_GET_VERSION 0xe1 |
| |
| static int dsp_get_byte(void __iomem *port, u8 *val) |
| { |
| int loops = 1000; |
| |
| while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) { |
| if (!loops--) |
| return -EIO; |
| cpu_relax(); |
| } |
| *val = ioread8(port + DSP_PORT_READ); |
| return 0; |
| } |
| |
| static int dsp_reset(void __iomem *port) |
| { |
| u8 val; |
| |
| iowrite8(1, port + DSP_PORT_RESET); |
| udelay(10); |
| iowrite8(0, port + DSP_PORT_RESET); |
| |
| if (dsp_get_byte(port, &val) < 0 || val != DSP_SIGNATURE) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static int dsp_command(void __iomem *port, u8 cmd) |
| { |
| int loops = 1000; |
| |
| while (ioread8(port + DSP_PORT_STATUS) & 0x80) { |
| if (!loops--) |
| return -EIO; |
| cpu_relax(); |
| } |
| iowrite8(cmd, port + DSP_PORT_COMMAND); |
| return 0; |
| } |
| |
| static int dsp_get_version(void __iomem *port, u8 *major, u8 *minor) |
| { |
| int err; |
| |
| err = dsp_command(port, DSP_COMMAND_GET_VERSION); |
| if (err < 0) |
| return err; |
| |
| err = dsp_get_byte(port, major); |
| if (err < 0) |
| return err; |
| |
| err = dsp_get_byte(port, minor); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| /* |
| * Generic WSS support routines |
| */ |
| |
| #define WSS_CONFIG_DMA_0 (1 << 0) |
| #define WSS_CONFIG_DMA_1 (2 << 0) |
| #define WSS_CONFIG_DMA_3 (3 << 0) |
| #define WSS_CONFIG_DUPLEX (1 << 2) |
| #define WSS_CONFIG_IRQ_7 (1 << 3) |
| #define WSS_CONFIG_IRQ_9 (2 << 3) |
| #define WSS_CONFIG_IRQ_10 (3 << 3) |
| #define WSS_CONFIG_IRQ_11 (4 << 3) |
| |
| #define WSS_PORT_CONFIG 0 |
| #define WSS_PORT_SIGNATURE 3 |
| |
| #define WSS_SIGNATURE 4 |
| |
| static int wss_detect(void __iomem *wss_port) |
| { |
| if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static void wss_set_config(void __iomem *wss_port, u8 wss_config) |
| { |
| iowrite8(wss_config, wss_port + WSS_PORT_CONFIG); |
| } |
| |
| /* |
| * Aztech Sound Galaxy specifics |
| */ |
| |
| #define GALAXY_PORT_CONFIG 1024 |
| #define CONFIG_PORT_SET 4 |
| |
| #define DSP_COMMAND_GALAXY_8 8 |
| #define GALAXY_COMMAND_GET_TYPE 5 |
| |
| #define DSP_COMMAND_GALAXY_9 9 |
| #define GALAXY_COMMAND_WSSMODE 0 |
| #define GALAXY_COMMAND_SB8MODE 1 |
| |
| #define GALAXY_MODE_WSS GALAXY_COMMAND_WSSMODE |
| #define GALAXY_MODE_SB8 GALAXY_COMMAND_SB8MODE |
| |
| struct snd_galaxy { |
| void __iomem *port; |
| void __iomem *config_port; |
| void __iomem *wss_port; |
| u32 config; |
| struct resource *res_port; |
| struct resource *res_config_port; |
| struct resource *res_wss_port; |
| }; |
| |
| static u32 config[SNDRV_CARDS]; |
| static u8 wss_config[SNDRV_CARDS]; |
| |
| static int snd_galaxy_match(struct device *dev, unsigned int n) |
| { |
| if (!enable[n]) |
| return 0; |
| |
| switch (port[n]) { |
| case SNDRV_AUTO_PORT: |
| dev_err(dev, "please specify port\n"); |
| return 0; |
| case 0x220: |
| config[n] |= GALAXY_CONFIG_SBA_220; |
| break; |
| case 0x240: |
| config[n] |= GALAXY_CONFIG_SBA_240; |
| break; |
| case 0x260: |
| config[n] |= GALAXY_CONFIG_SBA_260; |
| break; |
| case 0x280: |
| config[n] |= GALAXY_CONFIG_SBA_280; |
| break; |
| default: |
| dev_err(dev, "invalid port %#lx\n", port[n]); |
| return 0; |
| } |
| |
| switch (wss_port[n]) { |
| case SNDRV_AUTO_PORT: |
| dev_err(dev, "please specify wss_port\n"); |
| return 0; |
| case 0x530: |
| config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530; |
| break; |
| case 0x604: |
| config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604; |
| break; |
| case 0xe80: |
| config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80; |
| break; |
| case 0xf40: |
| config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40; |
| break; |
| default: |
| dev_err(dev, "invalid WSS port %#lx\n", wss_port[n]); |
| return 0; |
| } |
| |
| switch (irq[n]) { |
| case SNDRV_AUTO_IRQ: |
| dev_err(dev, "please specify irq\n"); |
| return 0; |
| case 7: |
| wss_config[n] |= WSS_CONFIG_IRQ_7; |
| break; |
| case 2: |
| irq[n] = 9; |
| /* Fall through */ |
| case 9: |
| wss_config[n] |= WSS_CONFIG_IRQ_9; |
| break; |
| case 10: |
| wss_config[n] |= WSS_CONFIG_IRQ_10; |
| break; |
| case 11: |
| wss_config[n] |= WSS_CONFIG_IRQ_11; |
| break; |
| default: |
| dev_err(dev, "invalid IRQ %d\n", irq[n]); |
| return 0; |
| } |
| |
| switch (dma1[n]) { |
| case SNDRV_AUTO_DMA: |
| dev_err(dev, "please specify dma1\n"); |
| return 0; |
| case 0: |
| wss_config[n] |= WSS_CONFIG_DMA_0; |
| break; |
| case 1: |
| wss_config[n] |= WSS_CONFIG_DMA_1; |
| break; |
| case 3: |
| wss_config[n] |= WSS_CONFIG_DMA_3; |
| break; |
| default: |
| dev_err(dev, "invalid playback DMA %d\n", dma1[n]); |
| return 0; |
| } |
| |
| if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) { |
| dma2[n] = -1; |
| goto mpu; |
| } |
| |
| wss_config[n] |= WSS_CONFIG_DUPLEX; |
| switch (dma2[n]) { |
| case 0: |
| break; |
| case 1: |
| if (dma1[n] == 0) |
| break; |
| /* Fall through */ |
| default: |
| dev_err(dev, "invalid capture DMA %d\n", dma2[n]); |
| return 0; |
| } |
| |
| mpu: |
| switch (mpu_port[n]) { |
| case SNDRV_AUTO_PORT: |
| dev_warn(dev, "mpu_port not specified; not using MPU-401\n"); |
| mpu_port[n] = -1; |
| goto fm; |
| case 0x300: |
| config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300; |
| break; |
| case 0x330: |
| config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330; |
| break; |
| default: |
| dev_err(dev, "invalid MPU port %#lx\n", mpu_port[n]); |
| return 0; |
| } |
| |
| switch (mpu_irq[n]) { |
| case SNDRV_AUTO_IRQ: |
| dev_warn(dev, "mpu_irq not specified: using polling mode\n"); |
| mpu_irq[n] = -1; |
| break; |
| case 2: |
| mpu_irq[n] = 9; |
| /* Fall through */ |
| case 9: |
| config[n] |= GALAXY_CONFIG_MPUIRQ_2; |
| break; |
| #ifdef AZT1605 |
| case 3: |
| config[n] |= GALAXY_CONFIG_MPUIRQ_3; |
| break; |
| #endif |
| case 5: |
| config[n] |= GALAXY_CONFIG_MPUIRQ_5; |
| break; |
| case 7: |
| config[n] |= GALAXY_CONFIG_MPUIRQ_7; |
| break; |
| #ifdef AZT2316 |
| case 10: |
| config[n] |= GALAXY_CONFIG_MPUIRQ_10; |
| break; |
| #endif |
| default: |
| dev_err(dev, "invalid MPU IRQ %d\n", mpu_irq[n]); |
| return 0; |
| } |
| |
| if (mpu_irq[n] == irq[n]) { |
| dev_err(dev, "cannot share IRQ between WSS and MPU-401\n"); |
| return 0; |
| } |
| |
| fm: |
| switch (fm_port[n]) { |
| case SNDRV_AUTO_PORT: |
| dev_warn(dev, "fm_port not specified: not using OPL3\n"); |
| fm_port[n] = -1; |
| break; |
| case 0x388: |
| break; |
| default: |
| dev_err(dev, "illegal FM port %#lx\n", fm_port[n]); |
| return 0; |
| } |
| |
| config[n] |= GALAXY_CONFIG_GAME_ENABLE; |
| return 1; |
| } |
| |
| static int galaxy_init(struct snd_galaxy *galaxy, u8 *type) |
| { |
| u8 major; |
| u8 minor; |
| int err; |
| |
| err = dsp_reset(galaxy->port); |
| if (err < 0) |
| return err; |
| |
| err = dsp_get_version(galaxy->port, &major, &minor); |
| if (err < 0) |
| return err; |
| |
| if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR) |
| return -ENODEV; |
| |
| err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_8); |
| if (err < 0) |
| return err; |
| |
| err = dsp_command(galaxy->port, GALAXY_COMMAND_GET_TYPE); |
| if (err < 0) |
| return err; |
| |
| err = dsp_get_byte(galaxy->port, type); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static int galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode) |
| { |
| int err; |
| |
| err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_9); |
| if (err < 0) |
| return err; |
| |
| err = dsp_command(galaxy->port, mode); |
| if (err < 0) |
| return err; |
| |
| #ifdef AZT1605 |
| /* |
| * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again |
| */ |
| err = dsp_reset(galaxy->port); |
| if (err < 0) |
| return err; |
| #endif |
| |
| return 0; |
| } |
| |
| static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config) |
| { |
| u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET); |
| int i; |
| |
| iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET); |
| for (i = 0; i < GALAXY_CONFIG_SIZE; i++) { |
| iowrite8(config, galaxy->config_port + i); |
| config >>= 8; |
| } |
| iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET); |
| msleep(10); |
| } |
| |
| static void galaxy_config(struct snd_galaxy *galaxy, u32 config) |
| { |
| int i; |
| |
| for (i = GALAXY_CONFIG_SIZE; i; i--) { |
| u8 tmp = ioread8(galaxy->config_port + i - 1); |
| galaxy->config = (galaxy->config << 8) | tmp; |
| } |
| config |= galaxy->config & GALAXY_CONFIG_MASK; |
| galaxy_set_config(galaxy, config); |
| } |
| |
| static int galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config) |
| { |
| int err; |
| |
| err = wss_detect(galaxy->wss_port); |
| if (err < 0) |
| return err; |
| |
| wss_set_config(galaxy->wss_port, wss_config); |
| |
| err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static void snd_galaxy_free(struct snd_card *card) |
| { |
| struct snd_galaxy *galaxy = card->private_data; |
| |
| if (galaxy->wss_port) { |
| wss_set_config(galaxy->wss_port, 0); |
| ioport_unmap(galaxy->wss_port); |
| release_and_free_resource(galaxy->res_wss_port); |
| } |
| if (galaxy->config_port) { |
| galaxy_set_config(galaxy, galaxy->config); |
| ioport_unmap(galaxy->config_port); |
| release_and_free_resource(galaxy->res_config_port); |
| } |
| if (galaxy->port) { |
| ioport_unmap(galaxy->port); |
| release_and_free_resource(galaxy->res_port); |
| } |
| } |
| |
| static int snd_galaxy_probe(struct device *dev, unsigned int n) |
| { |
| struct snd_galaxy *galaxy; |
| struct snd_wss *chip; |
| struct snd_card *card; |
| u8 type; |
| int err; |
| |
| err = snd_card_new(dev, index[n], id[n], THIS_MODULE, |
| sizeof(*galaxy), &card); |
| if (err < 0) |
| return err; |
| |
| card->private_free = snd_galaxy_free; |
| galaxy = card->private_data; |
| |
| galaxy->res_port = request_region(port[n], 16, DRV_NAME); |
| if (!galaxy->res_port) { |
| dev_err(dev, "could not grab ports %#lx-%#lx\n", port[n], |
| port[n] + 15); |
| err = -EBUSY; |
| goto error; |
| } |
| galaxy->port = ioport_map(port[n], 16); |
| |
| err = galaxy_init(galaxy, &type); |
| if (err < 0) { |
| dev_err(dev, "did not find a Sound Galaxy at %#lx\n", port[n]); |
| goto error; |
| } |
| dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n", type, port[n]); |
| |
| galaxy->res_config_port = request_region(port[n] + GALAXY_PORT_CONFIG, |
| 16, DRV_NAME); |
| if (!galaxy->res_config_port) { |
| dev_err(dev, "could not grab ports %#lx-%#lx\n", |
| port[n] + GALAXY_PORT_CONFIG, |
| port[n] + GALAXY_PORT_CONFIG + 15); |
| err = -EBUSY; |
| goto error; |
| } |
| galaxy->config_port = ioport_map(port[n] + GALAXY_PORT_CONFIG, 16); |
| |
| galaxy_config(galaxy, config[n]); |
| |
| galaxy->res_wss_port = request_region(wss_port[n], 4, DRV_NAME); |
| if (!galaxy->res_wss_port) { |
| dev_err(dev, "could not grab ports %#lx-%#lx\n", wss_port[n], |
| wss_port[n] + 3); |
| err = -EBUSY; |
| goto error; |
| } |
| galaxy->wss_port = ioport_map(wss_port[n], 4); |
| |
| err = galaxy_wss_config(galaxy, wss_config[n]); |
| if (err < 0) { |
| dev_err(dev, "could not configure WSS\n"); |
| goto error; |
| } |
| |
| strcpy(card->driver, DRV_NAME); |
| strcpy(card->shortname, DRV_NAME); |
| sprintf(card->longname, "%s at %#lx/%#lx, irq %d, dma %d/%d", |
| card->shortname, port[n], wss_port[n], irq[n], dma1[n], |
| dma2[n]); |
| |
| err = snd_wss_create(card, wss_port[n] + 4, -1, irq[n], dma1[n], |
| dma2[n], WSS_HW_DETECT, 0, &chip); |
| if (err < 0) |
| goto error; |
| |
| err = snd_wss_pcm(chip, 0); |
| if (err < 0) |
| goto error; |
| |
| err = snd_wss_mixer(chip); |
| if (err < 0) |
| goto error; |
| |
| err = snd_wss_timer(chip, 0); |
| if (err < 0) |
| goto error; |
| |
| if (mpu_port[n] >= 0) { |
| err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, |
| mpu_port[n], 0, mpu_irq[n], NULL); |
| if (err < 0) |
| goto error; |
| } |
| |
| if (fm_port[n] >= 0) { |
| struct snd_opl3 *opl3; |
| |
| err = snd_opl3_create(card, fm_port[n], fm_port[n] + 2, |
| OPL3_HW_AUTO, 0, &opl3); |
| if (err < 0) { |
| dev_err(dev, "no OPL device at %#lx\n", fm_port[n]); |
| goto error; |
| } |
| err = snd_opl3_timer_new(opl3, 1, 2); |
| if (err < 0) |
| goto error; |
| |
| err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); |
| if (err < 0) |
| goto error; |
| } |
| |
| err = snd_card_register(card); |
| if (err < 0) |
| goto error; |
| |
| dev_set_drvdata(dev, card); |
| return 0; |
| |
| error: |
| snd_card_free(card); |
| return err; |
| } |
| |
| static int snd_galaxy_remove(struct device *dev, unsigned int n) |
| { |
| snd_card_free(dev_get_drvdata(dev)); |
| return 0; |
| } |
| |
| static struct isa_driver snd_galaxy_driver = { |
| .match = snd_galaxy_match, |
| .probe = snd_galaxy_probe, |
| .remove = snd_galaxy_remove, |
| |
| .driver = { |
| .name = DEV_NAME |
| } |
| }; |
| |
| module_isa_driver(snd_galaxy_driver, SNDRV_CARDS); |