diff mbox series

[v4,02/11] soc: imx: add i.MX8MP HSIO blk-ctrl

Message ID 20220406153402.1265474-3-l.stach@pengutronix.de (mailing list archive)
State New, archived
Headers show
Series consolidated i.MX8MP HSIO/MEDIA/HDMI blk-ctrl series | expand

Commit Message

Lucas Stach April 6, 2022, 3:33 p.m. UTC
The i.MX8MP added some blk-ctrl peripherals that don't follow the regular
structure of the blk-ctrls in the previous SoCs. Add a new file for those
with currently only the HSIO blk-ctrl being supported. Others will be added
later on.

Signed-off-by: Lucas Stach <l.stach@pengutronix.de>
---
 drivers/soc/imx/Makefile          |   1 +
 drivers/soc/imx/imx8mp-blk-ctrl.c | 503 ++++++++++++++++++++++++++++++
 2 files changed, 504 insertions(+)
 create mode 100644 drivers/soc/imx/imx8mp-blk-ctrl.c

Comments

Marek Vasut April 6, 2022, 11:43 p.m. UTC | #1
On 4/6/22 17:33, Lucas Stach wrote:

[...]

> +static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
> +	.max_reg = 0x24,

Doesn't the HSIO_BLK_CTL go up to 0x10c ?

> +	.power_on = imx8mp_hsio_blk_ctrl_power_on,
> +	.power_off = imx8mp_hsio_blk_ctrl_power_off,
> +	.power_notifier_fn = imx8mp_hsio_power_notifier,
> +	.domains = imx8mp_hsio_domain_data,
> +	.num_domains = ARRAY_SIZE(imx8mp_hsio_domain_data),
> +};
> +
> +static int imx8mp_blk_ctrl_power_on(struct generic_pm_domain *genpd)
> +{
> +	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
> +	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
> +	struct imx8mp_blk_ctrl *bc = domain->bc;
> +	int ret;
> +
> +	/* make sure bus domain is awake */
> +	ret = pm_runtime_resume_and_get(bc->bus_power_dev);
> +	if (ret < 0) {
> +		dev_err(bc->dev, "failed to power up bus domain\n");
> +		return ret;
> +	}
> +
> +	/* enable upstream clocks */
> +	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
> +	if (ret) {
> +		dev_err(bc->dev, "failed to enable clocks\n");
> +		goto bus_put;
> +	}
> +
> +	/* domain specific blk-ctrl manipulation */
> +	bc->power_on(bc, domain);

Would it make sense to add error checking ?

> +	/* power up upstream GPC domain */
> +	ret = pm_runtime_resume_and_get(domain->power_dev);
> +	if (ret < 0) {
> +		dev_err(bc->dev, "failed to power up peripheral domain\n");
> +		goto clk_disable;
> +	}
> +
> +	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
> +
> +	return 0;
> +
> +clk_disable:
> +	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
> +bus_put:
> +	pm_runtime_put(bc->bus_power_dev);
> +
> +	return ret;
> +}

[...]
Lucas Stach April 7, 2022, 9:12 a.m. UTC | #2
Am Donnerstag, dem 07.04.2022 um 01:43 +0200 schrieb Marek Vasut:
> On 4/6/22 17:33, Lucas Stach wrote:
> 
> [...]
> 
> > +static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
> > +	.max_reg = 0x24,
> 
> Doesn't the HSIO_BLK_CTL go up to 0x10c ?

Technically yes, but there is already a driver for the USB glue
(fsl,imx8mp-dwc3) that occupies the USB registers at and above 0x100.
 
