Re: [PATCH v2 1/2] mmc: sdhci-acpi: Switch signal voltage back to 3.3V on suspend on external microSD on Lenovo Miix 320

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 14/03/20 3:59 pm, Hans de Goede wrote:
> Hi,
> 
> On 3/9/20 10:26 AM, Adrian Hunter wrote:
>> Thanks for doing this.  A couple of questions below.
>>
>> On 6/03/20 4:30 pm, Hans de Goede wrote:
>>> Based on a sample of 7 DSDTs from Cherry Trail devices using an AXP288
>>> PMIC depending on the design one of 2 possible LDOs on the PMIC is used
>>> for the MMC signalling voltage, either DLDO3 or GPIO1LDO (GPIO1 pin in
>>> low noise LDO mode).
>>>
>>> The Lenovo Miix 320-10ICR uses GPIO1LDO in the SHC1 ACPI device's DSM
>>> methods to set 3.3 or 1.8 signalling voltage and this appears to work
>>> as advertised, so presumably the device is actually using GPIO1LDO for
>>> the external microSD signalling voltage.
>>>
>>> But this device has a bug in the _PS0 method of the SHC1 ACPI device,
>>> the DSM remembers the last set signalling voltage and the _PS0 restores
>>> this after a (runtime) suspend-resume cycle, but it "restores" the voltage
>>> on DLDO3 instead of setting it on GPIO1LDO as the DSM method does. DLDO3
>>> is used for the LCD and setting it to 1.8V causes the LCD to go black.
>>>
>>> This commit works around this issue by calling the Intel DSM to reset the
>>> signal voltage to 3.3V after the host has been runtime suspended.
>>> This will make the _PS0 method reprogram the DLDO3 voltage to 3.3V, which
>>> leaves it at its original setting fixing the LCD going black.
>>>
>>> And this commit then resets the signal voltage back to the original 1.8V
>>> from the (runtime) resume handler, which runs after the ACPI _PS0 method
>>> has run.
>>
>> Don't sdhci_resume_host, sdhci_runtime_resume_host do that anyway?
> 
> It does not look like that, I've added the following debugging patch:
> 
> --- a/drivers/mmc/host/sdhci-acpi.c
> +++ b/drivers/mmc/host/sdhci-acpi.c
> @@ -147,6 +147,10 @@ static int intel_dsm(struct intel_host *intel_host,
> struct device *dev,
>      if (fn > 31 || !(intel_host->dsm_fns & (1 << fn)))
>          return -EOPNOTSUPP;
> 
> +    if (fn == INTEL_DSM_V18_SWITCH || fn == INTEL_DSM_V33_SWITCH)
> +        pr_err("Intel DSM switching to %s volt\n",
> +            (fn == INTEL_DSM_V18_SWITCH) ? "1.8" : "3.3");
> +
>      return __intel_dsm(intel_host, dev, fn, result);
>  }
> 
> @@ -903,8 +907,9 @@ static void __maybe_unused
> sdhci_acpi_restore_signal_voltage_if_needed(
>          struct intel_host *intel_host = sdhci_acpi_priv(c);
>          unsigned int fn = INTEL_DSM_V18_SWITCH;
>          u32 result = 0;
> -
> +        pr_err("Calling Intel DSM from %s\n", __func__);
>          intel_dsm(intel_host, dev, fn, &result);
> +        pr_err("Calling Intel DSM from %s done\n", __func__);
>      }
>  }
> 
> 
> And this gives the following output:
> 
> [  368.079932] Intel DSM switching to 3.3 volt
> [  368.414832] Intel DSM switching to 1.8 volt
> [  371.989717] Intel DSM switching to 3.3 volt
> [  407.176050] Calling Intel DSM from
> sdhci_acpi_restore_signal_voltage_if_needed
> [  407.176052] Intel DSM switching to 1.8 volt
> [  407.200276] Calling Intel DSM from
> sdhci_acpi_restore_signal_voltage_if_needed done
> [  407.205846] Intel DSM switching to 3.3 volt
> [  407.527003] Intel DSM switching to 1.8 volt
> [  407.571991] Intel DSM switching to 3.3 volt
> [  407.893990] Intel DSM switching to 1.8 volt
> 
> [  412.242658] Intel DSM switching to 1.8 volt
> [  412.289387] Intel DSM switching to 3.3 volt
> [  412.606210] Intel DSM switching to 1.8 volt
> [  423.292135] Intel DSM switching to 3.3 volt
> [  424.520057] Calling Intel DSM from
> sdhci_acpi_restore_signal_voltage_if_needed
> [  424.520065] Intel DSM switching to 1.8 volt
> [  424.546960] Calling Intel DSM from
> sdhci_acpi_restore_signal_voltage_if_needed done
> [  424.552940] Intel DSM switching to 3.3 volt
> [  424.890483] Intel DSM switching to 1.8 volt
> 
> Notice how the switch to 1.8 volt is not repeated.

