diff mbox series

[v3,2/6] PCI: brcmstb: Add control of EP voltage regulators

Message ID 20210326191906.43567-3-jim2101024@gmail.com
State New
Headers show
Series PCI: brcmstb: add EP regulators and panic handler | expand

Commit Message

Jim Quinlan March 26, 2021, 7:19 p.m. UTC
Control of EP regulators by the RC is needed because of the chicken-and-egg
situation: although the regulator is "owned" by the EP and would be best
handled on its driver, the EP cannot be discovered and probed unless its
regulator is already turned on.

Signed-off-by: Jim Quinlan <jim2101024@gmail.com>
---
 drivers/pci/controller/pcie-brcmstb.c | 90 ++++++++++++++++++++++++++-
 1 file changed, 87 insertions(+), 3 deletions(-)

Comments

Bjorn Helgaas March 26, 2021, 8:11 p.m. UTC | #1
On Fri, Mar 26, 2021 at 03:19:00PM -0400, Jim Quinlan wrote:
> Control of EP regulators by the RC is needed because of the chicken-and-egg

Can you expand "EP"?  Not sure if this refers to "endpoint" or
something else.

If this refers to a device in a slot, I guess it isn't necessarily a
PCIe *endpoint*; it could also be a switch upstream port.

> situation: although the regulator is "owned" by the EP and would be best
> handled on its driver, the EP cannot be discovered and probed unless its
> regulator is already turned on.
> 
> Signed-off-by: Jim Quinlan <jim2101024@gmail.com>
> ---
>  drivers/pci/controller/pcie-brcmstb.c | 90 ++++++++++++++++++++++++++-
>  1 file changed, 87 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c
> index e330e6811f0b..b76ec7d9af32 100644
> --- a/drivers/pci/controller/pcie-brcmstb.c
> +++ b/drivers/pci/controller/pcie-brcmstb.c
> @@ -24,6 +24,7 @@
>  #include <linux/pci.h>
>  #include <linux/pci-ecam.h>
>  #include <linux/printk.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/reset.h>
>  #include <linux/sizes.h>
>  #include <linux/slab.h>
> @@ -169,6 +170,7 @@
>  #define SSC_STATUS_SSC_MASK		0x400
>  #define SSC_STATUS_PLL_LOCK_MASK	0x800
>  #define PCIE_BRCM_MAX_MEMC		3
> +#define PCIE_BRCM_MAX_EP_REGULATORS	4
>  
>  #define IDX_ADDR(pcie)			(pcie->reg_offsets[EXT_CFG_INDEX])
>  #define DATA_ADDR(pcie)			(pcie->reg_offsets[EXT_CFG_DATA])
> @@ -295,8 +297,27 @@ struct brcm_pcie {
>  	u32			hw_rev;
>  	void			(*perst_set)(struct brcm_pcie *pcie, u32 val);
>  	void			(*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
> +	struct regulator_bulk_data supplies[PCIE_BRCM_MAX_EP_REGULATORS];
> +	unsigned int		num_supplies;
>  };
>  
> +static int brcm_set_regulators(struct brcm_pcie *pcie, bool on)
> +{
> +	struct device *dev = pcie->dev;
> +	int ret;
> +
> +	if (!pcie->num_supplies)
> +		return 0;
> +	if (on)
> +		ret = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
> +	else
> +		ret = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
> +	if (ret)
> +		dev_err(dev, "failed to %s EP regulators\n",
> +			on ? "enable" : "disable");
> +	return ret;
> +}
> +
>  /*
>   * This is to convert the size of the inbound "BAR" region to the
>   * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE
> @@ -1141,16 +1162,63 @@ static void brcm_pcie_turn_off(struct brcm_pcie *pcie)
>  	pcie->bridge_sw_init_set(pcie, 1);
>  }
>  
> +static int brcm_pcie_get_regulators(struct brcm_pcie *pcie)
> +{
> +	struct device_node *child, *parent = pcie->np;
> +	const unsigned int max_name_len = 64 + 4;
> +	struct property *pp;
> +
> +	/* Look for regulator supply property in the EP device subnodes */
> +	for_each_available_child_of_node(parent, child) {
> +		/*
> +		 * Do a santiy test to ensure that this is an EP node

s/santiy/sanity/

> +		 * (e.g. node name: "pci-ep@0,0").  The slot number
> +		 * should always be 0 as our controller only has a single
> +		 * port.
> +		 */
> +		const char *p = strstr(child->full_name, "@0");
> +
> +		if (!p || (p[2] && p[2] != ','))
> +			continue;
> +
> +		/* Now look for regulator supply properties */
> +		for_each_property_of_node(child, pp) {
> +			int i, n = strnlen(pp->name, max_name_len);
> +
> +			if (n <= 7 || strncmp("-supply", &pp->name[n - 7], 7))
> +				continue;
> +
> +			/* Make sure this is not a duplicate */
> +			for (i = 0; i < pcie->num_supplies; i++)
> +				if (strncmp(pcie->supplies[i].supply,
> +					    pp->name, max_name_len) == 0)
> +					continue;
> +
> +			if (pcie->num_supplies < PCIE_BRCM_MAX_EP_REGULATORS)
> +				pcie->supplies[pcie->num_supplies++].supply = pp->name;
> +			else
> +				dev_warn(pcie->dev, "No room for EP supply %s\n",
> +					 pp->name);
> +		}
> +	}
> +	/*
> +	 * Get the regulators that the EP devices require.  We cannot use
> +	 * pcie->dev as the device argument in regulator_bulk_get() since
> +	 * it will not find the regulators.  Instead, use NULL and the
> +	 * regulators are looked up by their name.

The comment doesn't explain the interesting part of why you need NULL
instead of "pcie->dev".  I assume it has something to do with the
platform topology and its DT description.

This appears to be the only instance in the whole kernel of a use of
regulator_bulk_get() or devm_regulator_bulk_get() with NULL.  That
definitely warrants a comment, so I'm glad you've got something here.

The regulator_bulk_get() function comment doesn't mention the
possibility of "dev == NULL", although regulator_dev_lookup(),
create_regulator(), device_link_add() do check for it being NULL, so I
guess it's not a surprise.  We may call dev_err(NULL), which I think
will *work* without crashing even though it will look like a mistake
on the output.

> +	 */
> +	return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);