> 
> > +	.power_on = imx8mp_hsio_blk_ctrl_power_on,
> > +	.power_off = imx8mp_hsio_blk_ctrl_power_off,
> > +	.power_notifier_fn = imx8mp_hsio_power_notifier,
> > +	.domains = imx8mp_hsio_domain_data,
> > +	.num_domains = ARRAY_SIZE(imx8mp_hsio_domain_data),
> > +};
> > +
> > +static int imx8mp_blk_ctrl_power_on(struct generic_pm_domain *genpd)
> > +{
> > +	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
> > +	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
> > +	struct imx8mp_blk_ctrl *bc = domain->bc;
> > +	int ret;
> > +
> > +	/* make sure bus domain is awake */
> > +	ret = pm_runtime_resume_and_get(bc->bus_power_dev);
> > +	if (ret < 0) {
> > +		dev_err(bc->dev, "failed to power up bus domain\n");
> > +		return ret;
> > +	}
> > +
> > +	/* enable upstream clocks */
> > +	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
> > +	if (ret) {
> > +		dev_err(bc->dev, "failed to enable clocks\n");
> > +		goto bus_put;
> > +	}
> > +
> > +	/* domain specific blk-ctrl manipulation */
> > +	bc->power_on(bc, domain);
> 
> Would it make sense to add error checking ?
> 
I don't expect those functions to do any more than a bit of blk-ctrl
MMIO register poking. If that fails you are in much more trouble than
what you can reasonably fix with some error checking.

Regards,
Lucas

> > +	/* power up upstream GPC domain */
> > +	ret = pm_runtime_resume_and_get(domain->power_dev);
> > +	if (ret < 0) {
> > +		dev_err(bc->dev, "failed to power up peripheral domain\n");
> > +		goto clk_disable;
> > +	}
> > +
> > +	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
> > +
> > +	return 0;
> > +
> > +clk_disable:
> > +	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
> > +bus_put:
> > +	pm_runtime_put(bc->bus_power_dev);
> > +
> > +	return ret;
> > +}
> 
> [...]
Marek Vasut April 10, 2022, 10:11 p.m. UTC | #3
On 4/7/22 11:12, Lucas Stach wrote:
> Am Donnerstag, dem 07.04.2022 um 01:43 +0200 schrieb Marek Vasut:
>> On 4/6/22 17:33, Lucas Stach wrote:
>>
>> [...]
>>
>>> +static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
>>> +	.max_reg = 0x24,
>>
>> Doesn't the HSIO_BLK_CTL go up to 0x10c ?
> 
> Technically yes, but there is already a driver for the USB glue
> (fsl,imx8mp-dwc3) that occupies the USB registers at and above 0x100.

Shouldn't that imx8mp-dwc3 glue code be switched to syscon and access 
the registers through this driver then ?

