Commit 0e24dbb7 authored by Mengdong Lin's avatar Mengdong Lin Committed by Takashi Iwai

ALSA: hda - suspend codecs in parallel

The time to suspend a single codec may be several hundreds of ms. When runtime
power saving is disabled, driver suspend time can be long especially when there
are more than one codec on the bus.

To reduce driver suspend time, this patch creates a work queue for the bus, and
suspends the codecs in parallel if there are multiple codecs on the bus.

[fixed cosmetic issues by tiwai]
Signed-off-by: default avatarMengdong Lin <mengdong.lin@intel.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 3e9bc58f
...@@ -96,6 +96,7 @@ EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset); ...@@ -96,6 +96,7 @@ EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset);
#ifdef CONFIG_PM #ifdef CONFIG_PM
#define codec_in_pm(codec) ((codec)->in_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_power_work(struct work_struct *work);
static void hda_keep_power_on(struct hda_codec *codec); static void hda_keep_power_on(struct hda_codec *codec);
#define hda_codec_is_power_on(codec) ((codec)->power_on) #define hda_codec_is_power_on(codec) ((codec)->power_on)
...@@ -839,6 +840,12 @@ static int snd_hda_bus_free(struct hda_bus *bus) ...@@ -839,6 +840,12 @@ static int snd_hda_bus_free(struct hda_bus *bus)
bus->ops.private_free(bus); bus->ops.private_free(bus);
if (bus->workq) if (bus->workq)
destroy_workqueue(bus->workq); destroy_workqueue(bus->workq);
#ifdef CONFIG_PM
if (bus->pm_wq)
destroy_workqueue(bus->pm_wq);
#endif
kfree(bus); kfree(bus);
return 0; return 0;
} }
...@@ -883,6 +890,9 @@ int snd_hda_bus_new(struct snd_card *card, ...@@ -883,6 +890,9 @@ int snd_hda_bus_new(struct snd_card *card,
.dev_register = snd_hda_bus_dev_register, .dev_register = snd_hda_bus_dev_register,
.dev_free = snd_hda_bus_dev_free, .dev_free = snd_hda_bus_dev_free,
}; };
#ifdef CONFIG_PM
char wqname[16];
#endif
if (snd_BUG_ON(!temp)) if (snd_BUG_ON(!temp))
return -EINVAL; return -EINVAL;
...@@ -919,6 +929,16 @@ int snd_hda_bus_new(struct snd_card *card, ...@@ -919,6 +929,16 @@ int snd_hda_bus_new(struct snd_card *card,
return -ENOMEM; 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); err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
if (err < 0) { if (err < 0) {
snd_hda_bus_free(bus); snd_hda_bus_free(bus);
...@@ -1388,6 +1408,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) ...@@ -1388,6 +1408,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
kfree(codec->chip_name); kfree(codec->chip_name);
kfree(codec->modelname); kfree(codec->modelname);
kfree(codec->wcaps); kfree(codec->wcaps);
codec->bus->num_codecs--;
kfree(codec); kfree(codec);
} }
...@@ -1453,6 +1474,7 @@ int snd_hda_codec_new(struct hda_bus *bus, ...@@ -1453,6 +1474,7 @@ int snd_hda_codec_new(struct hda_bus *bus,
#ifdef CONFIG_PM #ifdef CONFIG_PM
spin_lock_init(&codec->power_lock); spin_lock_init(&codec->power_lock);
INIT_DELAYED_WORK(&codec->power_work, hda_power_work); 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. /* snd_hda_codec_new() marks the codec as power-up, and leave it as is.
* the caller has to power down appropriatley after initialization * the caller has to power down appropriatley after initialization
* phase. * phase.
...@@ -1469,6 +1491,11 @@ int snd_hda_codec_new(struct hda_bus *bus, ...@@ -1469,6 +1491,11 @@ int snd_hda_codec_new(struct hda_bus *bus,
} }
list_add_tail(&codec->list, &bus->codec_list); 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; bus->caddr_tbl[codec_addr] = codec;
codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, 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, ...@@ -5088,6 +5115,14 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec,
return 0; return 0;
} }
EXPORT_SYMBOL_HDA(snd_hda_check_amp_list_power); 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 #endif
/* /*
...@@ -5663,9 +5698,17 @@ int snd_hda_suspend(struct hda_bus *bus) ...@@ -5663,9 +5698,17 @@ int snd_hda_suspend(struct hda_bus *bus)
list_for_each_entry(codec, &bus->codec_list, list) { list_for_each_entry(codec, &bus->codec_list, list) {
cancel_delayed_work_sync(&codec->jackpoll_work); cancel_delayed_work_sync(&codec->jackpoll_work);
if (hda_codec_is_power_on(codec)) 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); hda_call_codec_suspend(codec, false);
} }
}
if (bus->num_codecs > 1)
flush_workqueue(bus->pm_wq);
return 0; return 0;
} }
EXPORT_SYMBOL_HDA(snd_hda_suspend); EXPORT_SYMBOL_HDA(snd_hda_suspend);
......
...@@ -673,6 +673,7 @@ struct hda_bus { ...@@ -673,6 +673,7 @@ struct hda_bus {
/* codec linked list */ /* codec linked list */
struct list_head codec_list; struct list_head codec_list;
unsigned int num_codecs;
/* link caddr -> codec */ /* link caddr -> codec */
struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1]; struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
...@@ -683,6 +684,9 @@ struct hda_bus { ...@@ -683,6 +684,9 @@ struct hda_bus {
struct hda_bus_unsolicited *unsol; struct hda_bus_unsolicited *unsol;
char workq_name[16]; char workq_name[16];
struct workqueue_struct *workq; /* common workqueue for codecs */ struct workqueue_struct *workq; /* common workqueue for codecs */
#ifdef CONFIG_PM
struct workqueue_struct *pm_wq; /* workqueue to parallel codec PM */
#endif
/* assigned PCMs */ /* assigned PCMs */
DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES); DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES);
...@@ -917,6 +921,7 @@ struct hda_codec { ...@@ -917,6 +921,7 @@ struct hda_codec {
unsigned long power_off_acct; unsigned long power_off_acct;
unsigned long power_jiffies; unsigned long power_jiffies;
spinlock_t power_lock; spinlock_t power_lock;
struct work_struct pm_work; /* task to parallel multi-codec PM */
#endif #endif
/* filter the requested power state per nid */ /* filter the requested power state per nid */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment