ALSA: hda - set intel audio clock to a proper value
authorLibin Yang <libin.yang@intel.com>
Thu, 6 Apr 2017 11:18:21 +0000 (19:18 +0800)
committerTakashi Iwai <tiwai@suse.de>
Fri, 7 Apr 2017 08:39:21 +0000 (10:39 +0200)
On some Intel platforms, the audio clock may not be set correctly
with initial setting. This will cause the audio playback/capture
rates wrong.

This patch checks the audio clock setting and will set it to a
proper value if it is set incorrectly.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=188411

Signed-off-by: Libin Yang <libin.yang@intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_intel.c

index 64db6698214c101a7fede6117a8339221aca00a3..59ab34fa0bc838fea0dc906bb1f0fc6e5d7c1a4c 100644 (file)
@@ -540,6 +540,98 @@ static void bxt_reduce_dma_latency(struct azx *chip)
        azx_writel(chip, VS_EM4L, val);
 }
 
+/*
+ * ML_LCAP bits:
+ *  bit 0: 6 MHz Supported
+ *  bit 1: 12 MHz Supported
+ *  bit 2: 24 MHz Supported
+ *  bit 3: 48 MHz Supported
+ *  bit 4: 96 MHz Supported
+ *  bit 5: 192 MHz Supported
+ */
+static int intel_get_lctl_scf(struct azx *chip)
+{
+       struct hdac_bus *bus = azx_bus(chip);
+       static int preferred_bits[] = { 2, 3, 1, 4, 5 };
+       u32 val, t;
+       int i;
+
+       val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP);
+
+       for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) {
+               t = preferred_bits[i];
+               if (val & (1 << t))
+                       return t;
+       }
+
+       dev_warn(chip->card->dev, "set audio clock frequency to 6MHz");
+       return 0;
+}
+
+static int intel_ml_lctl_set_power(struct azx *chip, int state)
+{
+       struct hdac_bus *bus = azx_bus(chip);
+       u32 val;
+       int timeout;
+
+       /*
+        * the codecs are sharing the first link setting by default
+        * If other links are enabled for stream, they need similar fix
+        */
+       val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+       val &= ~AZX_MLCTL_SPA;
+       val |= state << AZX_MLCTL_SPA_SHIFT;
+       writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+       /* wait for CPA */
+       timeout = 50;
+       while (timeout) {
+               if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) &
+                   AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT))
+                       return 0;
+               timeout--;
+               udelay(10);
+       }
+
+       return -1;
+}
+
+static void intel_init_lctl(struct azx *chip)
+{
+       struct hdac_bus *bus = azx_bus(chip);
+       u32 val;
+       int ret;
+
+       /* 0. check lctl register value is correct or not */
+       val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+       /* if SCF is already set, let's use it */
+       if ((val & ML_LCTL_SCF_MASK) != 0)
+               return;
+
+       /*
+        * Before operating on SPA, CPA must match SPA.
+        * Any deviation may result in undefined behavior.
+        */
+       if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) !=
+               ((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT))
+               return;
+
+       /* 1. turn link down: set SPA to 0 and wait CPA to 0 */
+       ret = intel_ml_lctl_set_power(chip, 0);
+       udelay(100);
+       if (ret)
+               goto set_spa;
+
+       /* 2. update SCF to select a properly audio clock*/
+       val &= ~ML_LCTL_SCF_MASK;
+       val |= intel_get_lctl_scf(chip);
+       writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL);
+
+set_spa:
+       /* 4. turn link up: set SPA to 1 and wait CPA to 1 */
+       intel_ml_lctl_set_power(chip, 1);
+       udelay(100);
+}
+
 static void hda_intel_init_chip(struct azx *chip, bool full_reset)
 {
        struct hdac_bus *bus = azx_bus(chip);
@@ -565,6 +657,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset)
        /* reduce dma latency to avoid noise */
        if (IS_BXT(pci))
                bxt_reduce_dma_latency(chip);
+
+       if (bus->mlcap != NULL)
+               intel_init_lctl(chip);
 }
 
 /* calculate runtime delay from LPIB */