162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for audio on multifunction CS5535/6 companion device 462306a36Sopenharmony_ci * Copyright (C) Jaya Kumar 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Based on Jaroslav Kysela and Takashi Iwai's examples. 762306a36Sopenharmony_ci * This work was sponsored by CIS(M) Sdn Bhd. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/interrupt.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/pci.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci#include <sound/core.h> 1862306a36Sopenharmony_ci#include <sound/control.h> 1962306a36Sopenharmony_ci#include <sound/pcm.h> 2062306a36Sopenharmony_ci#include <sound/rawmidi.h> 2162306a36Sopenharmony_ci#include <sound/ac97_codec.h> 2262306a36Sopenharmony_ci#include <sound/initval.h> 2362306a36Sopenharmony_ci#include <sound/asoundef.h> 2462306a36Sopenharmony_ci#include "cs5535audio.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define DRIVER_NAME "cs5535audio" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic char *ac97_quirk; 2962306a36Sopenharmony_cimodule_param(ac97_quirk, charp, 0444); 3062306a36Sopenharmony_ciMODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds."); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic const struct ac97_quirk ac97_quirks[] = { 3362306a36Sopenharmony_ci#if 0 /* Not yet confirmed if all 5536 boards are HP only */ 3462306a36Sopenharmony_ci { 3562306a36Sopenharmony_ci .subvendor = PCI_VENDOR_ID_AMD, 3662306a36Sopenharmony_ci .subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, 3762306a36Sopenharmony_ci .name = "AMD RDK", 3862306a36Sopenharmony_ci .type = AC97_TUNE_HP_ONLY 3962306a36Sopenharmony_ci }, 4062306a36Sopenharmony_ci#endif 4162306a36Sopenharmony_ci {} 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; 4562306a36Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; 4662306a36Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cimodule_param_array(index, int, NULL, 0444); 4962306a36Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for " DRIVER_NAME); 5062306a36Sopenharmony_cimodule_param_array(id, charp, NULL, 0444); 5162306a36Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for " DRIVER_NAME); 5262306a36Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444); 5362306a36Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable " DRIVER_NAME); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic const struct pci_device_id snd_cs5535audio_ids[] = { 5662306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) }, 5762306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) }, 5862306a36Sopenharmony_ci {} 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci unsigned int tmp; 6662306a36Sopenharmony_ci do { 6762306a36Sopenharmony_ci tmp = cs_readl(cs5535au, ACC_CODEC_CNTL); 6862306a36Sopenharmony_ci if (!(tmp & CMD_NEW)) 6962306a36Sopenharmony_ci break; 7062306a36Sopenharmony_ci udelay(1); 7162306a36Sopenharmony_ci } while (--timeout); 7262306a36Sopenharmony_ci if (!timeout) 7362306a36Sopenharmony_ci dev_err(cs5535au->card->dev, 7462306a36Sopenharmony_ci "Failure writing to cs5535 codec\n"); 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au, 7862306a36Sopenharmony_ci unsigned short reg) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci unsigned int regdata; 8162306a36Sopenharmony_ci unsigned int timeout; 8262306a36Sopenharmony_ci unsigned int val; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci regdata = ((unsigned int) reg) << 24; 8562306a36Sopenharmony_ci regdata |= ACC_CODEC_CNTL_RD_CMD; 8662306a36Sopenharmony_ci regdata |= CMD_NEW; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); 8962306a36Sopenharmony_ci wait_till_cmd_acked(cs5535au, 50); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci timeout = 50; 9262306a36Sopenharmony_ci do { 9362306a36Sopenharmony_ci val = cs_readl(cs5535au, ACC_CODEC_STATUS); 9462306a36Sopenharmony_ci if ((val & STS_NEW) && reg == (val >> 24)) 9562306a36Sopenharmony_ci break; 9662306a36Sopenharmony_ci udelay(1); 9762306a36Sopenharmony_ci } while (--timeout); 9862306a36Sopenharmony_ci if (!timeout) 9962306a36Sopenharmony_ci dev_err(cs5535au->card->dev, 10062306a36Sopenharmony_ci "Failure reading codec reg 0x%x, Last value=0x%x\n", 10162306a36Sopenharmony_ci reg, val); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return (unsigned short) val; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au, 10762306a36Sopenharmony_ci unsigned short reg, unsigned short val) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci unsigned int regdata; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci regdata = ((unsigned int) reg) << 24; 11262306a36Sopenharmony_ci regdata |= val; 11362306a36Sopenharmony_ci regdata &= CMD_MASK; 11462306a36Sopenharmony_ci regdata |= CMD_NEW; 11562306a36Sopenharmony_ci regdata &= ACC_CODEC_CNTL_WR_CMD; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); 11862306a36Sopenharmony_ci wait_till_cmd_acked(cs5535au, 50); 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97, 12262306a36Sopenharmony_ci unsigned short reg, unsigned short val) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci struct cs5535audio *cs5535au = ac97->private_data; 12562306a36Sopenharmony_ci snd_cs5535audio_codec_write(cs5535au, reg, val); 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97, 12962306a36Sopenharmony_ci unsigned short reg) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct cs5535audio *cs5535au = ac97->private_data; 13262306a36Sopenharmony_ci return snd_cs5535audio_codec_read(cs5535au, reg); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic int snd_cs5535audio_mixer(struct cs5535audio *cs5535au) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci struct snd_card *card = cs5535au->card; 13862306a36Sopenharmony_ci struct snd_ac97_bus *pbus; 13962306a36Sopenharmony_ci struct snd_ac97_template ac97; 14062306a36Sopenharmony_ci int err; 14162306a36Sopenharmony_ci static const struct snd_ac97_bus_ops ops = { 14262306a36Sopenharmony_ci .write = snd_cs5535audio_ac97_codec_write, 14362306a36Sopenharmony_ci .read = snd_cs5535audio_ac97_codec_read, 14462306a36Sopenharmony_ci }; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci err = snd_ac97_bus(card, 0, &ops, NULL, &pbus); 14762306a36Sopenharmony_ci if (err < 0) 14862306a36Sopenharmony_ci return err; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci memset(&ac97, 0, sizeof(ac97)); 15162306a36Sopenharmony_ci ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM 15262306a36Sopenharmony_ci | AC97_SCAP_POWER_SAVE; 15362306a36Sopenharmony_ci ac97.private_data = cs5535au; 15462306a36Sopenharmony_ci ac97.pci = cs5535au->pci; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* set any OLPC-specific scaps */ 15762306a36Sopenharmony_ci olpc_prequirks(card, &ac97); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97); 16062306a36Sopenharmony_ci if (err < 0) { 16162306a36Sopenharmony_ci dev_err(card->dev, "mixer failed\n"); 16262306a36Sopenharmony_ci return err; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci snd_ac97_tune_hardware(cs5535au->ac97, ac97_quirks, ac97_quirk); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci err = olpc_quirks(card, cs5535au->ac97); 16862306a36Sopenharmony_ci if (err < 0) { 16962306a36Sopenharmony_ci dev_err(card->dev, "olpc quirks failed\n"); 17062306a36Sopenharmony_ci return err; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return 0; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic void process_bm0_irq(struct cs5535audio *cs5535au) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci u8 bm_stat; 17962306a36Sopenharmony_ci spin_lock(&cs5535au->reg_lock); 18062306a36Sopenharmony_ci bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS); 18162306a36Sopenharmony_ci spin_unlock(&cs5535au->reg_lock); 18262306a36Sopenharmony_ci if (bm_stat & EOP) { 18362306a36Sopenharmony_ci snd_pcm_period_elapsed(cs5535au->playback_substream); 18462306a36Sopenharmony_ci } else { 18562306a36Sopenharmony_ci dev_err(cs5535au->card->dev, 18662306a36Sopenharmony_ci "unexpected bm0 irq src, bm_stat=%x\n", 18762306a36Sopenharmony_ci bm_stat); 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic void process_bm1_irq(struct cs5535audio *cs5535au) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci u8 bm_stat; 19462306a36Sopenharmony_ci spin_lock(&cs5535au->reg_lock); 19562306a36Sopenharmony_ci bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS); 19662306a36Sopenharmony_ci spin_unlock(&cs5535au->reg_lock); 19762306a36Sopenharmony_ci if (bm_stat & EOP) 19862306a36Sopenharmony_ci snd_pcm_period_elapsed(cs5535au->capture_substream); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci u16 acc_irq_stat; 20462306a36Sopenharmony_ci unsigned char count; 20562306a36Sopenharmony_ci struct cs5535audio *cs5535au = dev_id; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (cs5535au == NULL) 20862306a36Sopenharmony_ci return IRQ_NONE; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci if (!acc_irq_stat) 21362306a36Sopenharmony_ci return IRQ_NONE; 21462306a36Sopenharmony_ci for (count = 0; count < 4; count++) { 21562306a36Sopenharmony_ci if (acc_irq_stat & (1 << count)) { 21662306a36Sopenharmony_ci switch (count) { 21762306a36Sopenharmony_ci case IRQ_STS: 21862306a36Sopenharmony_ci cs_readl(cs5535au, ACC_GPIO_STATUS); 21962306a36Sopenharmony_ci break; 22062306a36Sopenharmony_ci case WU_IRQ_STS: 22162306a36Sopenharmony_ci cs_readl(cs5535au, ACC_GPIO_STATUS); 22262306a36Sopenharmony_ci break; 22362306a36Sopenharmony_ci case BM0_IRQ_STS: 22462306a36Sopenharmony_ci process_bm0_irq(cs5535au); 22562306a36Sopenharmony_ci break; 22662306a36Sopenharmony_ci case BM1_IRQ_STS: 22762306a36Sopenharmony_ci process_bm1_irq(cs5535au); 22862306a36Sopenharmony_ci break; 22962306a36Sopenharmony_ci default: 23062306a36Sopenharmony_ci dev_err(cs5535au->card->dev, 23162306a36Sopenharmony_ci "Unexpected irq src: 0x%x\n", 23262306a36Sopenharmony_ci acc_irq_stat); 23362306a36Sopenharmony_ci break; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci return IRQ_HANDLED; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic void snd_cs5535audio_free(struct snd_card *card) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci olpc_quirks_cleanup(); 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int snd_cs5535audio_create(struct snd_card *card, 24662306a36Sopenharmony_ci struct pci_dev *pci) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct cs5535audio *cs5535au = card->private_data; 24962306a36Sopenharmony_ci int err; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci err = pcim_enable_device(pci); 25262306a36Sopenharmony_ci if (err < 0) 25362306a36Sopenharmony_ci return err; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { 25662306a36Sopenharmony_ci dev_warn(card->dev, "unable to get 32bit dma\n"); 25762306a36Sopenharmony_ci return -ENXIO; 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci spin_lock_init(&cs5535au->reg_lock); 26162306a36Sopenharmony_ci cs5535au->card = card; 26262306a36Sopenharmony_ci cs5535au->pci = pci; 26362306a36Sopenharmony_ci cs5535au->irq = -1; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci err = pci_request_regions(pci, "CS5535 Audio"); 26662306a36Sopenharmony_ci if (err < 0) 26762306a36Sopenharmony_ci return err; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci cs5535au->port = pci_resource_start(pci, 0); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (devm_request_irq(&pci->dev, pci->irq, snd_cs5535audio_interrupt, 27262306a36Sopenharmony_ci IRQF_SHARED, KBUILD_MODNAME, cs5535au)) { 27362306a36Sopenharmony_ci dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq); 27462306a36Sopenharmony_ci return -EBUSY; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci cs5535au->irq = pci->irq; 27862306a36Sopenharmony_ci card->sync_irq = cs5535au->irq; 27962306a36Sopenharmony_ci pci_set_master(pci); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic int __snd_cs5535audio_probe(struct pci_dev *pci, 28562306a36Sopenharmony_ci const struct pci_device_id *pci_id) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci static int dev; 28862306a36Sopenharmony_ci struct snd_card *card; 28962306a36Sopenharmony_ci struct cs5535audio *cs5535au; 29062306a36Sopenharmony_ci int err; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (dev >= SNDRV_CARDS) 29362306a36Sopenharmony_ci return -ENODEV; 29462306a36Sopenharmony_ci if (!enable[dev]) { 29562306a36Sopenharmony_ci dev++; 29662306a36Sopenharmony_ci return -ENOENT; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, 30062306a36Sopenharmony_ci sizeof(*cs5535au), &card); 30162306a36Sopenharmony_ci if (err < 0) 30262306a36Sopenharmony_ci return err; 30362306a36Sopenharmony_ci cs5535au = card->private_data; 30462306a36Sopenharmony_ci card->private_free = snd_cs5535audio_free; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci err = snd_cs5535audio_create(card, pci); 30762306a36Sopenharmony_ci if (err < 0) 30862306a36Sopenharmony_ci return err; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci err = snd_cs5535audio_mixer(cs5535au); 31162306a36Sopenharmony_ci if (err < 0) 31262306a36Sopenharmony_ci return err; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci err = snd_cs5535audio_pcm(cs5535au); 31562306a36Sopenharmony_ci if (err < 0) 31662306a36Sopenharmony_ci return err; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci strcpy(card->driver, DRIVER_NAME); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci strcpy(card->shortname, "CS5535 Audio"); 32162306a36Sopenharmony_ci sprintf(card->longname, "%s %s at 0x%lx, irq %i", 32262306a36Sopenharmony_ci card->shortname, card->driver, 32362306a36Sopenharmony_ci cs5535au->port, cs5535au->irq); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci err = snd_card_register(card); 32662306a36Sopenharmony_ci if (err < 0) 32762306a36Sopenharmony_ci return err; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci pci_set_drvdata(pci, card); 33062306a36Sopenharmony_ci dev++; 33162306a36Sopenharmony_ci return 0; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic int snd_cs5535audio_probe(struct pci_dev *pci, 33562306a36Sopenharmony_ci const struct pci_device_id *pci_id) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic struct pci_driver cs5535audio_driver = { 34162306a36Sopenharmony_ci .name = KBUILD_MODNAME, 34262306a36Sopenharmony_ci .id_table = snd_cs5535audio_ids, 34362306a36Sopenharmony_ci .probe = snd_cs5535audio_probe, 34462306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 34562306a36Sopenharmony_ci .driver = { 34662306a36Sopenharmony_ci .pm = &snd_cs5535audio_pm, 34762306a36Sopenharmony_ci }, 34862306a36Sopenharmony_ci#endif 34962306a36Sopenharmony_ci}; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cimodule_pci_driver(cs5535audio_driver); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ciMODULE_AUTHOR("Jaya Kumar"); 35462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 35562306a36Sopenharmony_ciMODULE_DESCRIPTION("CS5535 Audio"); 356