diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 6deb8d1852cc..85886220ee74 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -96,6 +96,7 @@ EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset); #ifdef CONFIG_PM #define codec_in_pm(codec) ((codec)->in_pm) +static void hda_pm_work(struct work_struct *work); static void hda_power_work(struct work_struct *work); static void hda_keep_power_on(struct hda_codec *codec); #define hda_codec_is_power_on(codec) ((codec)->power_on) @@ -839,6 +840,12 @@ static int snd_hda_bus_free(struct hda_bus *bus) bus->ops.private_free(bus); if (bus->workq) destroy_workqueue(bus->workq); + +#ifdef CONFIG_PM + if (bus->pm_wq) + destroy_workqueue(bus->pm_wq); +#endif + kfree(bus); return 0; } @@ -883,6 +890,9 @@ int snd_hda_bus_new(struct snd_card *card, .dev_register = snd_hda_bus_dev_register, .dev_free = snd_hda_bus_dev_free, }; +#ifdef CONFIG_PM + char wqname[16]; +#endif if (snd_BUG_ON(!temp)) return -EINVAL; @@ -919,6 +929,16 @@ int snd_hda_bus_new(struct snd_card *card, return -ENOMEM; } +#ifdef CONFIG_PM + sprintf(wqname, "hda-pm-wq-%d", card->number); + bus->pm_wq = create_workqueue(wqname); + if (!bus->pm_wq) { + snd_printk(KERN_ERR "cannot create PM workqueue\n"); + snd_hda_bus_free(bus); + return -ENOMEM; + } +#endif + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops); if (err < 0) { snd_hda_bus_free(bus); @@ -1388,6 +1408,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) kfree(codec->chip_name); kfree(codec->modelname); kfree(codec->wcaps); + codec->bus->num_codecs--; kfree(codec); } @@ -1453,6 +1474,7 @@ int snd_hda_codec_new(struct hda_bus *bus, #ifdef CONFIG_PM spin_lock_init(&codec->power_lock); INIT_DELAYED_WORK(&codec->power_work, hda_power_work); + INIT_WORK(&codec->pm_work, hda_pm_work); /* snd_hda_codec_new() marks the codec as power-up, and leave it as is. * the caller has to power down appropriatley after initialization * phase. @@ -1469,6 +1491,11 @@ int snd_hda_codec_new(struct hda_bus *bus, } list_add_tail(&codec->list, &bus->codec_list); + bus->num_codecs++; +#ifdef CONFIG_PM + workqueue_set_max_active(bus->pm_wq, bus->num_codecs); +#endif + bus->caddr_tbl[codec_addr] = codec; codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, @@ -5088,6 +5115,14 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec, return 0; } EXPORT_SYMBOL_HDA(snd_hda_check_amp_list_power); + +static void hda_pm_work(struct work_struct *work) +{ + struct hda_codec *codec = + container_of(work, struct hda_codec, pm_work); + + hda_call_codec_suspend(codec, false); +} #endif /* @@ -5663,9 +5698,17 @@ int snd_hda_suspend(struct hda_bus *bus) list_for_each_entry(codec, &bus->codec_list, list) { cancel_delayed_work_sync(&codec->jackpoll_work); - if (hda_codec_is_power_on(codec)) - hda_call_codec_suspend(codec, false); + if (hda_codec_is_power_on(codec)) { + if (bus->num_codecs > 1) + queue_work(bus->pm_wq, &codec->pm_work); + else + hda_call_codec_suspend(codec, false); + } } + + if (bus->num_codecs > 1) + flush_workqueue(bus->pm_wq); + return 0; } EXPORT_SYMBOL_HDA(snd_hda_suspend); diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index a3e5f45d74c1..9a2798a0313c 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -673,6 +673,7 @@ struct hda_bus { /* codec linked list */ struct list_head codec_list; + unsigned int num_codecs; /* link caddr -> codec */ struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1]; @@ -683,6 +684,9 @@ struct hda_bus { struct hda_bus_unsolicited *unsol; char workq_name[16]; struct workqueue_struct *workq; /* common workqueue for codecs */ +#ifdef CONFIG_PM + struct workqueue_struct *pm_wq; /* workqueue to parallel codec PM */ +#endif /* assigned PCMs */ DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES); @@ -917,6 +921,7 @@ struct hda_codec { unsigned long power_off_acct; unsigned long power_jiffies; spinlock_t power_lock; + struct work_struct pm_work; /* task to parallel multi-codec PM */ #endif /* filter the requested power state per nid */