[RFC] ALSA: hda: Add workaround to adapt to Loongson 7A1000 controller

Kaige Li likaige at loongson.cn
Fri Jul 31 04:41:54 CEST 2020


There's some issues that cause palyback without a sound on Loongson
platform (3A3000 + 7A1000) with a Realtek ALC269 codec. After lengthy
debugging sessions, we solved it by adding workaround.

Signed-off-by: Kaige Li <likaige at loongson.cn>
---
 sound/hda/hdac_controller.c    | 63 ++++++++++++++++++++++++++++++++++++------
 sound/hda/hdac_stream.c        | 22 +++++++++++++--
 sound/pci/hda/hda_controller.h |  2 +-
 sound/pci/hda/hda_intel.c      |  9 +++++-
 4 files changed, 83 insertions(+), 13 deletions(-)

diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c
index 011b17c..f60f49a 100644
--- a/sound/hda/hdac_controller.c
+++ b/sound/hda/hdac_controller.c
@@ -9,6 +9,7 @@
 #include <sound/core.h>
 #include <sound/hdaudio.h>
 #include <sound/hda_register.h>
+#include "../pci/hda/hda_controller.h"
 #include "local.h"
 
 /* clear CORB read pointer properly */
@@ -42,6 +43,8 @@ static void azx_clear_corbrp(struct hdac_bus *bus)
  */
 void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 {
+	struct azx *chip = bus_to_azx(bus);
+
 	WARN_ON_ONCE(!bus->rb.area);
 
 	spin_lock_irq(&bus->reg_lock);
@@ -58,11 +61,15 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 
 	/* reset the corb hw read pointer */
 	snd_hdac_chip_writew(bus, CORBRP, AZX_CORBRP_RST);
-	if (!bus->corbrp_self_clear)
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		snd_hdac_chip_writew(bus, CORBRP, 0);
+	else if (!bus->corbrp_self_clear)
 		azx_clear_corbrp(bus);
 
 	/* enable corb dma */
 	snd_hdac_chip_writeb(bus, CORBCTL, AZX_CORBCTL_RUN);
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		snd_hdac_chip_readb(bus, CORBCTL);
 
 	/* RIRB set up */
 	bus->rirb.addr = bus->rb.addr + 2048;
@@ -79,7 +86,12 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 	/* set N=1, get RIRB response interrupt for new entry */
 	snd_hdac_chip_writew(bus, RINTCNT, 1);
 	/* enable rirb dma and response irq */
-	snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND) {
+		snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN);
+		snd_hdac_chip_readb(bus, RIRBCTL);
+	} else {
+		snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+	}
 	/* Accept unsolicited responses */
 	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
 	spin_unlock_irq(&bus->reg_lock);
@@ -132,6 +144,18 @@ static unsigned int azx_command_addr(u32 cmd)
 	return addr;
 }
 
+static unsigned int azx_response_addr(u32 res)
+{
+	unsigned int addr = res & 0xf;
+
+	if (addr >= AZX_MAX_CODECS) {
+		snd_BUG();
+		addr = 0;
+	}
+
+	return addr;
+}
+
 /**
  * snd_hdac_bus_send_cmd - send a command verb via CORB
  * @bus: HD-audio core bus
@@ -189,6 +213,7 @@ void snd_hdac_bus_update_rirb(struct hdac_bus *bus)
 	unsigned int rp, wp;
 	unsigned int addr;
 	u32 res, res_ex;
+	struct azx *chip = bus_to_azx(bus);
 
 	wp = snd_hdac_chip_readw(bus, RIRBWP);
 	if (wp == 0xffff) {
@@ -207,7 +232,11 @@ void snd_hdac_bus_update_rirb(struct hdac_bus *bus)
 		rp = bus->rirb.rp << 1; /* an RIRB entry is 8-bytes */
 		res_ex = le32_to_cpu(bus->rirb.buf[rp + 1]);
 		res = le32_to_cpu(bus->rirb.buf[rp]);