devm_regulator_bulk_get()?

> +}
> +
>  static int brcm_pcie_suspend(struct device *dev)
>  {
>  	struct brcm_pcie *pcie = dev_get_drvdata(dev);
> -	int ret;
>  
>  	brcm_pcie_turn_off(pcie);
> -	ret = brcm_phy_stop(pcie);
> +	brcm_phy_stop(pcie);

If we no longer care whether brcm_phy_stop() returns an error, nobody
looks at the return value and it could be void.

>  	clk_disable_unprepare(pcie->clk);
>  
> -	return ret;
> +	return brcm_set_regulators(pcie, false);
>  }
>  
>  static int brcm_pcie_resume(struct device *dev)
> @@ -1163,6 +1231,10 @@ static int brcm_pcie_resume(struct device *dev)
>  	base = pcie->base;
>  	clk_prepare_enable(pcie->clk);
>  
> +	ret = brcm_set_regulators(pcie, true);
> +	if (ret)
> +		return ret;
> +
>  	ret = brcm_phy_start(pcie);
>  	if (ret)
>  		goto err;
> @@ -1199,6 +1271,8 @@ static void __brcm_pcie_remove(struct brcm_pcie *pcie)
>  	brcm_phy_stop(pcie);
>  	reset_control_assert(pcie->rescal);
>  	clk_disable_unprepare(pcie->clk);
> +	brcm_set_regulators(pcie, false);
> +	regulator_bulk_free(pcie->num_supplies, pcie->supplies);
>  }
>  
>  static int brcm_pcie_remove(struct platform_device *pdev)
> @@ -1289,6 +1363,16 @@ static int brcm_pcie_probe(struct platform_device *pdev)
>  		return ret;
>  	}
>  
> +	ret = brcm_pcie_get_regulators(pcie);
> +	if (ret) {
> +		dev_err(pcie->dev, "failed to get regulators (err=%d)\n", ret);
> +		goto fail;
> +	}
> +
> +	ret = brcm_set_regulators(pcie, true);
> +	if (ret)
> +		goto fail;
> +
>  	ret = brcm_pcie_setup(pcie);
>  	if (ret)
>  		goto fail;
> -- 
> 2.17.1
>
Jim Quinlan March 27, 2021, 10:20 p.m. UTC | #2
On Fri, Mar 26, 2021 at 4:11 PM Bjorn Helgaas <helgaas@kernel.org> wrote:
>
> On Fri, Mar 26, 2021 at 03:19:00PM -0400, Jim Quinlan wrote:
> > Control of EP regulators by the RC is needed because of the chicken-and-egg
>
> Can you expand "EP"?  Not sure if this refers to "endpoint" or
> something else.
Yes I meant "endpoint" -- I will expand it.
>
> If this refers to a device in a slot, I guess it isn't necessarily aWe only support this feature for endpoint devices; it they hav
> PCIe *endpoint*; it could also be a switch upstream port.
True; to be precise I mean the device directly connected to the single RC port.
>
> > situation: although the regulator is "owned" by the EP and would be best
> > handled on its driver, the EP cannot be discovered and probed unless its
> > regulator is already turned on.
> >
> > Signed-off-by: Jim Quinlan <jim2101024@gmail.com>
> > ---
> >  drivers/pci/controller/pcie-brcmstb.c | 90 ++++++++++++++++++++++++++-
> >  1 file changed, 87 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c
> > index e330e6811f0b..b76ec7d9af32 100644
> > --- a/drivers/pci/controller/pcie-brcmstb.c
> > +++ b/drivers/pci/controller/pcie-brcmstb.c
> > @@ -24,6 +24,7 @@
> >  #include <linux/pci.h>
> >  #include <linux/pci-ecam.h>
> >  #include <linux/printk.h>
> > +#include <linux/regulator/consumer.h>
> >  #include <linux/reset.h>
> >  #include <linux/sizes.h>
> >  #include <linux/slab.h>
> > @@ -169,6 +170,7 @@
> >  #define SSC_STATUS_SSC_MASK          0x400
> >  #define SSC_STATUS_PLL_LOCK_MASK     0x800
> >  #define PCIE_BRCM_MAX_MEMC           3
> > +#define PCIE_BRCM_MAX_EP_REGULATORS  4
> >
> >  #define IDX_ADDR(pcie)                       (pcie->reg_offsets[EXT_CFG_INDEX])
> >  #define DATA_ADDR(pcie)                      (pcie->reg_offsets[EXT_CFG_DATA])
> > @@ -295,8 +297,27 @@ struct brcm_pcie {
> >       u32                     hw_rev;
> >       void                    (*perst_set)(struct brcm_pcie *pcie, u32 val);
> >       void                    (*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
> > +     struct regulator_bulk_data supplies[PCIE_BRCM_MAX_EP_REGULATORS];
> > +     unsigned int            num_supplies;
> >  };
> >
> > +static int brcm_set_regulators(struct brcm_pcie *pcie, bool on)
> > +{
> > +     struct device *dev = pcie->dev;
> > +     int ret;
> > +
> > +     if (!pcie->num_supplies)
> > +             return 0;
> > +     if (on)
> > +             ret = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
> > +     else
> > +             ret = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
> > +     if (ret)
> > +             dev_err(dev, "failed to %s EP regulators\n",
> > +                     on ? "enable" : "disable");
> > +     return ret;
> > +}
> > +
> >  /*
> >   * This is to convert the size of the inbound "BAR" region to the
> >   * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE
> > @@ -1141,16 +1162,63 @@ static void brcm_pcie_turn_off(struct brcm_pcie *pcie)
> >       pcie->bridge_sw_init_set(pcie, 1);
> >  }
> >
> > +static int brcm_pcie_get_regulators(struct brcm_pcie *pcie)
> > +{
> > +     struct device_node *child, *parent = pcie->np;
> > +     const unsigned int max_name_len = 64 + 4;
> > +     struct property *pp;
> > +
> > +     /* Look for regulator supply property in the EP device subnodes */
> > +     for_each_available_child_of_node(parent, child) {
> > +             /*
> > +              * Do a santiy test to ensure that this is an EP node
>
> s/santiy/sanity/
>
> > +              * (e.g. node name: "pci-ep@0,0").  The slot number
> > +              * should always be 0 as our controller only has a single
> > +              * port.
> > +              */
> > +             const char *p = strstr(child->full_name, "@0");
> > +
> > +             if (!p || (p[2] && p[2] != ','))
> > +                     continue;
> > +
> > +             /* Now look for regulator supply properties */
> > +             for_each_property_of_node(child, pp) {
> > +                     int i, n = strnlen(pp->name, max_name_len);
> > +
> > +                     if (n <= 7 || strncmp("-supply", &pp->name[n - 7], 7))
> > +                             continue;
> > +
> > +                     /* Make sure this is not a duplicate */
> > +                     for (i = 0; i < pcie->num_supplies; i++)
> > +                             if (strncmp(pcie->supplies[i].supply,
> > +                                         pp->name, max_name_len) == 0)
> > +                                     continue;
> > +
> > +                     if (pcie->num_supplies < PCIE_BRCM_MAX_EP_REGULATORS)
> > +                             pcie->supplies[pcie->num_supplies++].supply = pp->name;
> > +                     else
> > +                             dev_warn(pcie->dev, "No room for EP supply %s\n",
> > +                                      pp->name);
> > +             }
> > +     }
> > +     /*
> > +      * Get the regulators that the EP devices require.  We cannot use
> > +      * pcie->dev as the device argument in regulator_bulk_get() since
> > +      * it will not find the regulators.  Instead, use NULL and the
> > +      * regulators are looked up by their name.
>
> The comment doesn't explain the interesting part of why you need NULL
> instead of "pcie->dev".  I assume it has something to do with the
> platform topology and its DT description.
>
> This appears to be the only instance in the whole kernel of a use of
> regulator_bulk_get() or devm_regulator_bulk_get() with NULL.  That
> definitely warrants a comment, so I'm glad you've got something here.
>
> The regulator_bulk_get() function comment doesn't mention the
> possibility of "dev == NULL", although regulator_dev_lookup(),
> create_regulator(), device_link_add() do check for it being NULL, so I
> guess it's not a surprise.  We may call dev_err(NULL), which I thinkWe only support this feature for endpoint devices; it they hav
> will *work* without crashing even though it will look like a mistake
> on the output.
Folks wanted me to put the "supply" in the endpoint subnode.  After
looking at the regulator code I assumed that using the pcie->dev in
this call would not work as the supply property is not in its DT node.
Turns out it works fine; I will fix it.
>
> > +      */
> > +     return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);
>
> devm_regulator_bulk_get()?
Yep.
>
> > +}
> > +
> >  static int brcm_pcie_suspend(struct device *dev)
> >  {
> >       struct brcm_pcie *pcie = dev_get_drvdata(dev);
> > -     int ret;
> >
> >       brcm_pcie_turn_off(pcie);
> > -     ret = brcm_phy_stop(pcie);
> > +     brcm_phy_stop(pcie);We only support this feature for endpoint devices; it they hav
>
> If we no longer care whether brcm_phy_stop() returns an error, nobody
> looks at the return value and it could be void.
Will fix.

Thanks,
Jim Quinlan
Broadcom STB
>
> >       clk_disable_unprepare(pcie->clk);
> >
> > -     return ret;
> > +     return brcm_set_regulators(pcie, false);
> >  }
> >
> >  static int brcm_pcie_resume(struct device *dev)
> > @@ -1163,6 +1231,10 @@ static int brcm_pcie_resume(struct device *dev)
> >       base = pcie->base;
> >       clk_prepare_enable(pcie->clk);
> >
> > +     ret = brcm_set_regulators(pcie, true);
> > +     if (ret)
> > +             return ret;
> > +
> >       ret = brcm_phy_start(pcie);
> >       if (ret)
> >               goto err;
> > @@ -1199,6 +1271,8 @@ static void __brcm_pcie_remove(struct brcm_pcie *pcie)
> >       brcm_phy_stop(pcie);
> >       reset_control_assert(pcie->rescal);
> >       clk_disable_unprepare(pcie->clk);
> > +     brcm_set_regulators(pcie, false);
> > +     regulator_bulk_free(pcie->num_supplies, pcie->supplies);
> >  }
> >
> >  static int brcm_pcie_remove(struct platform_device *pdev)
> > @@ -1289,6 +1363,16 @@ static int brcm_pcie_probe(struct platform_device *pdev)
> >               return ret;
> >       }
> >
> > +     ret = brcm_pcie_get_regulators(pcie);
> > +     if (ret) {
> > +             dev_err(pcie->dev, "failed to get regulators (err=%d)\n", ret);
> > +             goto fail;
> > +     }
> > +
> > +     ret = brcm_set_regulators(pcie, true);
> > +     if (ret)
> > +             goto fail;
> > +
> >       ret = brcm_pcie_setup(pcie);
> >       if (ret)
> >               goto fail;
> > --
> > 2.17.1
> >
Mark Brown March 29, 2021, 4:19 p.m. UTC | #3
On Fri, Mar 26, 2021 at 03:19:00PM -0400, Jim Quinlan wrote:

