[PATCH V8 3/4] soc: imx: Add generic blk-ctl driver

Peng Fan peng.fan at nxp.com
Wed Jul 7 02:56:52 PDT 2021


Hi Lucas,

> Subject: Re: [PATCH V8 3/4] soc: imx: Add generic blk-ctl driver

After rethinking about this driver, I think we still need
expose to pgc to touch the blk-ctl hardware bus handshake.

Currently we are assuming the blk-ctl probe will finish
the handshake. But there is another case, saying gpu.

----------------------------------------------------------------------->
GPU on(1)
          VPUMIX on (2)
                        GPU off(3)
        
Between GPU on/off, VPUMIX-BLK-CTL not done, so
vpumix pgc handshake not done. Then GPU off (3) will
trigger failed to command pgc, because the last pgc(vpu power on)
not finished.

I think this could be not avoided if we split the handshake into
blk-ctl driver. How do you think?

BTW: #linux-imx IRC moved to Libre.Chat     

> 
> Hi Peng,
> 
> Am Dienstag, dem 29.06.2021 um 15:29 +0800 schrieb Peng Fan (OSS):
> > From: Peng Fan <peng.fan at nxp.com>
> >
> > The i.MX8MM introduces an IP named BLK_CTL and usually is comprised of
> > some GPRs.
> >
> > The GPRs has some clock bits and reset bits, but here we take it as
> > virtual PDs, because of the clock and power domain A/B lock issue when
> > taking it as a clock controller.
> >
> > For some bits, it might be good to also make it as a reset controller,
> > but to i.MX8MM, we not add that support for now.
> >
> > Signed-off-by: Peng Fan <peng.fan at nxp.com>
> > ---
> >  drivers/soc/imx/Makefile  |   2 +-
> >  drivers/soc/imx/blk-ctl.c | 324
> > ++++++++++++++++++++++++++++++++++++++
> >  drivers/soc/imx/blk-ctl.h |  85 ++++++++++
> >  3 files changed, 410 insertions(+), 1 deletion(-)  create mode 100644
> > drivers/soc/imx/blk-ctl.c  create mode 100644
> > drivers/soc/imx/blk-ctl.h
> >
> > diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index
> > 078dc918f4f3..d3d2b49a386c 100644
> > --- a/drivers/soc/imx/Makefile
> > +++ b/drivers/soc/imx/Makefile
> > @@ -4,4 +4,4 @@ obj-$(CONFIG_ARCH_MXC) += soc-imx.o  endif
> >  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) += soc-imx8m.o blk-ctl.o
> > diff --git a/drivers/soc/imx/blk-ctl.c b/drivers/soc/imx/blk-ctl.c new
> > file mode 100644 index 000000000000..cec1884202e0
> > --- /dev/null
> > +++ b/drivers/soc/imx/blk-ctl.c
> > @@ -0,0 +1,324 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2021 NXP.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/err.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/pm_domain.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +#include "blk-ctl.h"
> > +
> > +static inline struct imx_blk_ctl_domain *to_imx_blk_ctl_pd(struct
> > +generic_pm_domain *genpd) {
> > +	return container_of(genpd, struct imx_blk_ctl_domain, genpd); }
> > +
> > +static int imx_blk_ctl_enable_hsk(struct device *dev) {
> > +	struct imx_blk_ctl *blk_ctl = dev_get_drvdata(dev);
> > +	const struct imx_blk_ctl_hw *hw = blk_ctl->dev_data->hw_hsk;
> > +	struct regmap *regmap = blk_ctl->regmap;
> > +	int ret;
> > +
> > +	if (hw->flags & IMX_BLK_CTL_PD_RESET) {
> > +		ret = regmap_update_bits(regmap, hw->rst_offset, hw->rst_mask,
> hw->rst_mask);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	ret = regmap_update_bits(regmap, hw->offset, hw->mask, hw->mask);
> > +
> > +	/* Wait for handshake */
> > +	udelay(5);
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_blk_ctl_power_on(struct generic_pm_domain *domain) {
> > +	struct imx_blk_ctl_domain *pd = to_imx_blk_ctl_pd(domain);
> > +	struct imx_blk_ctl *blk_ctl = pd->blk_ctl;
> > +	struct regmap *regmap = blk_ctl->regmap;
> > +	const struct imx_blk_ctl_hw *hw = &blk_ctl->dev_data->pds[pd->id];
> > +	int ret;
> > +
> > +	mutex_lock(&blk_ctl->lock);
> > +
> > +	ret = clk_bulk_prepare_enable(blk_ctl->num_clks, blk_ctl->clks);
> 
> I'm still not a fan of enabling all the clocks going into the blk-ctl to power
> up/down one specific domain. It's not really a problem with clocks, where the
> parents are always on, as the clock gate/ungate is pretty cheap, but as soon
> as you get to something like the display pixel clock, where the parent PLL may
> be shut down, the clock enable may easily be the most costly operation of this
> whole function, even if this specific clock isn't even needed for the domain in
> question.

We had an agreement to use bulk clks in the beginning :)
But I could look into use dedicated clk, but that requires new logic to map each
pd with needed clks.

> 
> > +	if (ret) {
> > +		mutex_unlock(&blk_ctl->lock);
> > +		return ret;
> > +	}
> > +
> > +	if (hw->flags & IMX_BLK_CTL_PD_HANDSHAKE) {
> > +		ret = imx_blk_ctl_enable_hsk(blk_ctl->dev);
> > +		if (ret)
> > +			dev_err(blk_ctl->dev, "Handshake failed when power on\n");
> > +
> > +		/* Expected, handshake already handle reset*/
> > +		goto disable_clk;
> > +	}
> > +
> > +	if (hw->flags & IMX_BLK_CTL_PD_RESET) {
> > +		ret = regmap_clear_bits(regmap, hw->rst_offset, hw->rst_mask);
> > +		if (ret)
> > +			goto disable_clk;
> > +
> > +		/* Wait for reset propagate */
> > +		udelay(5);
> > +
> > +		ret = regmap_update_bits(regmap, hw->rst_offset, hw->rst_mask,
> hw->rst_mask);
> > +		if (ret)
> > +			goto disable_clk;
> > +	}
> > +
> > +	ret = regmap_update_bits(regmap, hw->offset, hw->mask, hw->mask);
> >
> 
> I find this very hard to follow and reason about. Why do we even need those
> different paths for domains with or without the handshake?
> 
> Shouldn't it be enough to just be enough to do the following in all
> cases:
> 1. release sft reset
> 2. enable sft clock
> 3. wait a little for reset to propagate or ADB to ack power up

Reset flow is: assert reset, deassert reset, enable clk
Handshake flow: deassert reset, enable clk.

Per my previous test, use reset flow for handshake seems
not work. I could recall clearly.

> 
> > +disable_clk:
> > +	clk_bulk_disable_unprepare(blk_ctl->num_clks, blk_ctl->clks);
> > +
> > +	mutex_unlock(&blk_ctl->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_blk_ctl_power_off(struct generic_pm_domain *domain) {
> > +	struct imx_blk_ctl_domain *pd = to_imx_blk_ctl_pd(domain);
> > +	struct imx_blk_ctl *blk_ctl = pd->blk_ctl;
> > +	struct regmap *regmap = blk_ctl->regmap;
> > +	const struct imx_blk_ctl_hw *hw = &blk_ctl->dev_data->pds[pd->id];
> > +	int ret = 0;
> > +
> > +	mutex_lock(&blk_ctl->lock);
> > +
> > +	ret = clk_bulk_prepare_enable(blk_ctl->num_clks, blk_ctl->clks);
> > +	if (ret) {
> > +		mutex_unlock(&blk_ctl->lock);
> > +		return ret;
> > +	}
> > +
> > +	if (!(hw->flags & IMX_BLK_CTL_PD_HANDSHAKE)) {
> > +		ret = regmap_clear_bits(regmap, hw->offset, hw->mask);
> > +		if (ret)
> > +			goto disable_clk;
> > +
> > +		if (hw->flags & IMX_BLK_CTL_PD_RESET) {
> > +			ret = regmap_clear_bits(regmap, hw->rst_offset,
> hw->rst_mask);
> > +			if (ret)
> > +				goto disable_clk;
> > +		}
> > +	} else {
> > +		ret = imx_blk_ctl_enable_hsk(blk_ctl->dev);
> 
> Why would we need to enable the handshake again in the power DOWN
> path?
> The clock/reset bits should still be set from the power up. The power down
> should probably just be a no-op for the blk-ctl bus domains, to allow the
> proper ADB handshake in the PGC domain power-down.

I exported VPU-H1 as VPU-BUS for handshake, because they share
same bits.  But you raise a valid idea, I think I could drop VPU-BUS,
then just no-op here.

BTW: bus should be enabled when power down others.

> 
> > +		if (ret)
> > +			dev_err(blk_ctl->dev, "Handshake failed when power off\n");
> > +	}
> > +
> > +disable_clk:
> > +	clk_bulk_disable_unprepare(blk_ctl->num_clks, blk_ctl->clks);
> > +
> > +	mutex_unlock(&blk_ctl->lock);
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_blk_ctl_probe(struct platform_device *pdev) {
> > +	struct imx_blk_ctl_domain *domain = pdev->dev.platform_data;
> > +	struct imx_blk_ctl *blk_ctl = domain->blk_ctl;
> > +	struct generic_pm_domain *parent_genpd;
> > +	struct device *dev = &pdev->dev;
> > +	struct device *active_pd;
> > +	int ret;
> > +
> > +	pdev->dev.of_node = blk_ctl->dev->of_node;
> > +
> > +	if (domain->hw->active_pd_name) {
> > +		active_pd = dev_pm_domain_attach_by_name(dev,
> domain->hw->active_pd_name);
> > +		if (IS_ERR_OR_NULL(active_pd)) {
> > +			ret = PTR_ERR(active_pd) ? : -ENODATA;
> > +			pdev->dev.of_node = NULL;
> 
> This is extremely ugly. I think we should not even have separate platform
> drivers for the blk-ctl domains, there is just no reason for it. See below for
> more comments in that direction.
> 
> > +			return ret;
> > +		}
> > +
> > +		domain->active_pd = active_pd;
> > +	} else {
> > +		if (!blk_ctl->bus_domain) {
> > +			pdev->dev.of_node = NULL;
> > +			return -EPROBE_DEFER;
> > +		}
> > +	}
> > +
> > +	if (domain->hw->active_pd_name)
> > +		parent_genpd = pd_to_genpd(active_pd->pm_domain);
> > +	else
> > +		parent_genpd = blk_ctl->bus_domain;
> > +
> > +	if (pm_genpd_add_subdomain(parent_genpd, &domain->genpd)) {
> > +		dev_warn(dev, "failed to add subdomain: %s\n",
> domain->genpd.name);
> 
> I don't see where the dispmix_bus domain and clock is kept enabled. I would
> guess that the bus domain should be some kind of parent to the lcdif and mipi
> domains,

Yes. vpumix-blk-ctl bus domain is parent to vpu-g1/g2 and similar to others.

 as I don't think it would be okay to disable the bus clock, while any
> of the peripherals in the dispmix complex are still active. Am I missing
> something here?

You are right, bus clock should be always kept enable if any periphals in
dispmix is alive.

> 
> > +	} else {
> > +		mutex_lock(&blk_ctl->lock);
> > +		domain->hooked = true;
> > +		mutex_unlock(&blk_ctl->lock);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int imx_blk_ctl_remove(struct platform_device *pdev) {
> > +	struct imx_blk_ctl_domain *domain = pdev->dev.platform_data;
> > +	struct imx_blk_ctl *blk_ctl = domain->blk_ctl;
> > +	struct generic_pm_domain *parent_genpd;
> > +	struct device *active_pd;
> > +
> > +	if (domain->hw->active_pd_name)
> > +		parent_genpd = pd_to_genpd(active_pd->pm_domain);
> 
> This has probably never been tested. active_pd is undefined at this point, so
> will most likely lead to a kernel crash.

My bad. Indeed, remove not tested.

> > +	else
> > +		parent_genpd = blk_ctl->bus_domain;
> > +
> > +	pm_genpd_remove_subdomain(parent_genpd, &domain->genpd);
> > +
> > +	mutex_lock(&blk_ctl->lock);
> > +	domain->hooked = false;
> > +	mutex_unlock(&blk_ctl->lock);
> > +
> > +	if (domain->hw->active_pd_name)
> > +		dev_pm_domain_detach(domain->active_pd, false);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct platform_device_id imx_blk_ctl_id[] = {
> > +	{ "imx-vpumix-blk-ctl", },
> > +	{ "imx-dispmix-blk-ctl", },
> > +	{ },
> > +};
> > +
> > +static struct platform_driver imx_blk_ctl_driver = {
> > +	.driver = {
> > +		.name = "imx-blk-ctl",
> > +	},
> > +	.probe    = imx_blk_ctl_probe,
> > +	.remove   = imx_blk_ctl_remove,
> > +	.id_table = imx_blk_ctl_id,
> > +};
> > +builtin_platform_driver(imx_blk_ctl_driver)
> > +
> > +static struct generic_pm_domain *imx_blk_ctl_genpd_xlate(struct
> of_phandle_args *genpdspec,
> > +							 void *data)
> > +{
> > +	struct genpd_onecell_data *genpd_data = data;
> > +	unsigned int idx = genpdspec->args[0];
> > +	struct imx_blk_ctl_domain *domain;
> > +	struct generic_pm_domain *genpd = ERR_PTR(-EPROBE_DEFER);
> > +
> > +	if (genpdspec->args_count != 1)
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	if (idx >= genpd_data->num_domains)
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	if (!genpd_data->domains[idx])
> > +		return ERR_PTR(-ENOENT);
> > +
> > +	domain = to_imx_blk_ctl_pd(genpd_data->domains[idx]);
> > +
> > +	mutex_lock(&domain->blk_ctl->lock);
> > +	if (domain->hooked)
> > +		genpd = genpd_data->domains[idx];
> > +	mutex_unlock(&domain->blk_ctl->lock);
> > +
> > +	return genpd;
> > +}
> > +
> > +int imx_blk_ctl_register(struct device *dev) {
> > +	struct imx_blk_ctl *blk_ctl = dev_get_drvdata(dev);
> > +	const struct imx_blk_ctl_dev_data *dev_data = blk_ctl->dev_data;
> > +	int num = dev_data->pds_num;
> > +	struct imx_blk_ctl_domain *domain;
> > +	struct generic_pm_domain *genpd;
> > +	struct platform_device *pd_pdev;
> > +	int domain_index;
> > +	int i, ret;
> > +
> > +	blk_ctl->onecell_data.num_domains = num;
> > +	blk_ctl->onecell_data.xlate = imx_blk_ctl_genpd_xlate;
> > +	blk_ctl->onecell_data.domains = devm_kcalloc(dev, num, sizeof(struct
> generic_pm_domain *),
> > +						     GFP_KERNEL);
> > +	if (!blk_ctl->onecell_data.domains)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < num; i++) {
> > +		domain_index = dev_data->pds[i].id;
> > +		if (domain_index >= num) {
> > +			dev_warn(dev, "Domain index %d is out of bounds\n",
> domain_index);
> > +			continue;
> > +		}
> > +
> > +		domain = devm_kzalloc(dev, sizeof(struct imx_blk_ctl_domain),
> GFP_KERNEL);
> > +		if (!domain)
> > +			goto error;
> > +
> > +		pd_pdev = platform_device_alloc(dev_data->name, domain_index);
> > +		if (!pd_pdev) {
> > +			dev_err(dev, "Failed to allocate platform device\n");
> > +			goto error;
> > +		}
> 
> We don't need a full blow platform device and a driver for the individual
> domains. The only point where we need the device is to attach the parent
> PGC power domains and for this we only need a device.
> 
> So we could either have a dummy device for this usage in the domain or we
> could even reuse the device in the genpd, which is initialized in
> pm_genpd_init.
> 
> Now while I think about it... genpd_dev_pm_attach_by_name already
> allocates the virtual dummy device. I don't think we need to do anything here
> on our own. Just get rid of the platform device and driver.

ok, let me rethink about it. If you have chance to be on IRC, I could talk
with you later if I come about new implementation.

> 
> > +
> > +		pd_pdev->dev.platform_data = domain;
> > +
> > +		domain->blk_ctl = blk_ctl;
> > +		domain->hw = &dev_data->pds[i];
> > +		domain->id = domain_index;
> > +		domain->genpd.name = dev_data->pds[i].name;
> > +		domain->genpd.power_off = imx_blk_ctl_power_off;
> > +		domain->genpd.power_on = imx_blk_ctl_power_on;
> > +		domain->dev = &pd_pdev->dev;
> > +		domain->hooked = false;
> > +
> > +		ret = pm_genpd_init(&domain->genpd, NULL, true);
> > +		pd_pdev->dev.parent = dev;
> > +
> > +		if (domain->hw->flags & IMX_BLK_CTL_PD_HANDSHAKE)
> > +			blk_ctl->bus_domain = &domain->genpd;
> > +
> > +		ret = platform_device_add(pd_pdev);
> > +		if (ret) {
> > +			platform_device_put(pd_pdev);
> > +			goto error;
> > +		}
> 
> There is really no need for the complexity with the hooked property (the
> mutex around the read/write access still doesn't make it properly thread safe)
> and the blk-ctl domain probe/remove calls. Just handle everything within this
> loop. This would make the driver a whole lot more easy to follow.

I see.

> 
> > +		blk_ctl->onecell_data.domains[i] = &domain->genpd;
> > +	}
> > +
> > +	return of_genpd_add_provider_onecell(dev->of_node,
> > +&blk_ctl->onecell_data);
> > +
> > +error:
> > +	for (; i >= 0; i--) {
> > +		genpd = blk_ctl->onecell_data.domains[i];
> > +		if (!genpd)
> > +			continue;
> > +		domain = to_imx_blk_ctl_pd(genpd);
> > +		if (domain->dev)
> > +			platform_device_put(to_platform_device(domain->dev));
> > +	}
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(imx_blk_ctl_register);
> > +
> > +const struct dev_pm_ops imx_blk_ctl_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > +pm_runtime_force_resume) };
> EXPORT_SYMBOL_GPL(imx_blk_ctl_pm_ops);
> 
> This code is linked into the same module as the platform driver using it. So
> there is no need to export those symbols and expose them to the whole
> kernel.

Sure. Fix in next version.

> 
> > diff --git a/drivers/soc/imx/blk-ctl.h b/drivers/soc/imx/blk-ctl.h new
> > file mode 100644 index 000000000000..6780d00ec8c5
> > --- /dev/null
> > +++ b/drivers/soc/imx/blk-ctl.h
> > @@ -0,0 +1,85 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __SOC_IMX_BLK_CTL_H
> > +#define __SOC_IMX_BLK_CTL_H
> > +
> > +enum imx_blk_ctl_pd_type {
> > +	BLK_CTL_PD,
> > +};
> > +
> > +struct imx_blk_ctl_hw {
> > +	int type;
> 
> Initialized, but unused.
> 
> > +	char *name;
> > +	char *active_pd_name;
> > +	u32 offset;
> > +	u32 mask;
> 
> offset and mask are way too generic names. I had to spend some time reading
> the driver to find out that those are the clock enable bits.
> This should be clear from the naming.
> 
> > +	u32 flags;
> > +	u32 id;
> > +	u32 rst_offset;
> > +	u32 rst_mask;
> > +	u32 errata;
> 
> Unused.
> 
> > +};
> > +
> > +struct imx_blk_ctl_domain {
> > +	struct generic_pm_domain genpd;
> > +	struct device *active_pd;
> > +	struct imx_blk_ctl *blk_ctl;
> > +	struct imx_blk_ctl_hw *hw;
> > +	struct device *dev;
> > +	bool hooked;
> > +	u32 id;
> 
> There are already a lot of pointers between the different structures.
> Why do we need those id properties? You should be able to get to all needed
> information by chasing pointers, instead of  indexing into arrays. Really the
> only point where a id->domain mapping is done should be the xlate function.

Will simplify this.

> 
> > +};
> > +
> > +struct imx_blk_ctl_dev_data {
> > +	struct regmap_config config;
> > +	struct imx_blk_ctl_hw *pds;
> > +	struct imx_blk_ctl_hw *hw_hsk;
> > +	u32 pds_num;
> > +	u32 max_num;
> > +	char *name;
> > +};
> > +
> > +struct imx_blk_ctl {
> > +	struct device *dev;
> > +	struct regmap *regmap;
> > +	struct genpd_onecell_data onecell_data;
> > +	const struct imx_blk_ctl_dev_data *dev_data;
> > +	struct clk_bulk_data *clks;
> > +	u32 num_clks;
> > +	struct generic_pm_domain *bus_domain;
> 
> There should be nothing special about the bus domain at all. Right now this
> permeates through the whole driver that the bus domain is somehow special.
> It should just be a parent domain of the other domains, or kept alive via some
> other appropriate means.

Ok, will try to make it a generic parent domain.

> 
> > +
> > +	struct mutex lock;
> 
> This mutex is only used in the common blk-ctl code, but is initialized in
> imx8mm_blk_ctrl_probe. This seems very inconsistent. I would have expected
> this mutex to be initialized in imx_blk_ctl_register. However, once you get rid
> of the hooked and bus domain magic, this mutex may as well be per-domain,
> at which point I think you don't even need the mutex, as the genpd locking
> should be enough then.

Let me rewrite the code and see how it goes.

Thanks,
Peng.

> 
> > +};
> > +
> > +#define IMX_BLK_CTL(_type, _name, _active_pd, _id, _offset, _mask,
> _rst_offset, _rst_mask,	\
> > +		    _flags, _errata)								\
> > +	{											\
> > +		.type = _type,									\
> > +		.name = _name,									\
> > +		.active_pd_name = _active_pd,							\
> > +		.id = _id,									\
> > +		.offset = _offset,								\
> > +		.mask = _mask,									\
> > +		.flags = _flags,								\
> > +		.rst_offset = _rst_offset,							\
> > +		.rst_mask = _rst_mask,								\
> > +		.errata = _errata,								\
> > +	}
> > +
> > +#define IMX_BLK_CTL_PD(_name, _active_pd, _id, _offset, _mask,
> _rst_offset, _rst_mask, _flags)	\
> > +	IMX_BLK_CTL(BLK_CTL_PD, _name, _active_pd, _id, _offset, _mask,
> _rst_offset,		\
> > +		    _rst_mask, _flags, 0)
> > +
> > +#define IMX_BLK_CTL_PD_ERRATA(_name, _active_pd, _id, _offset, _mask,
> _rst_offset, _rst_mask,	\
> > +			      _flags, _errata)							\
> > +	IMX_BLK_CTL(BLK_CTL_PD, _name, _active_pd, _id, _offset, _mask,
> _rst_offset,		\
> > +		    _rst_mask, _flags, _errata)
> > +
> > +int imx_blk_ctl_register(struct device *dev);
> > +
> > +#define IMX_BLK_CTL_PD_HANDSHAKE	BIT(0)
> > +#define IMX_BLK_CTL_PD_RESET		BIT(1)
> > +#define IMX_BLK_CTL_PD_BUS		BIT(2)
> > +
> > +const extern struct dev_pm_ops imx_blk_ctl_pm_ops;
> > +
> > +#endif
> 



More information about the linux-arm-kernel mailing list