18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (C) Copyright 2007 Ash Willis <ashwillis@programmer.net> 68c2ecf20Sopenharmony_ci * (C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did 98c2ecf20Sopenharmony_ci * mess with it a bit. The chip seems to have to have trouble with full duplex 108c2ecf20Sopenharmony_ci * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to 118c2ecf20Sopenharmony_ci * simultaneously play back audio at 16bit 44100kHz, the device actually plays 128c2ecf20Sopenharmony_ci * back in the same format in which it is capturing. By forcing the chip to 138c2ecf20Sopenharmony_ci * always play/capture in 16/44100, we can let alsa-lib convert the samples and 148c2ecf20Sopenharmony_ci * that way we can hack up some full duplex audio. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. 178c2ecf20Sopenharmony_ci * The older version (VSA1) provides fairly good soundblaster emulation 188c2ecf20Sopenharmony_ci * although there are a couple of bugs: large DMA buffers break record, 198c2ecf20Sopenharmony_ci * and the MPU event handling seems suspect. VSA2 allows the native driver 208c2ecf20Sopenharmony_ci * to control the AC97 audio engine directly and requires a different driver. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * Thanks to National Semiconductor for providing the needed information 238c2ecf20Sopenharmony_ci * on the XpressAudio(tm) internals. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * TO DO: 268c2ecf20Sopenharmony_ci * Investigate whether we can portably support Cognac (5520) in the 278c2ecf20Sopenharmony_ci * same manner. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/delay.h> 318c2ecf20Sopenharmony_ci#include <linux/module.h> 328c2ecf20Sopenharmony_ci#include <linux/pci.h> 338c2ecf20Sopenharmony_ci#include <linux/slab.h> 348c2ecf20Sopenharmony_ci#include <sound/core.h> 358c2ecf20Sopenharmony_ci#include <sound/sb.h> 368c2ecf20Sopenharmony_ci#include <sound/initval.h> 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ash Willis"); 398c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("CS5530 Audio"); 408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; 438c2ecf20Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; 448c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for CS5530 Audio driver."); 488c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444); 498c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for CS5530 Audio driver."); 508c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444); 518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable CS5530 Audio driver."); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct snd_cs5530 { 548c2ecf20Sopenharmony_ci struct snd_card *card; 558c2ecf20Sopenharmony_ci struct pci_dev *pci; 568c2ecf20Sopenharmony_ci struct snd_sb *sb; 578c2ecf20Sopenharmony_ci unsigned long pci_base; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic const struct pci_device_id snd_cs5530_ids[] = { 618c2ecf20Sopenharmony_ci {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, 628c2ecf20Sopenharmony_ci PCI_ANY_ID, 0, 0}, 638c2ecf20Sopenharmony_ci {0,} 648c2ecf20Sopenharmony_ci}; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, snd_cs5530_ids); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int snd_cs5530_free(struct snd_cs5530 *chip) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci pci_release_regions(chip->pci); 718c2ecf20Sopenharmony_ci pci_disable_device(chip->pci); 728c2ecf20Sopenharmony_ci kfree(chip); 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic int snd_cs5530_dev_free(struct snd_device *device) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct snd_cs5530 *chip = device->device_data; 798c2ecf20Sopenharmony_ci return snd_cs5530_free(chip); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void snd_cs5530_remove(struct pci_dev *pci) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci snd_card_free(pci_get_drvdata(pci)); 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic u8 snd_cs5530_mixer_read(unsigned long io, u8 reg) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci outb(reg, io + 4); 908c2ecf20Sopenharmony_ci udelay(20); 918c2ecf20Sopenharmony_ci reg = inb(io + 5); 928c2ecf20Sopenharmony_ci udelay(20); 938c2ecf20Sopenharmony_ci return reg; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic int snd_cs5530_create(struct snd_card *card, 978c2ecf20Sopenharmony_ci struct pci_dev *pci, 988c2ecf20Sopenharmony_ci struct snd_cs5530 **rchip) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci struct snd_cs5530 *chip; 1018c2ecf20Sopenharmony_ci unsigned long sb_base; 1028c2ecf20Sopenharmony_ci u8 irq, dma8, dma16 = 0; 1038c2ecf20Sopenharmony_ci u16 map; 1048c2ecf20Sopenharmony_ci void __iomem *mem; 1058c2ecf20Sopenharmony_ci int err; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci static const struct snd_device_ops ops = { 1088c2ecf20Sopenharmony_ci .dev_free = snd_cs5530_dev_free, 1098c2ecf20Sopenharmony_ci }; 1108c2ecf20Sopenharmony_ci *rchip = NULL; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci err = pci_enable_device(pci); 1138c2ecf20Sopenharmony_ci if (err < 0) 1148c2ecf20Sopenharmony_ci return err; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci chip = kzalloc(sizeof(*chip), GFP_KERNEL); 1178c2ecf20Sopenharmony_ci if (chip == NULL) { 1188c2ecf20Sopenharmony_ci pci_disable_device(pci); 1198c2ecf20Sopenharmony_ci return -ENOMEM; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci chip->card = card; 1238c2ecf20Sopenharmony_ci chip->pci = pci; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci err = pci_request_regions(pci, "CS5530"); 1268c2ecf20Sopenharmony_ci if (err < 0) { 1278c2ecf20Sopenharmony_ci kfree(chip); 1288c2ecf20Sopenharmony_ci pci_disable_device(pci); 1298c2ecf20Sopenharmony_ci return err; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci chip->pci_base = pci_resource_start(pci, 0); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci mem = pci_ioremap_bar(pci, 0); 1348c2ecf20Sopenharmony_ci if (mem == NULL) { 1358c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 1368c2ecf20Sopenharmony_ci return -EBUSY; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci map = readw(mem + 0x18); 1408c2ecf20Sopenharmony_ci iounmap(mem); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Map bits 1438c2ecf20Sopenharmony_ci 0:1 * 0x20 + 0x200 = sb base 1448c2ecf20Sopenharmony_ci 2 sb enable 1458c2ecf20Sopenharmony_ci 3 adlib enable 1468c2ecf20Sopenharmony_ci 5 MPU enable 0x330 1478c2ecf20Sopenharmony_ci 6 MPU enable 0x300 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci The other bits may be used internally so must be masked */ 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci sb_base = 0x220 + 0x20 * (map & 3); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (map & (1<<2)) 1548c2ecf20Sopenharmony_ci dev_info(card->dev, "XpressAudio at 0x%lx\n", sb_base); 1558c2ecf20Sopenharmony_ci else { 1568c2ecf20Sopenharmony_ci dev_err(card->dev, "Could not find XpressAudio!\n"); 1578c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 1588c2ecf20Sopenharmony_ci return -ENODEV; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci if (map & (1<<5)) 1628c2ecf20Sopenharmony_ci dev_info(card->dev, "MPU at 0x300\n"); 1638c2ecf20Sopenharmony_ci else if (map & (1<<6)) 1648c2ecf20Sopenharmony_ci dev_info(card->dev, "MPU at 0x330\n"); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F; 1678c2ecf20Sopenharmony_ci dma8 = snd_cs5530_mixer_read(sb_base, 0x81); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (dma8 & 0x20) 1708c2ecf20Sopenharmony_ci dma16 = 5; 1718c2ecf20Sopenharmony_ci else if (dma8 & 0x40) 1728c2ecf20Sopenharmony_ci dma16 = 6; 1738c2ecf20Sopenharmony_ci else if (dma8 & 0x80) 1748c2ecf20Sopenharmony_ci dma16 = 7; 1758c2ecf20Sopenharmony_ci else { 1768c2ecf20Sopenharmony_ci dev_err(card->dev, "No 16bit DMA enabled\n"); 1778c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 1788c2ecf20Sopenharmony_ci return -ENODEV; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (dma8 & 0x01) 1828c2ecf20Sopenharmony_ci dma8 = 0; 1838c2ecf20Sopenharmony_ci else if (dma8 & 02) 1848c2ecf20Sopenharmony_ci dma8 = 1; 1858c2ecf20Sopenharmony_ci else if (dma8 & 0x08) 1868c2ecf20Sopenharmony_ci dma8 = 3; 1878c2ecf20Sopenharmony_ci else { 1888c2ecf20Sopenharmony_ci dev_err(card->dev, "No 8bit DMA enabled\n"); 1898c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 1908c2ecf20Sopenharmony_ci return -ENODEV; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (irq & 1) 1948c2ecf20Sopenharmony_ci irq = 9; 1958c2ecf20Sopenharmony_ci else if (irq & 2) 1968c2ecf20Sopenharmony_ci irq = 5; 1978c2ecf20Sopenharmony_ci else if (irq & 4) 1988c2ecf20Sopenharmony_ci irq = 7; 1998c2ecf20Sopenharmony_ci else if (irq & 8) 2008c2ecf20Sopenharmony_ci irq = 10; 2018c2ecf20Sopenharmony_ci else { 2028c2ecf20Sopenharmony_ci dev_err(card->dev, "SoundBlaster IRQ not set\n"); 2038c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 2048c2ecf20Sopenharmony_ci return -ENODEV; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci dev_info(card->dev, "IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, dma16); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8, 2108c2ecf20Sopenharmony_ci dma16, SB_HW_CS5530, &chip->sb); 2118c2ecf20Sopenharmony_ci if (err < 0) { 2128c2ecf20Sopenharmony_ci dev_err(card->dev, "Could not create SoundBlaster\n"); 2138c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 2148c2ecf20Sopenharmony_ci return err; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci err = snd_sb16dsp_pcm(chip->sb, 0); 2188c2ecf20Sopenharmony_ci if (err < 0) { 2198c2ecf20Sopenharmony_ci dev_err(card->dev, "Could not create PCM\n"); 2208c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 2218c2ecf20Sopenharmony_ci return err; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci err = snd_sbmixer_new(chip->sb); 2258c2ecf20Sopenharmony_ci if (err < 0) { 2268c2ecf20Sopenharmony_ci dev_err(card->dev, "Could not create Mixer\n"); 2278c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 2288c2ecf20Sopenharmony_ci return err; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); 2328c2ecf20Sopenharmony_ci if (err < 0) { 2338c2ecf20Sopenharmony_ci snd_cs5530_free(chip); 2348c2ecf20Sopenharmony_ci return err; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci *rchip = chip; 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic int snd_cs5530_probe(struct pci_dev *pci, 2428c2ecf20Sopenharmony_ci const struct pci_device_id *pci_id) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci static int dev; 2458c2ecf20Sopenharmony_ci struct snd_card *card; 2468c2ecf20Sopenharmony_ci struct snd_cs5530 *chip = NULL; 2478c2ecf20Sopenharmony_ci int err; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci if (dev >= SNDRV_CARDS) 2508c2ecf20Sopenharmony_ci return -ENODEV; 2518c2ecf20Sopenharmony_ci if (!enable[dev]) { 2528c2ecf20Sopenharmony_ci dev++; 2538c2ecf20Sopenharmony_ci return -ENOENT; 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, 2578c2ecf20Sopenharmony_ci 0, &card); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (err < 0) 2608c2ecf20Sopenharmony_ci return err; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci err = snd_cs5530_create(card, pci, &chip); 2638c2ecf20Sopenharmony_ci if (err < 0) { 2648c2ecf20Sopenharmony_ci snd_card_free(card); 2658c2ecf20Sopenharmony_ci return err; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci strcpy(card->driver, "CS5530"); 2698c2ecf20Sopenharmony_ci strcpy(card->shortname, "CS5530 Audio"); 2708c2ecf20Sopenharmony_ci sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci err = snd_card_register(card); 2738c2ecf20Sopenharmony_ci if (err < 0) { 2748c2ecf20Sopenharmony_ci snd_card_free(card); 2758c2ecf20Sopenharmony_ci return err; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci pci_set_drvdata(pci, card); 2788c2ecf20Sopenharmony_ci dev++; 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_cistatic struct pci_driver cs5530_driver = { 2838c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 2848c2ecf20Sopenharmony_ci .id_table = snd_cs5530_ids, 2858c2ecf20Sopenharmony_ci .probe = snd_cs5530_probe, 2868c2ecf20Sopenharmony_ci .remove = snd_cs5530_remove, 2878c2ecf20Sopenharmony_ci}; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cimodule_pci_driver(cs5530_driver); 290