diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
index c0ef27b2d4b22..c736b1d3e4931 100644
--- a/include/sound/soc-dapm.h
+++ b/include/sound/soc-dapm.h
@@ -275,7 +275,12 @@ struct device;
 
 #define SND_SOC_DAPM_RATE(wname, wreg, wshift, winvert, wops, wpriv) \
 {	.id = snd_soc_dapm_rate, .name = wname, \
-	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) }
+	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+	.event = snd_soc_domain_event, \
+	.event_flags = SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_PRE_PMU | \
+		       SND_SOC_DAPM_POST_PMD, \
+	.priv = (&(struct snd_soc_domain_group_driver){ \
+	.name = wname, .ops = wops, .private_data = wpriv}),}
 
 
 /* dapm kcontrol types */
@@ -410,6 +415,9 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
 int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
 void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
 
+int snd_soc_dapm_connect_domains(struct snd_soc_dapm_context *dapm,
+				 const char * const a, const char * const b);
+
 /* dapm path setup */
 int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
 void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm);
@@ -629,6 +637,8 @@ struct snd_soc_dapm_widget {
 	int endpoints[2];
 
 	struct clk *clk;
+
+	struct snd_soc_domain_group *dgroup;
 };
 
 struct snd_soc_dapm_update {
diff --git a/include/sound/soc-domain.h b/include/sound/soc-domain.h
new file mode 100644
index 0000000000000..94e1c1ae9ff00
--- /dev/null
+++ b/include/sound/soc-domain.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ASoC Sample Rate Domain Support
+ *
+ * Copyright (c) 2018 Cirrus Logic, Inc. and
+ *                    Cirrus Logic International Semiconductor Ltd.
+ *
+ * Author: Charles Keepax <ckeepax@opensource.cirrus.com>
+ */
+
+#ifndef LINUX_SND_SOC_DOMAIN_H
+#define LINUX_SND_SOC_DOMAIN_H
+
+#define SND_SOC_DOMAIN_CURRENT -1
+
+struct snd_soc_domain;
+struct snd_soc_domain_group;
+
+struct snd_soc_domain_ops {
+	int (*set_rate)(struct snd_soc_domain *domain, int rate);
+	int (*get_rate)(struct snd_soc_domain *domain);
+};
+
+struct snd_soc_domain_driver {
+	const char * const name;
+
+	const struct snd_soc_domain_ops *ops;
+
+	void *private_data;
+};
+
+struct snd_soc_domain {
+	const struct snd_soc_domain_driver *driver;
+	struct snd_soc_component *component;
+
+	/* TODO: Probably should be a snd_pcm_hw_params */
+	int rate;
+
+	int active_groups;
+};
+
+struct snd_soc_domain_group_ops {
+	int (*set_domain)(struct snd_soc_domain_group *group, int dom);
+
+	int (*mask_domains)(struct snd_soc_domain_group *group,
+			    unsigned long *domain_mask);
+	/* optional */
+	int (*pick_domain)(struct snd_soc_domain_group *group,
+			   const unsigned long *domain_mask);
+};
+
+struct snd_soc_domain_group_driver {
+	const char * const name;
+
+	const struct snd_soc_domain_group_ops *ops;
+
+	void *private_data;
+};
+
+struct snd_soc_domain_group {
+	const struct snd_soc_domain_group_driver *driver;
+	struct snd_soc_component *component;
+
+	int domain_index;
+	int attach_count;
+
+	struct list_head peers;
+
+	unsigned int walking:1;
+	unsigned int power:1;
+};
+
+int devm_snd_soc_domain_init(struct snd_soc_component *component);
+
+struct snd_soc_domain_group *
+devm_snd_soc_domain_group_new(struct snd_soc_component *component,
+			      const struct snd_soc_domain_group_driver *drv);
+
+struct snd_soc_domain *snd_soc_domain_get(struct snd_soc_domain_group *group,
+					  int index);
+bool snd_soc_domain_active(struct snd_soc_domain *domain);
+int snd_soc_domain_get_rate(struct snd_soc_domain *domain);
+
+int snd_soc_domain_set_rate(struct snd_soc_domain_group *group, int rate);
+
+/* TODO: API to force a particular domain onto a group? */
+int snd_soc_domain_attach(struct snd_soc_domain_group *group);
+int snd_soc_domain_detach(struct snd_soc_domain_group *group);
+
+int snd_soc_domain_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol,
+			 int event);
+
+int snd_soc_domain_connect_widgets(struct snd_soc_dapm_widget *a,
+				   struct snd_soc_dapm_widget *b,
+				   bool connect);
+
+#endif
diff --git a/include/sound/soc.h b/include/sound/soc.h
index f1dab1f4b194d..475843a17ebd4 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -413,6 +413,7 @@ struct snd_soc_jack_pin;
 #include <sound/soc-dapm.h>
 #include <sound/soc-dpcm.h>
 #include <sound/soc-topology.h>