-		addr = res_ex & 0xf;
+		if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND) {
+			addr = azx_response_addr(res_ex);
+		} else {
+			addr = res_ex & 0xf;
+		}
 		if (addr >= HDA_MAX_CODECS) {
 			dev_err(bus->dev,
 				"spurious response %#x:%#x, rp = %d, wp = %d",
@@ -245,6 +274,7 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
 	unsigned long loopcounter;
 	wait_queue_entry_t wait;
 	bool warned = false;
+	struct azx *chip = bus_to_azx(bus);
 
 	init_wait_entry(&wait, 0);
 	timeout = jiffies + msecs_to_jiffies(1000);
@@ -254,8 +284,11 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
 		if (!bus->polling_mode)
 			prepare_to_wait(&bus->rirb_wq, &wait,
 					TASK_UNINTERRUPTIBLE);
-		if (bus->polling_mode)
+		if (bus->polling_mode) {
+			if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+				bus->rirb.cmds[addr] %= AZX_MAX_RIRB_ENTRIES;
 			snd_hdac_bus_update_rirb(bus);
+		}
 		if (!bus->rirb.cmds[addr]) {
 			if (res)
 				*res = bus->rirb.res[addr]; /* the last value */
@@ -484,16 +517,24 @@ static void azx_int_disable(struct hdac_bus *bus)
 static void azx_int_clear(struct hdac_bus *bus)
 {
 	struct hdac_stream *azx_dev;
+	struct azx *chip = bus_to_azx(bus);
 
 	/* clear stream status */
-	list_for_each_entry(azx_dev, &bus->stream_list, list)
-		snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+	list_for_each_entry(azx_dev, &bus->stream_list, list) {
+		if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+			snd_hdac_stream_updateb(azx_dev, SD_STS, 0, 0);
+		else
+			snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+	}
 
 	/* clear STATESTS */
 	snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
 
 	/* clear rirb status */
-	snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		snd_hdac_chip_updateb(bus, RIRBSTS, ~RIRB_INT_MASK, 0);
+	else
+		snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
 
 	/* clear int status */
 	snd_hdac_chip_writel(bus, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM);
@@ -585,11 +626,17 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status,
 	struct hdac_stream *azx_dev;
 	u8 sd_status;
 	int handled = 0;
+	struct azx *chip = bus_to_azx(bus);
 
 	list_for_each_entry(azx_dev, &bus->stream_list, list) {
 		if (status & azx_dev->sd_int_sta_mask) {
 			sd_status = snd_hdac_stream_readb(azx_dev, SD_STS);
-			snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+			if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND) {
+				snd_hdac_stream_writeb(azx_dev, SD_STS, sd_status);
+				snd_hdac_stream_readb(azx_dev, SD_STS);
+			} else {
+				snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+			}
 			handled |= 1 << azx_dev->index;
 			if (!azx_dev->substream || !azx_dev->running ||
 			    !(sd_status & SD_INT_COMPLETE))
diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c
index a38a2af..fa91832 100644
--- a/sound/hda/hdac_stream.c
+++ b/sound/hda/hdac_stream.c
@@ -12,6 +12,7 @@
 #include <sound/hdaudio.h>
 #include <sound/hda_register.h>
 #include "trace.h"
+#include "../pci/hda/hda_controller.h"
 
 /**
  * snd_hdac_get_stream_stripe_ctl - get stripe control value
@@ -83,6 +84,7 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_init);
 void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start)
 {
 	struct hdac_bus *bus = azx_dev->bus;
+	struct azx *chip = bus_to_azx(bus);
 	int stripe_ctl;
 
 	trace_snd_hdac_stream_start(bus, azx_dev);
@@ -105,7 +107,11 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start)
 					stripe_ctl);
 	}
 	/* set DMA start and interrupt mask */
-	snd_hdac_stream_updateb(azx_dev, SD_CTL,
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		snd_hdac_stream_updatel(azx_dev, SD_CTL,
+				0, SD_CTL_DMA_START | SD_INT_MASK);
+	else
+		snd_hdac_stream_updateb(azx_dev, SD_CTL,
 				0, SD_CTL_DMA_START | SD_INT_MASK);
 	azx_dev->running = true;
 }
@@ -190,6 +196,7 @@ int snd_hdac_stream_setup(struct hdac_stream *azx_dev)
 	struct hdac_bus *bus = azx_dev->bus;
 	struct snd_pcm_runtime *runtime;
 	unsigned int val;
+	struct azx *chip = bus_to_azx(bus);
 
 	if (azx_dev->substream)
 		runtime = azx_dev->substream->runtime;
@@ -206,8 +213,14 @@ int snd_hdac_stream_setup(struct hdac_stream *azx_dev)
 	snd_hdac_stream_writel(azx_dev, SD_CTL, val);
 
 	/* program the length of samples in cyclic buffer */
-	snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize);
-
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND) {
+		if (azx_dev->substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize - 64);
+		else
+			snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize - 16);
+	} else {
+		snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize);
+	}
 	/* program the stream format */
 	/* this value needs to be the same as the one programmed */
 	snd_hdac_stream_writew(azx_dev, SD_FORMAT, azx_dev->format_val);
@@ -412,6 +425,7 @@ int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
 	__le32 *bdl;
 	int i, ofs, periods, period_bytes;
 	int pos_adj, pos_align;
+	struct azx *chip = bus_to_azx(bus);
 
 	/* reset BDL address */
 	snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
@@ -426,6 +440,8 @@ int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
 	azx_dev->frags = 0;
 
 	pos_adj = bus->bdl_pos_adj;
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		pos_adj = 0;
 	if (!azx_dev->no_period_wakeup && pos_adj > 0) {
 		pos_align = pos_adj;
 		pos_adj = (pos_adj * runtime->rate + 47999) / 48000;
diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h
index fe17168..74fae0b 100644
--- a/sound/pci/hda/hda_controller.h
+++ b/sound/pci/hda/hda_controller.h
@@ -36,7 +36,7 @@
 /* 19 unused */
 #define AZX_DCAPS_OLD_SSYNC	(1 << 20)	/* Old SSYNC reg for ICH */
 #define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21)	/* no buffer size alignment */
-/* 22 unused */
+#define AZX_DCAPS_LOONGSON_HDA_WORKAROUND (1 << 22)   /* Loongson-HDA workaround */
 #define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23)	/* BDLE in 4k boundary */
 /* 24 unused */
 #define AZX_DCAPS_COUNT_LPIB_DELAY  (1 << 25)	/* Take LPIB as delay */
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index ea1d535..573e12f 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -678,6 +678,8 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
 	u32 wallclk;
 	unsigned int pos;
 
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND)
+		return 1;
 	wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk;
 	if (wallclk < (azx_dev->core.period_wallclk * 2) / 3)
 		return -1;	/* bogus (too early) interrupt */
@@ -1566,6 +1568,10 @@ static int check_position_fix(struct azx *chip, int fix)
 		dev_dbg(chip->card->dev, "Using SKL position fix\n");
 		return POS_FIX_SKL;
 	}
+	if (chip->driver_caps & AZX_DCAPS_LOONGSON_HDA_WORKAROUND) {
+		dev_dbg(chip->card->dev, "Using LPIB position fix\n");
+		return POS_FIX_LPIB;
+	}
 	return POS_FIX_AUTO;
 }
 
@@ -2736,7 +2742,8 @@ static const struct pci_device_id azx_ids[] = {
 	/* Zhaoxin */
 	{ PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN },
 	/* Loongson */
-	{ PCI_DEVICE(0x0014, 0x7a07), .driver_data = AZX_DRIVER_GENERIC },
+	{ PCI_DEVICE(0x0014, 0x7a07),
+	  .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_LOONGSON_HDA_WORKAROUND},
 	{ 0, }
 };
 MODULE_DEVICE_TABLE(pci, azx_ids);
-- 
2.1.0



More information about the Alsa-devel mailing list