[PATCH 0/3] RFC/RFT: Powering on MMC Wifi/BT modules in MMC core

Russell King - ARM Linux linux at arm.linux.org.uk
Thu Feb 13 05:36:40 EST 2014


Any comments on this?

On Sat, Feb 01, 2014 at 04:14:20PM +0000, Russell King - ARM Linux wrote:
> On Thu, Jan 30, 2014 at 09:49:17PM +0000, Russell King - ARM Linux wrote:
> > On Sun, Jan 19, 2014 at 07:56:52PM -0800, Olof Johansson wrote:
> > > This is a small series enhancing the MMC core code to power on modules
> > > before the host in cases where needed, and the corresponding DT bindings
> > > changes.
> > > 
> > > I've got some other issues to debug on the Chromebook, i.e. the interface
> > > doens't actually work. So far it seems unrelated to this patch set so
> > > it's worth posting this and get things going since others need the same
> > > functionality (i.e Cubox-i).
> > > 
> > > As mentioned in the patch in the series, I haven't implemented power-down
> > > yet, I wanted to make sure that the power-on side will be adequate for
> > > those who are looking to use it right away.
> > > 
> > > Comments/test reports/etc welcome.
> > 
> > So, I thought I'd give this a go on the Cubox-i4, and... it doesn't work
> > there.  It's not your patches, it's down to sdhci-esdhc-imx.c not using
> > mmc_of_parse() at all, so those new properties have no way to be used
> > there.
> > 
> > It doesn't look like it could in its current form use mmc_of_parse(),
> > as the imx code manually parses some of the generic properties to hand
> > them into the sdhci layer.  This looks icky, and it looks like something
> > that should be fixed - why should drivers be parsing the core attributes
> > themselves?
> 
> Here's an illustration of why it's icky.
> 
> If we call mmc_of_parse() in the sdhci-esdhc-imx driver (which we'd need to
> do in order to get information on how to configure the card detection etc)
> then this fills in mmc->f_max.
> 
> However, the subsequent call to sdhci_add_host() computes the maximum clock
> from the sdhci capabilities, and then does this:
> 
>         host->max_clk *= 1000000;
>         if (host->max_clk == 0 || host->quirks &
>                         SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN) {
>                 if (!host->ops->get_max_clock) {
>                         pr_err("%s: Hardware doesn't specify base clock "
>                                "frequency.\n", mmc_hostname(mmc));
>                         return -ENODEV;
>                 }
>                 host->max_clk = host->ops->get_max_clock(host);
>         }
> ...
>         /*
>          * Set host parameters.
>          */
>         mmc->ops = &sdhci_ops;
>         mmc->f_max = host->max_clk;
> 
> which would have the effect of overwriting a previously set f_max from
> the OF data.
> 
> There's also the whole "cd-gpios" thing which would need sorting out -
> the imx sdhci driver already parses this property itself, and sets its
> own internal data (so it knows whether it has to use the controller
> based card detect or the gpio card detect) and simply adding a call to
> mmc_of_parse() would result in the gpio slot stuff being setup twice.
> 
> The obvious solution here is to rewrite the sdhci initialisation such
> that it uses the generic infrastructure, but I don't have the motivation
> to do that (I've already plenty of patches to deal with that I don't
> need any more at the moment.)
> 
> A simpler solution would be to split mmc_of_parse() so that the new bits
> are a separate function, which the generic MMC core always calls for
> every host - taking the decision over whether this is supported completely
> away from hosts.  I think that makes a lot of sense, especially as this
> has nothing to do with the facilities found on any particular host.
> 
> There's another issue here about resets.  Let's take the case where the
> external card is powered off, but has active high resets.  At the moment,
> the sequence is this:
> 
> power: _____/~~~~~~~~~~~~
> reset: __/~~~~\__________
> 
> That's not particularly nice, as the reset signal will tend do drive power
> into the device before it's powered up via the clamping diodes in the case.
> Generally, devices are not designed to be powered in this way.  However,
> this is a relatively minor issue though compared to this one, which is what
> happens if the card uses active low reset:
> 
> power: _____/~~~~~~~~~~~~
> reset: ~~\_____/~~~~~~~~~
> 
> This is definitely not good, because it means that the reset is higher for
> longer, which may result in unacceptable dissapation in the package from
> those clamping diodes.  What we need instead is for active low reset is:
> 
> power: _____/~~~~~~~~~~~~
> reset: ________/~~~~~~~~~
> 
> So, we need the GPIO layer to tell us whether the output is active high or
> active low and adjust the initial setting accordingly.  Basically, whenever
> the attached device is powered down, GPIOs to it should be held at low level
> or high impedance (with a pull-down to reduce the risks of ESD damage.)
> 
> I've seen designs get this wrong in the past - Intel Assabet is a good one
> where the UDA1341 audio codec ends up illuminating a LED by being powered
> not via it's supply pin, but by a CPLD output driving one of the I2S pins
> high.  The result is that the CPLD output sources quite a bit of current
> into the UDA1341, which then holds other pins on the SA1110 around mid-rail,
> which is the /worst/ thing you can do with CMOS.  Powering chips via their
> inputs is basically a big no-no.
> 
> So, I think something like the below is needed on top of your patches.
> Note that I added -EPROBE_DEFER handling too (which fixes a bug, because
> regulator_get() returns pointer-errors):
> 
>  drivers/mmc/core/host.c            | 90 +++++++++++++++++++++++++++-----------
>  1 files changed, 65 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
> index e6b850b3241f..64942eb495b6 100644
> --- a/drivers/mmc/core/host.c
> +++ b/drivers/mmc/core/host.c
> @@ -316,7 +316,7 @@ int mmc_of_parse(struct mmc_host *host)
>  	u32 bus_width;
>  	bool explicit_inv_wp, gpio_inv_wp = false;
>  	enum of_gpio_flags flags;
> -	int i, len, ret, gpio;
> +	int len, ret, gpio;
>  
>  	if (!host->parent || !host->parent->of_node)
>  		return 0;
> @@ -419,30 +419,6 @@ int mmc_of_parse(struct mmc_host *host)
>  	if (explicit_inv_wp ^ gpio_inv_wp)
>  		host->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH;
>  
> -	/* Parse card power/reset/clock control */
> -	if (of_find_property(np, "card-reset-gpios", NULL)) {
> -		struct gpio_desc *gpd;
> -		for (i = 0; i < ARRAY_SIZE(host->card_reset_gpios); i++) {
> -			gpd = devm_gpiod_get_index(host->parent, "card-reset", i);
> -			if (IS_ERR(gpd))
> -				break;
> -			gpiod_direction_output(gpd, 0);
> -			host->card_reset_gpios[i] = gpd;
> -		}
> -
> -		gpd = devm_gpiod_get_index(host->parent, "card-reset", ARRAY_SIZE(host->card_reset_gpios));
> -		if (!IS_ERR(gpd)) {
> -			dev_warn(host->parent, "More reset gpios than we can handle");
> -			gpiod_put(gpd);
> -		}
> -	}
> -
> -	host->card_clk = of_clk_get_by_name(np, "card_ext_clock");
> -	if (IS_ERR(host->card_clk))
> -		host->card_clk = NULL;
> -
> -	host->card_regulator = regulator_get(host->parent, "card-external-vcc");
> -
>  	if (of_find_property(np, "cap-sd-highspeed", &len))
>  		host->caps |= MMC_CAP_SD_HIGHSPEED;
>  	if (of_find_property(np, "cap-mmc-highspeed", &len))
> @@ -467,6 +443,66 @@ int mmc_of_parse(struct mmc_host *host)
>  
>  EXPORT_SYMBOL(mmc_of_parse);
>  
> +static int mmc_of_parse_child(struct mmc_host *host)
> +{
> +	struct device_node *np;
> +	struct clk *clk;
> +	int i;
> +
> +	if (!host->parent || !host->parent->of_node)
> +		return 0;
> +
> +	np = host->parent->of_node;
> +
> +	host->card_regulator = regulator_get(host->parent, "card-external-vcc");
> +	if (IS_ERR(host->card_regulator)) {
> +		if (PTR_ERR(host->card_regulator) == -EPROBE_DEFER)
> +			return PTR_ERR(host->card_regulator);
> +		host->card_regulator = NULL;
> +	}
> +
> +	/* Parse card power/reset/clock control */
> +	if (of_find_property(np, "card-reset-gpios", NULL)) {
> +		struct gpio_desc *gpd;
> +		int level = 0;
> +
> +		/*
> +		 * If the regulator is enabled, then we can hold the
> +		 * card in reset with an active high resets.  Otherwise,
> +		 * hold the resets low.
> +		 */
> +		if (host->card_regulator && regulator_is_enabled(host->card_regulator))
> +			level = 1;
> +
> +		for (i = 0; i < ARRAY_SIZE(host->card_reset_gpios); i++) {
> +			gpd = devm_gpiod_get_index(host->parent, "card-reset", i);
> +			if (IS_ERR(gpd)) {
> +				if (PTR_ERR(gpd) == -EPROBE_DEFER)
> +					return PTR_ERR(gpd);
> +				break;
> +			}
> +			gpiod_direction_output(gpd, gpiod_is_active_low(gpd) | level);
> +			host->card_reset_gpios[i] = gpd;
> +		}
> +
> +		gpd = devm_gpiod_get_index(host->parent, "card-reset", ARRAY_SIZE(host->card_reset_gpios));
> +		if (!IS_ERR(gpd)) {
> +			dev_warn(host->parent, "More reset gpios than we can handle");
> +			gpiod_put(gpd);
> +		}
> +	}
> +
> +	clk = of_clk_get_by_name(np, "card_ext_clock");
> +	if (IS_ERR(clk)) {
> +		if (PTR_ERR(clk) == -EPROBE_DEFER)
> +			return PTR_ERR(clk);
> +		clk = NULL;
> +	}
> +	host->card_clk = clk;
> +
> +	return 0;
> +}
> +
>  /**
>   *	mmc_alloc_host - initialise the per-host structure.
>   *	@extra: sizeof private data structure
> @@ -546,6 +582,10 @@ int mmc_add_host(struct mmc_host *host)
>  {
>  	int err;
>  
> +	err = mmc_of_parse_child(host);
> +	if (err)
> +		return err;
> +
>  	WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
>  		!host->ops->enable_sdio_irq);
>  
> 
> -- 
> FTTC broadband for 0.8mile line: 5.8Mbps down 500kbps up.  Estimation
> in database were 13.1 to 19Mbit for a good line, about 7.5+ for a bad.
> Estimate before purchase was "up to 13.2Mbit".
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

-- 
FTTC broadband for 0.8mile line: 5.8Mbps down 500kbps up.  Estimation
in database were 13.1 to 19Mbit for a good line, about 7.5+ for a bad.
Estimate before purchase was "up to 13.2Mbit".



More information about the linux-arm-kernel mailing list