+#include <sound/soc-domain.h>
 
 struct snd_soc_jack_gpio;
 
@@ -763,6 +764,9 @@ struct snd_soc_component_driver {
 	const struct snd_soc_dapm_route *dapm_routes;
 	unsigned int num_dapm_routes;
 
+	const struct snd_soc_domain_driver *domains;
+	unsigned int num_domains;
+
 	int (*probe)(struct snd_soc_component *);
 	void (*remove)(struct snd_soc_component *);
 	int (*suspend)(struct snd_soc_component *);
@@ -838,6 +842,9 @@ struct snd_soc_component {
 	struct list_head dai_list;
 	int num_dai;
 
+	struct snd_soc_domain *domains;
+	int num_domains;
+
 	struct regmap *regmap;
 	int val_bytes;
 
@@ -1036,6 +1043,7 @@ struct snd_soc_card {
 
 	struct mutex mutex;
 	struct mutex dapm_mutex;
+	struct mutex domain_mutex;
 
 	bool instantiated;
 	bool topology_shortname_created;
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 62a5f87c3cfc4..185f51aa963a2 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o
-snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o
+snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o soc-domain.o
 snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
 
 ifneq ($(CONFIG_SND_SOC_TOPOLOGY),)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 62e8e36062df0..4623adb27543b 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1345,6 +1345,13 @@ static int soc_probe_component(struct snd_soc_card *card,
 		}
 	}
 
+	ret = devm_snd_soc_domain_init(component);
+	if (ret < 0) {
+		dev_err(component->dev, "Failed to initialise domains: %d\n",
+			ret);
+		goto err_probe;
+	}
+
 	if (component->driver->controls)
 		snd_soc_add_component_controls(component,
 					       component->driver->controls,
@@ -2739,6 +2746,7 @@ int snd_soc_register_card(struct snd_soc_card *card)
 	card->instantiated = 0;
 	mutex_init(&card->mutex);
 	mutex_init(&card->dapm_mutex);
+	mutex_init(&card->domain_mutex);
 
 	return snd_soc_bind_card(card);
 }
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 7e3858d1e81dc..a3f01626fda76 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2179,6 +2179,9 @@ static void soc_dapm_connect_path(struct snd_soc_dapm_path *path,
 	if (path->connect == connect)
 		return;
 
+	/* TODO: Need to handle routes that are already connected */
+	snd_soc_domain_connect_widgets(path->source, path->sink, connect);
+
 	path->connect = connect;
 	dapm_mark_dirty(path->source, reason);
 	dapm_mark_dirty(path->sink, reason);
@@ -2685,6 +2688,14 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
 	if (wsource->is_supply || wsink->is_supply)
 		path->is_supply = 1;
 
+	switch (wsource->id) {
+	case snd_soc_dapm_rate:
+		wsink->dgroup = wsource->dgroup;
+		break;
+	default:
+		break;
+	}
+
 	/* connect static paths */
 	if (control == NULL) {
 		path->connect = 1;
@@ -3463,6 +3474,14 @@ snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
 			goto request_failed;
 		}
 		break;
+	case snd_soc_dapm_rate:
+		w->dgroup = devm_snd_soc_domain_group_new(dapm->component,
+							  w->priv);
+		if (IS_ERR(w->dgroup)) {
+			ret = PTR_ERR(w->dgroup);
+			goto request_failed;
+		}
+		break;
 	default:
 		break;
 	}
@@ -4566,6 +4585,22 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card)
 					    SND_SOC_BIAS_OFF);
 }
 
