[alsa-devel] [PATCH 00/10] Modernization of HD-audio bus and PM stuff
Hi,
here is yet another patchset for HD-audio modernization. The device binding is replaced with the standard bus, and the power-save stuff is rewritten with the standard runtime PM. In addition, a few cleanup patches follow.
There won't (and shouldn't) be big changes from the functionality POV. This is rather a large restructuring inside. It should work mostly without any visible changes. You can see a bit finer control for runtime PM in sysfs now, for example, but the original interface via power_save option is still kept.
Takashi
===
Takashi Iwai (10): ALSA: hda - Decouple PCM and hwdep devices from codec object ALSA: hda - Bind codecs via standard bus ALSA: hda - Move codec suspend/resume to codec driver ALSA: hda - Use standard runtime PM for codec power-save control ALSA: hda - Drop power_save value indirection in hda_bus ALSA: hda - Replace bus pm_notify with the standard runtime PM framework ALSA: hda - Power down codec automatically at registration ALSA: hda - Set parent of input beep devices ALSA: hda - Remove channel mode helper functions ALSA: hda - Clear pcm pointer assigned to hda_pcm at device removal
sound/pci/hda/Makefile | 2 +- sound/pci/hda/hda_beep.c | 2 +- sound/pci/hda/hda_bind.c | 321 ++++++++++++++++++ sound/pci/hda/hda_codec.c | 731 ++++++++--------------------------------- sound/pci/hda/hda_codec.h | 108 ++---- sound/pci/hda/hda_controller.c | 62 ++-- sound/pci/hda/hda_controller.h | 4 +- sound/pci/hda/hda_generic.c | 18 +- sound/pci/hda/hda_hwdep.c | 3 - sound/pci/hda/hda_intel.c | 33 +- sound/pci/hda/hda_local.h | 41 +-- sound/pci/hda/hda_tegra.c | 23 +- sound/pci/hda/hda_trace.h | 24 -- sound/pci/hda/patch_analog.c | 16 +- sound/pci/hda/patch_ca0110.c | 16 +- sound/pci/hda/patch_ca0132.c | 16 +- sound/pci/hda/patch_cirrus.c | 16 +- sound/pci/hda/patch_cmedia.c | 16 +- sound/pci/hda/patch_conexant.c | 16 +- sound/pci/hda/patch_hdmi.c | 27 +- sound/pci/hda/patch_realtek.c | 16 +- sound/pci/hda/patch_si3054.c | 16 +- sound/pci/hda/patch_sigmatel.c | 20 +- sound/pci/hda/patch_via.c | 16 +- 24 files changed, 572 insertions(+), 991 deletions(-) create mode 100644 sound/pci/hda/hda_bind.c
This is a preliminary patch for the hda_bus implementation, removing the parent device setup to codec device. Since the bus and the class devices can't be crossed over, leave the sound devices to the default parent device as is.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_beep.c | 1 - sound/pci/hda/hda_controller.c | 3 --- sound/pci/hda/hda_hwdep.c | 3 --- 3 files changed, 7 deletions(-)
diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c index 1e7de08e77cb..d6be4e852c4d 100644 --- a/sound/pci/hda/hda_beep.c +++ b/sound/pci/hda/hda_beep.c @@ -168,7 +168,6 @@ static int snd_hda_do_attach(struct hda_beep *beep) input_dev->evbit[0] = BIT_MASK(EV_SND); input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); input_dev->event = snd_hda_beep_event; - input_dev->dev.parent = &codec->dev; input_set_drvdata(input_dev, beep);
beep->dev = input_dev; diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index ebb7a644bd86..4c7a6f9bfcde 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -958,9 +958,6 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, chip->card->dev, size, MAX_PREALLOC_SIZE); - /* link to codec */ - for (s = 0; s < 2; s++) - pcm->streams[s].dev.parent = &codec->dev; return 0; }
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c index 11b5a42b4ec8..125f3420fa6a 100644 --- a/sound/pci/hda/hda_hwdep.c +++ b/sound/pci/hda/hda_hwdep.c @@ -116,9 +116,6 @@ int snd_hda_create_hwdep(struct hda_codec *codec) hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat; #endif
- /* link to codec */ - hwdep->dev.parent = &codec->dev; - /* for sysfs */ hwdep->dev.groups = snd_hda_dev_attr_groups; dev_set_drvdata(&hwdep->dev, codec);
Now we create the standard HD-audio bus (/sys/bus/hdaudio), and bind the codec driver with the codec device over there. This is the first step of the whole transition so that the changes to each codec driver are kept as minimal as possible.
Each codec driver needs to register hda_codec_driver struct containing the currently existing preset via the new helper macro module_hda_codec_driver(). The old hda_codec_preset_list is replaced with this infrastructure. The generic parsers (for HDMI and other) are also included in the preset with the special IDs to bind uniquely.
In HD-audio core side, the device binding code is split to hda_bind.c. It provides the snd_hda_bus_type implementation to match the codec driver with the given codec vendor ID. It also manages the module auto-loading by itself like before: when the matching isn't found, it tries to probe the corresponding codec modules, and finally falls back to the generic drivers. (The special ID mentioned above is set at this stage.)
The only visible change to outside is that the hdaudio sysfs entry now appears in /sys/bus/devices, not as a sound class device.
More works to move the suspend/resume and remove ops will be (hopefully) done in later patches.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/Makefile | 2 +- sound/pci/hda/hda_bind.c | 320 +++++++++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_codec.c | 307 +++------------------------------------ sound/pci/hda/hda_codec.h | 27 ++-- sound/pci/hda/hda_generic.c | 18 ++- sound/pci/hda/hda_local.h | 18 ++- sound/pci/hda/patch_analog.c | 16 +-- sound/pci/hda/patch_ca0110.c | 16 +-- sound/pci/hda/patch_ca0132.c | 16 +-- sound/pci/hda/patch_cirrus.c | 16 +-- sound/pci/hda/patch_cmedia.c | 16 +-- sound/pci/hda/patch_conexant.c | 16 +-- sound/pci/hda/patch_hdmi.c | 27 +--- sound/pci/hda/patch_realtek.c | 16 +-- sound/pci/hda/patch_si3054.c | 16 +-- sound/pci/hda/patch_sigmatel.c | 16 +-- sound/pci/hda/patch_via.c | 16 +-- 17 files changed, 401 insertions(+), 478 deletions(-) create mode 100644 sound/pci/hda/hda_bind.c
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 194f30935e77..96caaebfc19d 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -4,7 +4,7 @@ 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_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o +snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o snd-hda-codec-$(CONFIG_PROC_FS) += hda_proc.o snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c new file mode 100644 index 000000000000..adf6b475dee1 --- /dev/null +++ b/sound/pci/hda/hda_bind.c @@ -0,0 +1,320 @@ +/* + * HD-audio codec driver binding + * Copyright (c) Takashi Iwai tiwai@suse.de + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/export.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + +/* codec vendor labels */ +struct hda_vendor_id { + unsigned int id; + const char *name; +}; + +static struct hda_vendor_id hda_vendor_ids[] = { + { 0x1002, "ATI" }, + { 0x1013, "Cirrus Logic" }, + { 0x1057, "Motorola" }, + { 0x1095, "Silicon Image" }, + { 0x10de, "Nvidia" }, + { 0x10ec, "Realtek" }, + { 0x1102, "Creative" }, + { 0x1106, "VIA" }, + { 0x111d, "IDT" }, + { 0x11c1, "LSI" }, + { 0x11d4, "Analog Devices" }, + { 0x13f6, "C-Media" }, + { 0x14f1, "Conexant" }, + { 0x17e8, "Chrontel" }, + { 0x1854, "LG" }, + { 0x1aec, "Wolfson Microelectronics" }, + { 0x1af4, "QEMU" }, + { 0x434d, "C-Media" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +}; + +/* + * find a matching codec preset + */ +static int hda_bus_match(struct device *dev, struct device_driver *drv) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, dev); + struct hda_codec_driver *driver = + container_of(drv, struct hda_codec_driver, driver); + const struct hda_codec_preset *preset; + /* check probe_id instead of vendor_id if set */ + u32 id = codec->probe_id ? codec->probe_id : codec->vendor_id; + + for (preset = driver->preset; preset->id; preset++) { + u32 mask = preset->mask; + + if (preset->afg && preset->afg != codec->afg) + continue; + if (preset->mfg && preset->mfg != codec->mfg) + continue; + if (!mask) + mask = ~0; + if (preset->id == (id & mask) && + (!preset->rev || preset->rev == codec->revision_id)) { + codec->preset = preset; + return 1; + } + } + return 0; +} + +/* reset the codec name from the preset */ +static int codec_refresh_name(struct hda_codec *codec, const char *name) +{ + char tmp[16]; + + kfree(codec->chip_name); + if (!name) { + sprintf(tmp, "ID %x", codec->vendor_id & 0xffff); + name = tmp; + } + codec->chip_name = kstrdup(name, GFP_KERNEL); + return codec->chip_name ? 0 : -ENOMEM; +} + +static int hda_codec_driver_probe(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + struct module *owner = dev->driver->owner; + int err; + + if (WARN_ON(!codec->preset)) + return -EINVAL; + + err = codec_refresh_name(codec, codec->preset->name); + if (err < 0) + goto error; + + if (!try_module_get(owner)) { + err = -EINVAL; + goto error; + } + + err = codec->preset->patch(codec); + if (err < 0) { + module_put(owner); + goto error; + } + + return 0; + + error: + codec->preset = NULL; + memset(&codec->patch_ops, 0, sizeof(codec->patch_ops)); + return err; +} + +static int hda_codec_driver_remove(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + codec->preset = NULL; + memset(&codec->patch_ops, 0, sizeof(codec->patch_ops)); + module_put(dev->driver->owner); + return 0; +} + +int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, + struct module *owner) +{ + drv->driver.name = name; + drv->driver.owner = owner; + drv->driver.bus = &snd_hda_bus_type; + drv->driver.probe = hda_codec_driver_probe; + drv->driver.remove = hda_codec_driver_remove; + /* TODO: PM and others */ + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__hda_codec_driver_register); + +void hda_codec_driver_unregister(struct hda_codec_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(hda_codec_driver_unregister); + +static inline bool codec_probed(struct hda_codec *codec) +{ + return device_attach(hda_codec_dev(codec)) > 0 && codec->preset; +} + +/* try to auto-load and bind the codec module */ +static void codec_bind_module(struct hda_codec *codec) +{ +#ifdef MODULE + request_module("snd-hda-codec-id:%08x", codec->vendor_id); + if (codec_probed(codec)) + return; + request_module("snd-hda-codec-id:%04x*", + (codec->vendor_id >> 16) & 0xffff); + if (codec_probed(codec)) + return; +#endif +} + +/* store the codec vendor name */ +static int get_codec_vendor_name(struct hda_codec *codec) +{ + const struct hda_vendor_id *c; + const char *vendor = NULL; + u16 vendor_id = codec->vendor_id >> 16; + char tmp[16]; + + for (c = hda_vendor_ids; c->id; c++) { + if (c->id == vendor_id) { + vendor = c->name; + break; + } + } + if (!vendor) { + sprintf(tmp, "Generic %04x", vendor_id); + vendor = tmp; + } + codec->vendor_name = kstrdup(vendor, GFP_KERNEL); + if (!codec->vendor_name) + return -ENOMEM; + return 0; +} + +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) +/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */ +static bool is_likely_hdmi_codec(struct hda_codec *codec) +{ + hda_nid_t nid = codec->start_nid; + int i; + + for (i = 0; i < codec->num_nodes; i++, nid++) { + unsigned int wcaps = get_wcaps(codec, nid); + switch (get_wcaps_type(wcaps)) { + case AC_WID_AUD_IN: + return false; /* HDMI parser supports only HDMI out */ + case AC_WID_AUD_OUT: + if (!(wcaps & AC_WCAP_DIGITAL)) + return false; + break; + } + } + return true; +} +#else +/* no HDMI codec parser support */ +#define is_likely_hdmi_codec(codec) false +#endif /* CONFIG_SND_HDA_CODEC_HDMI */ + +static int codec_bind_generic(struct hda_codec *codec) +{ + if (codec->probe_id) + return -ENODEV; + + if (is_likely_hdmi_codec(codec)) { + codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI; +#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI) + request_module("snd-hda-codec-hdmi"); +#endif + if (codec_probed(codec)) + return 0; + } + + codec->probe_id = HDA_CODEC_ID_GENERIC; +#if IS_MODULE(CONFIG_SND_HDA_GENERIC) + request_module("snd-hda-codec-generic"); +#endif + if (codec_probed(codec)) + return 0; + return -ENODEV; +} + +#if IS_ENABLED(CONFIG_SND_HDA_GENERIC) +#define is_generic_config(codec) \ + (codec->modelname && !strcmp(codec->modelname, "generic")) +#else +#define is_generic_config(codec) 0 +#endif + +/** + * snd_hda_codec_configure - (Re-)configure the HD-audio codec + * @codec: the HDA codec + * + * Start parsing of the given codec tree and (re-)initialize the whole + * patch instance. + * + * Returns 0 if successful or a negative error code. + */ +int snd_hda_codec_configure(struct hda_codec *codec) +{ + int err; + + if (!codec->vendor_name) { + err = get_codec_vendor_name(codec); + if (err < 0) + return err; + } + + if (is_generic_config(codec)) + codec->probe_id = HDA_CODEC_ID_GENERIC; + else + codec->probe_id = 0; + + err = device_add(hda_codec_dev(codec)); + if (err < 0) + return err; + + if (!codec->preset) + codec_bind_module(codec); + if (!codec->preset) { + err = codec_bind_generic(codec); + if (err < 0) { + codec_err(codec, "Unable to bind the codec\n"); + goto error; + } + } + + /* audio codec should override the mixer name */ + if (codec->afg || !*codec->bus->card->mixername) + snprintf(codec->bus->card->mixername, + sizeof(codec->bus->card->mixername), + "%s %s", codec->vendor_name, codec->chip_name); + return 0; + + error: + device_del(hda_codec_dev(codec)); + return err; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_configure); + +/* + * bus registration + */ +struct bus_type snd_hda_bus_type = { + .name = "hdaudio", + .match = hda_bus_match, +}; + +static int __init hda_codec_init(void) +{ + return bus_register(&snd_hda_bus_type); +} + +static void __exit hda_codec_exit(void) +{ + bus_unregister(&snd_hda_bus_type); +} + +module_init(hda_codec_init); +module_exit(hda_codec_exit); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 5e755eb6f19a..61c8174e8013 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -40,69 +40,6 @@ #define CREATE_TRACE_POINTS #include "hda_trace.h"
-/* - * vendor / preset table - */ - -struct hda_vendor_id { - unsigned int id; - const char *name; -}; - -/* codec vendor labels */ -static struct hda_vendor_id hda_vendor_ids[] = { - { 0x1002, "ATI" }, - { 0x1013, "Cirrus Logic" }, - { 0x1057, "Motorola" }, - { 0x1095, "Silicon Image" }, - { 0x10de, "Nvidia" }, - { 0x10ec, "Realtek" }, - { 0x1102, "Creative" }, - { 0x1106, "VIA" }, - { 0x111d, "IDT" }, - { 0x11c1, "LSI" }, - { 0x11d4, "Analog Devices" }, - { 0x13f6, "C-Media" }, - { 0x14f1, "Conexant" }, - { 0x17e8, "Chrontel" }, - { 0x1854, "LG" }, - { 0x1aec, "Wolfson Microelectronics" }, - { 0x1af4, "QEMU" }, - { 0x434d, "C-Media" }, - { 0x8086, "Intel" }, - { 0x8384, "SigmaTel" }, - {} /* terminator */ -}; - -static DEFINE_MUTEX(preset_mutex); -static LIST_HEAD(hda_preset_tables); - -/** - * snd_hda_add_codec_preset - Add a codec preset to the chain - * @preset: codec preset table to add - */ -int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset) -{ - mutex_lock(&preset_mutex); - list_add_tail(&preset->list, &hda_preset_tables); - mutex_unlock(&preset_mutex); - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_add_codec_preset); - -/** - * snd_hda_delete_codec_preset - Delete a codec preset from the chain - * @preset: codec preset table to delete - */ -int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset) -{ - mutex_lock(&preset_mutex); - list_del(&preset->list); - mutex_unlock(&preset_mutex); - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_delete_codec_preset); - #ifdef CONFIG_PM #define codec_in_pm(codec) ((codec)->in_pm) static void hda_power_work(struct work_struct *work); @@ -885,111 +822,6 @@ int snd_hda_bus_new(struct snd_card *card, } EXPORT_SYMBOL_GPL(snd_hda_bus_new);
-#if IS_ENABLED(CONFIG_SND_HDA_GENERIC) -#define is_generic_config(codec) \ - (codec->modelname && !strcmp(codec->modelname, "generic")) -#else -#define is_generic_config(codec) 0 -#endif - -#ifdef MODULE -#define HDA_MODREQ_MAX_COUNT 2 /* two request_modules()'s */ -#else -#define HDA_MODREQ_MAX_COUNT 0 /* all presets are statically linked */ -#endif - -/* - * find a matching codec preset - */ -static const struct hda_codec_preset * -find_codec_preset(struct hda_codec *codec) -{ - struct hda_codec_preset_list *tbl; - const struct hda_codec_preset *preset; - unsigned int mod_requested = 0; - - again: - mutex_lock(&preset_mutex); - list_for_each_entry(tbl, &hda_preset_tables, list) { - if (!try_module_get(tbl->owner)) { - codec_err(codec, "cannot module_get\n"); - continue; - } - for (preset = tbl->preset; preset->id; preset++) { - u32 mask = preset->mask; - if (preset->afg && preset->afg != codec->afg) - continue; - if (preset->mfg && preset->mfg != codec->mfg) - continue; - if (!mask) - mask = ~0; - if (preset->id == (codec->vendor_id & mask) && - (!preset->rev || - preset->rev == codec->revision_id)) { - mutex_unlock(&preset_mutex); - codec->owner = tbl->owner; - return preset; - } - } - module_put(tbl->owner); - } - mutex_unlock(&preset_mutex); - - if (mod_requested < HDA_MODREQ_MAX_COUNT) { - if (!mod_requested) - request_module("snd-hda-codec-id:%08x", - codec->vendor_id); - else - request_module("snd-hda-codec-id:%04x*", - (codec->vendor_id >> 16) & 0xffff); - mod_requested++; - goto again; - } - return NULL; -} - -/* - * get_codec_name - store the codec name - */ -static int get_codec_name(struct hda_codec *codec) -{ - const struct hda_vendor_id *c; - const char *vendor = NULL; - u16 vendor_id = codec->vendor_id >> 16; - char tmp[16]; - - if (codec->vendor_name) - goto get_chip_name; - - for (c = hda_vendor_ids; c->id; c++) { - if (c->id == vendor_id) { - vendor = c->name; - break; - } - } - if (!vendor) { - sprintf(tmp, "Generic %04x", vendor_id); - vendor = tmp; - } - codec->vendor_name = kstrdup(vendor, GFP_KERNEL); - if (!codec->vendor_name) - return -ENOMEM; - - get_chip_name: - if (codec->chip_name) - return 0; - - if (codec->preset && codec->preset->name) - codec->chip_name = kstrdup(codec->preset->name, GFP_KERNEL); - else { - sprintf(tmp, "ID %x", codec->vendor_id & 0xffff); - codec->chip_name = kstrdup(tmp, GFP_KERNEL); - } - if (!codec->chip_name) - return -ENOMEM; - return 0; -} - /* * look for an AFG and MFG nodes */ @@ -1301,20 +1133,6 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid) }
/* - * Dynamic symbol binding for the codec parsers - */ - -#define load_parser(codec, sym) \ - ((codec)->parser = (int (*)(struct hda_codec *))symbol_request(sym)) - -static void unload_parser(struct hda_codec *codec) -{ - if (codec->parser) - symbol_put_addr(codec->parser); - codec->parser = NULL; -} - -/* * codec destructor */ static void snd_hda_codec_free(struct hda_codec *codec) @@ -1322,6 +1140,8 @@ static void snd_hda_codec_free(struct hda_codec *codec) if (!codec) return; cancel_delayed_work_sync(&codec->jackpoll_work); + if (device_is_registered(hda_codec_dev(codec))) + device_del(hda_codec_dev(codec)); snd_hda_jack_tbl_clear(codec); free_init_pincfgs(codec); #ifdef CONFIG_PM @@ -1335,12 +1155,8 @@ static void snd_hda_codec_free(struct hda_codec *codec) snd_array_free(&codec->spdif_out); remove_conn_list(codec); codec->bus->caddr_tbl[codec->addr] = NULL; - if (codec->patch_ops.free) - codec->patch_ops.free(codec); hda_call_pm_notify(codec, false); /* cancel leftover refcounts */ snd_hda_sysfs_clear(codec); - unload_parser(codec); - module_put(codec->owner); free_hda_cache(&codec->amp_cache); free_hda_cache(&codec->cmd_cache); kfree(codec->vendor_name); @@ -1348,7 +1164,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) kfree(codec->modelname); kfree(codec->wcaps); codec->bus->num_codecs--; - put_device(&codec->dev); + put_device(hda_codec_dev(codec)); }
static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec, @@ -1360,10 +1176,7 @@ static unsigned int hda_set_power_state(struct hda_codec *codec, static int snd_hda_codec_dev_register(struct snd_device *device) { struct hda_codec *codec = device->device_data; - int err = device_add(&codec->dev);
- if (err < 0) - return err; snd_hda_register_beep_device(codec); return 0; } @@ -1373,7 +1186,6 @@ static int snd_hda_codec_dev_disconnect(struct snd_device *device) struct hda_codec *codec = device->device_data;
snd_hda_detach_beep_device(codec); - device_del(&codec->dev); return 0; }
@@ -1386,7 +1198,7 @@ static int snd_hda_codec_dev_free(struct snd_device *device) /* just free the container */ static void snd_hda_codec_dev_release(struct device *dev) { - kfree(container_of(dev, struct hda_codec, dev)); + kfree(dev_to_hda_codec(dev)); }
/** @@ -1402,6 +1214,7 @@ int snd_hda_codec_new(struct hda_bus *bus, struct hda_codec **codecp) { struct hda_codec *codec; + struct device *dev; char component[31]; hda_nid_t fg; int err; @@ -1429,14 +1242,14 @@ int snd_hda_codec_new(struct hda_bus *bus, return -ENOMEM; }
- device_initialize(&codec->dev); - codec->dev.parent = &bus->card->card_dev; - codec->dev.class = sound_class; - codec->dev.release = snd_hda_codec_dev_release; - codec->dev.groups = snd_hda_dev_attr_groups; - dev_set_name(&codec->dev, "hdaudioC%dD%d", bus->card->number, - codec_addr); - dev_set_drvdata(&codec->dev, codec); /* for sysfs */ + dev = hda_codec_dev(codec); + device_initialize(dev); + dev->parent = bus->card->dev; + dev->bus = &snd_hda_bus_type; + dev->release = snd_hda_codec_dev_release; + dev->groups = snd_hda_dev_attr_groups; + dev_set_name(dev, "hdaudioC%dD%d", bus->card->number, codec_addr); + dev_set_drvdata(dev, codec); /* for sysfs */
codec->bus = bus; codec->addr = codec_addr; @@ -1587,92 +1400,6 @@ int snd_hda_codec_update_widgets(struct hda_codec *codec) } EXPORT_SYMBOL_GPL(snd_hda_codec_update_widgets);
- -#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) -/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */ -static bool is_likely_hdmi_codec(struct hda_codec *codec) -{ - hda_nid_t nid = codec->start_nid; - int i; - - for (i = 0; i < codec->num_nodes; i++, nid++) { - unsigned int wcaps = get_wcaps(codec, nid); - switch (get_wcaps_type(wcaps)) { - case AC_WID_AUD_IN: - return false; /* HDMI parser supports only HDMI out */ - case AC_WID_AUD_OUT: - if (!(wcaps & AC_WCAP_DIGITAL)) - return false; - break; - } - } - return true; -} -#else -/* no HDMI codec parser support */ -#define is_likely_hdmi_codec(codec) false -#endif /* CONFIG_SND_HDA_CODEC_HDMI */ - -/** - * snd_hda_codec_configure - (Re-)configure the HD-audio codec - * @codec: the HDA codec - * - * Start parsing of the given codec tree and (re-)initialize the whole - * patch instance. - * - * Returns 0 if successful or a negative error code. - */ -int snd_hda_codec_configure(struct hda_codec *codec) -{ - int (*patch)(struct hda_codec *) = NULL; - int err; - - codec->preset = find_codec_preset(codec); - if (!codec->vendor_name || !codec->chip_name) { - err = get_codec_name(codec); - if (err < 0) - return err; - } - - if (!is_generic_config(codec) && codec->preset) - patch = codec->preset->patch; - if (!patch) { - unload_parser(codec); /* to be sure */ - if (is_likely_hdmi_codec(codec)) { -#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI) - patch = load_parser(codec, snd_hda_parse_hdmi_codec); -#elif IS_BUILTIN(CONFIG_SND_HDA_CODEC_HDMI) - patch = snd_hda_parse_hdmi_codec; -#endif - } - if (!patch) { -#if IS_MODULE(CONFIG_SND_HDA_GENERIC) - patch = load_parser(codec, snd_hda_parse_generic_codec); -#elif IS_BUILTIN(CONFIG_SND_HDA_GENERIC) - patch = snd_hda_parse_generic_codec; -#endif - } - if (!patch) { - codec_err(codec, "No codec parser is available\n"); - return -ENODEV; - } - } - - err = patch(codec); - if (err < 0) { - unload_parser(codec); - return err; - } - - /* audio codec should override the mixer name */ - if (codec->afg || !*codec->bus->card->mixername) - snprintf(codec->bus->card->mixername, - sizeof(codec->bus->card->mixername), - "%s %s", codec->vendor_name, codec->chip_name); - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_codec_configure); - /* update the stream-id if changed */ static void update_pcm_stream_id(struct hda_codec *codec, struct hda_cvt_setup *p, hda_nid_t nid, @@ -2739,8 +2466,9 @@ int snd_hda_codec_reset(struct hda_codec *codec) } } snd_hda_detach_beep_device(codec); - if (codec->patch_ops.free) - codec->patch_ops.free(codec); + if (device_is_registered(hda_codec_dev(codec))) + device_del(hda_codec_dev(codec)); + memset(&codec->patch_ops, 0, sizeof(codec->patch_ops)); snd_hda_jack_tbl_clear(codec); codec->proc_widget_hook = NULL; @@ -2759,9 +2487,6 @@ int snd_hda_codec_reset(struct hda_codec *codec) codec->preset = NULL; codec->slave_dig_outs = NULL; codec->spdif_status_reset = 0; - unload_parser(codec); - module_put(codec->owner); - codec->owner = NULL;
/* allow device access again */ snd_hda_unlock_devices(bus); diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 96421a3b32cd..3d42717e0e65 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -174,15 +174,22 @@ struct hda_codec_preset { int (*patch)(struct hda_codec *codec); }; -struct hda_codec_preset_list { +#define HDA_CODEC_ID_GENERIC_HDMI 0x00000101 +#define HDA_CODEC_ID_GENERIC 0x00000201 + +struct hda_codec_driver { + struct device_driver driver; const struct hda_codec_preset *preset; - struct module *owner; - struct list_head list; };
-/* initial hook */ -int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset); -int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset); +int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, + struct module *owner); +#define hda_codec_driver_register(drv) \ + __hda_codec_driver_register(drv, KBUILD_MODNAME, THIS_MODULE) +void hda_codec_driver_unregister(struct hda_codec_driver *drv); +#define module_hda_codec_driver(drv) \ + module_driver(drv, hda_codec_driver_register, \ + hda_codec_driver_unregister)
/* ops set by the preset patch */ struct hda_codec_ops { @@ -286,11 +293,10 @@ struct hda_codec { u32 vendor_id; u32 subsystem_id; u32 revision_id; + u32 probe_id; /* overridden id for probing */
/* detected preset */ const struct hda_codec_preset *preset; - struct module *owner; - int (*parser)(struct hda_codec *codec); const char *vendor_name; /* codec vendor name */ const char *chip_name; /* codec chip name */ const char *modelname; /* model name for preset */ @@ -408,6 +414,11 @@ struct hda_codec { struct snd_array verbs; };
+#define dev_to_hda_codec(_dev) container_of(_dev, struct hda_codec, dev) +#define hda_codec_dev(_dev) (&(_dev)->dev) + +extern struct bus_type snd_hda_bus_type; + /* direction */ enum { HDA_INPUT, HDA_OUTPUT diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index b680b4ec6331..947d1a50f384 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -5493,13 +5493,11 @@ static const struct hda_codec_ops generic_patch_ops = { #endif };
-/** +/* * snd_hda_parse_generic_codec - Generic codec parser * @codec: the HDA codec - * - * This should be called from the HDA codec core. */ -int snd_hda_parse_generic_codec(struct hda_codec *codec) +static int snd_hda_parse_generic_codec(struct hda_codec *codec) { struct hda_gen_spec *spec; int err; @@ -5525,7 +5523,17 @@ error: snd_hda_gen_free(codec); return err; } -EXPORT_SYMBOL_GPL(snd_hda_parse_generic_codec); + +static const struct hda_codec_preset snd_hda_preset_generic[] = { + { .id = HDA_CODEC_ID_GENERIC, .patch = snd_hda_parse_generic_codec }, + {} /* terminator */ +}; + +static struct hda_codec_driver generic_driver = { + .preset = snd_hda_preset_generic, +}; + +module_hda_codec_driver(generic_driver);
MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Generic HD-audio codec parser"); diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 49c08a7278c4..2f7d9646a41d 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -351,12 +351,6 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_out *mout);
/* - * generic codec parser - */ -int snd_hda_parse_generic_codec(struct hda_codec *codec); -int snd_hda_parse_hdmi_codec(struct hda_codec *codec); - -/* * generic proc interface */ #ifdef CONFIG_PROC_FS @@ -783,9 +777,13 @@ void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
/* */ -#define codec_err(codec, fmt, args...) dev_err(&(codec)->dev, fmt, ##args) -#define codec_warn(codec, fmt, args...) dev_warn(&(codec)->dev, fmt, ##args) -#define codec_info(codec, fmt, args...) dev_info(&(codec)->dev, fmt, ##args) -#define codec_dbg(codec, fmt, args...) dev_dbg(&(codec)->dev, fmt, ##args) +#define codec_err(codec, fmt, args...) \ + dev_err(hda_codec_dev(codec), fmt, ##args) +#define codec_warn(codec, fmt, args...) \ + dev_warn(hda_codec_dev(codec), fmt, ##args) +#define codec_info(codec, fmt, args...) \ + dev_info(hda_codec_dev(codec), fmt, ##args) +#define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args)
#endif /* __SOUND_HDA_LOCAL_H */ diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index d285904cdb64..af4c7be86c27 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -1194,20 +1194,8 @@ MODULE_ALIAS("snd-hda-codec-id:11d4*"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Analog Devices HD-audio codec");
-static struct hda_codec_preset_list analog_list = { +static struct hda_codec_driver analog_driver = { .preset = snd_hda_preset_analog, - .owner = THIS_MODULE, };
-static int __init patch_analog_init(void) -{ - return snd_hda_add_codec_preset(&analog_list); -} - -static void __exit patch_analog_exit(void) -{ - snd_hda_delete_codec_preset(&analog_list); -} - -module_init(patch_analog_init) -module_exit(patch_analog_exit) +module_hda_codec_driver(analog_driver); diff --git a/sound/pci/hda/patch_ca0110.c b/sound/pci/hda/patch_ca0110.c index 5e65999e0d8e..447302695195 100644 --- a/sound/pci/hda/patch_ca0110.c +++ b/sound/pci/hda/patch_ca0110.c @@ -98,20 +98,8 @@ MODULE_ALIAS("snd-hda-codec-id:1102000d"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec");
-static struct hda_codec_preset_list ca0110_list = { +static struct hda_codec_driver ca0110_driver = { .preset = snd_hda_preset_ca0110, - .owner = THIS_MODULE, };
-static int __init patch_ca0110_init(void) -{ - return snd_hda_add_codec_preset(&ca0110_list); -} - -static void __exit patch_ca0110_exit(void) -{ - snd_hda_delete_codec_preset(&ca0110_list); -} - -module_init(patch_ca0110_init) -module_exit(patch_ca0110_exit) +module_hda_codec_driver(ca0110_driver); diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index e0383eea9880..81991b4134cd 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -4702,20 +4702,8 @@ MODULE_ALIAS("snd-hda-codec-id:11020011"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Creative Sound Core3D codec");
-static struct hda_codec_preset_list ca0132_list = { +static struct hda_codec_driver ca0132_driver = { .preset = snd_hda_preset_ca0132, - .owner = THIS_MODULE, };
-static int __init patch_ca0132_init(void) -{ - return snd_hda_add_codec_preset(&ca0132_list); -} - -static void __exit patch_ca0132_exit(void) -{ - snd_hda_delete_codec_preset(&ca0132_list); -} - -module_init(patch_ca0132_init) -module_exit(patch_ca0132_exit) +module_hda_codec_driver(ca0132_driver); diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c index 1589c9bcce3e..1af133933bc0 100644 --- a/sound/pci/hda/patch_cirrus.c +++ b/sound/pci/hda/patch_cirrus.c @@ -1219,20 +1219,8 @@ MODULE_ALIAS("snd-hda-codec-id:10134213"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Cirrus Logic HD-audio codec");
-static struct hda_codec_preset_list cirrus_list = { +static struct hda_codec_driver cirrus_driver = { .preset = snd_hda_preset_cirrus, - .owner = THIS_MODULE, };
-static int __init patch_cirrus_init(void) -{ - return snd_hda_add_codec_preset(&cirrus_list); -} - -static void __exit patch_cirrus_exit(void) -{ - snd_hda_delete_codec_preset(&cirrus_list); -} - -module_init(patch_cirrus_init) -module_exit(patch_cirrus_exit) +module_hda_codec_driver(cirrus_driver); diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c index c895a8f21192..617d9012e78a 100644 --- a/sound/pci/hda/patch_cmedia.c +++ b/sound/pci/hda/patch_cmedia.c @@ -137,20 +137,8 @@ MODULE_ALIAS("snd-hda-codec-id:434d4980"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("C-Media HD-audio codec");
-static struct hda_codec_preset_list cmedia_list = { +static struct hda_codec_driver cmedia_driver = { .preset = snd_hda_preset_cmedia, - .owner = THIS_MODULE, };
-static int __init patch_cmedia_init(void) -{ - return snd_hda_add_codec_preset(&cmedia_list); -} - -static void __exit patch_cmedia_exit(void) -{ - snd_hda_delete_codec_preset(&cmedia_list); -} - -module_init(patch_cmedia_init) -module_exit(patch_cmedia_exit) +module_hda_codec_driver(cmedia_driver); diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index fd3ed18670e9..15a0a7d38c35 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -1007,20 +1007,8 @@ MODULE_ALIAS("snd-hda-codec-id:14f151d7"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Conexant HD-audio codec");
-static struct hda_codec_preset_list conexant_list = { +static struct hda_codec_driver conexant_driver = { .preset = snd_hda_preset_conexant, - .owner = THIS_MODULE, };
-static int __init patch_conexant_init(void) -{ - return snd_hda_add_codec_preset(&conexant_list); -} - -static void __exit patch_conexant_exit(void) -{ - snd_hda_delete_codec_preset(&conexant_list); -} - -module_init(patch_conexant_init) -module_exit(patch_conexant_exit) +module_hda_codec_driver(conexant_driver); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index b422e406a9cb..f1812aabd63e 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -3301,15 +3301,6 @@ static int patch_via_hdmi(struct hda_codec *codec) }
/* - * called from hda_codec.c for generic HDMI support - */ -int snd_hda_parse_hdmi_codec(struct hda_codec *codec) -{ - return patch_generic_hdmi(codec); -} -EXPORT_SYMBOL_GPL(snd_hda_parse_hdmi_codec); - -/* * patch entries */ static const struct hda_codec_preset snd_hda_preset_hdmi[] = { @@ -3373,6 +3364,8 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = { { .id = 0x80862882, .name = "Valleyview2 HDMI", .patch = patch_generic_hdmi }, { .id = 0x80862883, .name = "Braswell HDMI", .patch = patch_generic_hdmi }, { .id = 0x808629fb, .name = "Crestline HDMI", .patch = patch_generic_hdmi }, +/* special ID for generic HDMI */ +{ .id = HDA_CODEC_ID_GENERIC_HDMI, .patch = patch_generic_hdmi }, {} /* terminator */ };
@@ -3442,20 +3435,8 @@ MODULE_ALIAS("snd-hda-codec-intelhdmi"); MODULE_ALIAS("snd-hda-codec-nvhdmi"); MODULE_ALIAS("snd-hda-codec-atihdmi");
-static struct hda_codec_preset_list intel_list = { +static struct hda_codec_driver hdmi_driver = { .preset = snd_hda_preset_hdmi, - .owner = THIS_MODULE, };
-static int __init patch_hdmi_init(void) -{ - return snd_hda_add_codec_preset(&intel_list); -} - -static void __exit patch_hdmi_exit(void) -{ - snd_hda_delete_codec_preset(&intel_list); -} - -module_init(patch_hdmi_init) -module_exit(patch_hdmi_exit) +module_hda_codec_driver(hdmi_driver); diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index b2b24a8b3dac..70808f92276a 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6514,20 +6514,8 @@ MODULE_ALIAS("snd-hda-codec-id:10ec*"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Realtek HD-audio codec");
-static struct hda_codec_preset_list realtek_list = { +static struct hda_codec_driver realtek_driver = { .preset = snd_hda_preset_realtek, - .owner = THIS_MODULE, };
-static int __init patch_realtek_init(void) -{ - return snd_hda_add_codec_preset(&realtek_list); -} - -static void __exit patch_realtek_exit(void) -{ - snd_hda_delete_codec_preset(&realtek_list); -} - -module_init(patch_realtek_init) -module_exit(patch_realtek_exit) +module_hda_codec_driver(realtek_driver); diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c index 3208ad69583e..38a477333321 100644 --- a/sound/pci/hda/patch_si3054.c +++ b/sound/pci/hda/patch_si3054.c @@ -319,20 +319,8 @@ MODULE_ALIAS("snd-hda-codec-id:18540018"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Si3054 HD-audio modem codec");
-static struct hda_codec_preset_list si3054_list = { +static struct hda_codec_driver si3054_driver = { .preset = snd_hda_preset_si3054, - .owner = THIS_MODULE, };
-static int __init patch_si3054_init(void) -{ - return snd_hda_add_codec_preset(&si3054_list); -} - -static void __exit patch_si3054_exit(void) -{ - snd_hda_delete_codec_preset(&si3054_list); -} - -module_init(patch_si3054_init) -module_exit(patch_si3054_exit) +module_hda_codec_driver(si3054_driver); diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 87eff3173ce9..6a2163056216 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -5091,20 +5091,8 @@ MODULE_ALIAS("snd-hda-codec-id:111d*"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("IDT/Sigmatel HD-audio codec");
-static struct hda_codec_preset_list sigmatel_list = { +static struct hda_codec_driver sigmatel_driver = { .preset = snd_hda_preset_sigmatel, - .owner = THIS_MODULE, };
-static int __init patch_sigmatel_init(void) -{ - return snd_hda_add_codec_preset(&sigmatel_list); -} - -static void __exit patch_sigmatel_exit(void) -{ - snd_hda_delete_codec_preset(&sigmatel_list); -} - -module_init(patch_sigmatel_init) -module_exit(patch_sigmatel_exit) +module_hda_codec_driver(sigmatel_driver); diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index 3de6d3d779c9..2045f33b1ace 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -1884,23 +1884,11 @@ static const struct hda_codec_preset snd_hda_preset_via[] = {
MODULE_ALIAS("snd-hda-codec-id:1106*");
-static struct hda_codec_preset_list via_list = { +static struct hda_codec_driver via_driver = { .preset = snd_hda_preset_via, - .owner = THIS_MODULE, };
MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("VIA HD-audio codec");
-static int __init patch_via_init(void) -{ - return snd_hda_add_codec_preset(&via_list); -} - -static void __exit patch_via_exit(void) -{ - snd_hda_delete_codec_preset(&via_list); -} - -module_init(patch_via_init) -module_exit(patch_via_exit) +module_hda_codec_driver(via_driver);
On 02/26/2015 06:00 PM, Takashi Iwai wrote:
Now we create the standard HD-audio bus (/sys/bus/hdaudio), and bind the codec driver with the codec device over there. This is the first step of the whole transition so that the changes to each codec driver are kept as minimal as possible.
Each codec driver needs to register hda_codec_driver struct containing the currently existing preset via the new helper macro module_hda_codec_driver(). The old hda_codec_preset_list is replaced with this infrastructure. The generic parsers (for HDMI and other) are also included in the preset with the special IDs to bind uniquely.
In HD-audio core side, the device binding code is split to hda_bind.c. It provides the snd_hda_bus_type implementation to match the codec driver with the given codec vendor ID. It also manages the module auto-loading by itself like before: when the matching isn't found, it tries to probe the corresponding codec modules, and finally falls back to the generic drivers. (The special ID mentioned above is set at this stage.)
The only visible change to outside is that the hdaudio sysfs entry now appears in /sys/bus/devices, not as a sound class device.
More works to move the suspend/resume and remove ops will be (hopefully) done in later patches.
Great stuff. AC'97 is next? ;)
[...]
- drv->driver.probe = hda_codec_driver_probe;
- drv->driver.remove = hda_codec_driver_remove;
Small nitpick, in my opinion it would be cleaner to use the snd_hda_bus_type probe/remove callbacks for this.
- /* TODO: PM and others */
At Sat, 28 Feb 2015 10:55:32 +0100, Lars-Peter Clausen wrote:
On 02/26/2015 06:00 PM, Takashi Iwai wrote:
Now we create the standard HD-audio bus (/sys/bus/hdaudio), and bind the codec driver with the codec device over there. This is the first step of the whole transition so that the changes to each codec driver are kept as minimal as possible.
Each codec driver needs to register hda_codec_driver struct containing the currently existing preset via the new helper macro module_hda_codec_driver(). The old hda_codec_preset_list is replaced with this infrastructure. The generic parsers (for HDMI and other) are also included in the preset with the special IDs to bind uniquely.
In HD-audio core side, the device binding code is split to hda_bind.c. It provides the snd_hda_bus_type implementation to match the codec driver with the given codec vendor ID. It also manages the module auto-loading by itself like before: when the matching isn't found, it tries to probe the corresponding codec modules, and finally falls back to the generic drivers. (The special ID mentioned above is set at this stage.)
The only visible change to outside is that the hdaudio sysfs entry now appears in /sys/bus/devices, not as a sound class device.
More works to move the suspend/resume and remove ops will be (hopefully) done in later patches.
Great stuff. AC'97 is next? ;)
Feel free to join to the club 97! :)
[...]
- drv->driver.probe = hda_codec_driver_probe;
- drv->driver.remove = hda_codec_driver_remove;
Small nitpick, in my opinion it would be cleaner to use the snd_hda_bus_type probe/remove callbacks for this.
Correct. However, this would need more changes. In this series, I'm trying a gradual transition without a big surprise. The merit of this patch is that you need minimum changes in the codec driver side.
So, yes, I'm going to move to that direction, but in a later stage.
thanks,
Takashi
On 02/28/2015 11:06 AM, Takashi Iwai wrote:
At Sat, 28 Feb 2015 10:55:32 +0100, Lars-Peter Clausen wrote:
On 02/26/2015 06:00 PM, Takashi Iwai wrote:
Now we create the standard HD-audio bus (/sys/bus/hdaudio), and bind the codec driver with the codec device over there. This is the first step of the whole transition so that the changes to each codec driver are kept as minimal as possible.
Each codec driver needs to register hda_codec_driver struct containing the currently existing preset via the new helper macro module_hda_codec_driver(). The old hda_codec_preset_list is replaced with this infrastructure. The generic parsers (for HDMI and other) are also included in the preset with the special IDs to bind uniquely.
In HD-audio core side, the device binding code is split to hda_bind.c. It provides the snd_hda_bus_type implementation to match the codec driver with the given codec vendor ID. It also manages the module auto-loading by itself like before: when the matching isn't found, it tries to probe the corresponding codec modules, and finally falls back to the generic drivers. (The special ID mentioned above is set at this stage.)
The only visible change to outside is that the hdaudio sysfs entry now appears in /sys/bus/devices, not as a sound class device.
More works to move the suspend/resume and remove ops will be (hopefully) done in later patches.
Great stuff. AC'97 is next? ;)
Feel free to join to the club 97! :)
[...]
- drv->driver.probe = hda_codec_driver_probe;
- drv->driver.remove = hda_codec_driver_remove;
Small nitpick, in my opinion it would be cleaner to use the snd_hda_bus_type probe/remove callbacks for this.
Correct. However, this would need more changes. In this series, I'm trying a gradual transition without a big surprise. The merit of this patch is that you need minimum changes in the codec driver side.
What I meant was instead of dynamically assigning the probe/remove callbacks of each driver struct, just statistically setup the probe/remove callbacks for the bus_type with the same callbacks. From a functional point of view there should not be any difference.
- Lars
At Sat, 28 Feb 2015 11:24:55 +0100, Lars-Peter Clausen wrote:
On 02/28/2015 11:06 AM, Takashi Iwai wrote:
At Sat, 28 Feb 2015 10:55:32 +0100, Lars-Peter Clausen wrote:
On 02/26/2015 06:00 PM, Takashi Iwai wrote:
Now we create the standard HD-audio bus (/sys/bus/hdaudio), and bind the codec driver with the codec device over there. This is the first step of the whole transition so that the changes to each codec driver are kept as minimal as possible.
Each codec driver needs to register hda_codec_driver struct containing the currently existing preset via the new helper macro module_hda_codec_driver(). The old hda_codec_preset_list is replaced with this infrastructure. The generic parsers (for HDMI and other) are also included in the preset with the special IDs to bind uniquely.
In HD-audio core side, the device binding code is split to hda_bind.c. It provides the snd_hda_bus_type implementation to match the codec driver with the given codec vendor ID. It also manages the module auto-loading by itself like before: when the matching isn't found, it tries to probe the corresponding codec modules, and finally falls back to the generic drivers. (The special ID mentioned above is set at this stage.)
The only visible change to outside is that the hdaudio sysfs entry now appears in /sys/bus/devices, not as a sound class device.
More works to move the suspend/resume and remove ops will be (hopefully) done in later patches.
Great stuff. AC'97 is next? ;)
Feel free to join to the club 97! :)
[...]
- drv->driver.probe = hda_codec_driver_probe;
- drv->driver.remove = hda_codec_driver_remove;
Small nitpick, in my opinion it would be cleaner to use the snd_hda_bus_type probe/remove callbacks for this.
Correct. However, this would need more changes. In this series, I'm trying a gradual transition without a big surprise. The merit of this patch is that you need minimum changes in the codec driver side.
What I meant was instead of dynamically assigning the probe/remove callbacks of each driver struct, just statistically setup the probe/remove callbacks for the bus_type with the same callbacks. From a functional point of view there should not be any difference.
Point taken. Yes, it's feasible, but there is another plan for future to make the bus more accessible to other drivers over HD-audio bus, so the bus functionality is kept minimal as of now.
thanks,
Takashi
This patch moves the suspend/resume mechanisms down to each codec driver level, as we have a proper codec driver bound on the bus now. Then we get the asynchronous PM gratis without fiddling much in the driver level.
As a soft-landing transition, implement the common suspend/resume pm ops for hda_codec_driver and keep the each codec driver intact. Only the callers of suspend/resume in the controller side (azx_suspend() and azx_resume()) are removed.
Another involved place is azx_bus_reset() calling the temporary suspend and resume as a hackish method of bus reset. The HD-audio core provide a helper function snd_hda_bus_reset() instead.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_bind.c | 3 +- sound/pci/hda/hda_codec.c | 93 +++++++++++++++--------------------------- sound/pci/hda/hda_codec.h | 6 +-- sound/pci/hda/hda_controller.c | 11 +---- sound/pci/hda/hda_intel.c | 6 --- sound/pci/hda/hda_tegra.c | 6 --- 6 files changed, 39 insertions(+), 86 deletions(-)
diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c index adf6b475dee1..ce2dd7b0dc07 100644 --- a/sound/pci/hda/hda_bind.c +++ b/sound/pci/hda/hda_bind.c @@ -8,6 +8,7 @@ #include <linux/mutex.h> #include <linux/module.h> #include <linux/export.h> +#include <linux/pm.h> #include <sound/core.h> #include "hda_codec.h" #include "hda_local.h" @@ -138,7 +139,7 @@ int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, drv->driver.bus = &snd_hda_bus_type; drv->driver.probe = hda_codec_driver_probe; drv->driver.remove = hda_codec_driver_remove; - /* TODO: PM and others */ + drv->driver.pm = &hda_codec_driver_pm; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(__hda_codec_driver_register); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 61c8174e8013..3d6ff60e6c14 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1250,6 +1250,7 @@ int snd_hda_codec_new(struct hda_bus *bus, dev->groups = snd_hda_dev_attr_groups; dev_set_name(dev, "hdaudioC%dD%d", bus->card->number, codec_addr); dev_set_drvdata(dev, codec); /* for sysfs */ + device_enable_async_suspend(dev);
codec->bus = bus; codec->addr = codec_addr; @@ -3970,8 +3971,31 @@ static void hda_call_codec_resume(struct hda_codec *codec) codec->in_pm = 0; snd_hda_power_down(codec); /* flag down before returning */ } + +static int hda_codec_driver_suspend(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + int i; + + cancel_delayed_work_sync(&codec->jackpoll_work); + for (i = 0; i < codec->num_pcms; i++) + snd_pcm_suspend_all(codec->pcm_info[i].pcm); + hda_call_codec_suspend(codec, false); + return 0; +} + +static int hda_codec_driver_resume(struct device *dev) +{ + hda_call_codec_resume(dev_to_hda_codec(dev)); + return 0; +} #endif /* CONFIG_PM */
+/* referred in hda_bind.c */ +const struct dev_pm_ops hda_codec_driver_pm = { + SET_SYSTEM_SLEEP_PM_OPS(hda_codec_driver_suspend, + hda_codec_driver_resume) +};
/** * snd_hda_build_controls - build mixer controls @@ -5505,77 +5529,26 @@ int snd_hda_add_imux_item(struct hda_codec *codec, } EXPORT_SYMBOL_GPL(snd_hda_add_imux_item);
- -#ifdef CONFIG_PM -/* - * power management - */ - - -static void hda_async_suspend(void *data, async_cookie_t cookie) -{ - hda_call_codec_suspend(data, false); -} - -static void hda_async_resume(void *data, async_cookie_t cookie) -{ - hda_call_codec_resume(data); -} - /** - * snd_hda_suspend - suspend the codecs - * @bus: the HDA bus - * - * Returns 0 if successful. + * snd_hda_bus_reset - Reset the bus + * @bus: HD-audio bus */ -int snd_hda_suspend(struct hda_bus *bus) +void snd_hda_bus_reset(struct hda_bus *bus) { struct hda_codec *codec; - ASYNC_DOMAIN_EXCLUSIVE(domain);
list_for_each_entry(codec, &bus->codec_list, list) { + /* FIXME: maybe a better way needed for forced reset */ cancel_delayed_work_sync(&codec->jackpoll_work); +#ifdef CONFIG_PM if (hda_codec_is_power_on(codec)) { - if (bus->num_codecs > 1) - async_schedule_domain(hda_async_suspend, codec, - &domain); - else - hda_call_codec_suspend(codec, false); - } - } - - if (bus->num_codecs > 1) - async_synchronize_full_domain(&domain); - - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_suspend); - -/** - * snd_hda_resume - resume the codecs - * @bus: the HDA bus - * - * Returns 0 if successful. - */ -int snd_hda_resume(struct hda_bus *bus) -{ - struct hda_codec *codec; - ASYNC_DOMAIN_EXCLUSIVE(domain); - - list_for_each_entry(codec, &bus->codec_list, list) { - if (bus->num_codecs > 1) - async_schedule_domain(hda_async_resume, codec, &domain); - else + hda_call_codec_suspend(codec, false); hda_call_codec_resume(codec); + } +#endif } - - if (bus->num_codecs > 1) - async_synchronize_full_domain(&domain); - - return 0; } -EXPORT_SYMBOL_GPL(snd_hda_resume); -#endif /* CONFIG_PM */ +EXPORT_SYMBOL_GPL(snd_hda_bus_reset);
/* * generic arrays diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 3d42717e0e65..1fa3dd9baf52 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -567,14 +567,12 @@ void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
int snd_hda_lock_devices(struct hda_bus *bus); void snd_hda_unlock_devices(struct hda_bus *bus); +void snd_hda_bus_reset(struct hda_bus *bus);
/* * power management */ -#ifdef CONFIG_PM -int snd_hda_suspend(struct hda_bus *bus); -int snd_hda_resume(struct hda_bus *bus); -#endif +extern const struct dev_pm_ops hda_codec_driver_pm;
static inline int hda_call_check_power_status(struct hda_codec *codec, hda_nid_t nid) diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 4c7a6f9bfcde..30ddb751806a 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -1780,15 +1780,8 @@ static void azx_bus_reset(struct hda_bus *bus) bus->in_reset = 1; azx_stop_chip(chip); azx_init_chip(chip, true); -#ifdef CONFIG_PM - if (chip->initialized) { - struct azx_pcm *p; - list_for_each_entry(p, &chip->pcm_list, list) - snd_pcm_suspend_all(p->pcm); - snd_hda_suspend(chip->bus); - snd_hda_resume(chip->bus); - } -#endif + if (chip->initialized) + snd_hda_bus_reset(chip->bus); bus->in_reset = 0; }
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 5e00cc4a722f..9db1b078801f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -772,7 +772,6 @@ static int azx_suspend(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip; struct hda_intel *hda; - struct azx_pcm *p;
if (!card) return 0; @@ -784,10 +783,6 @@ static int azx_suspend(struct device *dev)
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); azx_clear_irq_pending(chip); - list_for_each_entry(p, &chip->pcm_list, list) - snd_pcm_suspend_all(p->pcm); - if (chip->initialized) - snd_hda_suspend(chip->bus); azx_stop_chip(chip); azx_enter_link_reset(chip); if (chip->irq >= 0) { @@ -830,7 +825,6 @@ static int azx_resume(struct device *dev)
azx_init_chip(chip, true);
- snd_hda_resume(chip->bus); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; } diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c index 1bd7a9e04046..f6949e413a50 100644 --- a/sound/pci/hda/hda_tegra.c +++ b/sound/pci/hda/hda_tegra.c @@ -249,14 +249,9 @@ static int hda_tegra_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data; - struct azx_pcm *p; struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); - list_for_each_entry(p, &chip->pcm_list, list) - snd_pcm_suspend_all(p->pcm); - if (chip->initialized) - snd_hda_suspend(chip->bus);
azx_stop_chip(chip); azx_enter_link_reset(chip); @@ -277,7 +272,6 @@ static int hda_tegra_resume(struct device *dev)
azx_init_chip(chip, 1);
- snd_hda_resume(chip->bus); snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
Like the previous transition of suspend/resume, now move the power-save code to the standard runtime PM. As usual for runtime PM, it's a bit tricky, but this simplified codes a lot in the end.
For keeping the usage compatibility, power_save module option still controls the whole power-saving behavior on all codecs. The value is translated to pm_runtime_*_autosuspend() and pm_runtime_allow() / pm_runtime_forbid() calls.
snd_hda_power_up() and snd_hda_power_down() are translated to pm_runtime_get_sync() and pm_runtime_put_autosuspend(), respectively. Since we can do call pm_runtime_get_sync() more reliably, the sync version is used always and snd_hda_power_up_d3wait() is dropped. Another slight difference is that snd_hda_power_up()/down() don't call runtime_pm code during the suspend/resume transition phase. Calling them there isn't safe unlike our own code, resulted in unexpected behavior (endless wakeups).
The hda_power_count tracepoint was removed, as it doesn't match well with the new code.
Last but not least, we need to set ignore_children flag in the parent dev.power field so that the runtime PM of the controller chip won't get confused. The notification is still done in the bus pm_notify callback. We'll get rid of this hack in the later patch.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 241 ++++++++++++++++------------------------- sound/pci/hda/hda_codec.h | 67 ++---------- sound/pci/hda/hda_controller.c | 2 +- sound/pci/hda/hda_intel.c | 2 + sound/pci/hda/hda_trace.h | 24 ---- 5 files changed, 106 insertions(+), 230 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 3d6ff60e6c14..d0dbc62c9147 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -26,6 +26,8 @@ #include <linux/mutex.h> #include <linux/module.h> #include <linux/async.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> #include <sound/core.h> #include "hda_codec.h" #include <sound/asoundef.h> @@ -41,10 +43,9 @@ #include "hda_trace.h"
#ifdef CONFIG_PM -#define codec_in_pm(codec) ((codec)->in_pm) -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) +#define codec_in_pm(codec) atomic_read(&(codec)->in_pm) +#define hda_codec_is_power_on(codec) \ + (!pm_runtime_suspended(hda_codec_dev(codec)))
static void hda_call_pm_notify(struct hda_codec *codec, bool power_up) { @@ -60,7 +61,6 @@ static void hda_call_pm_notify(struct hda_codec *codec, bool power_up)
#else #define codec_in_pm(codec) 0 -static inline void hda_keep_power_on(struct hda_codec *codec) {} #define hda_codec_is_power_on(codec) 1 #define hda_call_pm_notify(codec, state) {} #endif @@ -1144,10 +1144,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) device_del(hda_codec_dev(codec)); snd_hda_jack_tbl_clear(codec); free_init_pincfgs(codec); -#ifdef CONFIG_PM - cancel_delayed_work(&codec->power_work); flush_workqueue(codec->bus->workq); -#endif list_del(&codec->list); snd_array_free(&codec->mixers); snd_array_free(&codec->nids); @@ -1178,6 +1175,10 @@ static int snd_hda_codec_dev_register(struct snd_device *device) struct hda_codec *codec = device->device_data;
snd_hda_register_beep_device(codec); + if (device_is_registered(hda_codec_dev(codec))) { + snd_hda_power_sync(codec); + pm_runtime_enable(hda_codec_dev(codec)); + } return 0; }
@@ -1274,13 +1275,14 @@ int snd_hda_codec_new(struct hda_bus *bus, codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
#ifdef CONFIG_PM - spin_lock_init(&codec->power_lock); - INIT_DELAYED_WORK(&codec->power_work, hda_power_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. */ - hda_keep_power_on(codec); + pm_runtime_set_active(hda_codec_dev(codec)); + pm_runtime_get_noresume(hda_codec_dev(codec)); + codec->power_jiffies = jiffies; + hda_call_pm_notify(codec, true); #endif
snd_hda_sysfs_init(codec); @@ -2453,10 +2455,7 @@ int snd_hda_codec_reset(struct hda_codec *codec)
/* OK, let it free */ cancel_delayed_work_sync(&codec->jackpoll_work); -#ifdef CONFIG_PM - cancel_delayed_work_sync(&codec->power_work); flush_workqueue(bus->workq); -#endif snd_hda_ctls_clear(codec); /* release PCMs */ for (i = 0; i < codec->num_pcms; i++) { @@ -3893,31 +3892,40 @@ static inline void hda_exec_init_verbs(struct hda_codec *codec) {} #endif
#ifdef CONFIG_PM +/* update the power on/off account with the current jiffies */ +static void update_power_acct(struct hda_codec *codec, bool on) +{ + unsigned long delta = jiffies - codec->power_jiffies; + + if (on) + codec->power_on_acct += delta; + else + codec->power_off_acct += delta; + codec->power_jiffies += delta; +} + +void snd_hda_update_power_acct(struct hda_codec *codec) +{ + update_power_acct(codec, hda_codec_is_power_on(codec)); +} + /* * call suspend and power-down; used both from PM and power-save * this function returns the power state in the end */ -static unsigned int hda_call_codec_suspend(struct hda_codec *codec, bool in_wq) +static unsigned int hda_call_codec_suspend(struct hda_codec *codec) { unsigned int state;
- codec->in_pm = 1; + atomic_inc(&codec->in_pm);
if (codec->patch_ops.suspend) codec->patch_ops.suspend(codec); hda_cleanup_all_streams(codec); state = hda_set_power_state(codec, AC_PWRST_D3); - /* Cancel delayed work if we aren't currently running from it. */ - if (!in_wq) - cancel_delayed_work_sync(&codec->power_work); - spin_lock(&codec->power_lock); - snd_hda_update_power_acct(codec); trace_hda_power_down(codec); - codec->power_on = 0; - codec->power_transition = 0; - codec->power_jiffies = jiffies; - spin_unlock(&codec->power_lock); - codec->in_pm = 0; + update_power_acct(codec, true); + atomic_dec(&codec->in_pm); return state; }
@@ -3942,14 +3950,14 @@ static void hda_mark_cmd_cache_dirty(struct hda_codec *codec) */ static void hda_call_codec_resume(struct hda_codec *codec) { - codec->in_pm = 1; + atomic_inc(&codec->in_pm);
+ trace_hda_power_up(codec); hda_mark_cmd_cache_dirty(codec);
- /* set as if powered on for avoiding re-entering the resume - * in the resume / power-save sequence - */ - hda_keep_power_on(codec); + codec->power_jiffies = jiffies; + hda_call_pm_notify(codec, true); + hda_set_power_state(codec, AC_PWRST_D0); restore_shutup_pins(codec); hda_exec_init_verbs(codec); @@ -3967,34 +3975,38 @@ static void hda_call_codec_resume(struct hda_codec *codec) hda_jackpoll_work(&codec->jackpoll_work.work); else snd_hda_jack_report_sync(codec); - - codec->in_pm = 0; - snd_hda_power_down(codec); /* flag down before returning */ + atomic_dec(&codec->in_pm); }
-static int hda_codec_driver_suspend(struct device *dev) +static int hda_codec_runtime_suspend(struct device *dev) { struct hda_codec *codec = dev_to_hda_codec(dev); + unsigned int state; int i;
cancel_delayed_work_sync(&codec->jackpoll_work); for (i = 0; i < codec->num_pcms; i++) snd_pcm_suspend_all(codec->pcm_info[i].pcm); - hda_call_codec_suspend(codec, false); + state = hda_call_codec_suspend(codec); + if (!codec->bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) + hda_call_pm_notify(codec, false); return 0; }
-static int hda_codec_driver_resume(struct device *dev) +static int hda_codec_runtime_resume(struct device *dev) { hda_call_codec_resume(dev_to_hda_codec(dev)); + pm_runtime_mark_last_busy(dev); return 0; } #endif /* CONFIG_PM */
/* referred in hda_bind.c */ const struct dev_pm_ops hda_codec_driver_pm = { - SET_SYSTEM_SLEEP_PM_OPS(hda_codec_driver_suspend, - hda_codec_driver_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(hda_codec_runtime_suspend, hda_codec_runtime_resume, + NULL) };
/** @@ -4733,127 +4745,66 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, EXPORT_SYMBOL_GPL(snd_hda_add_new_ctls);
#ifdef CONFIG_PM -static void hda_power_work(struct work_struct *work) +/** + * snd_hda_power_up - Power-up the codec + * @codec: HD-audio codec + * + * Increment the usage counter and resume the device if not done yet. + */ +void snd_hda_power_up(struct hda_codec *codec) { - struct hda_codec *codec = - container_of(work, struct hda_codec, power_work.work); - struct hda_bus *bus = codec->bus; - unsigned int state; + struct device *dev = hda_codec_dev(codec);
- spin_lock(&codec->power_lock); - if (codec->power_transition > 0) { /* during power-up sequence? */ - spin_unlock(&codec->power_lock); + if (codec_in_pm(codec)) return; - } - if (!codec->power_on || codec->power_count) { - codec->power_transition = 0; - spin_unlock(&codec->power_lock); - return; - } - spin_unlock(&codec->power_lock); - - state = hda_call_codec_suspend(codec, true); - if (!bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) - hda_call_pm_notify(codec, false); + pm_runtime_get_sync(dev); } +EXPORT_SYMBOL_GPL(snd_hda_power_up);
-static void hda_keep_power_on(struct hda_codec *codec) +/** + * snd_hda_power_down - Power-down the codec + * @codec: HD-audio codec + * + * Decrement the usage counter and schedules the autosuspend if none used. + */ +void snd_hda_power_down(struct hda_codec *codec) { - spin_lock(&codec->power_lock); - codec->power_count++; - codec->power_on = 1; - codec->power_jiffies = jiffies; - spin_unlock(&codec->power_lock); - hda_call_pm_notify(codec, true); -} + struct device *dev = hda_codec_dev(codec);
-/* update the power on/off account with the current jiffies */ -void snd_hda_update_power_acct(struct hda_codec *codec) -{ - unsigned long delta = jiffies - codec->power_jiffies; - if (codec->power_on) - codec->power_on_acct += delta; - else - codec->power_off_acct += delta; - codec->power_jiffies += delta; -} - -/* Transition to powered up, if wait_power_down then wait for a pending - * transition to D3 to complete. A pending D3 transition is indicated - * with power_transition == -1. */ -/* call this with codec->power_lock held! */ -static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down) -{ - /* Return if power_on or transitioning to power_on, unless currently - * powering down. */ - if ((codec->power_on || codec->power_transition > 0) && - !(wait_power_down && codec->power_transition < 0)) + if (codec_in_pm(codec)) return; - spin_unlock(&codec->power_lock); - - cancel_delayed_work_sync(&codec->power_work); - - spin_lock(&codec->power_lock); - /* If the power down delayed work was cancelled above before starting, - * then there is no need to go through power up here. - */ - if (codec->power_on) { - if (codec->power_transition < 0) - codec->power_transition = 0; - return; - } - - trace_hda_power_up(codec); - snd_hda_update_power_acct(codec); - codec->power_on = 1; - codec->power_jiffies = jiffies; - codec->power_transition = 1; /* avoid reentrance */ - spin_unlock(&codec->power_lock); - - hda_call_codec_resume(codec); - - spin_lock(&codec->power_lock); - codec->power_transition = 0; -} - -#define power_save(codec) \ - ((codec)->bus->power_save ? *(codec)->bus->power_save : 0) - -/* Transition to powered down */ -static void __snd_hda_power_down(struct hda_codec *codec) -{ - if (!codec->power_on || codec->power_count || codec->power_transition) - return; - - if (power_save(codec)) { - codec->power_transition = -1; /* avoid reentrance */ - queue_delayed_work(codec->bus->workq, &codec->power_work, - msecs_to_jiffies(power_save(codec) * 1000)); - } + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); } +EXPORT_SYMBOL_GPL(snd_hda_power_down);
/** - * snd_hda_power_save - Power-up/down/sync the codec + * snd_hda_power_sync - Synchronize the power_save option * @codec: HD-audio codec - * @delta: the counter delta to change - * @d3wait: sync for D3 transition complete * - * Change the power-up counter via @delta, and power up or down the hardware - * appropriately. For the power-down, queue to the delayed action. - * Passing zero to @delta means to synchronize the power state. + * Synchronize the runtime PM autosuspend state from the power_save option. */ -void snd_hda_power_save(struct hda_codec *codec, int delta, bool d3wait) +void snd_hda_power_sync(struct hda_codec *codec) { - spin_lock(&codec->power_lock); - codec->power_count += delta; - trace_hda_power_count(codec); - if (delta > 0) - __snd_hda_power_up(codec, d3wait); - else - __snd_hda_power_down(codec); - spin_unlock(&codec->power_lock); + struct device *dev = hda_codec_dev(codec); + int delay; + + if (!codec->bus->power_save) + return; + + delay = *codec->bus->power_save * 1000; + if (delay > 0) { + pm_runtime_set_autosuspend_delay(dev, delay); + pm_runtime_use_autosuspend(dev); + pm_runtime_allow(dev); + if (!pm_runtime_suspended(dev)) + pm_runtime_mark_last_busy(dev); + } else { + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_forbid(dev); + } } -EXPORT_SYMBOL_GPL(snd_hda_power_save); +EXPORT_SYMBOL_GPL(snd_hda_power_sync);
/** * snd_hda_check_amp_list_power - Check the amp list and update the power @@ -5542,7 +5493,7 @@ void snd_hda_bus_reset(struct hda_bus *bus) cancel_delayed_work_sync(&codec->jackpoll_work); #ifdef CONFIG_PM if (hda_codec_is_power_on(codec)) { - hda_call_codec_suspend(codec, false); + hda_call_codec_suspend(codec); hda_call_codec_resume(codec); } #endif diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 1fa3dd9baf52..593956fc384b 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -372,17 +372,12 @@ struct hda_codec { unsigned int dp_mst:1; /* support DP1.2 Multi-stream transport */ unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ #ifdef CONFIG_PM - unsigned int power_on :1; /* current (global) power-state */ unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */ unsigned int pm_up_notified:1; /* PM notified to controller */ - unsigned int in_pm:1; /* suspend/resume being performed */ - int power_transition; /* power-state in transition */ - int power_count; /* current (global) power refcount */ - struct delayed_work power_work; /* delayed task for powerdown */ + atomic_t in_pm; /* suspend/resume being performed */ unsigned long power_on_acct; unsigned long power_off_acct; unsigned long power_jiffies; - spinlock_t power_lock; #endif
/* filter the requested power state per nid */ @@ -595,64 +590,16 @@ const char *snd_hda_get_jack_location(u32 cfg); * power saving */ #ifdef CONFIG_PM -void snd_hda_power_save(struct hda_codec *codec, int delta, bool d3wait); +void snd_hda_power_up(struct hda_codec *codec); +void snd_hda_power_down(struct hda_codec *codec); +void snd_hda_power_sync(struct hda_codec *codec); void snd_hda_update_power_acct(struct hda_codec *codec); #else -static inline void snd_hda_power_save(struct hda_codec *codec, int delta, - bool d3wait) {} +static inline void snd_hda_power_up(struct hda_codec *codec) {} +static inline void snd_hda_power_down(struct hda_codec *codec) {} +static inline void snd_hda_power_sync(struct hda_codec *codec) {} #endif
-/** - * snd_hda_power_up - Power-up the codec - * @codec: HD-audio codec - * - * Increment the power-up counter and power up the hardware really when - * not turned on yet. - */ -static inline void snd_hda_power_up(struct hda_codec *codec) -{ - snd_hda_power_save(codec, 1, false); -} - -/** - * snd_hda_power_up_d3wait - Power-up the codec after waiting for any pending - * D3 transition to complete. This differs from snd_hda_power_up() when - * power_transition == -1. snd_hda_power_up sees this case as a nop, - * snd_hda_power_up_d3wait waits for the D3 transition to complete then powers - * back up. - * @codec: HD-audio codec - * - * Cancel any power down operation hapenning on the work queue, then power up. - */ -static inline void snd_hda_power_up_d3wait(struct hda_codec *codec) -{ - snd_hda_power_save(codec, 1, true); -} - -/** - * snd_hda_power_down - Power-down the codec - * @codec: HD-audio codec - * - * Decrement the power-up counter and schedules the power-off work if - * the counter rearches to zero. - */ -static inline void snd_hda_power_down(struct hda_codec *codec) -{ - snd_hda_power_save(codec, -1, false); -} - -/** - * snd_hda_power_sync - Synchronize the power-save status - * @codec: HD-audio codec - * - * Synchronize the actual power state with the power account; - * called when power_save parameter is changed - */ -static inline void snd_hda_power_sync(struct hda_codec *codec) -{ - snd_hda_power_save(codec, 0, false); -} - #ifdef CONFIG_SND_HDA_PATCH_LOADER /* * patch firmware diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 30ddb751806a..522c54f94740 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -836,7 +836,7 @@ static int azx_pcm_open(struct snd_pcm_substream *substream) buff_step); snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, buff_step); - snd_hda_power_up_d3wait(apcm->codec); + snd_hda_power_up(apcm->codec); err = hinfo->ops.open(hinfo, apcm->codec, substream); if (err < 0) { azx_release_device(azx_dev); diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 9db1b078801f..26510e60cbe2 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1087,6 +1087,7 @@ static int azx_free(struct azx *chip) azx_stop_chip(chip); }
+ pci->dev.power.ignore_children = 0; /* FIXME */ if (chip->irq >= 0) free_irq(chip->irq, (void*)chip); if (chip->msi) @@ -1796,6 +1797,7 @@ static int azx_probe(struct pci_dev *pci, return err; }
+ pci->dev.power.ignore_children = 1; /* FIXME */ err = azx_create(card, pci, dev, pci_id->driver_data, &pci_hda_ops, &chip); if (err < 0) diff --git a/sound/pci/hda/hda_trace.h b/sound/pci/hda/hda_trace.h index 3a1c63161eb1..c0e1c7d24dbe 100644 --- a/sound/pci/hda/hda_trace.h +++ b/sound/pci/hda/hda_trace.h @@ -87,30 +87,6 @@ DEFINE_EVENT(hda_power, hda_power_up, TP_PROTO(struct hda_codec *codec), TP_ARGS(codec) ); - -TRACE_EVENT(hda_power_count, - TP_PROTO(struct hda_codec *codec), - TP_ARGS(codec), - TP_STRUCT__entry( - __field( unsigned int, card ) - __field( unsigned int, addr ) - __field( int, power_count ) - __field( int, power_on ) - __field( int, power_transition ) - ), - - TP_fast_assign( - __entry->card = (codec)->bus->card->number; - __entry->addr = (codec)->addr; - __entry->power_count = (codec)->power_count; - __entry->power_on = (codec)->power_on; - __entry->power_transition = (codec)->power_transition; - ), - - TP_printk("[%d:%d] power_count=%d, power_on=%d, power_transition=%d", - __entry->card, __entry->addr, __entry->power_count, - __entry->power_on, __entry->power_transition) -); #endif /* CONFIG_PM */
TRACE_EVENT(hda_unsol_event,
We used to pass the power_save option value to hda_bus via a given pointer. This was needed to refer to the value from the HD-audio core side. However, after the transition to the runtime PM, this is no longer needed.
This patch drops the power_save value indirection in hda_bus above, and let the controller driver reprograms the autosuspend value explicitly by a new helper, snd_hda_set_power_save(). Without this call, the HD-audio core doesn't set up the autosuspend and flip the runtime PM. (User may still be able to set up via sysfs, though.)
Along with this change, the pointer argument of azx_bus_create() is dropped as well.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 34 ++++++++++++++++++---------------- sound/pci/hda/hda_codec.h | 5 ++--- sound/pci/hda/hda_controller.c | 5 +---- sound/pci/hda/hda_controller.h | 2 +- sound/pci/hda/hda_intel.c | 10 ++++------ sound/pci/hda/hda_tegra.c | 5 +++-- 6 files changed, 29 insertions(+), 32 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index d0dbc62c9147..36cebe0e76b2 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1175,10 +1175,8 @@ static int snd_hda_codec_dev_register(struct snd_device *device) struct hda_codec *codec = device->device_data;
snd_hda_register_beep_device(codec); - if (device_is_registered(hda_codec_dev(codec))) { - snd_hda_power_sync(codec); + if (device_is_registered(hda_codec_dev(codec))) pm_runtime_enable(hda_codec_dev(codec)); - } return 0; }
@@ -4778,21 +4776,10 @@ void snd_hda_power_down(struct hda_codec *codec) } EXPORT_SYMBOL_GPL(snd_hda_power_down);
-/** - * snd_hda_power_sync - Synchronize the power_save option - * @codec: HD-audio codec - * - * Synchronize the runtime PM autosuspend state from the power_save option. - */ -void snd_hda_power_sync(struct hda_codec *codec) +static void codec_set_power_save(struct hda_codec *codec, int delay) { struct device *dev = hda_codec_dev(codec); - int delay;
- if (!codec->bus->power_save) - return; - - delay = *codec->bus->power_save * 1000; if (delay > 0) { pm_runtime_set_autosuspend_delay(dev, delay); pm_runtime_use_autosuspend(dev); @@ -4804,7 +4791,22 @@ void snd_hda_power_sync(struct hda_codec *codec) pm_runtime_forbid(dev); } } -EXPORT_SYMBOL_GPL(snd_hda_power_sync); + +/** + * snd_hda_set_power_save - reprogram autosuspend for the given delay + * @bus: HD-audio bus + * @delay: autosuspend delay in msec, 0 = off + * + * Synchronize the runtime PM autosuspend state from the power_save option. + */ +void snd_hda_set_power_save(struct hda_bus *bus, int delay) +{ + struct hda_codec *c; + + list_for_each_entry(c, &bus->codec_list, list) + codec_set_power_save(c, delay); +} +EXPORT_SYMBOL_GPL(snd_hda_set_power_save);
/** * snd_hda_check_amp_list_power - Check the amp list and update the power diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 593956fc384b..89908f5b9601 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -122,7 +122,6 @@ struct hda_bus { void *private_data; struct pci_dev *pci; const char *modelname; - int *power_save; struct hda_bus_ops ops;
/* codec linked list */ @@ -592,12 +591,12 @@ const char *snd_hda_get_jack_location(u32 cfg); #ifdef CONFIG_PM void snd_hda_power_up(struct hda_codec *codec); void snd_hda_power_down(struct hda_codec *codec); -void snd_hda_power_sync(struct hda_codec *codec); +void snd_hda_set_power_save(struct hda_bus *bus, int delay); void snd_hda_update_power_acct(struct hda_codec *codec); #else static inline void snd_hda_power_up(struct hda_codec *codec) {} static inline void snd_hda_power_down(struct hda_codec *codec) {} -static inline void snd_hda_power_sync(struct hda_codec *codec) {} +static inline void snd_hda_set_power_save(struct hda_bus *bus, int delay) {} #endif
#ifdef CONFIG_SND_HDA_PATCH_LOADER diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 522c54f94740..cfe2c55296b6 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -1838,7 +1838,7 @@ static struct hda_bus_ops bus_ops = { };
/* HD-audio bus initialization */ -int azx_bus_create(struct azx *chip, const char *model, int *power_save_to) +int azx_bus_create(struct azx *chip, const char *model) { struct hda_bus *bus; int err; @@ -1852,9 +1852,6 @@ int azx_bus_create(struct azx *chip, const char *model, int *power_save_to) bus->pci = chip->pci; bus->modelname = model; bus->ops = bus_ops; -#ifdef CONFIG_PM - bus->power_save = power_save_to; -#endif
if (chip->driver_caps & AZX_DCAPS_RIRB_DELAY) { dev_dbg(chip->card->dev, "Enable delay in RIRB handling\n"); diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h index 0d09aa6b49ac..e4f46a21698a 100644 --- a/sound/pci/hda/hda_controller.h +++ b/sound/pci/hda/hda_controller.h @@ -432,7 +432,7 @@ void azx_enter_link_reset(struct azx *chip); irqreturn_t azx_interrupt(int irq, void *dev_id);
/* Codec interface */ -int azx_bus_create(struct azx *chip, const char *model, int *power_save_to); +int azx_bus_create(struct azx *chip, const char *model); int azx_probe_codecs(struct azx *chip, unsigned int max_slots); int azx_codec_configure(struct azx *chip); int azx_init_stream(struct azx *chip); diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 26510e60cbe2..40540048b002 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -173,7 +173,6 @@ static struct kernel_param_ops param_ops_xint = { #define param_check_xint param_check_int
static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; -static int *power_save_addr = &power_save; module_param(power_save, xint, 0644); MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " "(in second, 0 = disable)."); @@ -186,7 +185,7 @@ 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."); #else -static int *power_save_addr; +#define power_save 0 #endif /* CONFIG_PM */
static int align_buffer_size = -1; @@ -740,7 +739,6 @@ static int param_set_xint(const char *val, const struct kernel_param *kp) { struct hda_intel *hda; struct azx *chip; - struct hda_codec *c; int prev = power_save; int ret = param_set_int(val, kp);
@@ -752,8 +750,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp) chip = &hda->chip; if (!chip->bus || chip->disabled) continue; - list_for_each_entry(c, &chip->bus->codec_list, list) - snd_hda_power_sync(c); + snd_hda_set_power_save(chip->bus, power_save * 1000); } mutex_unlock(&card_list_lock); return 0; @@ -1889,7 +1886,7 @@ static int azx_probe_continue(struct azx *chip) #endif
/* create codec instances */ - err = azx_bus_create(chip, model[dev], power_save_addr); + err = azx_bus_create(chip, model[dev]); if (err < 0) goto out_free;
@@ -1933,6 +1930,7 @@ static int azx_probe_continue(struct azx *chip) power_down_all_codecs(chip); azx_notifier_register(chip); azx_add_card_list(chip); + snd_hda_set_power_save(chip->bus, power_save * 1000); if (azx_has_pm_runtime(chip) || hda->use_vga_switcheroo) pm_runtime_put_noidle(&pci->dev);
diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c index f6949e413a50..42bc17655df0 100644 --- a/sound/pci/hda/hda_tegra.c +++ b/sound/pci/hda/hda_tegra.c @@ -81,7 +81,7 @@ module_param(power_save, bint, 0644); MODULE_PARM_DESC(power_save, "Automatic power-saving timeout (in seconds, 0 = disable)."); #else -static int power_save = 0; +#define power_save 0 #endif
/* @@ -496,7 +496,7 @@ static int hda_tegra_probe(struct platform_device *pdev) goto out_free;
/* create codec instances */ - err = azx_bus_create(chip, NULL, &power_save); + err = azx_bus_create(chip, NULL); if (err < 0) goto out_free;
@@ -525,6 +525,7 @@ static int hda_tegra_probe(struct platform_device *pdev) chip->running = 1; power_down_all_codecs(chip); azx_notifier_register(chip); + snd_hda_set_power_save(chip->bus, power_save * 1000);
return 0;
Now the final bit of runtime PM cleanup: instead of manual notification of the power up/down of the codec via hda_bus pm_notify ops, use the standard runtime PM feature.
The child codec device will kick off the runtime PM of the parent (PCI) device upon suspend/resume automatically. For managing whether the link can be really turned off, we use the bit flags bus->codec_powered instead of the earlier bus->power_keep_link_on. flag. Each codec driver is responsible to set/clear the bit flag, and the controller device can be turned off only when all these bits are cleared.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 33 ++++++++------------------------- sound/pci/hda/hda_codec.h | 7 +------ sound/pci/hda/hda_controller.c | 19 ------------------- sound/pci/hda/hda_intel.c | 5 ++--- sound/pci/hda/patch_sigmatel.c | 4 +++- 5 files changed, 14 insertions(+), 54 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 36cebe0e76b2..33b8b71f8eaf 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -46,23 +46,9 @@ #define codec_in_pm(codec) atomic_read(&(codec)->in_pm) #define hda_codec_is_power_on(codec) \ (!pm_runtime_suspended(hda_codec_dev(codec))) - -static void hda_call_pm_notify(struct hda_codec *codec, bool power_up) -{ - struct hda_bus *bus = codec->bus; - - if ((power_up && codec->pm_up_notified) || - (!power_up && !codec->pm_up_notified)) - return; - if (bus->ops.pm_notify) - bus->ops.pm_notify(bus, power_up); - codec->pm_up_notified = power_up; -} - #else #define codec_in_pm(codec) 0 #define hda_codec_is_power_on(codec) 1 -#define hda_call_pm_notify(codec, state) {} #endif
/** @@ -1152,7 +1138,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) snd_array_free(&codec->spdif_out); remove_conn_list(codec); codec->bus->caddr_tbl[codec->addr] = NULL; - hda_call_pm_notify(codec, false); /* cancel leftover refcounts */ + clear_bit(codec->addr, &codec->bus->codec_powered); snd_hda_sysfs_clear(codec); free_hda_cache(&codec->amp_cache); free_hda_cache(&codec->cmd_cache); @@ -1277,10 +1263,10 @@ int snd_hda_codec_new(struct hda_bus *bus, * the caller has to power down appropriatley after initialization * phase. */ + set_bit(codec->addr, &bus->codec_powered); pm_runtime_set_active(hda_codec_dev(codec)); pm_runtime_get_noresume(hda_codec_dev(codec)); codec->power_jiffies = jiffies; - hda_call_pm_notify(codec, true); #endif
snd_hda_sysfs_init(codec); @@ -1340,11 +1326,6 @@ int snd_hda_codec_new(struct hda_bus *bus, #endif codec->epss = snd_hda_codec_get_supported_ps(codec, fg, AC_PWRST_EPSS); -#ifdef CONFIG_PM - if (!codec->d3_stop_clk || !codec->epss) - bus->power_keep_link_on = 1; -#endif -
/* power-up all before initialization */ hda_set_power_state(codec, AC_PWRST_D0); @@ -3954,7 +3935,6 @@ static void hda_call_codec_resume(struct hda_codec *codec) hda_mark_cmd_cache_dirty(codec);
codec->power_jiffies = jiffies; - hda_call_pm_notify(codec, true);
hda_set_power_state(codec, AC_PWRST_D0); restore_shutup_pins(codec); @@ -3986,14 +3966,17 @@ static int hda_codec_runtime_suspend(struct device *dev) for (i = 0; i < codec->num_pcms; i++) snd_pcm_suspend_all(codec->pcm_info[i].pcm); state = hda_call_codec_suspend(codec); - if (!codec->bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) - hda_call_pm_notify(codec, false); + if (codec->d3_stop_clk && codec->epss && (state & AC_PWRST_CLK_STOP_OK)) + clear_bit(codec->addr, &codec->bus->codec_powered); return 0; }
static int hda_codec_runtime_resume(struct device *dev) { - hda_call_codec_resume(dev_to_hda_codec(dev)); + struct hda_codec *codec = dev_to_hda_codec(dev); + + set_bit(codec->addr, &codec->bus->codec_powered); + hda_call_codec_resume(codec); pm_runtime_mark_last_busy(dev); return 0; } diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 89908f5b9601..457fc589eb46 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -83,10 +83,6 @@ struct hda_bus_ops { struct hda_pcm *pcm); /* reset bus for retry verb */ void (*bus_reset)(struct hda_bus *bus); -#ifdef CONFIG_PM - /* notify power-up/down from codec to controller */ - void (*pm_notify)(struct hda_bus *bus, bool power_up); -#endif #ifdef CONFIG_SND_HDA_DSP_LOADER /* prepare DSP transfer */ int (*load_dsp_prepare)(struct hda_bus *bus, unsigned int format, @@ -150,10 +146,10 @@ struct hda_bus { unsigned int rirb_error:1; /* error in codec communication */ unsigned int response_reset:1; /* controller was reset */ unsigned int in_reset:1; /* during reset operation */ - unsigned int power_keep_link_on:1; /* don't power off HDA link */ unsigned int no_response_fallback:1; /* don't fallback at RIRB error */
int primary_dig_out_type; /* primary digital out PCM type */ + unsigned long codec_powered; /* bit flags of powered codecs */ };
/* @@ -372,7 +368,6 @@ struct hda_codec { unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ #ifdef CONFIG_PM unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */ - unsigned int pm_up_notified:1; /* PM notified to controller */ atomic_t in_pm; /* suspend/resume being performed */ unsigned long power_on_acct; unsigned long power_off_acct; diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index cfe2c55296b6..789ca66c3094 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -1785,22 +1785,6 @@ static void azx_bus_reset(struct hda_bus *bus) bus->in_reset = 0; }
-#ifdef CONFIG_PM -/* power-up/down the controller */ -static void azx_power_notify(struct hda_bus *bus, bool power_up) -{ - struct azx *chip = bus->private_data; - - if (!azx_has_pm_runtime(chip)) - return; - - if (power_up) - pm_runtime_get_sync(chip->card->dev); - else - pm_runtime_put_sync(chip->card->dev); -} -#endif - static int get_jackpoll_interval(struct azx *chip) { int i; @@ -1827,9 +1811,6 @@ static struct hda_bus_ops bus_ops = { .get_response = azx_get_response, .attach_pcm = azx_attach_pcm_stream, .bus_reset = azx_bus_reset, -#ifdef CONFIG_PM - .pm_notify = azx_power_notify, -#endif #ifdef CONFIG_SND_HDA_DSP_LOADER .load_dsp_prepare = azx_load_dsp_prepare, .load_dsp_trigger = azx_load_dsp_trigger, diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 40540048b002..738d332351d5 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -918,7 +918,8 @@ static int azx_runtime_idle(struct device *dev) if (chip->disabled || hda->init_failed) return 0;
- if (!power_save_controller || !azx_has_pm_runtime(chip)) + if (!power_save_controller || !azx_has_pm_runtime(chip) || + chip->bus->codec_powered) return -EBUSY;
return 0; @@ -1084,7 +1085,6 @@ static int azx_free(struct azx *chip) azx_stop_chip(chip); }
- pci->dev.power.ignore_children = 0; /* FIXME */ if (chip->irq >= 0) free_irq(chip->irq, (void*)chip); if (chip->msi) @@ -1794,7 +1794,6 @@ static int azx_probe(struct pci_dev *pci, return err; }
- pci->dev.power.ignore_children = 1; /* FIXME */ err = azx_create(card, pci, dev, pci_id->driver_data, &pci_hda_ops, &chip); if (err < 0) diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 6a2163056216..2956a6ba6bf0 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -2132,8 +2132,10 @@ static void stac92hd83xxx_fixup_hp_mic_led(struct hda_codec *codec,
if (action == HDA_FIXUP_ACT_PRE_PROBE) { spec->mic_mute_led_gpio = 0x08; /* GPIO3 */ +#ifdef CONFIG_PM /* resetting controller clears GPIO, so we need to keep on */ - codec->bus->power_keep_link_on = 1; + codec->d3_stop_clk = 0; +#endif } }
So far, we let the controller driver power down the all codecs at the end of probe. But this can be done better in the codec's dev_register callback. This results in the reduction of duplicated codes in each control driver.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 5 +++-- sound/pci/hda/hda_intel.c | 14 -------------- sound/pci/hda/hda_tegra.c | 12 ------------ 3 files changed, 3 insertions(+), 28 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 33b8b71f8eaf..6580a367023e 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1163,6 +1163,8 @@ static int snd_hda_codec_dev_register(struct snd_device *device) snd_hda_register_beep_device(codec); if (device_is_registered(hda_codec_dev(codec))) pm_runtime_enable(hda_codec_dev(codec)); + /* it was powered up in snd_hda_codec_new(), now all done */ + snd_hda_power_down(codec); return 0; }
@@ -1260,8 +1262,7 @@ int snd_hda_codec_new(struct hda_bus *bus,
#ifdef CONFIG_PM /* 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. + * it's powered down later in snd_hda_codec_dev_register(). */ set_bit(codec->addr, &bus->codec_powered); pm_runtime_set_active(hda_codec_dev(codec)); diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 738d332351d5..e75e8137e296 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1604,19 +1604,6 @@ static int azx_first_init(struct azx *chip) return 0; }
-static void power_down_all_codecs(struct azx *chip) -{ -#ifdef CONFIG_PM - /* The codecs were powered up in snd_hda_codec_new(). - * Now all initialization done, so turn them down if possible - */ - struct hda_codec *codec; - list_for_each_entry(codec, &chip->bus->codec_list, list) { - snd_hda_power_down(codec); - } -#endif -} - #ifdef CONFIG_SND_HDA_PATCH_LOADER /* callback from request_firmware_nowait() */ static void azx_firmware_cb(const struct firmware *fw, void *context) @@ -1926,7 +1913,6 @@ static int azx_probe_continue(struct azx *chip) goto out_free;
chip->running = 1; - power_down_all_codecs(chip); azx_notifier_register(chip); azx_add_card_list(chip); snd_hda_set_power_save(chip->bus, power_save * 1000); diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c index 42bc17655df0..1359fdd20f02 100644 --- a/sound/pci/hda/hda_tegra.c +++ b/sound/pci/hda/hda_tegra.c @@ -337,17 +337,6 @@ static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev) return 0; }
-/* - * The codecs were powered up in snd_hda_codec_new(). - * Now all initialization done, so turn them down if possible - */ -static void power_down_all_codecs(struct azx *chip) -{ - struct hda_codec *codec; - list_for_each_entry(codec, &chip->bus->codec_list, list) - snd_hda_power_down(codec); -} - static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) { struct snd_card *card = chip->card; @@ -523,7 +512,6 @@ static int hda_tegra_probe(struct platform_device *pdev) goto out_free;
chip->running = 1; - power_down_all_codecs(chip); azx_notifier_register(chip); snd_hda_set_power_save(chip->bus, power_save * 1000);
Set the card device as the parent like other sound devices instead of leaving it empty.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_beep.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c index d6be4e852c4d..e98438e95e79 100644 --- a/sound/pci/hda/hda_beep.c +++ b/sound/pci/hda/hda_beep.c @@ -160,6 +160,7 @@ static int snd_hda_do_attach(struct hda_beep *beep) input_dev->name = "HDA Digital PCBeep"; input_dev->phys = beep->phys; input_dev->id.bustype = BUS_PCI; + input_dev->dev.parent = &codec->bus->card->card_dev;
input_dev->id.vendor = codec->vendor_id >> 16; input_dev->id.product = codec->vendor_id & 0xffff;
They are no longer used, let's kill them.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 82 ----------------------------------------------- sound/pci/hda/hda_local.h | 23 ------------- 2 files changed, 105 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 6580a367023e..db86b446743c 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -4844,88 +4844,6 @@ EXPORT_SYMBOL_GPL(snd_hda_check_amp_list_power); #endif
/* - * Channel mode helper - */ - -/** - * snd_hda_ch_mode_info - Info callback helper for the channel mode enum - * @codec: the HDA codec - * @uinfo: pointer to get/store the data - * @chmode: channel mode array - * @num_chmodes: channel mode array size - */ -int snd_hda_ch_mode_info(struct hda_codec *codec, - struct snd_ctl_elem_info *uinfo, - const struct hda_channel_mode *chmode, - int num_chmodes) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = num_chmodes; - if (uinfo->value.enumerated.item >= num_chmodes) - uinfo->value.enumerated.item = num_chmodes - 1; - sprintf(uinfo->value.enumerated.name, "%dch", - chmode[uinfo->value.enumerated.item].channels); - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_ch_mode_info); - -/** - * snd_hda_ch_mode_get - Get callback helper for the channel mode enum - * @codec: the HDA codec - * @ucontrol: pointer to get/store the data - * @chmode: channel mode array - * @num_chmodes: channel mode array size - * @max_channels: max number of channels - */ -int snd_hda_ch_mode_get(struct hda_codec *codec, - struct snd_ctl_elem_value *ucontrol, - const struct hda_channel_mode *chmode, - int num_chmodes, - int max_channels) -{ - int i; - - for (i = 0; i < num_chmodes; i++) { - if (max_channels == chmode[i].channels) { - ucontrol->value.enumerated.item[0] = i; - break; - } - } - return 0; -} -EXPORT_SYMBOL_GPL(snd_hda_ch_mode_get); - -/** - * snd_hda_ch_mode_put - Put callback helper for the channel mode enum - * @codec: the HDA codec - * @ucontrol: pointer to get/store the data - * @chmode: channel mode array - * @num_chmodes: channel mode array size - * @max_channelsp: pointer to store the max channels - */ -int snd_hda_ch_mode_put(struct hda_codec *codec, - struct snd_ctl_elem_value *ucontrol, - const struct hda_channel_mode *chmode, - int num_chmodes, - int *max_channelsp) -{ - unsigned int mode; - - mode = ucontrol->value.enumerated.item[0]; - if (mode >= num_chmodes) - return -EINVAL; - if (*max_channelsp == chmode[mode].channels) - return 0; - /* change the current channel setting */ - *max_channelsp = chmode[mode].channels; - if (chmode[mode].sequence) - snd_hda_sequence_write_cache(codec, chmode[mode].sequence); - return 1; -} -EXPORT_SYMBOL_GPL(snd_hda_ch_mode_put); - -/* * input MUX helper */
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 2f7d9646a41d..8588813163e3 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -273,29 +273,6 @@ int snd_hda_add_imux_item(struct hda_codec *codec, int index, int *type_index_ret);
/* - * Channel mode helper - */ -struct hda_channel_mode { - int channels; - const struct hda_verb *sequence; -}; - -int snd_hda_ch_mode_info(struct hda_codec *codec, - struct snd_ctl_elem_info *uinfo, - const struct hda_channel_mode *chmode, - int num_chmodes); -int snd_hda_ch_mode_get(struct hda_codec *codec, - struct snd_ctl_elem_value *ucontrol, - const struct hda_channel_mode *chmode, - int num_chmodes, - int max_channels); -int snd_hda_ch_mode_put(struct hda_codec *codec, - struct snd_ctl_elem_value *ucontrol, - const struct hda_channel_mode *chmode, - int num_chmodes, - int *max_channelsp); - -/* * Multi-channel / digital-out PCM helper */
We leave the pcm field of struct hda_pcm at removal of each device, so far. This hasn't been a problem since unbinding the codec driver isn't supposed to happen and another route via snd_hda_codec_reset() clears all the once. However, for a proper unbind implementation, we need to care about it.
This patch does the thing above properly:
- Include struct hda_pcm pointer instead of struct hda_pcm_stream pointers in struct azx_dev. This allows us to point the hda_pcm object at dev_free callback.
- Introduce to_hda_pcm_stream() macro for better readability.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_controller.c | 22 +++++++++++++++------- sound/pci/hda/hda_controller.h | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 789ca66c3094..1695f0e2bd9d 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -258,11 +258,18 @@ static void azx_timecounter_init(struct snd_pcm_substream *substream, tc->cycle_last = last; }
+static inline struct hda_pcm_stream * +to_hda_pcm_stream(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + return &apcm->info->stream[substream->stream]; +} + static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, u64 nsec) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); u64 codec_frames, codec_nsecs;
if (!hinfo->ops.get_delay) @@ -398,7 +405,7 @@ static int azx_setup_periods(struct azx *chip, static int azx_pcm_close(struct snd_pcm_substream *substream) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev = get_azx_dev(substream); unsigned long flags; @@ -440,7 +447,7 @@ static int azx_pcm_hw_free(struct snd_pcm_substream *substream) struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx_dev *azx_dev = get_azx_dev(substream); struct azx *chip = apcm->chip; - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); int err;
/* reset BDL address */ @@ -467,7 +474,7 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) struct azx_pcm *apcm = snd_pcm_substream_chip(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev = get_azx_dev(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); struct snd_pcm_runtime *runtime = substream->runtime; unsigned int bufsize, period_bytes, format_val, stream_tag; int err; @@ -707,7 +714,7 @@ unsigned int azx_get_position(struct azx *chip,
if (substream->runtime) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
if (chip->get_delay[stream]) delay += chip->get_delay[stream](chip, azx_dev, pos); @@ -790,7 +797,7 @@ static struct snd_pcm_hardware azx_pcm_hw = { static int azx_pcm_open(struct snd_pcm_substream *substream) { struct azx_pcm *apcm = snd_pcm_substream_chip(substream); - struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); struct azx *chip = apcm->chip; struct azx_dev *azx_dev; struct snd_pcm_runtime *runtime = substream->runtime; @@ -904,6 +911,7 @@ static void azx_pcm_free(struct snd_pcm *pcm) struct azx_pcm *apcm = pcm->private_data; if (apcm) { list_del(&apcm->list); + apcm->info->pcm = NULL; kfree(apcm); } } @@ -940,6 +948,7 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, apcm->chip = chip; apcm->pcm = pcm; apcm->codec = codec; + apcm->info = cpcm; pcm->private_data = apcm; pcm->private_free = azx_pcm_free; if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM) @@ -947,7 +956,6 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, list_add_tail(&apcm->list, &chip->pcm_list); cpcm->pcm = pcm; for (s = 0; s < 2; s++) { - apcm->hinfo[s] = &cpcm->stream[s]; if (cpcm->stream[s].substreams) snd_pcm_set_ops(pcm, s, &azx_pcm_ops); } diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h index e4f46a21698a..94c1a4719f7f 100644 --- a/sound/pci/hda/hda_controller.h +++ b/sound/pci/hda/hda_controller.h @@ -283,7 +283,7 @@ struct azx_pcm { struct azx *chip; struct snd_pcm *pcm; struct hda_codec *codec; - struct hda_pcm_stream *hinfo[2]; + struct hda_pcm *info; struct list_head list; };
participants (2)
-
Lars-Peter Clausen
-
Takashi Iwai