18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) by Jaroslav Kysela <perex@perex.cz> 48c2ecf20Sopenharmony_ci * Uros Bizjak <uros@kss-loka.si> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Lowlevel routines for control of Sound Blaster cards 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/ioport.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/io.h> 168c2ecf20Sopenharmony_ci#include <sound/core.h> 178c2ecf20Sopenharmony_ci#include <sound/sb.h> 188c2ecf20Sopenharmony_ci#include <sound/initval.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <asm/dma.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); 238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards"); 248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define BUSY_LOOPS 100000 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#undef IO_DEBUG 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ciint snd_sbdsp_command(struct snd_sb *chip, unsigned char val) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci int i; 338c2ecf20Sopenharmony_ci#ifdef IO_DEBUG 348c2ecf20Sopenharmony_ci snd_printk(KERN_DEBUG "command 0x%x\n", val); 358c2ecf20Sopenharmony_ci#endif 368c2ecf20Sopenharmony_ci for (i = BUSY_LOOPS; i; i--) 378c2ecf20Sopenharmony_ci if ((inb(SBP(chip, STATUS)) & 0x80) == 0) { 388c2ecf20Sopenharmony_ci outb(val, SBP(chip, COMMAND)); 398c2ecf20Sopenharmony_ci return 1; 408c2ecf20Sopenharmony_ci } 418c2ecf20Sopenharmony_ci snd_printd("%s [0x%lx]: timeout (0x%x)\n", __func__, chip->port, val); 428c2ecf20Sopenharmony_ci return 0; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ciint snd_sbdsp_get_byte(struct snd_sb *chip) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci int val; 488c2ecf20Sopenharmony_ci int i; 498c2ecf20Sopenharmony_ci for (i = BUSY_LOOPS; i; i--) { 508c2ecf20Sopenharmony_ci if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { 518c2ecf20Sopenharmony_ci val = inb(SBP(chip, READ)); 528c2ecf20Sopenharmony_ci#ifdef IO_DEBUG 538c2ecf20Sopenharmony_ci snd_printk(KERN_DEBUG "get_byte 0x%x\n", val); 548c2ecf20Sopenharmony_ci#endif 558c2ecf20Sopenharmony_ci return val; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port); 598c2ecf20Sopenharmony_ci return -ENODEV; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ciint snd_sbdsp_reset(struct snd_sb *chip) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci int i; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci outb(1, SBP(chip, RESET)); 678c2ecf20Sopenharmony_ci udelay(10); 688c2ecf20Sopenharmony_ci outb(0, SBP(chip, RESET)); 698c2ecf20Sopenharmony_ci udelay(30); 708c2ecf20Sopenharmony_ci for (i = BUSY_LOOPS; i; i--) 718c2ecf20Sopenharmony_ci if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { 728c2ecf20Sopenharmony_ci if (inb(SBP(chip, READ)) == 0xaa) 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci else 758c2ecf20Sopenharmony_ci break; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci snd_printdd("%s [0x%lx] failed...\n", __func__, chip->port); 788c2ecf20Sopenharmony_ci return -ENODEV; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic int snd_sbdsp_version(struct snd_sb * chip) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci unsigned int result; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci snd_sbdsp_command(chip, SB_DSP_GET_VERSION); 868c2ecf20Sopenharmony_ci result = (short) snd_sbdsp_get_byte(chip) << 8; 878c2ecf20Sopenharmony_ci result |= (short) snd_sbdsp_get_byte(chip); 888c2ecf20Sopenharmony_ci return result; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int snd_sbdsp_probe(struct snd_sb * chip) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci int version; 948c2ecf20Sopenharmony_ci int major, minor; 958c2ecf20Sopenharmony_ci char *str; 968c2ecf20Sopenharmony_ci unsigned long flags; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* 998c2ecf20Sopenharmony_ci * initialization sequence 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci spin_lock_irqsave(&chip->reg_lock, flags); 1038c2ecf20Sopenharmony_ci if (snd_sbdsp_reset(chip) < 0) { 1048c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&chip->reg_lock, flags); 1058c2ecf20Sopenharmony_ci return -ENODEV; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci version = snd_sbdsp_version(chip); 1088c2ecf20Sopenharmony_ci if (version < 0) { 1098c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&chip->reg_lock, flags); 1108c2ecf20Sopenharmony_ci return -ENODEV; 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&chip->reg_lock, flags); 1138c2ecf20Sopenharmony_ci major = version >> 8; 1148c2ecf20Sopenharmony_ci minor = version & 0xff; 1158c2ecf20Sopenharmony_ci snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n", 1168c2ecf20Sopenharmony_ci chip->port, major, minor); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci switch (chip->hardware) { 1198c2ecf20Sopenharmony_ci case SB_HW_AUTO: 1208c2ecf20Sopenharmony_ci switch (major) { 1218c2ecf20Sopenharmony_ci case 1: 1228c2ecf20Sopenharmony_ci chip->hardware = SB_HW_10; 1238c2ecf20Sopenharmony_ci str = "1.0"; 1248c2ecf20Sopenharmony_ci break; 1258c2ecf20Sopenharmony_ci case 2: 1268c2ecf20Sopenharmony_ci if (minor) { 1278c2ecf20Sopenharmony_ci chip->hardware = SB_HW_201; 1288c2ecf20Sopenharmony_ci str = "2.01+"; 1298c2ecf20Sopenharmony_ci } else { 1308c2ecf20Sopenharmony_ci chip->hardware = SB_HW_20; 1318c2ecf20Sopenharmony_ci str = "2.0"; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci break; 1348c2ecf20Sopenharmony_ci case 3: 1358c2ecf20Sopenharmony_ci chip->hardware = SB_HW_PRO; 1368c2ecf20Sopenharmony_ci str = "Pro"; 1378c2ecf20Sopenharmony_ci break; 1388c2ecf20Sopenharmony_ci case 4: 1398c2ecf20Sopenharmony_ci chip->hardware = SB_HW_16; 1408c2ecf20Sopenharmony_ci str = "16"; 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci default: 1438c2ecf20Sopenharmony_ci snd_printk(KERN_INFO "SB [0x%lx]: unknown DSP chip version %i.%i\n", 1448c2ecf20Sopenharmony_ci chip->port, major, minor); 1458c2ecf20Sopenharmony_ci return -ENODEV; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci break; 1488c2ecf20Sopenharmony_ci case SB_HW_ALS100: 1498c2ecf20Sopenharmony_ci str = "16 (ALS-100)"; 1508c2ecf20Sopenharmony_ci break; 1518c2ecf20Sopenharmony_ci case SB_HW_ALS4000: 1528c2ecf20Sopenharmony_ci str = "16 (ALS-4000)"; 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci case SB_HW_DT019X: 1558c2ecf20Sopenharmony_ci str = "(DT019X/ALS007)"; 1568c2ecf20Sopenharmony_ci break; 1578c2ecf20Sopenharmony_ci case SB_HW_CS5530: 1588c2ecf20Sopenharmony_ci str = "16 (CS5530)"; 1598c2ecf20Sopenharmony_ci break; 1608c2ecf20Sopenharmony_ci case SB_HW_JAZZ16: 1618c2ecf20Sopenharmony_ci str = "Pro (Jazz16)"; 1628c2ecf20Sopenharmony_ci break; 1638c2ecf20Sopenharmony_ci default: 1648c2ecf20Sopenharmony_ci return -ENODEV; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci sprintf(chip->name, "Sound Blaster %s", str); 1678c2ecf20Sopenharmony_ci chip->version = (major << 8) | minor; 1688c2ecf20Sopenharmony_ci return 0; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic int snd_sbdsp_free(struct snd_sb *chip) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci release_and_free_resource(chip->res_port); 1748c2ecf20Sopenharmony_ci if (chip->irq >= 0) 1758c2ecf20Sopenharmony_ci free_irq(chip->irq, (void *) chip); 1768c2ecf20Sopenharmony_ci#ifdef CONFIG_ISA 1778c2ecf20Sopenharmony_ci if (chip->dma8 >= 0) { 1788c2ecf20Sopenharmony_ci disable_dma(chip->dma8); 1798c2ecf20Sopenharmony_ci free_dma(chip->dma8); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) { 1828c2ecf20Sopenharmony_ci disable_dma(chip->dma16); 1838c2ecf20Sopenharmony_ci free_dma(chip->dma16); 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci#endif 1868c2ecf20Sopenharmony_ci kfree(chip); 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic int snd_sbdsp_dev_free(struct snd_device *device) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct snd_sb *chip = device->device_data; 1938c2ecf20Sopenharmony_ci return snd_sbdsp_free(chip); 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ciint snd_sbdsp_create(struct snd_card *card, 1978c2ecf20Sopenharmony_ci unsigned long port, 1988c2ecf20Sopenharmony_ci int irq, 1998c2ecf20Sopenharmony_ci irq_handler_t irq_handler, 2008c2ecf20Sopenharmony_ci int dma8, 2018c2ecf20Sopenharmony_ci int dma16, 2028c2ecf20Sopenharmony_ci unsigned short hardware, 2038c2ecf20Sopenharmony_ci struct snd_sb **r_chip) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct snd_sb *chip; 2068c2ecf20Sopenharmony_ci int err; 2078c2ecf20Sopenharmony_ci static const struct snd_device_ops ops = { 2088c2ecf20Sopenharmony_ci .dev_free = snd_sbdsp_dev_free, 2098c2ecf20Sopenharmony_ci }; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (snd_BUG_ON(!r_chip)) 2128c2ecf20Sopenharmony_ci return -EINVAL; 2138c2ecf20Sopenharmony_ci *r_chip = NULL; 2148c2ecf20Sopenharmony_ci chip = kzalloc(sizeof(*chip), GFP_KERNEL); 2158c2ecf20Sopenharmony_ci if (chip == NULL) 2168c2ecf20Sopenharmony_ci return -ENOMEM; 2178c2ecf20Sopenharmony_ci spin_lock_init(&chip->reg_lock); 2188c2ecf20Sopenharmony_ci spin_lock_init(&chip->open_lock); 2198c2ecf20Sopenharmony_ci spin_lock_init(&chip->midi_input_lock); 2208c2ecf20Sopenharmony_ci spin_lock_init(&chip->mixer_lock); 2218c2ecf20Sopenharmony_ci chip->irq = -1; 2228c2ecf20Sopenharmony_ci chip->dma8 = -1; 2238c2ecf20Sopenharmony_ci chip->dma16 = -1; 2248c2ecf20Sopenharmony_ci chip->port = port; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (request_irq(irq, irq_handler, 2278c2ecf20Sopenharmony_ci (hardware == SB_HW_ALS4000 || 2288c2ecf20Sopenharmony_ci hardware == SB_HW_CS5530) ? 2298c2ecf20Sopenharmony_ci IRQF_SHARED : 0, 2308c2ecf20Sopenharmony_ci "SoundBlaster", (void *) chip)) { 2318c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq); 2328c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2338c2ecf20Sopenharmony_ci return -EBUSY; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci chip->irq = irq; 2368c2ecf20Sopenharmony_ci card->sync_irq = chip->irq; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci if (hardware == SB_HW_ALS4000) 2398c2ecf20Sopenharmony_ci goto __skip_allocation; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) { 2428c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port); 2438c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2448c2ecf20Sopenharmony_ci return -EBUSY; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci#ifdef CONFIG_ISA 2488c2ecf20Sopenharmony_ci if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) { 2498c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8); 2508c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2518c2ecf20Sopenharmony_ci return -EBUSY; 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci chip->dma8 = dma8; 2548c2ecf20Sopenharmony_ci if (dma16 >= 0) { 2558c2ecf20Sopenharmony_ci if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) { 2568c2ecf20Sopenharmony_ci /* no duplex */ 2578c2ecf20Sopenharmony_ci dma16 = -1; 2588c2ecf20Sopenharmony_ci } else if (request_dma(dma16, "SoundBlaster - 16bit")) { 2598c2ecf20Sopenharmony_ci snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16); 2608c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2618c2ecf20Sopenharmony_ci return -EBUSY; 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci chip->dma16 = dma16; 2658c2ecf20Sopenharmony_ci#endif 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci __skip_allocation: 2688c2ecf20Sopenharmony_ci chip->card = card; 2698c2ecf20Sopenharmony_ci chip->hardware = hardware; 2708c2ecf20Sopenharmony_ci if ((err = snd_sbdsp_probe(chip)) < 0) { 2718c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2728c2ecf20Sopenharmony_ci return err; 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { 2758c2ecf20Sopenharmony_ci snd_sbdsp_free(chip); 2768c2ecf20Sopenharmony_ci return err; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci *r_chip = chip; 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbdsp_command); 2838c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbdsp_get_byte); 2848c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbdsp_reset); 2858c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbdsp_create); 2868c2ecf20Sopenharmony_ci/* sb_mixer.c */ 2878c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_write); 2888c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_read); 2898c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_new); 2908c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_add_ctl); 2918c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2928c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_suspend); 2938c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sbmixer_resume); 2948c2ecf20Sopenharmony_ci#endif 295