> Control of EP regulators by the RC is needed because of the chicken-and-egg
> situation: although the regulator is "owned" by the EP and would be best
> handled on its driver, the EP cannot be discovered and probed unless its
> regulator is already turned on.

Ideally the driver core would have a way for buses to register devices
pre physical enumeration and give drivers a callback that could be used
to do whatever is needed to trigger enumeration, letting the bus then
match newly physically enumerated devices with the software enumerated
struct devices.  Soundwire has something in this area for a bus level
solution.
Mark Brown March 29, 2021, 4:25 p.m. UTC | #4
On Fri, Mar 26, 2021 at 03:19:00PM -0400, Jim Quinlan wrote:

> +		/* Now look for regulator supply properties */
> +		for_each_property_of_node(child, pp) {
> +			int i, n = strnlen(pp->name, max_name_len);
> +
> +			if (n <= 7 || strncmp("-supply", &pp->name[n - 7], 7))
> +				continue;

Here you are figuring out a device local supply name...

> +	/*
> +	 * Get the regulators that the EP devices require.  We cannot use
> +	 * pcie->dev as the device argument in regulator_bulk_get() since
> +	 * it will not find the regulators.  Instead, use NULL and the
> +	 * regulators are looked up by their name.
> +	 */
> +	return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);

...and here you are trying to look up that device local name in the
global namespace.  That's not going to work well, the global names that
supplies are labelled with may be completely different to what the chip
designer called them and there could easily be naming collisions between
different chips.
Jim Quinlan March 29, 2021, 4:39 p.m. UTC | #5
On Mon, Mar 29, 2021 at 12:25 PM Mark Brown <broonie@kernel.org>
w./lib/python3.6/site-packages/dtschema/schemasrote:
>
> On Fri, Mar 26, 2021 at 03:19:00PM -0400, Jim Quinlan wrote:
>
> > +             /* Now look for regulator supply properties */
> > +             for_each_property_of_node(child, pp) {
> > +                     int i, n = strnlen(pp->name, max_name_len);
> > +
> > +                     if (n <= 7 || strncmp("-supply", &pp->name[n - 7], 7))
> > +                             continue;
>
> Here you are figuring out a device local supply name...
>
> > +     /*
> > +      * Get the regulators that the EP devianswerces require.  We cannot use
> > +      * pcie->dev as the device argument in regulator_bulk_get() since
> > +      * it will not find the regulators.  Instead, use NULL and the
> > +      * regulators are looked up by their name.
> > +      */
> > +     return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);
>
> ...and here you are trying to look up that device local name in the
> global namespace.  That's not going to work well, the global names that
> supplies are labelled with may be completely different to what the chip
> designer called them and there could easily be naming collisions between
> different chips.

Hello Mark,
I am re-submitting this pullreq using
"devm_regulator_bulk_get(pcie->dev, ...)"; is your concern about the
NULL for the device and if so does this fix it?  If not, what do you
suggest that I do?
Thanks,
Jim
Mark Brown March 29, 2021, 5:16 p.m. UTC | #6
On Mon, Mar 29, 2021 at 12:39:50PM -0400, Jim Quinlan wrote:
> On Mon, Mar 29, 2021 at 12:25 PM Mark Brown <broonie@kernel.org>

> > Here you are figuring out a device local supply name...

> > > +     /*
> > > +      * Get the regulators that the EP devianswerces require.  We cannot use
> > > +      * pcie->dev as the device argument in regulator_bulk_get() since
> > > +      * it will not find the regulators.  Instead, use NULL and the
> > > +      * regulators are looked up by their name.
> > > +      */
> > > +     return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);

> > ...and here you are trying to look up that device local name in the
> > global namespace.  That's not going to work well, the global names that
> > supplies are labelled with may be completely different to what the chip
> > designer called them and there could easily be naming collisions between
> > different chips.

> "devm_regulator_bulk_get(pcie->dev, ...)"; is your concern about the
> NULL for the device and if so does this fix it?  If not, what do you
> suggest that I do?

If you use the struct device for the PCIe controller then that's going
to first try the PCIe controller then the global namespace so you still
have all the same problems.  You really need to use the struct device
for the device that is being supplied not some random other struct
device you happened to find in the system.

