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@xxxxxxxxxxx> --- 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