>>> +	.power_on = imx8mp_hsio_blk_ctrl_power_on,
>>> +	.power_off = imx8mp_hsio_blk_ctrl_power_off,
>>> +	.power_notifier_fn = imx8mp_hsio_power_notifier,
>>> +	.domains = imx8mp_hsio_domain_data,
>>> +	.num_domains = ARRAY_SIZE(imx8mp_hsio_domain_data),
>>> +};
>>> +
>>> +static int imx8mp_blk_ctrl_power_on(struct generic_pm_domain *genpd)
>>> +{
>>> +	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
>>> +	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
>>> +	struct imx8mp_blk_ctrl *bc = domain->bc;
>>> +	int ret;
>>> +
>>> +	/* make sure bus domain is awake */
>>> +	ret = pm_runtime_resume_and_get(bc->bus_power_dev);
>>> +	if (ret < 0) {
>>> +		dev_err(bc->dev, "failed to power up bus domain\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	/* enable upstream clocks */
>>> +	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
>>> +	if (ret) {
>>> +		dev_err(bc->dev, "failed to enable clocks\n");
>>> +		goto bus_put;
>>> +	}
>>> +
>>> +	/* domain specific blk-ctrl manipulation */
>>> +	bc->power_on(bc, domain);
>>
>> Would it make sense to add error checking ?
>>
> I don't expect those functions to do any more than a bit of blk-ctrl
> MMIO register poking. If that fails you are in much more trouble than
> what you can reasonably fix with some error checking.

All right.
Lucas Stach April 11, 2022, 4:46 p.m. UTC | #4
Am Montag, dem 11.04.2022 um 00:11 +0200 schrieb Marek Vasut:
> On 4/7/22 11:12, Lucas Stach wrote:
> > Am Donnerstag, dem 07.04.2022 um 01:43 +0200 schrieb Marek Vasut:
> > > On 4/6/22 17:33, Lucas Stach wrote:
> > > 
> > > [...]
> > > 
> > > > +static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
> > > > +	.max_reg = 0x24,
> > > 
> > > Doesn't the HSIO_BLK_CTL go up to 0x10c ?
> > 
> > Technically yes, but there is already a driver for the USB glue
> > (fsl,imx8mp-dwc3) that occupies the USB registers at and above 0x100.
> 
> Shouldn't that imx8mp-dwc3 glue code be switched to syscon and access 
> the registers through this driver then ?

I don't see why this would be needed. Registers up to 0x24 are a mixed
bag of blk-ctrls, but the registers above 0x100 are only USB wakeup
related, I don't see the blk-ctrl ever wanting to touch them.

I think the current split is fine. Just because the RM lumps those
register regions together into the HSIO blk-ctrl description doesn't
mean we need to cover them all in a single syscon.

Regards,
Lucas

> 
> > > > +	.power_on = imx8mp_hsio_blk_ctrl_power_on,
> > > > +	.power_off = imx8mp_hsio_blk_ctrl_power_off,
> > > > +	.power_notifier_fn = imx8mp_hsio_power_notifier,
> > > > +	.domains = imx8mp_hsio_domain_data,
> > > > +	.num_domains = ARRAY_SIZE(imx8mp_hsio_domain_data),
> > > > +};
> > > > +
> > > > +static int imx8mp_blk_ctrl_power_on(struct generic_pm_domain *genpd)
> > > > +{
> > > > +	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
> > > > +	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
> > > > +	struct imx8mp_blk_ctrl *bc = domain->bc;
> > > > +	int ret;
> > > > +
> > > > +	/* make sure bus domain is awake */
> > > > +	ret = pm_runtime_resume_and_get(bc->bus_power_dev);
> > > > +	if (ret < 0) {
> > > > +		dev_err(bc->dev, "failed to power up bus domain\n");
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* enable upstream clocks */
> > > > +	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
> > > > +	if (ret) {
> > > > +		dev_err(bc->dev, "failed to enable clocks\n");
> > > > +		goto bus_put;
> > > > +	}
> > > > +
> > > > +	/* domain specific blk-ctrl manipulation */
> > > > +	bc->power_on(bc, domain);
> > > 
> > > Would it make sense to add error checking ?
> > > 
> > I don't expect those functions to do any more than a bit of blk-ctrl
> > MMIO register poking. If that fails you are in much more trouble than
> > what you can reasonably fix with some error checking.
> 
> All right.
Marek Vasut April 11, 2022, 10:25 p.m. UTC | #5
On 4/11/22 18:46, Lucas Stach wrote:
> Am Montag, dem 11.04.2022 um 00:11 +0200 schrieb Marek Vasut:
>> On 4/7/22 11:12, Lucas Stach wrote:
>>> Am Donnerstag, dem 07.04.2022 um 01:43 +0200 schrieb Marek Vasut:
>>>> On 4/6/22 17:33, Lucas Stach wrote:
>>>>
>>>> [...]
>>>>
>>>>> +static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
>>>>> +	.max_reg = 0x24,
>>>>
>>>> Doesn't the HSIO_BLK_CTL go up to 0x10c ?
>>>
>>> Technically yes, but there is already a driver for the USB glue
>>> (fsl,imx8mp-dwc3) that occupies the USB registers at and above 0x100.
>>
>> Shouldn't that imx8mp-dwc3 glue code be switched to syscon and access
>> the registers through this driver then ?
> 
> I don't see why this would be needed. Registers up to 0x24 are a mixed
> bag of blk-ctrls, but the registers above 0x100 are only USB wakeup
> related, I don't see the blk-ctrl ever wanting to touch them.
> 
> I think the current split is fine. Just because the RM lumps those
> register regions together into the HSIO blk-ctrl description doesn't
> mean we need to cover them all in a single syscon.

I guess we can also always fix this up later, OK.
diff mbox series

Patch

diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile
index 8a707077914c..63cd29f6d4d2 100644
--- a/drivers/soc/imx/Makefile
+++ b/drivers/soc/imx/Makefile
@@ -6,3 +6,4 @@  obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o
 obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o
 obj-$(CONFIG_SOC_IMX8M) += soc-imx8m.o
 obj-$(CONFIG_SOC_IMX8M) += imx8m-blk-ctrl.o
+obj-$(CONFIG_SOC_IMX8M) += imx8mp-blk-ctrl.o
diff --git a/drivers/soc/imx/imx8mp-blk-ctrl.c b/drivers/soc/imx/imx8mp-blk-ctrl.c
new file mode 100644
index 000000000000..97cc8f08911e
--- /dev/null
+++ b/drivers/soc/imx/imx8mp-blk-ctrl.c
@@ -0,0 +1,503 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/power/imx8mp-power.h>
+
+#define GPR_REG0		0x0
+#define  PCIE_CLOCK_MODULE_EN	BIT(0)
+#define  USB_CLOCK_MODULE_EN	BIT(1)
+
+struct imx8mp_blk_ctrl_domain;
+
+struct imx8mp_blk_ctrl {
+	struct device *dev;
+	struct notifier_block power_nb;
+	struct device *bus_power_dev;
+	struct regmap *regmap;
+	struct imx8mp_blk_ctrl_domain *domains;
+	struct genpd_onecell_data onecell_data;
+	void (*power_off) (struct imx8mp_blk_ctrl *bc, struct imx8mp_blk_ctrl_domain *domain);
+	void (*power_on) (struct imx8mp_blk_ctrl *bc, struct imx8mp_blk_ctrl_domain *domain);
+};
+
+struct imx8mp_blk_ctrl_domain_data {
+	const char *name;
+	const char * const *clk_names;
+	int num_clks;
+	const char *gpc_name;
+};
+
+#define DOMAIN_MAX_CLKS 2
+
+struct imx8mp_blk_ctrl_domain {
+	struct generic_pm_domain genpd;
+	const struct imx8mp_blk_ctrl_domain_data *data;
+	struct clk_bulk_data clks[DOMAIN_MAX_CLKS];
+	struct device *power_dev;
+	struct imx8mp_blk_ctrl *bc;
+	int id;
+};
+
+struct imx8mp_blk_ctrl_data {
+	int max_reg;
+	notifier_fn_t power_notifier_fn;
+	void (*power_off) (struct imx8mp_blk_ctrl *bc, struct imx8mp_blk_ctrl_domain *domain);
+	void (*power_on) (struct imx8mp_blk_ctrl *bc, struct imx8mp_blk_ctrl_domain *domain);
+	const struct imx8mp_blk_ctrl_domain_data *domains;
+	int num_domains;
+};
+
+static inline struct imx8mp_blk_ctrl_domain *
+to_imx8mp_blk_ctrl_domain(struct generic_pm_domain *genpd)
+{
+	return container_of(genpd, struct imx8mp_blk_ctrl_domain, genpd);
+}
+
+static void imx8mp_hsio_blk_ctrl_power_on(struct imx8mp_blk_ctrl *bc,
+					  struct imx8mp_blk_ctrl_domain *domain)
+{
+	switch (domain->id) {
+	case IMX8MP_HSIOBLK_PD_USB:
+		regmap_set_bits(bc->regmap, GPR_REG0, USB_CLOCK_MODULE_EN);
+		break;
+	case IMX8MP_HSIOBLK_PD_PCIE:
+		regmap_set_bits(bc->regmap, GPR_REG0, PCIE_CLOCK_MODULE_EN);
+		break;
+	default:
+		break;
+	}
+}
+
+static void imx8mp_hsio_blk_ctrl_power_off(struct imx8mp_blk_ctrl *bc,
+					   struct imx8mp_blk_ctrl_domain *domain)
+{
+	switch (domain->id) {
+	case IMX8MP_HSIOBLK_PD_USB:
+		regmap_clear_bits(bc->regmap, GPR_REG0, USB_CLOCK_MODULE_EN);
+		break;
+	case IMX8MP_HSIOBLK_PD_PCIE:
+		regmap_clear_bits(bc->regmap, GPR_REG0, PCIE_CLOCK_MODULE_EN);
+		break;
+	default:
+		break;
+	}
+}
+
+static int imx8mp_hsio_power_notifier(struct notifier_block *nb,
+				      unsigned long action, void *data)
+{
+	struct imx8mp_blk_ctrl *bc = container_of(nb, struct imx8mp_blk_ctrl,
+						 power_nb);
+	struct clk_bulk_data *usb_clk = bc->domains[IMX8MP_HSIOBLK_PD_USB].clks;
+	int num_clks = bc->domains[IMX8MP_HSIOBLK_PD_USB].data->num_clks;
+	int ret;
+
+	switch (action) {
+	case GENPD_NOTIFY_ON:
+		/*
+		 * enable USB clock for a moment for the power-on ADB handshake
+		 * to proceed
+		 */
+		ret = clk_bulk_prepare_enable(num_clks, usb_clk);
+		if (ret)
+			return NOTIFY_BAD;
+		regmap_set_bits(bc->regmap, GPR_REG0, USB_CLOCK_MODULE_EN);
+
+		udelay(5);
+
+		regmap_clear_bits(bc->regmap, GPR_REG0, USB_CLOCK_MODULE_EN);
+		clk_bulk_disable_unprepare(num_clks, usb_clk);
+		break;
+	case GENPD_NOTIFY_PRE_OFF:
+		/* enable USB clock for the power-down ADB handshake to work */
+		ret = clk_bulk_prepare_enable(num_clks, usb_clk);
+		if (ret)
+			return NOTIFY_BAD;
+
+		regmap_set_bits(bc->regmap, GPR_REG0, USB_CLOCK_MODULE_EN);
+		break;
+	case GENPD_NOTIFY_OFF:
+		clk_bulk_disable_unprepare(num_clks, usb_clk);
+		break;
+	default:
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static const struct imx8mp_blk_ctrl_domain_data imx8mp_hsio_domain_data[] = {
+	[IMX8MP_HSIOBLK_PD_USB] = {
+		.name = "hsioblk-usb",
+		.clk_names = (const char *[]){ "usb" },
+		.num_clks = 1,
+		.gpc_name = "usb",
+	},
+	[IMX8MP_HSIOBLK_PD_USB_PHY1] = {
+		.name = "hsioblk-usb-phy1",
+		.gpc_name = "usb-phy1",
+	},
+	[IMX8MP_HSIOBLK_PD_USB_PHY2] = {
+		.name = "hsioblk-usb-phy2",
+		.gpc_name = "usb-phy2",
+	},
+	[IMX8MP_HSIOBLK_PD_PCIE] = {
+		.name = "hsioblk-pcie",
+		.clk_names = (const char *[]){ "pcie" },
+		.num_clks = 1,
+		.gpc_name = "pcie",
+	},
+	[IMX8MP_HSIOBLK_PD_PCIE_PHY] = {
+		.name = "hsioblk-pcie-phy",
+		.gpc_name = "pcie-phy",
+	},
+};
+
+static const struct imx8mp_blk_ctrl_data imx8mp_hsio_blk_ctl_dev_data = {
+	.max_reg = 0x24,
+	.power_on = imx8mp_hsio_blk_ctrl_power_on,
+	.power_off = imx8mp_hsio_blk_ctrl_power_off,
+	.power_notifier_fn = imx8mp_hsio_power_notifier,
+	.domains = imx8mp_hsio_domain_data,
+	.num_domains = ARRAY_SIZE(imx8mp_hsio_domain_data),
+};
+
+static int imx8mp_blk_ctrl_power_on(struct generic_pm_domain *genpd)
+{
+	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
+	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
+	struct imx8mp_blk_ctrl *bc = domain->bc;
+	int ret;
+
+	/* make sure bus domain is awake */
+	ret = pm_runtime_resume_and_get(bc->bus_power_dev);
+	if (ret < 0) {
+		dev_err(bc->dev, "failed to power up bus domain\n");
+		return ret;
+	}
+
+	/* enable upstream clocks */
+	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
+	if (ret) {
+		dev_err(bc->dev, "failed to enable clocks\n");
+		goto bus_put;
+	}
+
+	/* domain specific blk-ctrl manipulation */
+	bc->power_on(bc, domain);
+
+	/* power up upstream GPC domain */
+	ret = pm_runtime_resume_and_get(domain->power_dev);
+	if (ret < 0) {
+		dev_err(bc->dev, "failed to power up peripheral domain\n");
+		goto clk_disable;
+	}
+
+	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
+
+	return 0;
+
+clk_disable:
+	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
+bus_put:
+	pm_runtime_put(bc->bus_power_dev);
+
+	return ret;
+}
+
+static int imx8mp_blk_ctrl_power_off(struct generic_pm_domain *genpd)
+{
+	struct imx8mp_blk_ctrl_domain *domain = to_imx8mp_blk_ctrl_domain(genpd);
+	const struct imx8mp_blk_ctrl_domain_data *data = domain->data;
+	struct imx8mp_blk_ctrl *bc = domain->bc;
+	int ret;
+
+	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);;
+	if (ret) {
+		dev_err(bc->dev, "failed to enable clocks\n");
+		return ret;
+	}
+
+	/* domain specific blk-ctrl manipulation */
+	bc->power_off(bc, domain);
+
+	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
+
+	/* power down upstream GPC domain */
+	pm_runtime_put(domain->power_dev);
+
+	/* allow bus domain to suspend */
+	pm_runtime_put(bc->bus_power_dev);
+
+	return 0;
+}
+
+static struct generic_pm_domain *
+imx8m_blk_ctrl_xlate(struct of_phandle_args *args, void *data)
+{
+	struct genpd_onecell_data *onecell_data = data;
+	unsigned int index = args->args[0];
+
+	if (args->args_count != 1 ||
+	    index >= onecell_data->num_domains)
+		return ERR_PTR(-EINVAL);
+
+	return onecell_data->domains[index];
+}
+
+static struct lock_class_key blk_ctrl_genpd_lock_class;
+
+static int imx8mp_blk_ctrl_probe(struct platform_device *pdev)
+{
+	const struct imx8mp_blk_ctrl_data *bc_data;
+	struct device *dev = &pdev->dev;
+	struct imx8mp_blk_ctrl *bc;
+	void __iomem *base;
+	int num_domains, i, ret;
+
+	struct regmap_config regmap_config = {
+		.reg_bits	= 32,
+		.val_bits	= 32,
+		.reg_stride	= 4,
+	};
+
+	bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL);
+	if (!bc)
+		return -ENOMEM;
+
+	bc->dev = dev;
+
+	bc_data = of_device_get_match_data(dev);
+	num_domains = bc_data->num_domains;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	regmap_config.max_register = bc_data->max_reg;
+	bc->regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
+	if (IS_ERR(bc->regmap))
+		return dev_err_probe(dev, PTR_ERR(bc->regmap),
+				     "failed to init regmap\n");
+
+	bc->domains = devm_kcalloc(dev, num_domains,
+				   sizeof(struct imx8mp_blk_ctrl_domain),
+				   GFP_KERNEL);
+	if (!bc->domains)
+		return -ENOMEM;
+
+	bc->onecell_data.num_domains = num_domains;
+	bc->onecell_data.xlate = imx8m_blk_ctrl_xlate;
+	bc->onecell_data.domains =
+		devm_kcalloc(dev, num_domains,
+			     sizeof(struct generic_pm_domain *), GFP_KERNEL);
+	if (!bc->onecell_data.domains)
+		return -ENOMEM;
+
+	bc->bus_power_dev = genpd_dev_pm_attach_by_name(dev, "bus");
+	if (IS_ERR(bc->bus_power_dev))
+		return dev_err_probe(dev, PTR_ERR(bc->bus_power_dev),
+				     "failed to attach bus power domain\n");
+
+	bc->power_off = bc_data->power_off;
+	bc->power_on = bc_data->power_on;
+
+	for (i = 0; i < num_domains; i++) {
+		const struct imx8mp_blk_ctrl_domain_data *data = &bc_data->domains[i];
+		struct imx8mp_blk_ctrl_domain *domain = &bc->domains[i];
+		int j;
+
+		domain->data = data;
+
+		for (j = 0; j < data->num_clks; j++)
+			domain->clks[j].id = data->clk_names[j];
+
+		ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks);
+		if (ret) {
+			dev_err_probe(dev, ret, "failed to get clock\n");
+			goto cleanup_pds;
+		}
+
+		domain->power_dev =
+			dev_pm_domain_attach_by_name(dev, data->gpc_name);
+		if (IS_ERR(domain->power_dev)) {
+			dev_err_probe(dev, PTR_ERR(domain->power_dev),
+				      "failed to attach power domain %s\n",
+				      data->gpc_name);
+			ret = PTR_ERR(domain->power_dev);
+			goto cleanup_pds;
+		}
+		dev_set_name(domain->power_dev, "%s", data->name);
+
+		domain->genpd.name = data->name;
+		domain->genpd.power_on = imx8mp_blk_ctrl_power_on;
+		domain->genpd.power_off = imx8mp_blk_ctrl_power_off;
+		domain->bc = bc;
+		domain->id = i;
+
+		ret = pm_genpd_init(&domain->genpd, NULL, true);
+		if (ret) {
+			dev_err_probe(dev, ret, "failed to init power domain\n");
+			dev_pm_domain_detach(domain->power_dev, true);
+			goto cleanup_pds;
+		}
+
+		/*
+		 * We use runtime PM to trigger power on/off of the upstream GPC
+		 * domain, as a strict hierarchical parent/child power domain
+		 * setup doesn't allow us to meet the sequencing requirements.
+		 * This means we have nested locking of genpd locks, without the
+		 * nesting being visible at the genpd level, so we need a
+		 * separate lock class to make lockdep aware of the fact that
+		 * this are separate domain locks that can be nested without a
+		 * self-deadlock.
+		 */
+		lockdep_set_class(&domain->genpd.mlock,
+				  &blk_ctrl_genpd_lock_class);
+
+		bc->onecell_data.domains[i] = &domain->genpd;
+	}
+
+	ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to add power domain provider\n");
+		goto cleanup_pds;
+	}
+
+	bc->power_nb.notifier_call = bc_data->power_notifier_fn;
+	ret = dev_pm_genpd_add_notifier(bc->bus_power_dev, &bc->power_nb);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to add power notifier\n");
+		goto cleanup_provider;
+	}
+
+	dev_set_drvdata(dev, bc);
+
+	return 0;
+
+cleanup_provider:
+	of_genpd_del_provider(dev->of_node);
+cleanup_pds:
+	for (i--; i >= 0; i--) {
+		pm_genpd_remove(&bc->domains[i].genpd);
+		dev_pm_domain_detach(bc->domains[i].power_dev, true);
+	}
+
+	dev_pm_domain_detach(bc->bus_power_dev, true);
+
+	return ret;
+}
+
+static int imx8mp_blk_ctrl_remove(struct platform_device *pdev)
+{
+	struct imx8mp_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
+	int i;
+
+	of_genpd_del_provider(pdev->dev.of_node);
+
+	for (i = 0; bc->onecell_data.num_domains; i++) {
+		struct imx8mp_blk_ctrl_domain *domain = &bc->domains[i];
+
+		pm_genpd_remove(&domain->genpd);
+		dev_pm_domain_detach(domain->power_dev, true);
+	}
+
+	dev_pm_genpd_remove_notifier(bc->bus_power_dev);
+
+	dev_pm_domain_detach(bc->bus_power_dev, true);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx8mp_blk_ctrl_suspend(struct device *dev)
+{
+	struct imx8mp_blk_ctrl *bc = dev_get_drvdata(dev);
+	int ret, i;
+
+	/*
+	 * This may look strange, but is done so the generic PM_SLEEP code
+	 * can power down our domains and more importantly power them up again
+	 * after resume, without tripping over our usage of runtime PM to
+	 * control the upstream GPC domains. Things happen in the right order
+	 * in the system suspend/resume paths due to the device parent/child
+	 * hierarchy.
+	 */
+	ret = pm_runtime_get_sync(bc->bus_power_dev);
+	if (ret < 0) {
+		pm_runtime_put_noidle(bc->bus_power_dev);
+		return ret;
+	}
+
+	for (i = 0; i < bc->onecell_data.num_domains; i++) {
+		struct imx8mp_blk_ctrl_domain *domain = &bc->domains[i];
+
+		ret = pm_runtime_get_sync(domain->power_dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(domain->power_dev);
+			goto out_fail;
+		}
+	}
+
+	return 0;
+
+out_fail:
+	for (i--; i >= 0; i--)
+		pm_runtime_put(bc->domains[i].power_dev);
+
+	pm_runtime_put(bc->bus_power_dev);
+
+	return ret;
+}
+
+static int imx8mp_blk_ctrl_resume(struct device *dev)
+{
+	struct imx8mp_blk_ctrl *bc = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < bc->onecell_data.num_domains; i++)
+		pm_runtime_put(bc->domains[i].power_dev);
+
+	pm_runtime_put(bc->bus_power_dev);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx8mp_blk_ctrl_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(imx8mp_blk_ctrl_suspend,
+				imx8mp_blk_ctrl_resume)
+};
+
+static const struct of_device_id imx8mp_blk_ctrl_of_match[] = {
+	{
+		.compatible = "fsl,imx8mp-hsio-blk-ctrl",
+		.data = &imx8mp_hsio_blk_ctl_dev_data,
+	}, {
+		/* Sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, imx8m_blk_ctrl_of_match);
+
+static struct platform_driver imx8mp_blk_ctrl_driver = {
+	.probe = imx8mp_blk_ctrl_probe,
+	.remove = imx8mp_blk_ctrl_remove,
+	.driver = {
+		.name = "imx8mp-blk-ctrl",
+		.pm = &imx8mp_blk_ctrl_pm_ops,
+		.of_match_table = imx8mp_blk_ctrl_of_match,
+	},
+};
+module_platform_driver(imx8mp_blk_ctrl_driver);