As I said in my earlier reply I think either the driver core or PCI
needs something like Soundwire has which lets it create struct devices
for things that have been enumerated via software but not enumerated by
hardware and a callback or something which lets those devices take
whatever steps are needed to trigger probe.
Jim Quinlan March 29, 2021, 7:48 p.m. UTC | #7
/* Pmap_idx to avs pmap number */
    const uint8_t pmap_idx_to_avs_id[20];

On Mon, Mar 29, 2021 at 1:16 PM Mark Brown <broonie@kernel.org> wrote:
>
> On Mon, Mar 29, 2021 at 12:39:50PM -0400, Jim Quinlan wrote:
> > On Mon, Mar 29, 2021 at 12:25 PM Mark Brown <broonie@kernel.org>
>
> > > Here you are figuring out a device local supply name...
>
> > > > +     /*
> > > > +      * Get the regulators that the EP devianswerces require.  We cannot use
> > > > +      * pcie->dev as the device argument in regulator_bulk_get() since
> > > > +      * it will not find the regulators.  Instead, use NULL and the
> > > > +      * regulators are looked up by their name.
> > > > +      */
> > > > +     return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);
>
> > > ...and here you are trying to look up that device local name in the
> > > global namespace.  That's not going to work well, the global names that
> > > supplies are labelled with may be completely different to what the chip
> > > designer called them and there could easily be naming collisions between
> > > different chips.
>
> > "devm_regulator_bulk_get(pcie->dev, ...)"; is your concern about the
> > NULL for the device and if so does this fix it?  If not, what do you
> > suggest that I do?
>
> If you use the struct device for the PCIe controller then that's going
> to first try the PCIe controller then the global namespace so you still
> have all the same problems.  You really need to use the struct device
> for the device that is being supplied not some random other struct
> device you happened to find in the system.
Hello Mark,
I'm not concerned about a namespace collision and I don't think you
should be concerned either.  First, this driver is for Broadcom STB
PCIe chips and boards, and we also deliver the DT to the customers.
We typically do not have any other regulators in the DT besides the
ones I am proposing.  For example, the 7216 SOC DT has 0 other
regulators -- no namespace collision possible.  Our DT-generating
scripts also flag namespace issues.  I admit that this driver is also
used by RPi chips, but I can easily exclude this feature from the RPI
if Nicolas has any objection.