+int snd_soc_dapm_connect_domains(struct snd_soc_dapm_context *dapm,
+				 const char * const a, const char * const b)
+{
+	struct snd_soc_dapm_widget *wa, *wb;
+
+	wa = dapm_find_widget(dapm, a, false);
+	if (!wa)
+		return -ENODEV;
+	wb = dapm_find_widget(dapm, b, false);
+	if (!wb)
+		return -ENODEV;
+
+	return snd_soc_domain_connect_widgets(wa, wb, true);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_connect_domains);
+
 /* Module information */
 MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
 MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
diff --git a/sound/soc/soc-domain.c b/sound/soc/soc-domain.c
new file mode 100644
index 0000000000000..01914d5971601
--- /dev/null
+++ b/sound/soc/soc-domain.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Sample Rate Domain Support
+ *
+ * Copyright (c) 2018 Cirrus Logic, Inc. and
+ *                    Cirrus Logic International Semiconductor Ltd.
+ *
+ * Author: Charles Keepax <ckeepax@opensource.cirrus.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+
+struct domain_group_peer {
+	struct list_head list;
+	int link_count;
+	struct snd_soc_domain_group *group;
+};
+
+static inline void domain_mutex_lock(struct snd_soc_component *component)
+{
+	mutex_lock(&component->card->domain_mutex);
+}
+
+static inline void domain_mutex_unlock(struct snd_soc_component *component)
+{
+	mutex_unlock(&component->card->domain_mutex);
+}
+
+static inline void domain_mutex_assert_held(struct snd_soc_component *component)
+{
+	lockdep_assert_held(&component->card->domain_mutex);
+}
+
+int devm_snd_soc_domain_init(struct snd_soc_component *component)
+{
+	int i;
+
+	if (!component->driver->num_domains)
+		return 0;
+
+	component->num_domains = component->driver->num_domains;
+	component->domains = devm_kcalloc(component->card->dev,
+					  component->num_domains,
+					  sizeof(*component->domains),
+					  GFP_KERNEL);
+	if (!component->domains)
+		return -ENOMEM;
+
+	for (i = 0; i < component->num_domains; i++) {
+		component->domains[i].component = component;
+		component->domains[i].driver = &component->driver->domains[i];
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(devm_snd_soc_domain_init);
+
+struct snd_soc_domain_group *
+devm_snd_soc_domain_group_new(struct snd_soc_component *component,
+			      const struct snd_soc_domain_group_driver *driver)
+{
+	struct snd_soc_domain_group *group;
+
+	group = devm_kzalloc(component->card->dev, sizeof(*group), GFP_KERNEL);
+	if (!group)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&group->peers);
+
+	group->component = component;
+	group->driver = driver;
+
+	return group;
+}
+EXPORT_SYMBOL_GPL(devm_snd_soc_domain_group_new);
+
+struct snd_soc_domain *snd_soc_domain_get(struct snd_soc_domain_group *group,
+					  int index)
+{
+	int ndomains = group->component->num_domains;
+
+	domain_mutex_assert_held(group->component);
+
+	if (index == SND_SOC_DOMAIN_CURRENT)
+		index = group->domain_index;
+
+	if (index < 0 || index >= ndomains)
+		return NULL;
+
+	return &group->component->domains[index];
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_get);
+
+bool snd_soc_domain_active(struct snd_soc_domain *domain)
+{
+	bool active;
+
+	domain_mutex_assert_held(domain->component);
+
+	active = !!domain->active_groups;
+
+	return active;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_active);
+
+int snd_soc_domain_get_rate(struct snd_soc_domain *domain)
+{
+	domain_mutex_assert_held(domain->component);
+
+	return domain->rate;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_get_rate);
+
+int snd_soc_domain_set_rate(struct snd_soc_domain_group *group, int rate)
+{
+	struct snd_soc_domain *domain;
+	int ret = -ENODEV;
+
+	domain_mutex_lock(group->component);
+
+	domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT);
+	if (domain) {
+		domain->rate = rate;
+		ret = domain->driver->ops->set_rate(domain, rate);
+	}
+
+	domain_mutex_unlock(group->component);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_set_rate);
+
+static struct snd_soc_domain_group *
+group_walk(struct snd_soc_domain_group *group, bool local,
+	   bool (*cond)(struct snd_soc_domain_group *g, void *c), void *cookie)
+{
+	struct domain_group_peer *link;
+	struct snd_soc_domain_group *target = NULL;
+
+	domain_mutex_assert_held(group->component);
+
+	if (group->walking)
+		return NULL;
+
+	dev_vdbg(group->component->dev, "Walking %s\n", group->driver->name);
+
+	if (cond(group, cookie))
+		return group;
+
+	group->walking = true;
+	list_for_each_entry(link, &group->peers, list) {
+		if (!link->group->power)
+			continue;
+
+		if (local && link->group->component != group->component)
+			continue;
+
+		target = group_walk(link->group, local, cond, cookie);
+		if (target)
+			break;
+	}
+	group->walking = false;
+
+	return target;
+}
+
+static bool group_mask(struct snd_soc_domain_group *group, void *cookie)
+{
+	unsigned long *mask = cookie;
+
+	if (group->attach_count)
+		*mask &= 1 << group->domain_index;
+	else if (group->driver->ops->mask_domains)
+		group->driver->ops->mask_domains(group, mask);
+
+	return false;
+}
+
+static int group_pick(struct snd_soc_domain_group *group,
+				const unsigned long *domain_mask)
+{
+	int ndomains = group->component->num_domains;
+	int i;
+
+	domain_mutex_assert_held(group->component);
+
+	for_each_set_bit(i, domain_mask, ndomains) {
+		struct snd_soc_domain *domain = &group->component->domains[i];
+
+		if (!snd_soc_domain_active(domain))
+			return i;
+	}
+
+	return find_first_bit(domain_mask, ndomains);
+}
+
+int snd_soc_domain_attach(struct snd_soc_domain_group *group)
+{
+	int ret = 0;
+
+	domain_mutex_lock(group->component);
+
+	dev_dbg(group->component->dev, "Attaching domain to %s: %d\n",
+		group->driver->name, group->attach_count);
+
+	if (!group->attach_count) {
+		const struct snd_soc_domain_group_ops *ops = group->driver->ops;
+		unsigned long dom_map = ~0UL;
+		struct snd_soc_domain *domain;
+
+		group_walk(group, true, group_mask, &dom_map);
+
+		if (ops->pick_domain)
+			group->domain_index = ops->pick_domain(group, &dom_map);
+		else
+			group->domain_index = group_pick(group, &dom_map);
+
+		domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT);
+		if (!domain) {
+			dev_err(group->component->dev,
+				"No suitable domain to attach for %s\n",
+				group->driver->name);
+			ret = -ENODEV;
+			goto error;
+		}
+
+		dev_dbg(group->component->dev, "Apply domain %s to %s\n",
+			domain->driver->name, group->driver->name);
+
+		ret = ops->set_domain(group, group->domain_index);
+		if (ret)
+			goto error;
+
+		domain->active_groups++;
+	}
+
+	group->attach_count++;
+
+error:
+	domain_mutex_unlock(group->component);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_attach);
+
+int snd_soc_domain_detach(struct snd_soc_domain_group *group)
+{
+	int ret = 0;
+
+	domain_mutex_lock(group->component);
+
+	dev_dbg(group->component->dev, "Detaching domain from %s: %d\n",
+		group->driver->name, group->attach_count);
+
+	if (!group->attach_count) {
+		dev_err(group->component->dev, "Unbalanced detach on %s\n",
+			group->driver->name);
+		ret = -EPERM;
+	} else {
+		struct snd_soc_domain *domain;
+
+		domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT);
+		if (!domain) {
+			dev_err(group->component->dev,
+				"Group %s has missing domain\n",
+				group->driver->name);
+			ret = -ENODEV;
+			goto error;
+		}
+
+		domain->active_groups--;
+		group->attach_count--;
+	}
+
+error:
+	domain_mutex_unlock(group->component);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_detach);
+
+int snd_soc_domain_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol,
+			 int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_WILL_PMU:
+		w->dgroup->power = true;
+		return 0;
+	case SND_SOC_DAPM_PRE_PMU:
+		return snd_soc_domain_attach(w->dgroup);
+	case SND_SOC_DAPM_POST_PMD:
+		w->dgroup->power = false;
+		return snd_soc_domain_detach(w->dgroup);
+	default:
+		return 0;
+	}
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_event);
+
+static struct domain_group_peer *
+group_peer_find(struct snd_soc_domain_group *group,
+		struct snd_soc_domain_group *peer)
+{
+	struct domain_group_peer *link;
+
+	domain_mutex_assert_held(group->component);
+
+	list_for_each_entry(link, &group->peers, list) {
+		if (link->group == peer)
+			return link;
+	}
+
+	return NULL;
+}
+
+static int group_peer_new(struct snd_soc_domain_group *group,
+			  struct snd_soc_domain_group *peer)
+{
+	struct domain_group_peer *link;
+
+	domain_mutex_lock(group->component);
+
+	link = group_peer_find(group, peer);
+	if (!link) {
+		dev_dbg(group->component->dev, "New peer: %s -> %s\n",
+			group->driver->name, peer->driver->name);
+
+		link = kzalloc(sizeof(*link), GFP_KERNEL);
+		if (!link)
+			return -ENOMEM;
+
+		INIT_LIST_HEAD(&link->list);
+		link->group = peer;
+
+		list_add_tail(&link->list, &group->peers);
+	}
+
+	link->link_count++;
+
+	domain_mutex_unlock(group->component);
+
+	return 0;
+}
+
+static int group_peer_delete(struct snd_soc_domain_group *group,
+			     struct snd_soc_domain_group *peer)
+{
+	struct domain_group_peer *link;
+	int ret = 0;
+
+	domain_mutex_lock(group->component);
+
+	link = group_peer_find(group, peer);
+	if (!link) {
+		dev_err(group->component->dev,
+			"Delete on invalid peer: %s -> %s\n",
+			group->driver->name, peer->driver->name);
+		ret = -ENOENT;
+		goto error;
+	}
+
+	link->link_count--;
+	if (!link->link_count) {
+		dev_dbg(group->component->dev, "Delete peer: %s -> %s\n",
+			group->driver->name, peer->driver->name);
+
+		list_del(&link->list);
+		kfree(link);
+	}
+
+error:
+	domain_mutex_unlock(group->component);
+
+	return ret;
+}
+
+int snd_soc_domain_connect_widgets(struct snd_soc_dapm_widget *a,
+				   struct snd_soc_dapm_widget *b,
+				   bool connect)
+{
+	int (*op)(struct snd_soc_domain_group *group,
+		  struct snd_soc_domain_group *peer);
+	int ret;
+
+	if (!a->dgroup || !b->dgroup)
+		return 0;
+
+	dev_dbg(a->dapm->dev, "%s %s,%s - %s,%s\n",
+		connect ? "Connecting" : "Disconnecting",
+		a->name, a->dgroup->driver->name,
+		b->name, b->dgroup->driver->name);
+
+	if (connect)
+		op = group_peer_new;
+	else
+		op = group_peer_delete;
+
+	ret = op(a->dgroup, b->dgroup);
+	if (ret)
+		return ret;
+
+	ret = op(b->dgroup, a->dgroup);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_domain_connect_widgets);
