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