Further, if you want, I can restrict the search to the two regulators
I am proposing to add to pci-bus.yaml:  "vpcie12v-supply" and
"vpcie3v3-supply".

Is the above enough to alleviate your concerns about global namespace collision?

>
> As I said in my earlier reply I think either the driver core or PCI
> needs something like Soundwire has which lets it create struct devices
> for things that have been enumerated via software but not enumerated by
> hardware and a callback or something which lets those devices take
> whatever steps are needed to trigger probe.

Can you please elaborate this further and in detail?  This sounds like
a decent-size undertaking, and the last time I followed a review
suggestion that was similar in spirit to this, the pullreq was
ironically NAKed by the person who suggested it.  Do you really think
it is necessary to construct such a subsystem just to avoid the  very
remote possibility of a namespace collision which is our (Broadcom
STB) responsibility and consequence to avoid?

Regards,
Jim Quinlan
Broadcom STB
Mark Brown March 29, 2021, 8:45 p.m. UTC | #8
On Mon, Mar 29, 2021 at 03:48:46PM -0400, Jim Quinlan wrote:

> I'm not concerned about a namespace collision and I don't think you
> should be concerned either.  First, this driver is for Broadcom STB
> PCIe chips and boards, and we also deliver the DT to the customers.
> We typically do not have any other regulators in the DT besides the
> ones I am proposing.  For example, the 7216 SOC DT has 0 other

You may not describe these regulators in the DT but you must have other
regulators in your system, most devices need power to operate.  In any
case "this works for me with my DT on my system and nobody will ever
change our reference design" isn't really a great approach, frankly it's
not a claim I entirely believe and even if it turns out to be true for
your systems if we establish this as being how regulators work for
soldered down PCI devices everyone else is going to want to do the same
thing, most likely making the same claims for how much control they have
over the systems things will run on.

> regulators -- no namespace collision possible.  Our DT-generating
> scripts also flag namespace issues.  I admit that this driver is also
> used by RPi chips, but I can easily exclude this feature from the RPI
> if Nicolas has any objection.

That's certainly an issue, obviously the RPI is the sort of system where
I'd imagine this would be particularly useful.

> Further, if you want, I can restrict the search to the two regulators
> I am proposing to add to pci-bus.yaml:  "vpcie12v-supply" and
> "vpcie3v3-supply".

No, that doesn't help - what happens if someone uses separate regulators
for different PCI devices?  The reason the supplies are device namespaced
is that each device can look up it's own supplies and label them how it
wants without reference to anything else on the board.  Alternatively
what happens if some device has another supply it needs to power on (eg,
something that wants a clean LDO output for analogue use)?

> Is the above enough to alleviate your concerns about global namespace collision?

