--- /dev/null
+/*
+ * HD-Audio helpers to sync with i915 driver
+ */
+#ifndef __SOUND_HDA_I915_H
+#define __SOUND_HDA_I915_H
+
+#ifdef CONFIG_SND_HDA_I915
+int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable);
+int snd_hdac_display_power(struct hdac_bus *bus, bool enable);
+int snd_hdac_get_display_clk(struct hdac_bus *bus);
+int snd_hdac_i915_init(struct hdac_bus *bus);
+int snd_hdac_i915_exit(struct hdac_bus *bus);
+#else
+static int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable)
+{
+ return 0;
+}
+static inline int snd_hdac_display_power(struct hdac_bus *bus, bool enable)
+{
+ return 0;
+}
+static inline int snd_hdac_get_display_clk(struct hdac_bus *bus)
+{
+ return 0;
+}
+static inline int snd_hdac_i915_init(struct hdac_bus *bus)
+{
+ return -ENODEV;
+}
+static inline int snd_hdac_i915_exit(struct hdac_bus *bus)
+{
+ return 0;
+}
+#endif
+
+#endif /* __SOUND_HDA_I915_H */
#define AZX_REG_SD_BDLPL 0x18
#define AZX_REG_SD_BDLPU 0x1c
+/* Haswell/Broadwell display HD-A controller Extended Mode registers */
+#define AZX_REG_HSW_EM4 0x100c
+#define AZX_REG_HSW_EM5 0x1010
+
/* PCI space */
#define AZX_PCIREG_TCSEL 0x44
#include <sound/core.h>
#include <sound/memalloc.h>
#include <sound/hda_verbs.h>
+#include <drm/i915_component.h>
/* codec node id */
typedef u16 hda_nid_t;
/* locks */
spinlock_t reg_lock;
struct mutex cmd_mutex;
+
+ /* i915 component interface */
+ struct i915_audio_component *audio_component;
+ int i915_power_refcount;
};
int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
config SND_HDA_DSP_LOADER
bool
+
+config SND_HDA_I915
+ bool
+ default y
+ depends on DRM_I915
+ depends on SND_HDA_CORE
snd-hda-core-objs += trace.o
CFLAGS_trace.o := -I$(src)
+# for sync with i915 gfx driver
+snd-hda-core-$(CONFIG_SND_HDA_I915) += hdac_i915.o
+
obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o
--- /dev/null
+/*
+ * hdac_i915.c - routines for sync between HD-A core and i915 display driver
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/component.h>
+#include <drm/i915_component.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+
+static struct i915_audio_component *hdac_acomp;
+
+int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable)
+{
+ struct i915_audio_component *acomp = bus->audio_component;
+
+ if (!acomp->ops)
+ return -ENODEV;
+
+ if (!acomp->ops->codec_wake_override) {
+ dev_warn(bus->dev,
+ "Invalid codec wake callback\n");
+ return 0;
+ }
+
+ dev_dbg(bus->dev, "%s codec wakeup\n",
+ enable ? "enable" : "disable");
+
+ acomp->ops->codec_wake_override(acomp->dev, enable);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup);
+
+int snd_hdac_display_power(struct hdac_bus *bus, bool enable)
+{
+ struct i915_audio_component *acomp = bus->audio_component;
+
+ if (!acomp->ops)
+ return -ENODEV;
+
+ dev_dbg(bus->dev, "display power %s\n",
+ enable ? "enable" : "disable");
+
+ if (enable) {
+ if (!bus->i915_power_refcount++)
+ acomp->ops->get_power(acomp->dev);
+ } else {
+ WARN_ON(!bus->i915_power_refcount);
+ if (!--bus->i915_power_refcount)
+ acomp->ops->put_power(acomp->dev);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_display_power);
+
+int snd_hdac_get_display_clk(struct hdac_bus *bus)
+{
+ struct i915_audio_component *acomp = bus->audio_component;
+
+ if (!acomp->ops)
+ return -ENODEV;
+
+ return acomp->ops->get_cdclk_freq(acomp->dev);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_display_clk);
+
+static int hdac_component_master_bind(struct device *dev)
+{
+ struct i915_audio_component *acomp = hdac_acomp;
+ int ret;
+
+ ret = component_bind_all(dev, acomp);
+ if (ret < 0)
+ return ret;
+
+ if (WARN_ON(!(acomp->dev && acomp->ops && acomp->ops->get_power &&
+ acomp->ops->put_power && acomp->ops->get_cdclk_freq))) {
+ ret = -EINVAL;
+ goto out_unbind;
+ }
+
+ /*
+ * Atm, we don't support dynamic unbinding initiated by the child
+ * component, so pin its containing module until we unbind.
+ */
+ if (!try_module_get(acomp->ops->owner)) {
+ ret = -ENODEV;
+ goto out_unbind;
+ }
+
+ return 0;
+
+out_unbind:
+ component_unbind_all(dev, acomp);
+
+ return ret;
+}
+
+static void hdac_component_master_unbind(struct device *dev)
+{
+ struct i915_audio_component *acomp = hdac_acomp;
+
+ module_put(acomp->ops->owner);
+ component_unbind_all(dev, acomp);
+ WARN_ON(acomp->ops || acomp->dev);
+}
+
+static const struct component_master_ops hdac_component_master_ops = {
+ .bind = hdac_component_master_bind,
+ .unbind = hdac_component_master_unbind,
+};
+
+static int hdac_component_master_match(struct device *dev, void *data)
+{
+ /* i915 is the only supported component */
+ return !strcmp(dev->driver->name, "i915");
+}
+
+int snd_hdac_i915_init(struct hdac_bus *bus)
+{
+ struct component_match *match = NULL;
+ struct device *dev = bus->dev;
+ struct i915_audio_component *acomp;
+ int ret;
+
+ acomp = kzalloc(sizeof(*acomp), GFP_KERNEL);
+ if (!acomp)
+ return -ENOMEM;
+ bus->audio_component = acomp;
+ hdac_acomp = acomp;
+
+ component_match_add(dev, &match, hdac_component_master_match, bus);
+ ret = component_master_add_with_match(dev, &hdac_component_master_ops,
+ match);
+ if (ret < 0)
+ goto out_err;
+
+ /*
+ * Atm, we don't support deferring the component binding, so make sure
+ * i915 is loaded and that the binding successfully completes.
+ */
+ request_module("i915");
+
+ if (!acomp->ops) {
+ ret = -ENODEV;
+ goto out_master_del;
+ }
+ dev_dbg(dev, "bound to i915 component master\n");
+
+ return 0;
+out_master_del:
+ component_master_del(dev, &hdac_component_master_ops);
+out_err:
+ kfree(acomp);
+ bus->audio_component = NULL;
+ dev_err(dev, "failed to add i915 component master (%d)\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_i915_init);
+
+int snd_hdac_i915_exit(struct hdac_bus *bus)
+{
+ struct device *dev = bus->dev;
+ struct i915_audio_component *acomp = bus->audio_component;
+
+ WARN_ON(bus->i915_power_refcount);
+ if (bus->i915_power_refcount > 0 && acomp && acomp->ops)
+ acomp->ops->put_power(acomp->dev);
+
+ component_master_del(dev, &hdac_component_master_ops);
+
+ kfree(acomp);
+ bus->audio_component = NULL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_i915_exit);
comment "Set to Y if you want auto-loading the codec driver"
depends on SND_HDA=y && SND_HDA_CODEC_HDMI=m
-config SND_HDA_I915
- bool
- default y
- depends on DRM_I915
-
config SND_HDA_CODEC_CIRRUS
tristate "Build Cirrus Logic codec support"
select SND_HDA_GENERIC
snd-hda-intel-objs := hda_intel.o
snd-hda-tegra-objs := hda_tegra.o
-# for haswell power well
-snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o
snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
snd-hda-codec-y += hda_controller.o
+++ /dev/null
-/*
- * hda_i915.c - routines for Haswell HDA controller power well support
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/pci.h>
-#include <linux/component.h>
-#include <drm/i915_component.h>
-#include <sound/core.h>
-#include "hda_controller.h"
-#include "hda_intel.h"
-
-/* Intel HSW/BDW display HDA controller Extended Mode registers.
- * EM4 (M value) and EM5 (N Value) are used to convert CDClk (Core Display
- * Clock) to 24MHz BCLK: BCLK = CDCLK * M / N
- * The values will be lost when the display power well is disabled.
- */
-#define AZX_REG_EM4 0x100c
-#define AZX_REG_EM5 0x1010
-
-int hda_set_codec_wakeup(struct hda_intel *hda, bool enable)
-{
- struct i915_audio_component *acomp = &hda->audio_component;
-
- if (!acomp->ops)
- return -ENODEV;
-
- if (!acomp->ops->codec_wake_override) {
- dev_warn(&hda->chip.pci->dev,
- "Invalid codec wake callback\n");
- return 0;
- }
-
- dev_dbg(&hda->chip.pci->dev, "%s codec wakeup\n",
- enable ? "enable" : "disable");
-
- acomp->ops->codec_wake_override(acomp->dev, enable);
-
- return 0;
-}
-
-int hda_display_power(struct hda_intel *hda, bool enable)
-{
- struct i915_audio_component *acomp = &hda->audio_component;
-
- if (!acomp->ops)
- return -ENODEV;
-
- dev_dbg(&hda->chip.pci->dev, "display power %s\n",
- enable ? "enable" : "disable");
-
- if (enable) {
- if (!hda->i915_power_refcount++)
- acomp->ops->get_power(acomp->dev);
- } else {
- WARN_ON(!hda->i915_power_refcount);
- if (!--hda->i915_power_refcount)
- acomp->ops->put_power(acomp->dev);
- }
-
- return 0;
-}
-
-void haswell_set_bclk(struct hda_intel *hda)
-{
- int cdclk_freq;
- unsigned int bclk_m, bclk_n;
- struct i915_audio_component *acomp = &hda->audio_component;
- struct pci_dev *pci = hda->chip.pci;
-
- /* Only Haswell/Broadwell need set BCLK */
- if (pci->device != 0x0a0c && pci->device != 0x0c0c
- && pci->device != 0x0d0c && pci->device != 0x160c)
- return;
-
- if (!acomp->ops)
- return;
-
- cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev);
- switch (cdclk_freq) {
- case 337500:
- bclk_m = 16;
- bclk_n = 225;
- break;
-
- case 450000:
- default: /* default CDCLK 450MHz */
- bclk_m = 4;
- bclk_n = 75;
- break;
-
- case 540000:
- bclk_m = 4;
- bclk_n = 90;
- break;
-
- case 675000:
- bclk_m = 8;
- bclk_n = 225;
- break;
- }
-
- azx_writew(&hda->chip, EM4, bclk_m);
- azx_writew(&hda->chip, EM5, bclk_n);
-}
-
-static int hda_component_master_bind(struct device *dev)
-{
- struct snd_card *card = dev_get_drvdata(dev);
- struct azx *chip = card->private_data;
- struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
- struct i915_audio_component *acomp = &hda->audio_component;
- int ret;
-
- ret = component_bind_all(dev, acomp);
- if (ret < 0)
- return ret;
-
- if (WARN_ON(!(acomp->dev && acomp->ops && acomp->ops->get_power &&
- acomp->ops->put_power && acomp->ops->get_cdclk_freq))) {
- ret = -EINVAL;
- goto out_unbind;
- }
-
- /*
- * Atm, we don't support dynamic unbinding initiated by the child
- * component, so pin its containing module until we unbind.
- */
- if (!try_module_get(acomp->ops->owner)) {
- ret = -ENODEV;
- goto out_unbind;
- }
-
- return 0;
-
-out_unbind:
- component_unbind_all(dev, acomp);
-
- return ret;
-}
-
-static void hda_component_master_unbind(struct device *dev)
-{
- struct snd_card *card = dev_get_drvdata(dev);
- struct azx *chip = card->private_data;
- struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
- struct i915_audio_component *acomp = &hda->audio_component;
-
- module_put(acomp->ops->owner);
- component_unbind_all(dev, acomp);
- WARN_ON(acomp->ops || acomp->dev);
-}
-
-static const struct component_master_ops hda_component_master_ops = {
- .bind = hda_component_master_bind,
- .unbind = hda_component_master_unbind,
-};
-
-static int hda_component_master_match(struct device *dev, void *data)
-{
- /* i915 is the only supported component */
- return !strcmp(dev->driver->name, "i915");
-}
-
-int hda_i915_init(struct hda_intel *hda)
-{
- struct component_match *match = NULL;
- struct device *dev = &hda->chip.pci->dev;
- struct i915_audio_component *acomp = &hda->audio_component;
- int ret;
-
- component_match_add(dev, &match, hda_component_master_match, hda);
- ret = component_master_add_with_match(dev, &hda_component_master_ops,
- match);
- if (ret < 0)
- goto out_err;
-
- /*
- * Atm, we don't support deferring the component binding, so make sure
- * i915 is loaded and that the binding successfully completes.
- */
- request_module("i915");
-
- if (!acomp->ops) {
- ret = -ENODEV;
- goto out_master_del;
- }
-
- dev_dbg(dev, "bound to i915 component master\n");
-
- return 0;
-out_master_del:
- component_master_del(dev, &hda_component_master_ops);
-out_err:
- dev_err(dev, "failed to add i915 component master (%d)\n", ret);
-
- return ret;
-}
-
-int hda_i915_exit(struct hda_intel *hda)
-{
- struct device *dev = &hda->chip.pci->dev;
- struct i915_audio_component *acomp = &hda->audio_component;
-
- WARN_ON(hda->i915_power_refcount);
- if (hda->i915_power_refcount > 0 && acomp->ops)
- acomp->ops->put_power(acomp->dev);
-
- component_master_del(dev, &hda_component_master_ops);
-
- return 0;
-}
#endif
#include <sound/core.h>
#include <sound/initval.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
#include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>
#include <linux/firmware.h>
static void hda_intel_init_chip(struct azx *chip, bool full_reset)
{
- struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
- hda_set_codec_wakeup(hda, true);
+ snd_hdac_set_codec_wakeup(bus, true);
azx_init_chip(chip, full_reset);
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
- hda_set_codec_wakeup(hda, false);
+ snd_hdac_set_codec_wakeup(bus, false);
}
/* calculate runtime delay from LPIB */
/* Enable/disable i915 display power for the link */
static int azx_intel_link_power(struct azx *chip, bool enable)
{
- struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
- return hda_display_power(hda, enable);
+ return snd_hdac_display_power(bus, enable);
}
/*
#define azx_del_card_list(chip) /* NOP */
#endif /* CONFIG_PM */
+/* Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK
+ * depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value)
+ * are used to convert CDClk (Core Display Clock) to 24MHz BCLK:
+ * BCLK = CDCLK * M / N
+ * The values will be lost when the display power well is disabled and need to
+ * be restored to avoid abnormal playback speed.
+ */
+static void haswell_set_bclk(struct hda_intel *hda)
+{
+ struct azx *chip = &hda->chip;
+ int cdclk_freq;
+ unsigned int bclk_m, bclk_n;
+
+ if (!hda->need_i915_power)
+ return;
+
+ cdclk_freq = snd_hdac_get_display_clk(azx_bus(chip));
+ switch (cdclk_freq) {
+ case 337500:
+ bclk_m = 16;
+ bclk_n = 225;
+ break;
+
+ case 450000:
+ default: /* default CDCLK 450MHz */
+ bclk_m = 4;
+ bclk_n = 75;
+ break;
+
+ case 540000:
+ bclk_m = 4;
+ bclk_n = 90;
+ break;
+
+ case 675000:
+ bclk_m = 8;
+ bclk_n = 225;
+ break;
+ }
+
+ azx_writew(chip, HSW_EM4, bclk_m);
+ azx_writew(chip, HSW_EM5, bclk_n);
+}
+
#if defined(CONFIG_PM_SLEEP) || defined(SUPPORT_VGA_SWITCHEROO)
/*
* power management
pci_disable_msi(chip->pci);
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
&& hda->need_i915_power)
- hda_display_power(hda, false);
+ snd_hdac_display_power(bus, false);
trace_azx_suspend(chip);
return 0;
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
&& hda->need_i915_power) {
- hda_display_power(hda, true);
+ snd_hdac_display_power(azx_bus(chip), true);
haswell_set_bclk(hda);
}
if (chip->msi)
azx_clear_irq_pending(chip);
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
&& hda->need_i915_power)
- hda_display_power(hda, false);
+ snd_hdac_display_power(azx_bus(chip), false);
trace_azx_runtime_suspend(chip);
return 0;
struct snd_card *card = dev_get_drvdata(dev);
struct azx *chip;
struct hda_intel *hda;
+ struct hdac_bus *bus;
struct hda_codec *codec;
int status;
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
&& hda->need_i915_power) {
- hda_display_power(hda, true);
+ bus = azx_bus(chip);
+ snd_hdac_display_power(bus, true);
haswell_set_bclk(hda);
/* toggle codec wakeup bit for STATESTS read */
- hda_set_codec_wakeup(hda, true);
- hda_set_codec_wakeup(hda, false);
+ snd_hdac_set_codec_wakeup(bus, true);
+ snd_hdac_set_codec_wakeup(bus, false);
}
/* Read STATESTS before controller reset */
#ifdef CONFIG_SND_HDA_PATCH_LOADER
release_firmware(chip->fw);
#endif
+
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) {
if (hda->need_i915_power)
- hda_display_power(hda, false);
- hda_i915_exit(hda);
+ snd_hdac_display_power(bus, false);
+ snd_hdac_i915_exit(bus);
}
kfree(hda);
static int azx_probe_continue(struct azx *chip)
{
struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+ struct hdac_bus *bus = azx_bus(chip);
struct pci_dev *pci = chip->pci;
int dev = chip->dev_index;
int err;
if (pci->device != 0x0f04 && pci->device != 0x2284)
hda->need_i915_power = 1;
- err = hda_i915_init(hda);
+ err = snd_hdac_i915_init(bus);
if (err < 0)
goto i915_power_fail;
- err = hda_display_power(hda, true);
+ err = snd_hdac_display_power(bus, true);
if (err < 0) {
dev_err(chip->card->dev,
"Cannot turn on display power on i915\n");
out_free:
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL
&& !hda->need_i915_power)
- hda_display_power(hda, false);
+ snd_hdac_display_power(bus, false);
i915_power_fail:
if (err < 0)
#ifndef __SOUND_HDA_INTEL_H
#define __SOUND_HDA_INTEL_H
-#include <drm/i915_component.h>
#include "hda_controller.h"
struct hda_intel {
/* secondary power domain for hdmi audio under vga device */
struct dev_pm_domain hdmi_pm_domain;
- /* i915 component interface */
bool need_i915_power:1; /* the hda controller needs i915 power */
- struct i915_audio_component audio_component;
- int i915_power_refcount;
};
-#ifdef CONFIG_SND_HDA_I915
-int hda_set_codec_wakeup(struct hda_intel *hda, bool enable);
-int hda_display_power(struct hda_intel *hda, bool enable);
-void haswell_set_bclk(struct hda_intel *hda);
-int hda_i915_init(struct hda_intel *hda);
-int hda_i915_exit(struct hda_intel *hda);
-#else
-static inline int hda_set_codec_wakeup(struct hda_intel *hda, bool enable)
-{
- return 0;
-}
-static inline int hda_display_power(struct hda_intel *hda, bool enable)
-{
- return 0;
-}
-static inline void haswell_set_bclk(struct hda_intel *hda) { return; }
-static inline int hda_i915_init(struct hda_intel *hda)
-{
- return 0;
-}
-static inline int hda_i915_exit(struct hda_intel *hda)
-{
- return 0;
-}
-#endif
-
#endif