#include <linux/mutex.h>
#include <linux/reboot.h>
#include <linux/io.h>
+#include <linux/pm_runtime.h>
#ifdef CONFIG_X86
/* for snoop control */
#include <asm/pgtable.h>
#include <sound/initval.h>
#include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>
+#include <linux/firmware.h>
#include "hda_codec.h"
"(0=off, 1=on) (default=1).");
#endif
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
+static int param_set_xint(const char *val, const struct kernel_param *kp);
+static struct kernel_param_ops param_ops_xint = {
+ .set = param_set_xint,
+ .get = param_get_int,
+};
+#define param_check_xint param_check_int
+
static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
-module_param(power_save, int, 0644);
+module_param(power_save, xint, 0644);
MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
"(in second, 0 = disable).");
static bool power_save_controller = 1;
module_param(power_save_controller, bool, 0644);
MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode.");
-#endif
+#endif /* CONFIG_PM */
static int align_buffer_size = -1;
module_param(align_buffer_size, bint, 0644);
"{Intel, CPT},"
"{Intel, PPT},"
"{Intel, LPT},"
+ "{Intel, LPT_LP},"
"{Intel, HPT},"
"{Intel, PBG},"
"{Intel, SCH},"
*/
unsigned int insufficient :1;
unsigned int wc_marked:1;
+ unsigned int no_period_wakeup:1;
};
/* CORB/RIRB */
struct snd_dma_buffer rb;
struct snd_dma_buffer posbuf;
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ const struct firmware *fw;
+#endif
+
/* flags */
int position_fix[2]; /* for both playback/capture streams */
int poll_count;
/* reboot notifier (for mysterious hangup problem at power-down) */
struct notifier_block reboot_notifier;
+
+ /* card list (for power_save trigger) */
+ struct list_head list;
};
/* driver types */
* VGA-switcher support
*/
#ifdef SUPPORT_VGA_SWITCHEROO
+#define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo)
+#else
+#define use_vga_switcheroo(chip) 0
+#endif
+
+#if defined(SUPPORT_VGA_SWITCHEROO) || defined(CONFIG_SND_HDA_PATCH_LOADER)
#define DELAYED_INIT_MARK
#define DELAYED_INITDATA_MARK
-#define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo)
#else
#define DELAYED_INIT_MARK __devinit
#define DELAYED_INITDATA_MARK __devinitdata
-#define use_vga_switcheroo(chip) 0
#endif
static char *driver_short_names[] DELAYED_INITDATA_MARK = {
return azx_rirb_get_response(bus, addr);
}
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-static void azx_power_notify(struct hda_bus *bus);
+#ifdef CONFIG_PM
+static void azx_power_notify(struct hda_bus *bus, bool power_up);
#endif
/* reset codec link */
u8 sd_status;
int i, ok;
+#ifdef CONFIG_PM_RUNTIME
+ if (chip->pci->dev.power.runtime_status != RPM_ACTIVE)
+ return IRQ_NONE;
+#endif
+
spin_lock(&chip->reg_lock);
if (chip->disabled) {
ofs = 0;
azx_dev->frags = 0;
pos_adj = bdl_pos_adj[chip->dev_index];
- if (pos_adj > 0) {
+ if (!azx_dev->no_period_wakeup && pos_adj > 0) {
struct snd_pcm_runtime *runtime = substream->runtime;
int pos_align = pos_adj;
pos_adj = (pos_adj * runtime->rate + 47999) / 48000;
pos_adj = 0;
} else {
ofs = setup_bdle(chip, substream, azx_dev,
- &bdl, ofs, pos_adj,
- !substream->runtime->no_period_wakeup);
+ &bdl, ofs, pos_adj, true);
if (ofs < 0)
goto error;
}
else
ofs = setup_bdle(chip, substream, azx_dev, &bdl, ofs,
period_bytes,
- !substream->runtime->no_period_wakeup);
+ !azx_dev->no_period_wakeup);
if (ofs < 0)
goto error;
}
bus_temp.ops.get_response = azx_get_response;
bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
bus_temp.ops.bus_reset = azx_bus_reset;
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
bus_temp.power_save = &power_save;
bus_temp.ops.pm_notify = azx_power_notify;
#endif
if (bufsize != azx_dev->bufsize ||
period_bytes != azx_dev->period_bytes ||
- format_val != azx_dev->format_val) {
+ format_val != azx_dev->format_val ||
+ runtime->no_period_wakeup != azx_dev->no_period_wakeup) {
azx_dev->bufsize = bufsize;
azx_dev->period_bytes = period_bytes;
azx_dev->format_val = format_val;
+ azx_dev->no_period_wakeup = runtime->no_period_wakeup;
err = azx_setup_periods(chip, substream, azx_dev);
if (err < 0)
return err;
chip->initialized = 0;
}
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
/* power-up/down the controller */
-static void azx_power_notify(struct hda_bus *bus)
+static void azx_power_notify(struct hda_bus *bus, bool power_up)
{
struct azx *chip = bus->private_data;
+
+ if (power_up)
+ pm_runtime_get_sync(&chip->pci->dev);
+ else
+ pm_runtime_put_sync(&chip->pci->dev);
+}
+
+static DEFINE_MUTEX(card_list_lock);
+static LIST_HEAD(card_list);
+
+static void azx_add_card_list(struct azx *chip)
+{
+ mutex_lock(&card_list_lock);
+ list_add(&chip->list, &card_list);
+ mutex_unlock(&card_list_lock);
+}
+
+static void azx_del_card_list(struct azx *chip)
+{
+ mutex_lock(&card_list_lock);
+ list_del_init(&chip->list);
+ mutex_unlock(&card_list_lock);
+}
+
+/* trigger power-save check at writing parameter */
+static int param_set_xint(const char *val, const struct kernel_param *kp)
+{
+ struct azx *chip;
struct hda_codec *c;
- int power_on = 0;
+ int prev = power_save;
+ int ret = param_set_int(val, kp);
- list_for_each_entry(c, &bus->codec_list, list) {
- if (c->power_on) {
- power_on = 1;
- break;
- }
+ if (ret || prev == power_save)
+ return ret;
+
+ mutex_lock(&card_list_lock);
+ list_for_each_entry(chip, &card_list, list) {
+ if (!chip->bus || chip->disabled)
+ continue;
+ list_for_each_entry(c, &chip->bus->codec_list, list)
+ snd_hda_power_sync(c);
}
- if (power_on)
- azx_init_chip(chip, 1);
- else if (chip->running && power_save_controller &&
- !bus->power_keep_link_on)
- azx_stop_chip(chip);
+ mutex_unlock(&card_list_lock);
+ return 0;
}
-#endif /* CONFIG_SND_HDA_POWER_SAVE */
+#else
+#define azx_add_card_list(chip) /* NOP */
+#define azx_del_card_list(chip) /* NOP */
+#endif /* CONFIG_PM */
-#ifdef CONFIG_PM
+#if defined(CONFIG_PM_SLEEP) || defined(SUPPORT_VGA_SWITCHEROO)
/*
* power management
*/
-
static int azx_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
-static SIMPLE_DEV_PM_OPS(azx_pm, azx_suspend, azx_resume);
+#endif /* CONFIG_PM_SLEEP || SUPPORT_VGA_SWITCHEROO */
+
+#ifdef CONFIG_PM_RUNTIME
+static int azx_runtime_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+
+ if (!power_save_controller)
+ return -EAGAIN;
+
+ azx_stop_chip(chip);
+ azx_clear_irq_pending(chip);
+ return 0;
+}
+
+static int azx_runtime_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+
+ azx_init_pci(chip);
+ azx_init_chip(chip, 1);
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops azx_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume)
+ SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, NULL)
+};
+
#define AZX_PM_OPS &azx_pm
#else
-#define azx_suspend(dev)
-#define azx_resume(dev)
#define AZX_PM_OPS NULL
#endif /* CONFIG_PM */
{
int i;
+ azx_del_card_list(chip);
+
azx_notifier_unregister(chip);
if (use_vga_switcheroo(chip)) {
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip->azx_dev);
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ if (chip->fw)
+ release_firmware(chip->fw);
+#endif
kfree(chip);
return 0;
SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", POS_FIX_LPIB),
SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS", POS_FIX_LPIB),
SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS M2V", POS_FIX_LPIB),
+ SND_PCI_QUIRK(0x1043, 0x1b43, "ASUS K53E", POS_FIX_POSBUF),
SND_PCI_QUIRK(0x104d, 0x9069, "Sony VPCS11V9E", POS_FIX_LPIB),
SND_PCI_QUIRK(0x10de, 0xcb89, "Macbook Pro 7,1", POS_FIX_LPIB),
SND_PCI_QUIRK(0x1297, 0x3166, "Shuttle", POS_FIX_LPIB),
chip->dev_index = dev;
INIT_WORK(&chip->irq_pending_work, azx_irq_pending_work);
INIT_LIST_HEAD(&chip->pcm_list);
+ INIT_LIST_HEAD(&chip->list);
init_vga_switcheroo(chip);
chip->position_fix[0] = chip->position_fix[1] =
static void power_down_all_codecs(struct azx *chip)
{
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
/* The codecs were powered up in snd_hda_codec_new().
* Now all initialization done, so turn them down if possible
*/
#endif
}
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+/* callback from request_firmware_nowait() */
+static void azx_firmware_cb(const struct firmware *fw, void *context)
+{
+ struct snd_card *card = context;
+ struct azx *chip = card->private_data;
+ struct pci_dev *pci = chip->pci;
+
+ if (!fw) {
+ snd_printk(KERN_ERR SFX "Cannot load firmware, aborting\n");
+ goto error;
+ }
+
+ chip->fw = fw;
+ if (!chip->disabled) {
+ /* continue probing */
+ if (azx_probe_continue(chip))
+ goto error;
+ }
+ return; /* OK */
+
+ error:
+ snd_card_free(card);
+ pci_set_drvdata(pci, NULL);
+}
+#endif
+
static int __devinit azx_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct azx *chip;
+ bool probe_now;
int err;
if (dev >= SNDRV_CARDS)
return err;
}
- /* set this here since it's referred in snd_hda_load_patch() */
snd_card_set_dev(card, &pci->dev);
err = azx_create(card, pci, dev, pci_id->driver_data, &chip);
if (err < 0)
goto out_free;
card->private_data = chip;
+ probe_now = !chip->disabled;
- if (!chip->disabled) {
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+ if (patch[dev] && *patch[dev]) {
+ snd_printk(KERN_ERR SFX "Applying patch firmware '%s'\n",
+ patch[dev]);
+ err = request_firmware_nowait(THIS_MODULE, true, patch[dev],
+ &pci->dev, GFP_KERNEL, card,
+ azx_firmware_cb);
+ if (err < 0)
+ goto out_free;
+ probe_now = false; /* continued in azx_firmware_cb() */
+ }
+#endif /* CONFIG_SND_HDA_PATCH_LOADER */
+
+ if (probe_now) {
err = azx_probe_continue(chip);
if (err < 0)
goto out_free;
pci_set_drvdata(pci, card);
+ if (pci_dev_run_wake(pci))
+ pm_runtime_put_noidle(&pci->dev);
+
dev++;
return 0;
if (err < 0)
goto out_free;
#ifdef CONFIG_SND_HDA_PATCH_LOADER
- if (patch[dev] && *patch[dev]) {
- snd_printk(KERN_ERR SFX "Applying patch firmware '%s'\n",
- patch[dev]);
- err = snd_hda_load_patch(chip->bus, patch[dev]);
+ if (chip->fw) {
+ err = snd_hda_load_patch(chip->bus, chip->fw->size,
+ chip->fw->data);
if (err < 0)
goto out_free;
+ release_firmware(chip->fw); /* no longer needed */
+ chip->fw = NULL;
}
#endif
if ((probe_only[dev] & 1) == 0) {
chip->running = 1;
power_down_all_codecs(chip);
azx_notifier_register(chip);
+ azx_add_card_list(chip);
return 0;
static void __devexit azx_remove(struct pci_dev *pci)
{
struct snd_card *card = pci_get_drvdata(pci);
+
+ if (pci_dev_run_wake(pci))
+ pm_runtime_get_noresume(&pci->dev);
+
if (card)
snd_card_free(card);
pci_set_drvdata(pci, NULL);
{ PCI_DEVICE(0x8086, 0x8c20),
.driver_data = AZX_DRIVER_PCH | AZX_DCAPS_SCH_SNOOP |
AZX_DCAPS_BUFSIZE | AZX_DCAPS_POSFIX_COMBO },
+ /* Lynx Point-LP */
+ { PCI_DEVICE(0x8086, 0x9c20),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_SCH_SNOOP |
+ AZX_DCAPS_BUFSIZE | AZX_DCAPS_POSFIX_COMBO },
+ /* Lynx Point-LP */
+ { PCI_DEVICE(0x8086, 0x9c21),
+ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_SCH_SNOOP |
+ AZX_DCAPS_BUFSIZE | AZX_DCAPS_POSFIX_COMBO },
/* Haswell */
{ PCI_DEVICE(0x8086, 0x0c0c),
.driver_data = AZX_DRIVER_SCH | AZX_DCAPS_SCH_SNOOP |