Not really.  TBH it looks like this driver is keeping the regulators
enabled all the time except for suspend and resume anyway, if that's all
that's going on I'd have thought that you wouldn't need any explicit
management in the driver anyway?  Just mark the regualtors as always on
and set up an appropriate suspend mode configuration and everything
should work without the drivers doing anything.  Unless your PMIC isn't
able to provide separate suspend mode configuration for the regulators?
Florian Fainelli March 29, 2021, 9:09 p.m. UTC | #9
On 3/29/21 1:45 PM, Mark Brown wrote:
> On Mon, Mar 29, 2021 at 03:48:46PM -0400, Jim Quinlan wrote:
> 
>> I'm not concerned about a namespace collision and I don't think you
>> should be concerned either.  First, this driver is for Broadcom STB
>> PCIe chips and boards, and we also deliver the DT to the customers.
>> We typically do not have any other regulators in the DT besides the
>> ones I am proposing.  For example, the 7216 SOC DT has 0 other
> 
> You may not describe these regulators in the DT but you must have other
> regulators in your system, most devices need power to operate.  In any
> case "this works for me with my DT on my system and nobody will ever
> change our reference design" isn't really a great approach, frankly it's
> not a claim I entirely believe and even if it turns out to be true for
> your systems if we establish this as being how regulators work for
> soldered down PCI devices everyone else is going to want to do the same
> thing, most likely making the same claims for how much control they have
> over the systems things will run on.
> 
>> regulators -- no namespace collision possible.  Our DT-generating
>> scripts also flag namespace issues.  I admit that this driver is also
>> used by RPi chips, but I can easily exclude this feature from the RPI
>> if Nicolas has any objection.
> 
> That's certainly an issue, obviously the RPI is the sort of system where
> I'd imagine this would be particularly useful.
> 
>> Further, if you want, I can restrict the search to the two regulators
>> I am proposing to add to pci-bus.yaml:  "vpcie12v-supply" and
>> "vpcie3v3-supply".
> 
> No, that doesn't help - what happens if someone uses separate regulators
> for different PCI devices?  The reason the supplies are device namespaced
> is that each device can look up it's own supplies and label them how it
> wants without reference to anything else on the board.  Alternatively
> what happens if some device has another supply it needs to power on (eg,
> something that wants a clean LDO output for analogue use)?
> 
>> Is the above enough to alleviate your concerns about global namespace collision?
> 
> Not really.  TBH it looks like this driver is keeping the regulators
> enabled all the time except for suspend and resume anyway, if that's all
> that's going on I'd have thought that you wouldn't need any explicit
> management in the driver anyway?  Just mark the regualtors as always on
> and set up an appropriate suspend mode configuration and everything
> should work without the drivers doing anything.  Unless your PMIC isn't
> able to provide separate suspend mode configuration for the regulators?
> 

We have typically GPIO-controlled and PMIC (via SCMI) controlled
regulators. During PCIe enumeration we need these regulators turned on
to be successful in training the PCIe link and discover the end-point
attached, so there an always on regulator would work.

When we enter a system suspend state however there are really two cases:

- the end-point supports Wake-on (typically wake-on-WLAN) and we need
its power supplied kept on to support that

- the end-point does not support or participate in any wake-up, there we
want to turn its supplies off to save power

How would we go about supporting such an use case with an always on
regulator?
Mark Brown March 29, 2021, 9:31 p.m. UTC | #10
On Mon, Mar 29, 2021 at 02:09:58PM -0700, Florian Fainelli wrote:
> On 3/29/21 1:45 PM, Mark Brown wrote:

> > management in the driver anyway?  Just mark the regualtors as always on
> > and set up an appropriate suspend mode configuration and everything
> > should work without the drivers doing anything.  Unless your PMIC isn't
> > able to provide separate suspend mode configuration for the regulators?

> We have typically GPIO-controlled and PMIC (via SCMI) controlled
> regulators. During PCIe enumeration we need these regulators turned on
> to be successful in training the PCIe link and discover the end-point
> attached, so there an always on regulator would work.

> When we enter a system suspend state however there are really two cases:

> - the end-point supports Wake-on (typically wake-on-WLAN) and we need
> its power supplied kept on to support that

> - the end-point does not support or participate in any wake-up, there we
> want to turn its supplies off to save power

> How would we go about supporting such an use case with an always on
> regulator?

With a PMIC most PMICs have a system suspend mode with separate
regulator configuration for that and there's seprate regulator API
control for those, including DT bindings.  If that needs runtime
configuration for something hidden by SCMI I'd hope the SCMI regulator
stuff has facilities for that, if not then I guess a spec extension is
needed.  If you want to dynamically select if something is on during
suspend there's not really a way around regulator API support.

For a GPIO regulator you probably need something that does a disable on
the way down, assuming that the GPIO/pin controller doesn't end up
having it's own suspend mode control that ends up powering things off
anyway.  With GPIOs pinctrl on the pins rather than exposing as a
regulator might be enough.
diff mbox series

Patch

diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c
index e330e6811f0b..b76ec7d9af32 100644
--- a/drivers/pci/controller/pcie-brcmstb.c
+++ b/drivers/pci/controller/pcie-brcmstb.c
@@ -24,6 +24,7 @@ 
 #include <linux/pci.h>
 #include <linux/pci-ecam.h>
 #include <linux/printk.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -169,6 +170,7 @@ 
 #define SSC_STATUS_SSC_MASK		0x400
 #define SSC_STATUS_PLL_LOCK_MASK	0x800
 #define PCIE_BRCM_MAX_MEMC		3
+#define PCIE_BRCM_MAX_EP_REGULATORS	4
 
 #define IDX_ADDR(pcie)			(pcie->reg_offsets[EXT_CFG_INDEX])
 #define DATA_ADDR(pcie)			(pcie->reg_offsets[EXT_CFG_DATA])