Because the card has been suspended i.e. it will be re-initialized completely.

> 
> It looks almost as if the voltage is being reset
> to 3.3 volt by some higher level code, but only
> after the sdhci_acpi_runtime_resume(), rather then
> before the sdhci_acpi_runtime_suspend() completes.
> 
> If that is indeed the case then indeed we might not
> need the sdhci_acpi_restore_signal_voltage_if_needed()
> function, since the first call to set the signal
> voltage to 3.3V on resume would just be a no-op
> because with the quirk we already do that on suspend.
> 
> And then the second call to switch to 1.8 volt would
> take care of switching to 1.8V for us.
> 
> But if this is indeed what is happening, then wouldn't
> it make more sense to switch back to 3.3V from somewhere
> in higher-level code before sdhci_runtime_suspend_host()
> completes, replacing the current switch-back done just
> after resume?  If we move that switch-back to 3.3V
> done just after resume to suspend time then this entire
> model-specific patch will likely become unnecessary ?
> 
> Note one more answer to some of your review remarks below.
> 
>>> This commit adds and uses a DMI quirk mechanism to only trigger this
>>> workaround on the Lenovo Miix 320 while leaving the behavior of the
>>> driver unchanged on other devices.
>>>
>>> BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=111294
>>> BugLink: https://gitlab.freedesktop.org/drm/intel/issues/355
>>> Reported-by: russianneuromancer <russianneuromancer@xxxxx>
>>> Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>
>>> ---
>>> Changes in v2:
>>> - Make the quirk reset the signal voltage to 3.3V at the end of the
>>>    (runtime) suspend handler instead of disabling 1.8V modes
>>> - Drop the module option to allow overridig the quirks
>>> ---
>>>   drivers/mmc/host/sdhci-acpi.c | 87 ++++++++++++++++++++++++++++++++++-
>>>   1 file changed, 85 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c
>>> index 9651dca6863e..d54a3592f40f 100644
>>> --- a/drivers/mmc/host/sdhci-acpi.c
>>> +++ b/drivers/mmc/host/sdhci-acpi.c
>>> @@ -23,6 +23,7 @@
>>>   #include <linux/pm.h>
>>>   #include <linux/pm_runtime.h>
>>>   #include <linux/delay.h>
>>> +#include <linux/dmi.h>
>>>     #include <linux/mmc/host.h>
>>>   #include <linux/mmc/pm.h>
>>> @@ -72,9 +73,14 @@ struct sdhci_acpi_host {
>>>       const struct sdhci_acpi_slot    *slot;
>>>       struct platform_device        *pdev;
>>>       bool                use_runtime_pm;
>>> +    bool                reset_signal_volt_on_suspend;
>>>       unsigned long            private[0] ____cacheline_aligned;
>>>   };
>>>   +enum {
>>> +    DMI_QUIRK_RESET_SD_SIGNAL_VOLT_ON_SUSP            = BIT(0),
>>> +};
>>> +
>>>   static inline void *sdhci_acpi_priv(struct sdhci_acpi_host *c)
>>>   {
>>>       return (void *)c->private;
>>> @@ -647,6 +653,24 @@ static const struct acpi_device_id sdhci_acpi_ids[] = {
>>>   };
>>>   MODULE_DEVICE_TABLE(acpi, sdhci_acpi_ids);
>>>   +static const struct dmi_system_id sdhci_acpi_quirks[] = {
>>> +    {
>>> +        /*
>>> +         * The Lenovo Miix 320-10ICR has a bug in the _PS0 method of
>>> +         * the SHC1 ACPI device, this bug causes it to reprogram the
>>> +         * wrong LDO (DLDO3) to 1.8V if 1.8V modes are used and the
>>> +         * card is (runtime) suspended + resumed. DLDO3 is used for
>>> +         * the LCD and setting it to 1.8V causes the LCD to go black.
>>> +         */
>>> +        .matches = {
>>> +            DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>>> +            DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"),
>>> +        },
>>> +        .driver_data = (void *)DMI_QUIRK_RESET_SD_SIGNAL_VOLT_ON_SUSP,
>>> +    },
>>> +    {} /* Terminating entry */
>>> +};
>>> +
>>>   static const struct sdhci_acpi_slot *sdhci_acpi_get_slot(struct
>>> acpi_device *adev)
>>>   {
>>>       const struct sdhci_acpi_uid_slot *u;
>>> @@ -663,17 +687,23 @@ static int sdhci_acpi_probe(struct platform_device
>>> *pdev)
>>>       struct device *dev = &pdev->dev;
>>>       const struct sdhci_acpi_slot *slot;
>>>       struct acpi_device *device, *child;
>>> +    const struct dmi_system_id *id;
>>>       struct sdhci_acpi_host *c;
>>>       struct sdhci_host *host;
>>>       struct resource *iomem;
>>>       resource_size_t len;
>>>       size_t priv_size;
>>> +    int quirks = 0;
>>>       int err;
>>>         device = ACPI_COMPANION(dev);
>>>       if (!device)
>>>           return -ENODEV;
>>>   +    id = dmi_first_match(sdhci_acpi_quirks);
>>> +    if (id)
>>> +        quirks = (long)id->driver_data;
>>> +
>>>       slot = sdhci_acpi_get_slot(device);
>>>         /* Power on the SDHCI controller and its children */
>>> @@ -759,6 +789,9 @@ static int sdhci_acpi_probe(struct platform_device
>>> *pdev)
>>>               dev_warn(dev, "failed to setup card detect gpio\n");
>>>               c->use_runtime_pm = false;
>>>           }
>>> +
>>> +        if (quirks & DMI_QUIRK_RESET_SD_SIGNAL_VOLT_ON_SUSP)
>>> +            c->reset_signal_volt_on_suspend = true;
>>>       }
>>>         err = sdhci_setup_host(host);
>>> @@ -823,17 +856,59 @@ static int sdhci_acpi_remove(struct platform_device
>>> *pdev)
>>>       return 0;
>>>   }
>>>   +static void __maybe_unused sdhci_acpi_reset_signal_voltage_if_needed(
>>> +    struct device *dev)
>>> +{
>>> +    struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>> +    struct sdhci_host *host = c->host;
>>> +
>>> +    if (c->reset_signal_volt_on_suspend &&
>>> +        host->mmc_host_ops.start_signal_voltage_switch ==
>>> +                    intel_start_signal_voltage_switch &&
>>
>> This creates a unexpected dependency on
>> host->mmc_host_ops.start_signal_voltage_switch.  Is it really necessary?
> 
> Well we are directly invoking the intel_dsm here, although the
> DMI match should only happen on a system which is using an
> Intel SDHCI controller, I thought it would be better to check for
> that rather then just assuming it.
> 
> Also see the:
> 
> +        struct intel_host *intel_host = sdhci_acpi_priv(c);
> 
> Line, doing this on a non Intel SDHCI ACPI controller would be bad.

Then you need to add a comment to intel_probe_slot() to explain that
sdhci_acpi_reset_signal_voltage_if_needed() is dependent on:
	host->mmc_host_ops.start_signal_voltage_switch =
					intel_start_signal_voltage_switch;


> 
> Regards,
> 
> Hans
> 
> 
>>
>>> +        host->mmc->ios.signal_voltage != MMC_SIGNAL_VOLTAGE_330) {
>>> +        struct intel_host *intel_host = sdhci_acpi_priv(c);
>>> +        unsigned int fn = INTEL_DSM_V33_SWITCH;
>>> +        u32 result = 0;
>>> +
>>> +        intel_dsm(intel_host, dev, fn, &result);
>>> +    }
>>> +}
>>> +
>>> +static void __maybe_unused sdhci_acpi_restore_signal_voltage_if_needed(
>>> +    struct device *dev)
>>> +{
>>> +    struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>> +    struct sdhci_host *host = c->host;
>>> +
>>> +    if (c->reset_signal_volt_on_suspend &&
>>> +        host->mmc_host_ops.start_signal_voltage_switch ==
>>> +                    intel_start_signal_voltage_switch &&
>>> +        host->mmc->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
>>> +        struct intel_host *intel_host = sdhci_acpi_priv(c);
>>> +        unsigned int fn = INTEL_DSM_V18_SWITCH;
>>> +        u32 result = 0;
>>> +
>>> +        intel_dsm(intel_host, dev, fn, &result);
>>> +    }
>>> +}
>>> +
>>>   #ifdef CONFIG_PM_SLEEP
>>>     static int sdhci_acpi_suspend(struct device *dev)
>>>   {
>>>       struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>>       struct sdhci_host *host = c->host;
>>> +    int ret;
>>>         if (host->tuning_mode != SDHCI_TUNING_MODE_3)
>>>           mmc_retune_needed(host->mmc);
>>>   -    return sdhci_suspend_host(host);
>>> +    ret = sdhci_suspend_host(host);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    sdhci_acpi_reset_signal_voltage_if_needed(dev);
>>> +    return 0;
>>>   }
>>>     static int sdhci_acpi_resume(struct device *dev)
>>> @@ -841,6 +916,7 @@ static int sdhci_acpi_resume(struct device *dev)
>>>       struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>>         sdhci_acpi_byt_setting(&c->pdev->dev);
>>> +    sdhci_acpi_restore_signal_voltage_if_needed(dev);
>>>         return sdhci_resume_host(c->host);
>>>   }
>>> @@ -853,11 +929,17 @@ static int sdhci_acpi_runtime_suspend(struct device
>>> *dev)
>>>   {
>>>       struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>>       struct sdhci_host *host = c->host;
>>> +    int ret;
>>>         if (host->tuning_mode != SDHCI_TUNING_MODE_3)
>>>           mmc_retune_needed(host->mmc);
>>>   -    return sdhci_runtime_suspend_host(host);
>>> +    ret = sdhci_runtime_suspend_host(host);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    sdhci_acpi_reset_signal_voltage_if_needed(dev);
>>> +    return 0;
>>>   }
>>>     static int sdhci_acpi_runtime_resume(struct device *dev)
>>> @@ -865,6 +947,7 @@ static int sdhci_acpi_runtime_resume(struct device *dev)
>>>       struct sdhci_acpi_host *c = dev_get_drvdata(dev);
>>>         sdhci_acpi_byt_setting(&c->pdev->dev);
>>> +    sdhci_acpi_restore_signal_voltage_if_needed(dev);
>>>         return sdhci_runtime_resume_host(c->host, 0);
>>>   }
>>>
>>
> 




[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux