162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HD-audio controller helpers
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/delay.h>
862306a36Sopenharmony_ci#include <linux/export.h>
962306a36Sopenharmony_ci#include <sound/core.h>
1062306a36Sopenharmony_ci#include <sound/hdaudio.h>
1162306a36Sopenharmony_ci#include <sound/hda_register.h>
1262306a36Sopenharmony_ci#include "local.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* clear CORB read pointer properly */
1562306a36Sopenharmony_cistatic void azx_clear_corbrp(struct hdac_bus *bus)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	int timeout;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	for (timeout = 1000; timeout > 0; timeout--) {
2062306a36Sopenharmony_ci		if (snd_hdac_chip_readw(bus, CORBRP) & AZX_CORBRP_RST)
2162306a36Sopenharmony_ci			break;
2262306a36Sopenharmony_ci		udelay(1);
2362306a36Sopenharmony_ci	}
2462306a36Sopenharmony_ci	if (timeout <= 0)
2562306a36Sopenharmony_ci		dev_err(bus->dev, "CORB reset timeout#1, CORBRP = %d\n",
2662306a36Sopenharmony_ci			snd_hdac_chip_readw(bus, CORBRP));
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, CORBRP, 0);
2962306a36Sopenharmony_ci	for (timeout = 1000; timeout > 0; timeout--) {
3062306a36Sopenharmony_ci		if (snd_hdac_chip_readw(bus, CORBRP) == 0)
3162306a36Sopenharmony_ci			break;
3262306a36Sopenharmony_ci		udelay(1);
3362306a36Sopenharmony_ci	}
3462306a36Sopenharmony_ci	if (timeout <= 0)
3562306a36Sopenharmony_ci		dev_err(bus->dev, "CORB reset timeout#2, CORBRP = %d\n",
3662306a36Sopenharmony_ci			snd_hdac_chip_readw(bus, CORBRP));
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/**
4062306a36Sopenharmony_ci * snd_hdac_bus_init_cmd_io - set up CORB/RIRB buffers
4162306a36Sopenharmony_ci * @bus: HD-audio core bus
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_civoid snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	WARN_ON_ONCE(!bus->rb.area);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	spin_lock_irq(&bus->reg_lock);
4862306a36Sopenharmony_ci	/* CORB set up */
4962306a36Sopenharmony_ci	bus->corb.addr = bus->rb.addr;
5062306a36Sopenharmony_ci	bus->corb.buf = (__le32 *)bus->rb.area;
5162306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, CORBLBASE, (u32)bus->corb.addr);
5262306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, CORBUBASE, upper_32_bits(bus->corb.addr));
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/* set the corb size to 256 entries (ULI requires explicitly) */
5562306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, CORBSIZE, 0x02);
5662306a36Sopenharmony_ci	/* set the corb write pointer to 0 */
5762306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, CORBWP, 0);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/* reset the corb hw read pointer */
6062306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, CORBRP, AZX_CORBRP_RST);
6162306a36Sopenharmony_ci	if (!bus->corbrp_self_clear)
6262306a36Sopenharmony_ci		azx_clear_corbrp(bus);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* enable corb dma */
6562306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, CORBCTL, AZX_CORBCTL_RUN);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	/* RIRB set up */
6862306a36Sopenharmony_ci	bus->rirb.addr = bus->rb.addr + 2048;
6962306a36Sopenharmony_ci	bus->rirb.buf = (__le32 *)(bus->rb.area + 2048);
7062306a36Sopenharmony_ci	bus->rirb.wp = bus->rirb.rp = 0;
7162306a36Sopenharmony_ci	memset(bus->rirb.cmds, 0, sizeof(bus->rirb.cmds));
7262306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, RIRBLBASE, (u32)bus->rirb.addr);
7362306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, RIRBUBASE, upper_32_bits(bus->rirb.addr));
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* set the rirb size to 256 entries (ULI requires explicitly) */
7662306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, RIRBSIZE, 0x02);
7762306a36Sopenharmony_ci	/* reset the rirb hw write pointer */
7862306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST);
7962306a36Sopenharmony_ci	/* set N=1, get RIRB response interrupt for new entry */
8062306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, RINTCNT, 1);
8162306a36Sopenharmony_ci	/* enable rirb dma and response irq */
8262306a36Sopenharmony_ci	if (bus->not_use_interrupts)
8362306a36Sopenharmony_ci		snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN);
8462306a36Sopenharmony_ci	else
8562306a36Sopenharmony_ci		snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
8662306a36Sopenharmony_ci	/* Accept unsolicited responses */
8762306a36Sopenharmony_ci	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
8862306a36Sopenharmony_ci	spin_unlock_irq(&bus->reg_lock);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci/* wait for cmd dmas till they are stopped */
9362306a36Sopenharmony_cistatic void hdac_wait_for_cmd_dmas(struct hdac_bus *bus)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	unsigned long timeout;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
9862306a36Sopenharmony_ci	while ((snd_hdac_chip_readb(bus, RIRBCTL) & AZX_RBCTL_DMA_EN)
9962306a36Sopenharmony_ci		&& time_before(jiffies, timeout))
10062306a36Sopenharmony_ci		udelay(10);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
10362306a36Sopenharmony_ci	while ((snd_hdac_chip_readb(bus, CORBCTL) & AZX_CORBCTL_RUN)
10462306a36Sopenharmony_ci		&& time_before(jiffies, timeout))
10562306a36Sopenharmony_ci		udelay(10);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/**
10962306a36Sopenharmony_ci * snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers
11062306a36Sopenharmony_ci * @bus: HD-audio core bus
11162306a36Sopenharmony_ci */
11262306a36Sopenharmony_civoid snd_hdac_bus_stop_cmd_io(struct hdac_bus *bus)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	spin_lock_irq(&bus->reg_lock);
11562306a36Sopenharmony_ci	/* disable ringbuffer DMAs */
11662306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, RIRBCTL, 0);
11762306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, CORBCTL, 0);
11862306a36Sopenharmony_ci	spin_unlock_irq(&bus->reg_lock);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	hdac_wait_for_cmd_dmas(bus);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	spin_lock_irq(&bus->reg_lock);
12362306a36Sopenharmony_ci	/* disable unsolicited responses */
12462306a36Sopenharmony_ci	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0);
12562306a36Sopenharmony_ci	spin_unlock_irq(&bus->reg_lock);
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_stop_cmd_io);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic unsigned int azx_command_addr(u32 cmd)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	unsigned int addr = cmd >> 28;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (snd_BUG_ON(addr >= HDA_MAX_CODECS))
13462306a36Sopenharmony_ci		addr = 0;
13562306a36Sopenharmony_ci	return addr;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci/**
13962306a36Sopenharmony_ci * snd_hdac_bus_send_cmd - send a command verb via CORB
14062306a36Sopenharmony_ci * @bus: HD-audio core bus
14162306a36Sopenharmony_ci * @val: encoded verb value to send
14262306a36Sopenharmony_ci *
14362306a36Sopenharmony_ci * Returns zero for success or a negative error code.
14462306a36Sopenharmony_ci */
14562306a36Sopenharmony_ciint snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	unsigned int addr = azx_command_addr(val);
14862306a36Sopenharmony_ci	unsigned int wp, rp;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	spin_lock_irq(&bus->reg_lock);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	bus->last_cmd[azx_command_addr(val)] = val;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	/* add command to corb */
15562306a36Sopenharmony_ci	wp = snd_hdac_chip_readw(bus, CORBWP);
15662306a36Sopenharmony_ci	if (wp == 0xffff) {
15762306a36Sopenharmony_ci		/* something wrong, controller likely turned to D3 */
15862306a36Sopenharmony_ci		spin_unlock_irq(&bus->reg_lock);
15962306a36Sopenharmony_ci		return -EIO;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci	wp++;
16262306a36Sopenharmony_ci	wp %= AZX_MAX_CORB_ENTRIES;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	rp = snd_hdac_chip_readw(bus, CORBRP);
16562306a36Sopenharmony_ci	if (wp == rp) {
16662306a36Sopenharmony_ci		/* oops, it's full */
16762306a36Sopenharmony_ci		spin_unlock_irq(&bus->reg_lock);
16862306a36Sopenharmony_ci		return -EAGAIN;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	bus->rirb.cmds[addr]++;
17262306a36Sopenharmony_ci	bus->corb.buf[wp] = cpu_to_le32(val);
17362306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, CORBWP, wp);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	spin_unlock_irq(&bus->reg_lock);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_send_cmd);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci#define AZX_RIRB_EX_UNSOL_EV	(1<<4)
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci/**
18462306a36Sopenharmony_ci * snd_hdac_bus_update_rirb - retrieve RIRB entries
18562306a36Sopenharmony_ci * @bus: HD-audio core bus
18662306a36Sopenharmony_ci *
18762306a36Sopenharmony_ci * Usually called from interrupt handler.
18862306a36Sopenharmony_ci * The caller needs bus->reg_lock spinlock before calling this.
18962306a36Sopenharmony_ci */
19062306a36Sopenharmony_civoid snd_hdac_bus_update_rirb(struct hdac_bus *bus)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	unsigned int rp, wp;
19362306a36Sopenharmony_ci	unsigned int addr;
19462306a36Sopenharmony_ci	u32 res, res_ex;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	wp = snd_hdac_chip_readw(bus, RIRBWP);
19762306a36Sopenharmony_ci	if (wp == 0xffff) {
19862306a36Sopenharmony_ci		/* something wrong, controller likely turned to D3 */
19962306a36Sopenharmony_ci		return;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	if (wp == bus->rirb.wp)
20362306a36Sopenharmony_ci		return;
20462306a36Sopenharmony_ci	bus->rirb.wp = wp;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	while (bus->rirb.rp != wp) {
20762306a36Sopenharmony_ci		bus->rirb.rp++;
20862306a36Sopenharmony_ci		bus->rirb.rp %= AZX_MAX_RIRB_ENTRIES;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		rp = bus->rirb.rp << 1; /* an RIRB entry is 8-bytes */
21162306a36Sopenharmony_ci		res_ex = le32_to_cpu(bus->rirb.buf[rp + 1]);
21262306a36Sopenharmony_ci		res = le32_to_cpu(bus->rirb.buf[rp]);
21362306a36Sopenharmony_ci		addr = res_ex & 0xf;
21462306a36Sopenharmony_ci		if (addr >= HDA_MAX_CODECS) {
21562306a36Sopenharmony_ci			dev_err(bus->dev,
21662306a36Sopenharmony_ci				"spurious response %#x:%#x, rp = %d, wp = %d",
21762306a36Sopenharmony_ci				res, res_ex, bus->rirb.rp, wp);
21862306a36Sopenharmony_ci			snd_BUG();
21962306a36Sopenharmony_ci		} else if (res_ex & AZX_RIRB_EX_UNSOL_EV)
22062306a36Sopenharmony_ci			snd_hdac_bus_queue_event(bus, res, res_ex);
22162306a36Sopenharmony_ci		else if (bus->rirb.cmds[addr]) {
22262306a36Sopenharmony_ci			bus->rirb.res[addr] = res;
22362306a36Sopenharmony_ci			bus->rirb.cmds[addr]--;
22462306a36Sopenharmony_ci			if (!bus->rirb.cmds[addr] &&
22562306a36Sopenharmony_ci			    waitqueue_active(&bus->rirb_wq))
22662306a36Sopenharmony_ci				wake_up(&bus->rirb_wq);
22762306a36Sopenharmony_ci		} else {
22862306a36Sopenharmony_ci			dev_err_ratelimited(bus->dev,
22962306a36Sopenharmony_ci				"spurious response %#x:%#x, last cmd=%#08x\n",
23062306a36Sopenharmony_ci				res, res_ex, bus->last_cmd[addr]);
23162306a36Sopenharmony_ci		}
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_update_rirb);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci/**
23762306a36Sopenharmony_ci * snd_hdac_bus_get_response - receive a response via RIRB
23862306a36Sopenharmony_ci * @bus: HD-audio core bus
23962306a36Sopenharmony_ci * @addr: codec address
24062306a36Sopenharmony_ci * @res: pointer to store the value, NULL when not needed
24162306a36Sopenharmony_ci *
24262306a36Sopenharmony_ci * Returns zero if a value is read, or a negative error code.
24362306a36Sopenharmony_ci */
24462306a36Sopenharmony_ciint snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
24562306a36Sopenharmony_ci			      unsigned int *res)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	unsigned long timeout;
24862306a36Sopenharmony_ci	unsigned long loopcounter;
24962306a36Sopenharmony_ci	wait_queue_entry_t wait;
25062306a36Sopenharmony_ci	bool warned = false;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	init_wait_entry(&wait, 0);
25362306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(1000);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	for (loopcounter = 0;; loopcounter++) {
25662306a36Sopenharmony_ci		spin_lock_irq(&bus->reg_lock);
25762306a36Sopenharmony_ci		if (!bus->polling_mode)
25862306a36Sopenharmony_ci			prepare_to_wait(&bus->rirb_wq, &wait,
25962306a36Sopenharmony_ci					TASK_UNINTERRUPTIBLE);
26062306a36Sopenharmony_ci		if (bus->polling_mode)
26162306a36Sopenharmony_ci			snd_hdac_bus_update_rirb(bus);
26262306a36Sopenharmony_ci		if (!bus->rirb.cmds[addr]) {
26362306a36Sopenharmony_ci			if (res)
26462306a36Sopenharmony_ci				*res = bus->rirb.res[addr]; /* the last value */
26562306a36Sopenharmony_ci			if (!bus->polling_mode)
26662306a36Sopenharmony_ci				finish_wait(&bus->rirb_wq, &wait);
26762306a36Sopenharmony_ci			spin_unlock_irq(&bus->reg_lock);
26862306a36Sopenharmony_ci			return 0;
26962306a36Sopenharmony_ci		}
27062306a36Sopenharmony_ci		spin_unlock_irq(&bus->reg_lock);
27162306a36Sopenharmony_ci		if (time_after(jiffies, timeout))
27262306a36Sopenharmony_ci			break;
27362306a36Sopenharmony_ci#define LOOP_COUNT_MAX	3000
27462306a36Sopenharmony_ci		if (!bus->polling_mode) {
27562306a36Sopenharmony_ci			schedule_timeout(msecs_to_jiffies(2));
27662306a36Sopenharmony_ci		} else if (bus->needs_damn_long_delay ||
27762306a36Sopenharmony_ci			   loopcounter > LOOP_COUNT_MAX) {
27862306a36Sopenharmony_ci			if (loopcounter > LOOP_COUNT_MAX && !warned) {
27962306a36Sopenharmony_ci				dev_dbg_ratelimited(bus->dev,
28062306a36Sopenharmony_ci						    "too slow response, last cmd=%#08x\n",
28162306a36Sopenharmony_ci						    bus->last_cmd[addr]);
28262306a36Sopenharmony_ci				warned = true;
28362306a36Sopenharmony_ci			}
28462306a36Sopenharmony_ci			msleep(2); /* temporary workaround */
28562306a36Sopenharmony_ci		} else {
28662306a36Sopenharmony_ci			udelay(10);
28762306a36Sopenharmony_ci			cond_resched();
28862306a36Sopenharmony_ci		}
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	if (!bus->polling_mode)
29262306a36Sopenharmony_ci		finish_wait(&bus->rirb_wq, &wait);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return -EIO;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_get_response);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci#define HDAC_MAX_CAPS 10
29962306a36Sopenharmony_ci/**
30062306a36Sopenharmony_ci * snd_hdac_bus_parse_capabilities - parse capability structure
30162306a36Sopenharmony_ci * @bus: the pointer to bus object
30262306a36Sopenharmony_ci *
30362306a36Sopenharmony_ci * Returns 0 if successful, or a negative error code.
30462306a36Sopenharmony_ci */
30562306a36Sopenharmony_ciint snd_hdac_bus_parse_capabilities(struct hdac_bus *bus)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	unsigned int cur_cap;
30862306a36Sopenharmony_ci	unsigned int offset;
30962306a36Sopenharmony_ci	unsigned int counter = 0;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	offset = snd_hdac_chip_readw(bus, LLCH);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	/* Lets walk the linked capabilities list */
31462306a36Sopenharmony_ci	do {
31562306a36Sopenharmony_ci		cur_cap = _snd_hdac_chip_readl(bus, offset);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		dev_dbg(bus->dev, "Capability version: 0x%x\n",
31862306a36Sopenharmony_ci			(cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci		dev_dbg(bus->dev, "HDA capability ID: 0x%x\n",
32162306a36Sopenharmony_ci			(cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci		if (cur_cap == -1) {
32462306a36Sopenharmony_ci			dev_dbg(bus->dev, "Invalid capability reg read\n");
32562306a36Sopenharmony_ci			break;
32662306a36Sopenharmony_ci		}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) {
32962306a36Sopenharmony_ci		case AZX_ML_CAP_ID:
33062306a36Sopenharmony_ci			dev_dbg(bus->dev, "Found ML capability\n");
33162306a36Sopenharmony_ci			bus->mlcap = bus->remap_addr + offset;
33262306a36Sopenharmony_ci			break;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		case AZX_GTS_CAP_ID:
33562306a36Sopenharmony_ci			dev_dbg(bus->dev, "Found GTS capability offset=%x\n", offset);
33662306a36Sopenharmony_ci			bus->gtscap = bus->remap_addr + offset;
33762306a36Sopenharmony_ci			break;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci		case AZX_PP_CAP_ID:
34062306a36Sopenharmony_ci			/* PP capability found, the Audio DSP is present */
34162306a36Sopenharmony_ci			dev_dbg(bus->dev, "Found PP capability offset=%x\n", offset);
34262306a36Sopenharmony_ci			bus->ppcap = bus->remap_addr + offset;
34362306a36Sopenharmony_ci			break;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci		case AZX_SPB_CAP_ID:
34662306a36Sopenharmony_ci			/* SPIB capability found, handler function */
34762306a36Sopenharmony_ci			dev_dbg(bus->dev, "Found SPB capability\n");
34862306a36Sopenharmony_ci			bus->spbcap = bus->remap_addr + offset;
34962306a36Sopenharmony_ci			break;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci		case AZX_DRSM_CAP_ID:
35262306a36Sopenharmony_ci			/* DMA resume  capability found, handler function */
35362306a36Sopenharmony_ci			dev_dbg(bus->dev, "Found DRSM capability\n");
35462306a36Sopenharmony_ci			bus->drsmcap = bus->remap_addr + offset;
35562306a36Sopenharmony_ci			break;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci		default:
35862306a36Sopenharmony_ci			dev_err(bus->dev, "Unknown capability %d\n", cur_cap);
35962306a36Sopenharmony_ci			cur_cap = 0;
36062306a36Sopenharmony_ci			break;
36162306a36Sopenharmony_ci		}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci		counter++;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci		if (counter > HDAC_MAX_CAPS) {
36662306a36Sopenharmony_ci			dev_err(bus->dev, "We exceeded HDAC capabilities!!!\n");
36762306a36Sopenharmony_ci			break;
36862306a36Sopenharmony_ci		}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci		/* read the offset of next capability */
37162306a36Sopenharmony_ci		offset = cur_cap & AZX_CAP_HDR_NXT_PTR_MASK;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	} while (offset);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	return 0;
37662306a36Sopenharmony_ci}
37762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_parse_capabilities);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci/*
38062306a36Sopenharmony_ci * Lowlevel interface
38162306a36Sopenharmony_ci */
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci/**
38462306a36Sopenharmony_ci * snd_hdac_bus_enter_link_reset - enter link reset
38562306a36Sopenharmony_ci * @bus: HD-audio core bus
38662306a36Sopenharmony_ci *
38762306a36Sopenharmony_ci * Enter to the link reset state.
38862306a36Sopenharmony_ci */
38962306a36Sopenharmony_civoid snd_hdac_bus_enter_link_reset(struct hdac_bus *bus)
39062306a36Sopenharmony_ci{
39162306a36Sopenharmony_ci	unsigned long timeout;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	/* reset controller */
39462306a36Sopenharmony_ci	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, 0);
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
39762306a36Sopenharmony_ci	while ((snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET) &&
39862306a36Sopenharmony_ci	       time_before(jiffies, timeout))
39962306a36Sopenharmony_ci		usleep_range(500, 1000);
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_enter_link_reset);
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci/**
40462306a36Sopenharmony_ci * snd_hdac_bus_exit_link_reset - exit link reset
40562306a36Sopenharmony_ci * @bus: HD-audio core bus
40662306a36Sopenharmony_ci *
40762306a36Sopenharmony_ci * Exit from the link reset state.
40862306a36Sopenharmony_ci */
40962306a36Sopenharmony_civoid snd_hdac_bus_exit_link_reset(struct hdac_bus *bus)
41062306a36Sopenharmony_ci{
41162306a36Sopenharmony_ci	unsigned long timeout;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
41662306a36Sopenharmony_ci	while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout))
41762306a36Sopenharmony_ci		usleep_range(500, 1000);
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_exit_link_reset);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci/* reset codec link */
42262306a36Sopenharmony_ciint snd_hdac_bus_reset_link(struct hdac_bus *bus, bool full_reset)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	if (!full_reset)
42562306a36Sopenharmony_ci		goto skip_reset;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	/* clear STATESTS if not in reset */
42862306a36Sopenharmony_ci	if (snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET)
42962306a36Sopenharmony_ci		snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	/* reset controller */
43262306a36Sopenharmony_ci	snd_hdac_bus_enter_link_reset(bus);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	/* delay for >= 100us for codec PLL to settle per spec
43562306a36Sopenharmony_ci	 * Rev 0.9 section 5.5.1
43662306a36Sopenharmony_ci	 */
43762306a36Sopenharmony_ci	usleep_range(500, 1000);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	/* Bring controller out of reset */
44062306a36Sopenharmony_ci	snd_hdac_bus_exit_link_reset(bus);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	/* Brent Chartrand said to wait >= 540us for codecs to initialize */
44362306a36Sopenharmony_ci	usleep_range(1000, 1200);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci skip_reset:
44662306a36Sopenharmony_ci	/* check to see if controller is ready */
44762306a36Sopenharmony_ci	if (!snd_hdac_chip_readb(bus, GCTL)) {
44862306a36Sopenharmony_ci		dev_dbg(bus->dev, "controller not ready!\n");
44962306a36Sopenharmony_ci		return -EBUSY;
45062306a36Sopenharmony_ci	}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	/* detect codecs */
45362306a36Sopenharmony_ci	if (!bus->codec_mask) {
45462306a36Sopenharmony_ci		bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
45562306a36Sopenharmony_ci		dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
45662306a36Sopenharmony_ci	}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	return 0;
45962306a36Sopenharmony_ci}
46062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_reset_link);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci/* enable interrupts */
46362306a36Sopenharmony_cistatic void azx_int_enable(struct hdac_bus *bus)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	/* enable controller CIE and GIE */
46662306a36Sopenharmony_ci	snd_hdac_chip_updatel(bus, INTCTL,
46762306a36Sopenharmony_ci			      AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN,
46862306a36Sopenharmony_ci			      AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
46962306a36Sopenharmony_ci}
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci/* disable interrupts */
47262306a36Sopenharmony_cistatic void azx_int_disable(struct hdac_bus *bus)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	struct hdac_stream *azx_dev;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	/* disable interrupts in stream descriptor */
47762306a36Sopenharmony_ci	list_for_each_entry(azx_dev, &bus->stream_list, list)
47862306a36Sopenharmony_ci		snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0);
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	/* disable SIE for all streams & disable controller CIE and GIE */
48162306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, INTCTL, 0);
48262306a36Sopenharmony_ci}
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci/* clear interrupts */
48562306a36Sopenharmony_cistatic void azx_int_clear(struct hdac_bus *bus)
48662306a36Sopenharmony_ci{
48762306a36Sopenharmony_ci	struct hdac_stream *azx_dev;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	/* clear stream status */
49062306a36Sopenharmony_ci	list_for_each_entry(azx_dev, &bus->stream_list, list)
49162306a36Sopenharmony_ci		snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	/* clear STATESTS */
49462306a36Sopenharmony_ci	snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	/* clear rirb status */
49762306a36Sopenharmony_ci	snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	/* clear int status */
50062306a36Sopenharmony_ci	snd_hdac_chip_writel(bus, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM);
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci/**
50462306a36Sopenharmony_ci * snd_hdac_bus_init_chip - reset and start the controller registers
50562306a36Sopenharmony_ci * @bus: HD-audio core bus
50662306a36Sopenharmony_ci * @full_reset: Do full reset
50762306a36Sopenharmony_ci */
50862306a36Sopenharmony_cibool snd_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset)
50962306a36Sopenharmony_ci{
51062306a36Sopenharmony_ci	if (bus->chip_init)
51162306a36Sopenharmony_ci		return false;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	/* reset controller */
51462306a36Sopenharmony_ci	snd_hdac_bus_reset_link(bus, full_reset);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	/* clear interrupts */
51762306a36Sopenharmony_ci	azx_int_clear(bus);
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	/* initialize the codec command I/O */
52062306a36Sopenharmony_ci	snd_hdac_bus_init_cmd_io(bus);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	/* enable interrupts after CORB/RIRB buffers are initialized above */
52362306a36Sopenharmony_ci	azx_int_enable(bus);
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	/* program the position buffer */
52662306a36Sopenharmony_ci	if (bus->use_posbuf && bus->posbuf.addr) {
52762306a36Sopenharmony_ci		snd_hdac_chip_writel(bus, DPLBASE, (u32)bus->posbuf.addr);
52862306a36Sopenharmony_ci		snd_hdac_chip_writel(bus, DPUBASE, upper_32_bits(bus->posbuf.addr));
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	bus->chip_init = true;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	return true;
53462306a36Sopenharmony_ci}
53562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_init_chip);
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci/**
53862306a36Sopenharmony_ci * snd_hdac_bus_stop_chip - disable the whole IRQ and I/Os
53962306a36Sopenharmony_ci * @bus: HD-audio core bus
54062306a36Sopenharmony_ci */
54162306a36Sopenharmony_civoid snd_hdac_bus_stop_chip(struct hdac_bus *bus)
54262306a36Sopenharmony_ci{
54362306a36Sopenharmony_ci	if (!bus->chip_init)
54462306a36Sopenharmony_ci		return;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	/* disable interrupts */
54762306a36Sopenharmony_ci	azx_int_disable(bus);
54862306a36Sopenharmony_ci	azx_int_clear(bus);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	/* disable CORB/RIRB */
55162306a36Sopenharmony_ci	snd_hdac_bus_stop_cmd_io(bus);
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	/* disable position buffer */
55462306a36Sopenharmony_ci	if (bus->posbuf.addr) {
55562306a36Sopenharmony_ci		snd_hdac_chip_writel(bus, DPLBASE, 0);
55662306a36Sopenharmony_ci		snd_hdac_chip_writel(bus, DPUBASE, 0);
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	bus->chip_init = false;
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_stop_chip);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci/**
56462306a36Sopenharmony_ci * snd_hdac_bus_handle_stream_irq - interrupt handler for streams
56562306a36Sopenharmony_ci * @bus: HD-audio core bus
56662306a36Sopenharmony_ci * @status: INTSTS register value
56762306a36Sopenharmony_ci * @ack: callback to be called for woken streams
56862306a36Sopenharmony_ci *
56962306a36Sopenharmony_ci * Returns the bits of handled streams, or zero if no stream is handled.
57062306a36Sopenharmony_ci */
57162306a36Sopenharmony_ciint snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status,
57262306a36Sopenharmony_ci				    void (*ack)(struct hdac_bus *,
57362306a36Sopenharmony_ci						struct hdac_stream *))
57462306a36Sopenharmony_ci{
57562306a36Sopenharmony_ci	struct hdac_stream *azx_dev;
57662306a36Sopenharmony_ci	u8 sd_status;
57762306a36Sopenharmony_ci	int handled = 0;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	list_for_each_entry(azx_dev, &bus->stream_list, list) {
58062306a36Sopenharmony_ci		if (status & azx_dev->sd_int_sta_mask) {
58162306a36Sopenharmony_ci			sd_status = snd_hdac_stream_readb(azx_dev, SD_STS);
58262306a36Sopenharmony_ci			snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
58362306a36Sopenharmony_ci			handled |= 1 << azx_dev->index;
58462306a36Sopenharmony_ci			if ((!azx_dev->substream && !azx_dev->cstream) ||
58562306a36Sopenharmony_ci			    !azx_dev->running || !(sd_status & SD_INT_COMPLETE))
58662306a36Sopenharmony_ci				continue;
58762306a36Sopenharmony_ci			if (ack)
58862306a36Sopenharmony_ci				ack(bus, azx_dev);
58962306a36Sopenharmony_ci		}
59062306a36Sopenharmony_ci	}
59162306a36Sopenharmony_ci	return handled;
59262306a36Sopenharmony_ci}
59362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_handle_stream_irq);
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci/**
59662306a36Sopenharmony_ci * snd_hdac_bus_alloc_stream_pages - allocate BDL and other buffers
59762306a36Sopenharmony_ci * @bus: HD-audio core bus
59862306a36Sopenharmony_ci *
59962306a36Sopenharmony_ci * Call this after assigning the all streams.
60062306a36Sopenharmony_ci * Returns zero for success, or a negative error code.
60162306a36Sopenharmony_ci */
60262306a36Sopenharmony_ciint snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus)
60362306a36Sopenharmony_ci{
60462306a36Sopenharmony_ci	struct hdac_stream *s;
60562306a36Sopenharmony_ci	int num_streams = 0;
60662306a36Sopenharmony_ci	int dma_type = bus->dma_type ? bus->dma_type : SNDRV_DMA_TYPE_DEV;
60762306a36Sopenharmony_ci	int err;
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	list_for_each_entry(s, &bus->stream_list, list) {
61062306a36Sopenharmony_ci		/* allocate memory for the BDL for each stream */
61162306a36Sopenharmony_ci		err = snd_dma_alloc_pages(dma_type, bus->dev,
61262306a36Sopenharmony_ci					  BDL_SIZE, &s->bdl);
61362306a36Sopenharmony_ci		num_streams++;
61462306a36Sopenharmony_ci		if (err < 0)
61562306a36Sopenharmony_ci			return -ENOMEM;
61662306a36Sopenharmony_ci	}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	if (WARN_ON(!num_streams))
61962306a36Sopenharmony_ci		return -EINVAL;
62062306a36Sopenharmony_ci	/* allocate memory for the position buffer */
62162306a36Sopenharmony_ci	err = snd_dma_alloc_pages(dma_type, bus->dev,
62262306a36Sopenharmony_ci				  num_streams * 8, &bus->posbuf);
62362306a36Sopenharmony_ci	if (err < 0)
62462306a36Sopenharmony_ci		return -ENOMEM;
62562306a36Sopenharmony_ci	list_for_each_entry(s, &bus->stream_list, list)
62662306a36Sopenharmony_ci		s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	/* single page (at least 4096 bytes) must suffice for both ringbuffes */
62962306a36Sopenharmony_ci	return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb);
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages);
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci/**
63462306a36Sopenharmony_ci * snd_hdac_bus_free_stream_pages - release BDL and other buffers
63562306a36Sopenharmony_ci * @bus: HD-audio core bus
63662306a36Sopenharmony_ci */
63762306a36Sopenharmony_civoid snd_hdac_bus_free_stream_pages(struct hdac_bus *bus)
63862306a36Sopenharmony_ci{
63962306a36Sopenharmony_ci	struct hdac_stream *s;
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	list_for_each_entry(s, &bus->stream_list, list) {
64262306a36Sopenharmony_ci		if (s->bdl.area)
64362306a36Sopenharmony_ci			snd_dma_free_pages(&s->bdl);
64462306a36Sopenharmony_ci	}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	if (bus->rb.area)
64762306a36Sopenharmony_ci		snd_dma_free_pages(&bus->rb);
64862306a36Sopenharmony_ci	if (bus->posbuf.area)
64962306a36Sopenharmony_ci		snd_dma_free_pages(&bus->posbuf);
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages);
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci/**
65462306a36Sopenharmony_ci * snd_hdac_bus_link_power - power up/down codec link
65562306a36Sopenharmony_ci * @codec: HD-audio device
65662306a36Sopenharmony_ci * @enable: whether to power-up the link
65762306a36Sopenharmony_ci */
65862306a36Sopenharmony_civoid snd_hdac_bus_link_power(struct hdac_device *codec, bool enable)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	if (enable)
66162306a36Sopenharmony_ci		set_bit(codec->addr, &codec->bus->codec_powered);
66262306a36Sopenharmony_ci	else
66362306a36Sopenharmony_ci		clear_bit(codec->addr, &codec->bus->codec_powered);
66462306a36Sopenharmony_ci}
66562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_bus_link_power);
666