@@ -295,8 +297,27 @@  struct brcm_pcie {
 	u32			hw_rev;
 	void			(*perst_set)(struct brcm_pcie *pcie, u32 val);
 	void			(*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
+	struct regulator_bulk_data supplies[PCIE_BRCM_MAX_EP_REGULATORS];
+	unsigned int		num_supplies;
 };
 
+static int brcm_set_regulators(struct brcm_pcie *pcie, bool on)
+{
+	struct device *dev = pcie->dev;
+	int ret;
+
+	if (!pcie->num_supplies)
+		return 0;
+	if (on)
+		ret = regulator_bulk_enable(pcie->num_supplies, pcie->supplies);
+	else
+		ret = regulator_bulk_disable(pcie->num_supplies, pcie->supplies);
+	if (ret)
+		dev_err(dev, "failed to %s EP regulators\n",
+			on ? "enable" : "disable");
+	return ret;
+}
+
 /*
  * This is to convert the size of the inbound "BAR" region to the
  * non-linear values of PCIE_X_MISC_RC_BAR[123]_CONFIG_LO.SIZE
@@ -1141,16 +1162,63 @@  static void brcm_pcie_turn_off(struct brcm_pcie *pcie)
 	pcie->bridge_sw_init_set(pcie, 1);
 }
 
+static int brcm_pcie_get_regulators(struct brcm_pcie *pcie)
+{
+	struct device_node *child, *parent = pcie->np;
+	const unsigned int max_name_len = 64 + 4;
+	struct property *pp;
+
+	/* Look for regulator supply property in the EP device subnodes */
+	for_each_available_child_of_node(parent, child) {
+		/*
+		 * Do a santiy test to ensure that this is an EP node
+		 * (e.g. node name: "pci-ep@0,0").  The slot number
+		 * should always be 0 as our controller only has a single
+		 * port.
+		 */
+		const char *p = strstr(child->full_name, "@0");
+
+		if (!p || (p[2] && p[2] != ','))
+			continue;
+
+		/* Now look for regulator supply properties */
+		for_each_property_of_node(child, pp) {
+			int i, n = strnlen(pp->name, max_name_len);
+
+			if (n <= 7 || strncmp("-supply", &pp->name[n - 7], 7))
+				continue;
+
+			/* Make sure this is not a duplicate */
+			for (i = 0; i < pcie->num_supplies; i++)
+				if (strncmp(pcie->supplies[i].supply,
+					    pp->name, max_name_len) == 0)
+					continue;
+
+			if (pcie->num_supplies < PCIE_BRCM_MAX_EP_REGULATORS)
+				pcie->supplies[pcie->num_supplies++].supply = pp->name;
+			else
+				dev_warn(pcie->dev, "No room for EP supply %s\n",
+					 pp->name);
+		}
+	}
+	/*
+	 * Get the regulators that the EP devices require.  We cannot use
+	 * pcie->dev as the device argument in regulator_bulk_get() since
+	 * it will not find the regulators.  Instead, use NULL and the
+	 * regulators are looked up by their name.
+	 */
+	return regulator_bulk_get(NULL, pcie->num_supplies, pcie->supplies);
+}
+
 static int brcm_pcie_suspend(struct device *dev)
 {
 	struct brcm_pcie *pcie = dev_get_drvdata(dev);
-	int ret;
 
 	brcm_pcie_turn_off(pcie);
-	ret = brcm_phy_stop(pcie);
+	brcm_phy_stop(pcie);
 	clk_disable_unprepare(pcie->clk);
 
-	return ret;
+	return brcm_set_regulators(pcie, false);
 }
 
 static int brcm_pcie_resume(struct device *dev)
@@ -1163,6 +1231,10 @@  static int brcm_pcie_resume(struct device *dev)
 	base = pcie->base;
 	clk_prepare_enable(pcie->clk);
 
+	ret = brcm_set_regulators(pcie, true);
+	if (ret)
+		return ret;
+
 	ret = brcm_phy_start(pcie);
 	if (ret)
 		goto err;
@@ -1199,6 +1271,8 @@  static void __brcm_pcie_remove(struct brcm_pcie *pcie)
 	brcm_phy_stop(pcie);
 	reset_control_assert(pcie->rescal);
 	clk_disable_unprepare(pcie->clk);
+	brcm_set_regulators(pcie, false);
+	regulator_bulk_free(pcie->num_supplies, pcie->supplies);
 }
 
 static int brcm_pcie_remove(struct platform_device *pdev)
@@ -1289,6 +1363,16 @@  static int brcm_pcie_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	ret = brcm_pcie_get_regulators(pcie);
+	if (ret) {
+		dev_err(pcie->dev, "failed to get regulators (err=%d)\n", ret);
+		goto fail;
+	}
+
+	ret = brcm_set_regulators(pcie, true);
+	if (ret)
+		goto fail;
+
 	ret = brcm_pcie_setup(pcie);
 	if (ret)
 		goto fail;