* RE: [PATCH v3 2/3] mmc: tmio-mmc: add support for 32bit data port
From: Chris Brandt @ 2016-11-01 15:13 UTC (permalink / raw)
To: Wolfram Sang
Cc: Ulf Hansson, Wolfram Sang, Sergei Shtylyov, Geert Uytterhoeven,
Simon Horman, linux-mmc@vger.kernel.org,
linux-renesas-soc@vger.kernel.org
In-Reply-To: <20161101150759.GA1436@katana>
> > I don't understand "TMIO_MMC_BIG_DATA_PORT" (How do you know 32-bit??
> How do you know 64-bit??)
>
> I think 16 << host->bus_shift should work; need to verify this, though.
Good idea.
^ permalink raw reply
* Re: [RFC 1/2] mmc: sdhci: dt: Add device tree properties sdhci-caps and sdhci-caps-mask
From: Zach Brown @ 2016-11-01 15:08 UTC (permalink / raw)
To: Jaehoon Chung
Cc: Adrian Hunter, Ulf Hansson, Rob Herring, Mark Rutland, linux-mmc,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <c254c113-8f21-ca71-da5b-02fb95fed412@samsung.com>
On Tue, Nov 01, 2016 at 07:13:29AM +0900, Jaehoon Chung wrote:
> On 10/31/2016 09:34 PM, Adrian Hunter wrote:
> > On 31/10/16 13:59, Jaehoon Chung wrote:
> >> On 10/28/2016 05:12 PM, Ulf Hansson wrote:
> >>> On 25 October 2016 at 21:58, Zach Brown <zach.brown@ni.com> wrote:
> >>>> On some systems the sdhci capabilty registers are incorrect for one
> >>>> reason or another.
> >>>>
> >>>> The sdhci-caps-mask property specifies which bits in the registers
> >>>> are incorrect and should be turned off before using sdhci-caps to turn
> >>>> on bits.
> >>>>
> >>>> The sdhci-caps property specifies which bits should be turned on.
> >>>>
> >>>> Signed-off-by: Zach Brown <zach.brown@ni.com>
> >>>> ---
> >>>> Documentation/devicetree/bindings/mmc/mmc.txt | 7 +++++++
> >>>> 1 file changed, 7 insertions(+)
> >>>>
> >>>> diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>> index 8a37782..1415aa0 100644
> >>>> --- a/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>> +++ b/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>
> >>> The bindings in this document are common mmc DT bindings, not bindings
> >>> specific to a mmc controller.
> >>>
> >>> So unless these bindings are applicable for another controller than
> >>> sdhci, I suggest we create a new file to document these.
> >>> How about Documentation/devicetree/bindings/mmc/sdhci.txt?
> >>>
> >>>> @@ -52,6 +52,13 @@ Optional properties:
> >>>> - no-sdio: controller is limited to send sdio cmd during initialization
> >>>> - no-sd: controller is limited to send sd cmd during initialization
> >>>> - no-mmc: controller is limited to send mmc cmd during initialization
> >>>> +- sdhci-caps-mask: The sdhci capabilities registers are incorrect. This 64bit
> >>>
> >>> /s/registers/register
> >>>
> >>> This applies to some more places below as well.
> >>>
> >>>> + property corresponds to the bits in the sdhci capabilty registers. If the bit
> >>>> + is on in the mask then the bit is incorrect in the registers and should be
> >>>> + turned off.
> >>>> +- sdhci-caps: The sdhci capabilities registers are incorrect. This 64bit
> >>>> + property corresponds to the bits in the sdhci capability registers. If the
> >>>> + bit is on in the property then the bit should be on in the reigsters.
> >>>
> >>> /s/reigsters/register
> >>>
> >>>>
> >>>> *NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
> >>>> polarity properties, we have to fix the meaning of the "normal" and "inverted"
> >>>> --
> >>>> 2.7.4
> >>>>
> >>>
> >>> Overall, I like this idea as it gives us good flexibility. Thus it
> >>> should avoid us to having to add any further new similar "sdhci broken
> >>> cap" DT binding. We could also decide to start deprecate some of the
> >>> existing sdhci bindings, if we think that makes sense.
> >>>
> >>> The downside is that we get a "magic" hex value in the dts. Although,
> >>> people could address this issue by providing some comments about what
> >>> the bits it means in the dts files themselves.
> >>
> >> I think it's not good about getting "magic" hex value.
> >> In my experience, it's too difficult what bits means and calculate..
> >> Because some people who i know had already used like this.(locally..)
> >>
> >> It needs to consider this...otherwise..it should become really complex magic code.
> >
> > The bits we use are listed in sdhci.h and how we use them can be determined
> > from the sdhci source code. Also, from the hardware perspective, there is
> > the SDHCI specification. So what the bits mean is readily available.
> >
> > With regard to calculating the values, won't it be obvious from testing if
> > they are wrong?
>
> You're right. But I didn't see the real use case for this properties.
> If it needs to add these properties, why didn't add codes relevant to these in device-tree?
> Otherwise, this code should be dead code.
>
The issue here is the sdhci has a register that is supposed report the
capabilities of the device, but it can be wrong. It might turn on a mode or
behavior that should not be on.
For example, in our use case the sdhci itself is capable of highspeed so it
naturally has the highspeed capable bit set in it's capabilities register.
However due to board setup, the entire system is not actually capable of
highspeed. So we need a way to say the sdhci capabilities register's highspeed
bit is incorrect.
A simple "no-sdhci-highspeed" device tree property might suffice, but there are
roughly ~64 sdhci capabilities represented in the capabilities register.
Instead of creating ~64 device-tree properties to handle each individually this
patch set creates just two that handle them all. In a way that is flexible
enough to correct all ~64 or just 1 or any subset depending on the use case.
^ permalink raw reply
* Re: [PATCH v3 2/3] mmc: tmio-mmc: add support for 32bit data port
From: Wolfram Sang @ 2016-11-01 15:07 UTC (permalink / raw)
To: Chris Brandt
Cc: Ulf Hansson, Wolfram Sang, Sergei Shtylyov, Geert Uytterhoeven,
Simon Horman, linux-mmc@vger.kernel.org,
linux-renesas-soc@vger.kernel.org
In-Reply-To: <SG2PR06MB11652157C1DA12165852747C8AA10@SG2PR06MB1165.apcprd06.prod.outlook.com>
[-- Attachment #1: Type: text/plain, Size: 278 bytes --]
> I don't understand "TMIO_MMC_BIG_DATA_PORT" (How do you know 32-bit?? How do you know 64-bit??)
I think 16 << host->bus_shift should work; need to verify this, though.
> So......maybe you can change it later and your idea will be more clear to me then.
Yes, fine with me.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* RE: [PATCH v3 2/3] mmc: tmio-mmc: add support for 32bit data port
From: Chris Brandt @ 2016-11-01 13:40 UTC (permalink / raw)
To: Wolfram Sang
Cc: Ulf Hansson, Wolfram Sang, Sergei Shtylyov, Geert Uytterhoeven,
Simon Horman, linux-mmc@vger.kernel.org,
linux-renesas-soc@vger.kernel.org
In-Reply-To: <20161101084938.GA1576@katana>
Hi Wolfram,
> Okay, I have a sketch how to add this feature to R-Car SoCs at a later
> stage. This patch is fine with one minor nit which can be done now or I
> can do it later when I add the R-Car support.
>
> > + * Some controllers have a 32-bit wide data port register */
> > +#define TMIO_MMC_32BIT_DATA_PORT (1 << 9)
>
> Since R-Car Gen3 has 64 bit port, I'd suggest to use
> TMIO_MMC_BIG_DATA_PORT or something. But as I said, I can also do this
> later. So
I don't understand "TMIO_MMC_BIG_DATA_PORT" (How do you know 32-bit?? How do you know 64-bit??)
So......maybe you can change it later and your idea will be more clear to me then.
Thank you,
Chris
^ permalink raw reply
* Re: [PATCH v3 3/3] mmc: sh_mobile_sdhi: Add r7s72100 support
From: Wolfram Sang @ 2016-11-01 8:49 UTC (permalink / raw)
To: Chris Brandt
Cc: Ulf Hansson, Wolfram Sang, Sergei Shtylyov, Geert Uytterhoeven,
Simon Horman, linux-mmc, linux-renesas-soc
In-Reply-To: <20160912141507.6837-4-chris.brandt@renesas.com>
[-- Attachment #1: Type: text/plain, Size: 224 bytes --]
On Mon, Sep 12, 2016 at 10:15:07AM -0400, Chris Brandt wrote:
> Add support for r7s72100 SoC.
>
> Signed-off-by: Chris Brandt <chris.brandt@renesas.com>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH v3 2/3] mmc: tmio-mmc: add support for 32bit data port
From: Wolfram Sang @ 2016-11-01 8:49 UTC (permalink / raw)
To: Chris Brandt
Cc: Ulf Hansson, Wolfram Sang, Sergei Shtylyov, Geert Uytterhoeven,
Simon Horman, linux-mmc, linux-renesas-soc
In-Reply-To: <20160912141507.6837-3-chris.brandt@renesas.com>
[-- Attachment #1: Type: text/plain, Size: 905 bytes --]
Hi Chris,
On Mon, Sep 12, 2016 at 10:15:06AM -0400, Chris Brandt wrote:
> For the r7s72100 SOC, the DATA_PORT register was changed to 32-bits wide.
> Therefore a new flag has been created that will allow 32-bit reads/writes
> to the DATA_PORT register instead of 16-bit (because 16-bits accesses are
> not supported).
>
> Signed-off-by: Chris Brandt <chris.brandt@renesas.com>
Okay, I have a sketch how to add this feature to R-Car SoCs at a later
stage. This patch is fine with one minor nit which can be done now or I
can do it later when I add the R-Car support.
> + * Some controllers have a 32-bit wide data port register
> + */
> +#define TMIO_MMC_32BIT_DATA_PORT (1 << 9)
Since R-Car Gen3 has 64 bit port, I'd suggest to use
TMIO_MMC_BIG_DATA_PORT or something. But as I said, I can also do this
later. So
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH v2 2/5] mmc: zx: Initial support for ZX mmc controller
From: Jun Nie @ 2016-11-01 7:45 UTC (permalink / raw)
To: Jaehoon Chung; +Cc: Shawn Guo, xie.baoyou, Ulf Hansson, Jason Liu, linux-mmc
In-Reply-To: <ef2d63d7-4f80-dd42-1987-39459e578d96@samsung.com>
2016-11-01 15:25 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
> On 11/01/2016 10:16 AM, Jun Nie wrote:
>> 2016-10-31 17:40 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
>>> On 10/31/2016 05:47 PM, Jun Nie wrote:
>>>> 2016-10-28 13:16 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
>>>>> On 10/28/2016 11:37 AM, Jun Nie wrote:
>>>>>> This platform driver adds initial support for the DW host controller
>>>>>> found on ZTE SoCs.
>>>>>>
>>>>>> It has been tested on ZX296718 EVB board currently. More support on
>>>>>> timing tuning will be added when hardware is available.
>>>>>>
>>>>>> Signed-off-by: Jun Nie <jun.nie@linaro.org>
>>>>>> ---
>>>>>> drivers/mmc/host/Kconfig | 9 ++
>>>>>> drivers/mmc/host/Makefile | 1 +
>>>>>> drivers/mmc/host/dw_mmc-zx.c | 230 +++++++++++++++++++++++++++++++++++++++++++
>>>>>> drivers/mmc/host/dw_mmc-zx.h | 23 +++++
>>>>>> 4 files changed, 263 insertions(+)
>>>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.c
>>>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.h
>>>>>>
>>>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>>>>> index 5274f50..2b3202c 100644
>>>>>> --- a/drivers/mmc/host/Kconfig
>>>>>> +++ b/drivers/mmc/host/Kconfig
>>>>>> @@ -662,6 +662,15 @@ config MMC_DW_ROCKCHIP
>>>>>> Synopsys DesignWare Memory Card Interface driver. Select this option
>>>>>> for platforms based on RK3066, RK3188 and RK3288 SoC's.
>>>>>>
>>>>>> +config MMC_DW_ZX
>>>>>> + tristate "ZTE specific extensions for Synopsys DW Memory Card Interface"
>>>>>> + depends on MMC_DW
>>>>>
>>>>> I guess MMC_DW_ZX depends on your SoC config, doesn't?
>>>>
>>>> Right, will add dependency.
>>>>
>>>>>
>>>>>> + select MMC_DW_PLTFM
>>>>>> + help
>>>>>> + This selects support for ZTE SoC specific extensions to the
>>>>>> + Synopsys DesignWare Memory Card Interface driver. Select this option
>>>>>> + for platforms based on ZX296718 SoC's.
>>>>>> +
>>>>>> config MMC_SH_MMCIF
>>>>>> tristate "SuperH Internal MMCIF support"
>>>>>> depends on HAS_DMA
>>>>>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>>>>>> index e2bdaaf..9766143 100644
>>>>>> --- a/drivers/mmc/host/Makefile
>>>>>> +++ b/drivers/mmc/host/Makefile
>>>>>> @@ -48,6 +48,7 @@ obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
>>>>>> obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
>>>>>> obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
>>>>>> obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
>>>>>> +obj-$(CONFIG_MMC_DW_ZX) += dw_mmc-zx.o
>>>>>> obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
>>>>>> obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
>>>>>> obj-$(CONFIG_MMC_VUB300) += vub300.o
>>>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.c b/drivers/mmc/host/dw_mmc-zx.c
>>>>>> new file mode 100644
>>>>>> index 0000000..0404f8e
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/mmc/host/dw_mmc-zx.c
>>>>>> @@ -0,0 +1,230 @@
>>>>>> +/*
>>>>>> + * ZX Specific Extensions for Synopsys DW Multimedia Card Interface driver
>>>>>> + *
>>>>>> + * Copyright (C) 2016, Linaro Ltd.
>>>>>> + * Copyright (C) 2016, ZTE Corp.
>>>>>> + *
>>>>>> + * This program is free software; you can redistribute it and/or modify
>>>>>> + * it under the terms of the GNU General Public License as published by
>>>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>>>> + * (at your option) any later version.
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/clk.h>
>>>>>> +#include <linux/mfd/syscon.h>
>>>>>> +#include <linux/mmc/dw_mmc.h>
>>>>>> +#include <linux/mmc/host.h>
>>>>>> +#include <linux/mmc/mmc.h>
>>>>>> +#include <linux/module.h>
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/platform_device.h>
>>>>>> +#include <linux/pm_runtime.h>
>>>>>> +#include <linux/regmap.h>
>>>>>> +#include <linux/slab.h>
>>>>>> +
>>>>>> +#include "dw_mmc.h"
>>>>>> +#include "dw_mmc-pltfm.h"
>>>>>> +#include "dw_mmc-zx.h"
>>>>>> +
>>>>>> +#define ZX_DLL_LOCKED BIT(2)
>>>>>
>>>>> Some DLL rigsters and bits are defined in dw_mmc-zx.h.
>>>>> why defined ZX_DLL_LOCKED at here.
>>>>>
>>>>> You can choose that all defines locates to dw_mmc-zx.c or dw_mmc-zx.h
>>>>>
>>>> Will move together.
>>>>
>>>>>> +
>>>>>> +struct dw_mci_zx_priv_data {
>>>>>> + struct regmap *sysc_base;
>>>>>> +};
>>>>>> +
>>>>>> +static int dw_mci_zx_emmc_set_delay(struct dw_mci *host, unsigned int delay,
>>>>>> + unsigned int clk_flag)
>>>>>
>>>>> Why do you use "unsigned int" as clk_flag? It's just use one of 0 and 1.
>>>>> And 0 and 1 are what means? On/Off?
>>>>>
>>>> Yes, enumeration shall looks better here. It is a flag for different
>>>> delay type. Will change to enumeration.
>>>>
>>>>>> +{
>>>>>> + struct dw_mci_zx_priv_data *priv = host->priv;
>>>>>> + struct regmap *sysc_base = priv->sysc_base;
>>>>>> + unsigned int clksel;
>>>>>> + unsigned int loop = 1000;
>>>>>> + int ret;
>>>>>> +
>>>>>
>>>>> priv->sysc_base doesn't never NULL?
>>>>
>>>> For this SoC, it is never NULL if dts is correct. Adding a check is
>>>> better anyway.
>>>>>
>>>>>> + ret = regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4));
>>>>>
>>>>> Could you add the comment for controlling this regs?
>>>>> I'm not sure because i didn't have ZX TRM..but PARA_DLL_LOCK_NUM should be locked?
>>>>>
>>>>> It doesn't affect to other bit?
>>>>> I think you can use the regmap_update_bits instead of regmap_write.
>>>>
>>>> It does not affect other bit as all bits are write as desired. But
>>>> your suggestion make it clearer.
>>>>
>>>>>
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG1, &clksel);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + if (clk_flag) {
>>>>>> + clksel &= ~(CLK_SAMP_DELAY(0x7F));
>>>>>
>>>>> It's meaningless..CLK_SAMP_DELAY used only at here.
>>>>> CLK_SAMP_DELAY ((x) & 0x7F << 0)
>>>>>
>>>>> It's just CLK_SAMP_DELAY_MASK.?
>>>>>
>>>>> #define CLK_SAMP_DELAY_MASK (0x7F << 0)
>>>>> clksel &= ~CLK_SAMP_DELAY_MASK;
>>>>>
>>>> Right, clearer.
>>>>
>>>>>
>>>>>> + clksel |= (delay << 8);
>>>>>
>>>>> Use the CLK_SAMP_DELAY_SHIFT instead of 8.
>>>>>
>>>>>> + } else {
>>>>>> + clksel &= ~(READ_DQS_DELAY(0x7F));
>>>>>
>>>>> Ditto.
>>>>> And i think it also can be changed to regmap_update_bits.
>>>>>
>>>>>> + clksel |= delay;
>>>>>> + }
>>>>>> +
>>>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG1, clksel);
>>>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4) |
>>>>>> + DLL_REG_SET);
>>>>>
>>>>> regmap_update_bits?
>>>>
>>>> Will do.
>>>>
>>>>>
>>>>>> +
>>>>>> + do {
>>>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG2, &clksel);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + } while (--loop && !(clksel & ZX_DLL_LOCKED));
>>>>>> +
>>>>>> + if (!loop) {
>>>>>> + dev_err(host->dev, "Error: %s dll lock fail\n", __func__);
>>>>>> + return -EIO;
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dw_mci_zx_emmc_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>>>> +{
>>>>>> + struct dw_mci *host = slot->host;
>>>>>> + struct mmc_host *mmc = slot->mmc;
>>>>>> + int len, start = 0, end = 0, delay, best = 0;
>>>>>> + int ret = 0;
>>>>>> +
>>>>>> + for (delay = 1 ; delay < 128; delay++) {
>>>>>> + ret = dw_mci_zx_emmc_set_delay(host, delay, 1);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>
>>>>> When it's failed, just returned.
>>>>> Doesn't need to try with next delay value?
>>>>
>>>> Yes, more retry make robust.
>>>>
>>>>>
>>>>>> +
>>>>>> + if (mmc_send_tuning(mmc, opcode, NULL)) {
>>>>>> + if (start >= 0) {
>>>>>> + end = delay - 1;
>>>>>> + /* check and update longest good range */
>>>>>> + if ((end - start) > len) {
>>>>>> + best = (start + end) >> 1;
>>>>>> + len = end - start;
>>>>>> + }
>>>>>> + }
>>>>>> + start = -1;
>>>>>> + end = 0;
>>>>>> + continue;
>>>>>> + }
>>>>>> + if (start < 0)
>>>>>> + start = delay;
>>>>>> + }
>>>>>> +
>>>>>> + if (start >= 0) {
>>>>>> + end = delay - 1;
>>>>>> + if ((end - start) > len) {
>>>>>> + best = (start + end) >> 1;
>>>>>> + len = end - start;
>>>>>> + }
>>>>>> + }
>>>>>> + if (best < 0)
>>>>>> + return -EIO;
>>>>>> +
>>>>>> + dev_info(host->dev, "%s best range: start %d end %d\n", __func__,
>>>>>> + start, end);
>>>>>> + dw_mci_zx_emmc_set_delay(host, best, 1);
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dw_mci_zx_prepare_hs400_tuning(struct dw_mci *host,
>>>>>> + struct mmc_ios *ios)
>>>>>> +{
>>>>>> + int ret;
>>>>>> +
>>>>>> + /* config phase shift 90 */
>>>>>> + ret = dw_mci_zx_emmc_set_delay(host, 32, 0);
>>>>>
>>>>> It's always fixed to 32? What means 32?
>>>>
>>>> It means 90 degree shift for value 32. This configuration comes from
>>>> ZTE engineer as I do not have hardware for this tuning. Let's just
>>>> keep it with adding more comments till tuning is needed.
>>>>
>>>>>
>>>>>> + if (ret < 0)
>>>>>> + return -EIO;
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dw_mci_zx_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>>>> +{
>>>>>> + struct dw_mci *host = slot->host;
>>>>>> +
>>>>>> + if (host->verid == 0x290a) /* emmc */
>>>>>> + return dw_mci_zx_emmc_execute_tuning(slot, opcode);
>>>>>
>>>>> I didn't know why you check host->verid is 2.90a..
>>>>> Is there any reason?
>>>> There are two version DW MMC IP on this SoC and different
>>>> configuration is needed for them. I do not have hardware for tuning
>>>> version 210A timing and will be added later.
>>>
>>> you means there are two IP version with same SoC?
>>>
>>> Best Regards,
>>> Jaehoon Chung
>>
>> Right, two version IP exist on the same SoC.
>
> It's strange. :) The latest and older versions are existed on same SoC.
> Then i recommend that add the comment for this status.
> Why you put this condition, and which case is used..
> Not just "/* emmc */" :)
>
> One more thing..dw_mci_zx_emmc_set_delay() and dw_mci_zx_emmc_set_delay() are for only emmc?
> other cards don't use this function?
>
> just can remove "_emmc_"?
>
> Best Regards,
> Jaehoon Chung
Yes, 290A version for eMMC and 210A version for SD/SDIO. Only 290A
version timing tuning use this function and tuning registers is SoC
specific. So *_emmc_* name is dedicated for 290A version here. We need
a name anyway, *_290A_* can be a candidate, but not as direct as
*_emmc_* for this SoCs.
>
>>
>>>
>>>>
>>>>>
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dw_mci_zx_parse_dt(struct dw_mci *host)
>>>>>> +{
>>>>>> + struct device_node *np = host->dev->of_node;
>>>>>> + struct device_node *node;
>>>>>> + struct dw_mci_zx_priv_data *priv;
>>>>>> + struct regmap *sysc_base;
>>>>>> + int ret;
>>>>>> +
>>>>>> + node = of_parse_phandle(np, "zte,aon-syscon", 0);
>>>>>> + if (node) {
>>>>>> + sysc_base = syscon_node_to_regmap(node);
>>>>>> + of_node_put(node);
>>>>>
>>>>> Use the syscon_regmap_lookup_by_phandle(). It's same behavior.
>>>> Will do.
>>>>>
>>>>>> +
>>>>>> + if (IS_ERR(sysc_base)) {
>>>>>> + ret = PTR_ERR(sysc_base);
>>>>>> + if (ret != -EPROBE_DEFER)
>>>>>> + dev_err(host->dev, "Can't get syscon: %d\n",
>>>>>> + ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
>>>>>> + if (!priv)
>>>>>> + return -ENOMEM;
>>>>>> + priv->sysc_base = sysc_base;
>>>>>
>>>>> Is there no case that sysc_base is NULL?
>>>>
>>>> sysc_base is needed only for eMMC. So it is NULL for SD/MMC cases, and
>>>> we can save memory for SD/MMC cases here :)
>>>>
>>>>>
>>>>>> + host->priv = priv;
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static unsigned long zx_dwmmc_caps[3] = {
>>>>>> + MMC_CAP_CMD23,
>>>>>> + MMC_CAP_CMD23,
>>>>>> + MMC_CAP_CMD23,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct dw_mci_drv_data zx_drv_data = {
>>>>>> + .caps = zx_dwmmc_caps,
>>>>>> + .execute_tuning = dw_mci_zx_execute_tuning,
>>>>>> + .prepare_hs400_tuning = dw_mci_zx_prepare_hs400_tuning,
>>>>>> + .parse_dt = dw_mci_zx_parse_dt,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct of_device_id dw_mci_zx_match[] = {
>>>>>> + { .compatible = "zte,dw-mshc", .data = &zx_drv_data},
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(of, dw_mci_zx_match);
>>>>>> +
>>>>>> +static int dw_mci_zx_probe(struct platform_device *pdev)
>>>>>> +{
>>>>>> + const struct dw_mci_drv_data *drv_data;
>>>>>> + const struct of_device_id *match;
>>>>>> +
>>>>>> + match = of_match_node(dw_mci_zx_match, pdev->dev.of_node);
>>>>>> + drv_data = match->data;
>>>>>> +
>>>>>> + return dw_mci_pltfm_register(pdev, drv_data);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct dev_pm_ops dw_mci_zx_dev_pm_ops = {
>>>>>> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>>>> + pm_runtime_force_resume)
>>>>>> + SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
>>>>>> + dw_mci_runtime_resume,
>>>>>> + NULL)
>>>>>> +};
>>>>>> +
>>>>>> +static struct platform_driver dw_mci_zx_pltfm_driver = {
>>>>>> + .probe = dw_mci_zx_probe,
>>>>>> + .remove = dw_mci_pltfm_remove,
>>>>>> + .driver = {
>>>>>> + .name = "dwmmc_zx",
>>>>>> + .of_match_table = dw_mci_zx_match,
>>>>>> + .pm = &dw_mci_zx_dev_pm_ops,
>>>>>> + },
>>>>>> +};
>>>>>> +
>>>>>> +module_platform_driver(dw_mci_zx_pltfm_driver);
>>>>>> +
>>>>>> +MODULE_DESCRIPTION("ZTE emmc/sd driver");
>>>>>> +MODULE_LICENSE("GPL v2");
>>>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.h b/drivers/mmc/host/dw_mmc-zx.h
>>>>>> new file mode 100644
>>>>>> index 0000000..b1aac52
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/mmc/host/dw_mmc-zx.h
>>>>>> @@ -0,0 +1,23 @@
>>>>>> +#ifndef _DW_MMC_ZX_H_
>>>>>> +#define _DW_MMC_ZX_H_
>>>>>> +
>>>>>> +/* dll reg offset*/
>>>>>> +#define LB_AON_EMMC_CFG_REG0 0x1B0
>>>>>> +#define LB_AON_EMMC_CFG_REG1 0x1B4
>>>>>> +#define LB_AON_EMMC_CFG_REG2 0x1B8
>>>>>> +
>>>>>> +/* LB_AON_EMMC_CFG_REG0 register defines */
>>>>>> +#define PARA_DLL_START_POINT(x) (((x) & 0xFF) << 0)
>>>>>> +#define DLL_REG_SET BIT(8)
>>>>>> +#define PARA_DLL_LOCK_NUM(x) (((x) & 7) << 16)
>>>>>> +#define PARA_PHASE_DET_SEL(x) (((x) & 7) << 20)
>>>>>> +#define PARA_DLL_BYPASS_MODE BIT(23)
>>>>>> +#define PARA_HALF_CLK_MODE BIT(24)
>>>>>
>>>>> PAR_PHASE_DET_SEL/PARA_DLL_BYPASS_MODE_BIT/PARA_HALF_CLK_MODE are never used anywhere.
>>>>>
>>>>>> +
>>>>>> +/* LB_AON_EMMC_CFG_REG1 register defines */
>>>>>> +#define READ_DQS_DELAY(x) (((x) & 0x7F) << 0)
>>>>>> +#define READ_DQS_BYPASS_MODE BIT(7)
>>>>>> +#define CLK_SAMP_DELAY(x) (((x) & 0x7F) << 8)
>>>>>> +#define CLK_SAMP_BYPASS_MODE BIT(15)
>>>>>
>>>>> Also READ_DQS_BYPASS_MODe/CLK_SAMP_BYBASS_MODE didnt used anywhere.
>>>>>
>>>>>
>>>>> Hmm. These are not dwmmc host controller's register.
>>>>> So If you needs to add these defines..I think you needs to add dessriptions in more detail.
>>>>>
>>>>> At least..Which board's DLL reg offset.
>>>>>
>>>>> Best Regards,
>>>>> Jaehoon Chung
>>>>>
>>>>>> +
>>>>>> +#endif /* _DW_MMC_ZX_H_ */
>>>>>>
>>>>>
>>>>
>>>>
>>>>
>>>
>>
>>
>>
>
^ permalink raw reply
* Re: [PATCH v2 2/5] mmc: zx: Initial support for ZX mmc controller
From: Jaehoon Chung @ 2016-11-01 7:25 UTC (permalink / raw)
To: Jun Nie; +Cc: Shawn Guo, xie.baoyou, Ulf Hansson, Jason Liu, linux-mmc
In-Reply-To: <CABymUCOzkUxzC-wSedni+bL3ZCNNrUDKS2-J+Guso8QR5ePf8w@mail.gmail.com>
On 11/01/2016 10:16 AM, Jun Nie wrote:
> 2016-10-31 17:40 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
>> On 10/31/2016 05:47 PM, Jun Nie wrote:
>>> 2016-10-28 13:16 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
>>>> On 10/28/2016 11:37 AM, Jun Nie wrote:
>>>>> This platform driver adds initial support for the DW host controller
>>>>> found on ZTE SoCs.
>>>>>
>>>>> It has been tested on ZX296718 EVB board currently. More support on
>>>>> timing tuning will be added when hardware is available.
>>>>>
>>>>> Signed-off-by: Jun Nie <jun.nie@linaro.org>
>>>>> ---
>>>>> drivers/mmc/host/Kconfig | 9 ++
>>>>> drivers/mmc/host/Makefile | 1 +
>>>>> drivers/mmc/host/dw_mmc-zx.c | 230 +++++++++++++++++++++++++++++++++++++++++++
>>>>> drivers/mmc/host/dw_mmc-zx.h | 23 +++++
>>>>> 4 files changed, 263 insertions(+)
>>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.c
>>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.h
>>>>>
>>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>>>> index 5274f50..2b3202c 100644
>>>>> --- a/drivers/mmc/host/Kconfig
>>>>> +++ b/drivers/mmc/host/Kconfig
>>>>> @@ -662,6 +662,15 @@ config MMC_DW_ROCKCHIP
>>>>> Synopsys DesignWare Memory Card Interface driver. Select this option
>>>>> for platforms based on RK3066, RK3188 and RK3288 SoC's.
>>>>>
>>>>> +config MMC_DW_ZX
>>>>> + tristate "ZTE specific extensions for Synopsys DW Memory Card Interface"
>>>>> + depends on MMC_DW
>>>>
>>>> I guess MMC_DW_ZX depends on your SoC config, doesn't?
>>>
>>> Right, will add dependency.
>>>
>>>>
>>>>> + select MMC_DW_PLTFM
>>>>> + help
>>>>> + This selects support for ZTE SoC specific extensions to the
>>>>> + Synopsys DesignWare Memory Card Interface driver. Select this option
>>>>> + for platforms based on ZX296718 SoC's.
>>>>> +
>>>>> config MMC_SH_MMCIF
>>>>> tristate "SuperH Internal MMCIF support"
>>>>> depends on HAS_DMA
>>>>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>>>>> index e2bdaaf..9766143 100644
>>>>> --- a/drivers/mmc/host/Makefile
>>>>> +++ b/drivers/mmc/host/Makefile
>>>>> @@ -48,6 +48,7 @@ obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
>>>>> obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
>>>>> obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
>>>>> obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
>>>>> +obj-$(CONFIG_MMC_DW_ZX) += dw_mmc-zx.o
>>>>> obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
>>>>> obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
>>>>> obj-$(CONFIG_MMC_VUB300) += vub300.o
>>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.c b/drivers/mmc/host/dw_mmc-zx.c
>>>>> new file mode 100644
>>>>> index 0000000..0404f8e
>>>>> --- /dev/null
>>>>> +++ b/drivers/mmc/host/dw_mmc-zx.c
>>>>> @@ -0,0 +1,230 @@
>>>>> +/*
>>>>> + * ZX Specific Extensions for Synopsys DW Multimedia Card Interface driver
>>>>> + *
>>>>> + * Copyright (C) 2016, Linaro Ltd.
>>>>> + * Copyright (C) 2016, ZTE Corp.
>>>>> + *
>>>>> + * This program is free software; you can redistribute it and/or modify
>>>>> + * it under the terms of the GNU General Public License as published by
>>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>>> + * (at your option) any later version.
>>>>> + */
>>>>> +
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/mfd/syscon.h>
>>>>> +#include <linux/mmc/dw_mmc.h>
>>>>> +#include <linux/mmc/host.h>
>>>>> +#include <linux/mmc/mmc.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/pm_runtime.h>
>>>>> +#include <linux/regmap.h>
>>>>> +#include <linux/slab.h>
>>>>> +
>>>>> +#include "dw_mmc.h"
>>>>> +#include "dw_mmc-pltfm.h"
>>>>> +#include "dw_mmc-zx.h"
>>>>> +
>>>>> +#define ZX_DLL_LOCKED BIT(2)
>>>>
>>>> Some DLL rigsters and bits are defined in dw_mmc-zx.h.
>>>> why defined ZX_DLL_LOCKED at here.
>>>>
>>>> You can choose that all defines locates to dw_mmc-zx.c or dw_mmc-zx.h
>>>>
>>> Will move together.
>>>
>>>>> +
>>>>> +struct dw_mci_zx_priv_data {
>>>>> + struct regmap *sysc_base;
>>>>> +};
>>>>> +
>>>>> +static int dw_mci_zx_emmc_set_delay(struct dw_mci *host, unsigned int delay,
>>>>> + unsigned int clk_flag)
>>>>
>>>> Why do you use "unsigned int" as clk_flag? It's just use one of 0 and 1.
>>>> And 0 and 1 are what means? On/Off?
>>>>
>>> Yes, enumeration shall looks better here. It is a flag for different
>>> delay type. Will change to enumeration.
>>>
>>>>> +{
>>>>> + struct dw_mci_zx_priv_data *priv = host->priv;
>>>>> + struct regmap *sysc_base = priv->sysc_base;
>>>>> + unsigned int clksel;
>>>>> + unsigned int loop = 1000;
>>>>> + int ret;
>>>>> +
>>>>
>>>> priv->sysc_base doesn't never NULL?
>>>
>>> For this SoC, it is never NULL if dts is correct. Adding a check is
>>> better anyway.
>>>>
>>>>> + ret = regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4));
>>>>
>>>> Could you add the comment for controlling this regs?
>>>> I'm not sure because i didn't have ZX TRM..but PARA_DLL_LOCK_NUM should be locked?
>>>>
>>>> It doesn't affect to other bit?
>>>> I think you can use the regmap_update_bits instead of regmap_write.
>>>
>>> It does not affect other bit as all bits are write as desired. But
>>> your suggestion make it clearer.
>>>
>>>>
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG1, &clksel);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + if (clk_flag) {
>>>>> + clksel &= ~(CLK_SAMP_DELAY(0x7F));
>>>>
>>>> It's meaningless..CLK_SAMP_DELAY used only at here.
>>>> CLK_SAMP_DELAY ((x) & 0x7F << 0)
>>>>
>>>> It's just CLK_SAMP_DELAY_MASK.?
>>>>
>>>> #define CLK_SAMP_DELAY_MASK (0x7F << 0)
>>>> clksel &= ~CLK_SAMP_DELAY_MASK;
>>>>
>>> Right, clearer.
>>>
>>>>
>>>>> + clksel |= (delay << 8);
>>>>
>>>> Use the CLK_SAMP_DELAY_SHIFT instead of 8.
>>>>
>>>>> + } else {
>>>>> + clksel &= ~(READ_DQS_DELAY(0x7F));
>>>>
>>>> Ditto.
>>>> And i think it also can be changed to regmap_update_bits.
>>>>
>>>>> + clksel |= delay;
>>>>> + }
>>>>> +
>>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG1, clksel);
>>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4) |
>>>>> + DLL_REG_SET);
>>>>
>>>> regmap_update_bits?
>>>
>>> Will do.
>>>
>>>>
>>>>> +
>>>>> + do {
>>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG2, &clksel);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + } while (--loop && !(clksel & ZX_DLL_LOCKED));
>>>>> +
>>>>> + if (!loop) {
>>>>> + dev_err(host->dev, "Error: %s dll lock fail\n", __func__);
>>>>> + return -EIO;
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int dw_mci_zx_emmc_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>>> +{
>>>>> + struct dw_mci *host = slot->host;
>>>>> + struct mmc_host *mmc = slot->mmc;
>>>>> + int len, start = 0, end = 0, delay, best = 0;
>>>>> + int ret = 0;
>>>>> +
>>>>> + for (delay = 1 ; delay < 128; delay++) {
>>>>> + ret = dw_mci_zx_emmc_set_delay(host, delay, 1);
>>>>> + if (ret)
>>>>> + return ret;
>>>>
>>>> When it's failed, just returned.
>>>> Doesn't need to try with next delay value?
>>>
>>> Yes, more retry make robust.
>>>
>>>>
>>>>> +
>>>>> + if (mmc_send_tuning(mmc, opcode, NULL)) {
>>>>> + if (start >= 0) {
>>>>> + end = delay - 1;
>>>>> + /* check and update longest good range */
>>>>> + if ((end - start) > len) {
>>>>> + best = (start + end) >> 1;
>>>>> + len = end - start;
>>>>> + }
>>>>> + }
>>>>> + start = -1;
>>>>> + end = 0;
>>>>> + continue;
>>>>> + }
>>>>> + if (start < 0)
>>>>> + start = delay;
>>>>> + }
>>>>> +
>>>>> + if (start >= 0) {
>>>>> + end = delay - 1;
>>>>> + if ((end - start) > len) {
>>>>> + best = (start + end) >> 1;
>>>>> + len = end - start;
>>>>> + }
>>>>> + }
>>>>> + if (best < 0)
>>>>> + return -EIO;
>>>>> +
>>>>> + dev_info(host->dev, "%s best range: start %d end %d\n", __func__,
>>>>> + start, end);
>>>>> + dw_mci_zx_emmc_set_delay(host, best, 1);
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int dw_mci_zx_prepare_hs400_tuning(struct dw_mci *host,
>>>>> + struct mmc_ios *ios)
>>>>> +{
>>>>> + int ret;
>>>>> +
>>>>> + /* config phase shift 90 */
>>>>> + ret = dw_mci_zx_emmc_set_delay(host, 32, 0);
>>>>
>>>> It's always fixed to 32? What means 32?
>>>
>>> It means 90 degree shift for value 32. This configuration comes from
>>> ZTE engineer as I do not have hardware for this tuning. Let's just
>>> keep it with adding more comments till tuning is needed.
>>>
>>>>
>>>>> + if (ret < 0)
>>>>> + return -EIO;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int dw_mci_zx_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>>> +{
>>>>> + struct dw_mci *host = slot->host;
>>>>> +
>>>>> + if (host->verid == 0x290a) /* emmc */
>>>>> + return dw_mci_zx_emmc_execute_tuning(slot, opcode);
>>>>
>>>> I didn't know why you check host->verid is 2.90a..
>>>> Is there any reason?
>>> There are two version DW MMC IP on this SoC and different
>>> configuration is needed for them. I do not have hardware for tuning
>>> version 210A timing and will be added later.
>>
>> you means there are two IP version with same SoC?
>>
>> Best Regards,
>> Jaehoon Chung
>
> Right, two version IP exist on the same SoC.
It's strange. :) The latest and older versions are existed on same SoC.
Then i recommend that add the comment for this status.
Why you put this condition, and which case is used..
Not just "/* emmc */" :)
One more thing..dw_mci_zx_emmc_set_delay() and dw_mci_zx_emmc_set_delay() are for only emmc?
other cards don't use this function?
just can remove "_emmc_"?
Best Regards,
Jaehoon Chung
>
>>
>>>
>>>>
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int dw_mci_zx_parse_dt(struct dw_mci *host)
>>>>> +{
>>>>> + struct device_node *np = host->dev->of_node;
>>>>> + struct device_node *node;
>>>>> + struct dw_mci_zx_priv_data *priv;
>>>>> + struct regmap *sysc_base;
>>>>> + int ret;
>>>>> +
>>>>> + node = of_parse_phandle(np, "zte,aon-syscon", 0);
>>>>> + if (node) {
>>>>> + sysc_base = syscon_node_to_regmap(node);
>>>>> + of_node_put(node);
>>>>
>>>> Use the syscon_regmap_lookup_by_phandle(). It's same behavior.
>>> Will do.
>>>>
>>>>> +
>>>>> + if (IS_ERR(sysc_base)) {
>>>>> + ret = PTR_ERR(sysc_base);
>>>>> + if (ret != -EPROBE_DEFER)
>>>>> + dev_err(host->dev, "Can't get syscon: %d\n",
>>>>> + ret);
>>>>> + return ret;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
>>>>> + if (!priv)
>>>>> + return -ENOMEM;
>>>>> + priv->sysc_base = sysc_base;
>>>>
>>>> Is there no case that sysc_base is NULL?
>>>
>>> sysc_base is needed only for eMMC. So it is NULL for SD/MMC cases, and
>>> we can save memory for SD/MMC cases here :)
>>>
>>>>
>>>>> + host->priv = priv;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static unsigned long zx_dwmmc_caps[3] = {
>>>>> + MMC_CAP_CMD23,
>>>>> + MMC_CAP_CMD23,
>>>>> + MMC_CAP_CMD23,
>>>>> +};
>>>>> +
>>>>> +static const struct dw_mci_drv_data zx_drv_data = {
>>>>> + .caps = zx_dwmmc_caps,
>>>>> + .execute_tuning = dw_mci_zx_execute_tuning,
>>>>> + .prepare_hs400_tuning = dw_mci_zx_prepare_hs400_tuning,
>>>>> + .parse_dt = dw_mci_zx_parse_dt,
>>>>> +};
>>>>> +
>>>>> +static const struct of_device_id dw_mci_zx_match[] = {
>>>>> + { .compatible = "zte,dw-mshc", .data = &zx_drv_data},
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, dw_mci_zx_match);
>>>>> +
>>>>> +static int dw_mci_zx_probe(struct platform_device *pdev)
>>>>> +{
>>>>> + const struct dw_mci_drv_data *drv_data;
>>>>> + const struct of_device_id *match;
>>>>> +
>>>>> + match = of_match_node(dw_mci_zx_match, pdev->dev.of_node);
>>>>> + drv_data = match->data;
>>>>> +
>>>>> + return dw_mci_pltfm_register(pdev, drv_data);
>>>>> +}
>>>>> +
>>>>> +static const struct dev_pm_ops dw_mci_zx_dev_pm_ops = {
>>>>> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>>> + pm_runtime_force_resume)
>>>>> + SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
>>>>> + dw_mci_runtime_resume,
>>>>> + NULL)
>>>>> +};
>>>>> +
>>>>> +static struct platform_driver dw_mci_zx_pltfm_driver = {
>>>>> + .probe = dw_mci_zx_probe,
>>>>> + .remove = dw_mci_pltfm_remove,
>>>>> + .driver = {
>>>>> + .name = "dwmmc_zx",
>>>>> + .of_match_table = dw_mci_zx_match,
>>>>> + .pm = &dw_mci_zx_dev_pm_ops,
>>>>> + },
>>>>> +};
>>>>> +
>>>>> +module_platform_driver(dw_mci_zx_pltfm_driver);
>>>>> +
>>>>> +MODULE_DESCRIPTION("ZTE emmc/sd driver");
>>>>> +MODULE_LICENSE("GPL v2");
>>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.h b/drivers/mmc/host/dw_mmc-zx.h
>>>>> new file mode 100644
>>>>> index 0000000..b1aac52
>>>>> --- /dev/null
>>>>> +++ b/drivers/mmc/host/dw_mmc-zx.h
>>>>> @@ -0,0 +1,23 @@
>>>>> +#ifndef _DW_MMC_ZX_H_
>>>>> +#define _DW_MMC_ZX_H_
>>>>> +
>>>>> +/* dll reg offset*/
>>>>> +#define LB_AON_EMMC_CFG_REG0 0x1B0
>>>>> +#define LB_AON_EMMC_CFG_REG1 0x1B4
>>>>> +#define LB_AON_EMMC_CFG_REG2 0x1B8
>>>>> +
>>>>> +/* LB_AON_EMMC_CFG_REG0 register defines */
>>>>> +#define PARA_DLL_START_POINT(x) (((x) & 0xFF) << 0)
>>>>> +#define DLL_REG_SET BIT(8)
>>>>> +#define PARA_DLL_LOCK_NUM(x) (((x) & 7) << 16)
>>>>> +#define PARA_PHASE_DET_SEL(x) (((x) & 7) << 20)
>>>>> +#define PARA_DLL_BYPASS_MODE BIT(23)
>>>>> +#define PARA_HALF_CLK_MODE BIT(24)
>>>>
>>>> PAR_PHASE_DET_SEL/PARA_DLL_BYPASS_MODE_BIT/PARA_HALF_CLK_MODE are never used anywhere.
>>>>
>>>>> +
>>>>> +/* LB_AON_EMMC_CFG_REG1 register defines */
>>>>> +#define READ_DQS_DELAY(x) (((x) & 0x7F) << 0)
>>>>> +#define READ_DQS_BYPASS_MODE BIT(7)
>>>>> +#define CLK_SAMP_DELAY(x) (((x) & 0x7F) << 8)
>>>>> +#define CLK_SAMP_BYPASS_MODE BIT(15)
>>>>
>>>> Also READ_DQS_BYPASS_MODe/CLK_SAMP_BYBASS_MODE didnt used anywhere.
>>>>
>>>>
>>>> Hmm. These are not dwmmc host controller's register.
>>>> So If you needs to add these defines..I think you needs to add dessriptions in more detail.
>>>>
>>>> At least..Which board's DLL reg offset.
>>>>
>>>> Best Regards,
>>>> Jaehoon Chung
>>>>
>>>>> +
>>>>> +#endif /* _DW_MMC_ZX_H_ */
>>>>>
>>>>
>>>
>>>
>>>
>>
>
>
>
^ permalink raw reply
* sd card not working on intel compute stick STK1AW32SC
From: Giacomo Comes @ 2016-11-01 3:22 UTC (permalink / raw)
To: linux-mmc
I have a couple of cherryview PC.
One is a Acer Aspire R11 and the other is a
Intel compute stick STK1AW32SC
On the Acer Aspire, the mmc card reader works ok,
but on the compute stick it doesn't.
I'm running kernel 4.9rc3 and
when I insert a mmc card I see this message in the journal:
mmc1: error -84 whilst initialising SD card
whith another card instead I got this error message:
mmc1: card never left bust state
mmc1: error -110 whilst initialising SD card
Both sd cards work ok on other linux PC.
Can this be fixed?
Giacomo
^ permalink raw reply
* Re: Regression after "do not use CMD13 to get status after speed mode switch"
From: Chaotian Jing @ 2016-11-01 1:43 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ulf Hansson, Linus Walleij, linux-mmc@vger.kernel.org,
linux-arm-msm@vger.kernel.org, Bjorn Andersson, Stephen Boyd,
Andy Gross
In-Reply-To: <da9c6695-4dfb-6ba3-99f3-4751880d650d@intel.com>
On Mon, 2016-10-31 at 15:09 +0200, Adrian Hunter wrote:
> On 27/10/16 13:04, Ulf Hansson wrote:
> > On 20 October 2016 at 09:06, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> >> On 20 October 2016 at 04:22, Chaotian Jing <chaotian.jing@mediatek.com> wrote:
> >>> On Wed, 2016-10-19 at 18:41 +0200, Ulf Hansson wrote:
> >>>> Adrian, Linus,
> >>>>
> >>>> Thanks for looking into this and reporting!
> >>>>
> >>>> On 18 October 2016 at 15:23, Adrian Hunter <adrian.hunter@intel.com> wrote:
> >>>>> On 18/10/16 11:36, Linus Walleij wrote:
> >>>>>> On Mon, Oct 17, 2016 at 4:32 PM, Linus Walleij <linus.walleij@linaro.org> wrote:
> >>>>>>
> >>>>>>> Before this patch the eMMC is detected and all partitions enumerated
> >>>>>>> immediately, but after the patch it doesn't come up at all, except
> >>>>>>> sometimes, when it appears minutes (!) after boot, all of a sudden.
> >>>>>>
> >>>>>> FYI this is what it looks like when it eventually happens:
> >>>>>> root@msm8660:/ [ 627.710175] mmc0: new high speed MMC card at address 0001
> >>>>>> [ 627.711641] mmcblk0: mmc0:0001 SEM04G 3.69 GiB
> >>>>>> [ 627.715485] mmcblk0boot0: mmc0:0001 SEM04G partition 1 1.00 MiB
> >>>>>> [ 627.736654] mmcblk0boot1: mmc0:0001 SEM04G partition 2 1.00 MiB
> >>>>>> [ 627.747397] mmcblk0rpmb: mmc0:0001 SEM04G partition 3 128 KiB
> >>>>>> [ 627.756326] mmcblk0: p1 p2 p3 p4 < p5 p6 p7 p8 p9 p10 p11 p12 p13
> >>>>>> p14 p15 p16 p17 p18 p19 p20 p21 >
> >>>>>>
> >>>>>> So after 627 seconds, a bit hard for users to wait this long for their
> >>>>>> root filesystem.
> >>>>>
> >>>>> If the driver does not support busy detection and the eMMC card provides
> >>>>> zero as the cmd6 generic timeout (which it may especially as cmd6 generic
> >>>>> timeout wasn't added until eMMCv4.5), then __mmc_switch() defaults to
> >>>>> waiting 10 minutes i.e.
> >>>>>
> >>>>> #define MMC_OPS_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */
> >>>>
> >>>> Urgh! Yes, I have verified that this is exactly what happens.
> >>>>
> >>>>>
> >>>>> So removal of CMD13 polling for HS mode (as per commit
> >>>>> 08573eaf1a70104f83fdbee9b84e5be03480e9ed) is going to be a problem for some
> >>>>> combinations of eMMC cards and host drivers.
> >>>>
> >>>> I was looking in the __mmc_switch() function, it's just a pain to walk
> >>>> trough it :-) So first out I decided to clean it up and factor out the
> >>>> polling parts. I will post the patches first out tomorrow morning,
> >>>> running some final test right now.
> >>>>
> >>>> Although, that of course doesn't solve our problem. As I see it we
> >>>> only have a few options here.
> >>>>
> >>>> 1) In case when cmd6 generic timeout isn't available, let's assign
> >>>> another empirically selected value.
> >>>> 2) Use a specific timeout when switching to HS mode.
> >>>> 3) Even if we deploy 1 (and 2), perhaps we still should allow polling
> >>>> with CMD13 for switching to HS mode - unless it causes issues for some
> >>>> cards/drivers combination?
> >>>>
> >>>> BTW, I already tried 2) and it indeed solves the problem, although
> >>>> depending on the selected timeout, it might delay the card detection
> >>>> to process.
> >>>>
> >>>> Thoughts?
> >>>
> >>> I just have a try of switching to HS mode with Hynix EMMC, the first
> >>> CMD13 gets response of 0x900, but the EMMC is still pull-low DAT0. so
> >>> that CMD13 cannot indicate current card status in this case.
> >>
> >> Thanks for sharing that. Okay, so clearly we have some cards that
> >> don't supports polling with CMD13 when switching to HS mode.
> >> One could of course add quirks for these kind of cards and do a fixed
> >> delay for them, but then to find out which these cards are is going to
> >> be hard.
> >>
> >> It seems like we are left with using a fixed delay. Any ideas of what
> >> such delay should be? And should we have one specific for switch to
> >> the various speed modes and a different one that overrides the CMD6
> >> generic timout, when it doesn't exist?
> >>
> >
> > Replying to my own earlier response, as I believe the problem could
> > also be related to another old commit, see below.
> >
> > commit a27fbf2f067b0cd6f172c8b696b9a44c58bfaa7a
> > Author: Seungwon Jeon <tgih.jun@samsung.com>
> > Date: Wed Sep 4 21:21:05 2013 +0900
> >
> > mmc: add ignorance case for CMD13 CRC error
> >
> > While speed mode is changed, CMD13 cannot be guaranteed.
> > According to the spec., it is not recommended to use CMD13
> > to check the busy completion of the timing change.
> > If CMD13 is used in this case, CRC error must be ignored.
> >
> > Signed-off-by: Seungwon Jeon <tgih.jun@samsung.com>
> > Acked-by: Ulf Hansson <ulf.hansson@linaro.org>
> > Signed-off-by: Chris Ball <cjb@laptop.org>
> >
> >
> > The intent with this commit was not really correct. We don't want to
> > ignore CRC errors, but instead we should *re-try* sending CMD13 once
> > we get a CRC error.
> >
> > Unfortunate since this commit, instead we tell the host driver to
> > *ignore* CRC errors and instead reads the status and returns 0
> > (indicating success). In the mmc core, in __mmc_switch(), it will thus
> > parse the status reply, even for a reply that might have been received
> > with a CRC error. Not good!
>
> I agree: ignoring CRC errors and then expecting the status in the response
> to be correct doesn't make sense.
>
> However, it raises the question of what to do if there are always CRC errors
> e.g. if it only works without CRC errors once the mode and frequency are
> changed in the host controller.
>
> > I am wondering whether this actually is the main problem to why we
> > think polling isn't working for some cases. And perhaps that was the
> > original problem Chaotian was trying to solve?
> >
> > Thoughts?
>
> Does Chaotian have a real problem since his driver has busy detection anyway?
In fact, I have not encounter CRC errors of CMD13, I have tried several
eMMC cards, after mode switch, CMD13 will only gets 0x800 response and
we don't know if card is busy by 0x800 response.
^ permalink raw reply
* Re: [PATCH v2 2/5] mmc: zx: Initial support for ZX mmc controller
From: Jun Nie @ 2016-11-01 1:16 UTC (permalink / raw)
To: Jaehoon Chung; +Cc: Shawn Guo, xie.baoyou, Ulf Hansson, Jason Liu, linux-mmc
In-Reply-To: <efa23148-caa2-e9ef-d3c4-f2a6e1d9a4c5@samsung.com>
2016-10-31 17:40 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
> On 10/31/2016 05:47 PM, Jun Nie wrote:
>> 2016-10-28 13:16 GMT+08:00 Jaehoon Chung <jh80.chung@samsung.com>:
>>> On 10/28/2016 11:37 AM, Jun Nie wrote:
>>>> This platform driver adds initial support for the DW host controller
>>>> found on ZTE SoCs.
>>>>
>>>> It has been tested on ZX296718 EVB board currently. More support on
>>>> timing tuning will be added when hardware is available.
>>>>
>>>> Signed-off-by: Jun Nie <jun.nie@linaro.org>
>>>> ---
>>>> drivers/mmc/host/Kconfig | 9 ++
>>>> drivers/mmc/host/Makefile | 1 +
>>>> drivers/mmc/host/dw_mmc-zx.c | 230 +++++++++++++++++++++++++++++++++++++++++++
>>>> drivers/mmc/host/dw_mmc-zx.h | 23 +++++
>>>> 4 files changed, 263 insertions(+)
>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.c
>>>> create mode 100644 drivers/mmc/host/dw_mmc-zx.h
>>>>
>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>>> index 5274f50..2b3202c 100644
>>>> --- a/drivers/mmc/host/Kconfig
>>>> +++ b/drivers/mmc/host/Kconfig
>>>> @@ -662,6 +662,15 @@ config MMC_DW_ROCKCHIP
>>>> Synopsys DesignWare Memory Card Interface driver. Select this option
>>>> for platforms based on RK3066, RK3188 and RK3288 SoC's.
>>>>
>>>> +config MMC_DW_ZX
>>>> + tristate "ZTE specific extensions for Synopsys DW Memory Card Interface"
>>>> + depends on MMC_DW
>>>
>>> I guess MMC_DW_ZX depends on your SoC config, doesn't?
>>
>> Right, will add dependency.
>>
>>>
>>>> + select MMC_DW_PLTFM
>>>> + help
>>>> + This selects support for ZTE SoC specific extensions to the
>>>> + Synopsys DesignWare Memory Card Interface driver. Select this option
>>>> + for platforms based on ZX296718 SoC's.
>>>> +
>>>> config MMC_SH_MMCIF
>>>> tristate "SuperH Internal MMCIF support"
>>>> depends on HAS_DMA
>>>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>>>> index e2bdaaf..9766143 100644
>>>> --- a/drivers/mmc/host/Makefile
>>>> +++ b/drivers/mmc/host/Makefile
>>>> @@ -48,6 +48,7 @@ obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
>>>> obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o
>>>> obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
>>>> obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
>>>> +obj-$(CONFIG_MMC_DW_ZX) += dw_mmc-zx.o
>>>> obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
>>>> obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
>>>> obj-$(CONFIG_MMC_VUB300) += vub300.o
>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.c b/drivers/mmc/host/dw_mmc-zx.c
>>>> new file mode 100644
>>>> index 0000000..0404f8e
>>>> --- /dev/null
>>>> +++ b/drivers/mmc/host/dw_mmc-zx.c
>>>> @@ -0,0 +1,230 @@
>>>> +/*
>>>> + * ZX Specific Extensions for Synopsys DW Multimedia Card Interface driver
>>>> + *
>>>> + * Copyright (C) 2016, Linaro Ltd.
>>>> + * Copyright (C) 2016, ZTE Corp.
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify
>>>> + * it under the terms of the GNU General Public License as published by
>>>> + * the Free Software Foundation; either version 2 of the License, or
>>>> + * (at your option) any later version.
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/mfd/syscon.h>
>>>> +#include <linux/mmc/dw_mmc.h>
>>>> +#include <linux/mmc/host.h>
>>>> +#include <linux/mmc/mmc.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <linux/slab.h>
>>>> +
>>>> +#include "dw_mmc.h"
>>>> +#include "dw_mmc-pltfm.h"
>>>> +#include "dw_mmc-zx.h"
>>>> +
>>>> +#define ZX_DLL_LOCKED BIT(2)
>>>
>>> Some DLL rigsters and bits are defined in dw_mmc-zx.h.
>>> why defined ZX_DLL_LOCKED at here.
>>>
>>> You can choose that all defines locates to dw_mmc-zx.c or dw_mmc-zx.h
>>>
>> Will move together.
>>
>>>> +
>>>> +struct dw_mci_zx_priv_data {
>>>> + struct regmap *sysc_base;
>>>> +};
>>>> +
>>>> +static int dw_mci_zx_emmc_set_delay(struct dw_mci *host, unsigned int delay,
>>>> + unsigned int clk_flag)
>>>
>>> Why do you use "unsigned int" as clk_flag? It's just use one of 0 and 1.
>>> And 0 and 1 are what means? On/Off?
>>>
>> Yes, enumeration shall looks better here. It is a flag for different
>> delay type. Will change to enumeration.
>>
>>>> +{
>>>> + struct dw_mci_zx_priv_data *priv = host->priv;
>>>> + struct regmap *sysc_base = priv->sysc_base;
>>>> + unsigned int clksel;
>>>> + unsigned int loop = 1000;
>>>> + int ret;
>>>> +
>>>
>>> priv->sysc_base doesn't never NULL?
>>
>> For this SoC, it is never NULL if dts is correct. Adding a check is
>> better anyway.
>>>
>>>> + ret = regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4));
>>>
>>> Could you add the comment for controlling this regs?
>>> I'm not sure because i didn't have ZX TRM..but PARA_DLL_LOCK_NUM should be locked?
>>>
>>> It doesn't affect to other bit?
>>> I think you can use the regmap_update_bits instead of regmap_write.
>>
>> It does not affect other bit as all bits are write as desired. But
>> your suggestion make it clearer.
>>
>>>
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG1, &clksel);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + if (clk_flag) {
>>>> + clksel &= ~(CLK_SAMP_DELAY(0x7F));
>>>
>>> It's meaningless..CLK_SAMP_DELAY used only at here.
>>> CLK_SAMP_DELAY ((x) & 0x7F << 0)
>>>
>>> It's just CLK_SAMP_DELAY_MASK.?
>>>
>>> #define CLK_SAMP_DELAY_MASK (0x7F << 0)
>>> clksel &= ~CLK_SAMP_DELAY_MASK;
>>>
>> Right, clearer.
>>
>>>
>>>> + clksel |= (delay << 8);
>>>
>>> Use the CLK_SAMP_DELAY_SHIFT instead of 8.
>>>
>>>> + } else {
>>>> + clksel &= ~(READ_DQS_DELAY(0x7F));
>>>
>>> Ditto.
>>> And i think it also can be changed to regmap_update_bits.
>>>
>>>> + clksel |= delay;
>>>> + }
>>>> +
>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG1, clksel);
>>>> + regmap_write(sysc_base, LB_AON_EMMC_CFG_REG0,
>>>> + PARA_DLL_START_POINT(4) | PARA_DLL_LOCK_NUM(4) |
>>>> + DLL_REG_SET);
>>>
>>> regmap_update_bits?
>>
>> Will do.
>>
>>>
>>>> +
>>>> + do {
>>>> + ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG2, &clksel);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + } while (--loop && !(clksel & ZX_DLL_LOCKED));
>>>> +
>>>> + if (!loop) {
>>>> + dev_err(host->dev, "Error: %s dll lock fail\n", __func__);
>>>> + return -EIO;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int dw_mci_zx_emmc_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>> +{
>>>> + struct dw_mci *host = slot->host;
>>>> + struct mmc_host *mmc = slot->mmc;
>>>> + int len, start = 0, end = 0, delay, best = 0;
>>>> + int ret = 0;
>>>> +
>>>> + for (delay = 1 ; delay < 128; delay++) {
>>>> + ret = dw_mci_zx_emmc_set_delay(host, delay, 1);
>>>> + if (ret)
>>>> + return ret;
>>>
>>> When it's failed, just returned.
>>> Doesn't need to try with next delay value?
>>
>> Yes, more retry make robust.
>>
>>>
>>>> +
>>>> + if (mmc_send_tuning(mmc, opcode, NULL)) {
>>>> + if (start >= 0) {
>>>> + end = delay - 1;
>>>> + /* check and update longest good range */
>>>> + if ((end - start) > len) {
>>>> + best = (start + end) >> 1;
>>>> + len = end - start;
>>>> + }
>>>> + }
>>>> + start = -1;
>>>> + end = 0;
>>>> + continue;
>>>> + }
>>>> + if (start < 0)
>>>> + start = delay;
>>>> + }
>>>> +
>>>> + if (start >= 0) {
>>>> + end = delay - 1;
>>>> + if ((end - start) > len) {
>>>> + best = (start + end) >> 1;
>>>> + len = end - start;
>>>> + }
>>>> + }
>>>> + if (best < 0)
>>>> + return -EIO;
>>>> +
>>>> + dev_info(host->dev, "%s best range: start %d end %d\n", __func__,
>>>> + start, end);
>>>> + dw_mci_zx_emmc_set_delay(host, best, 1);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int dw_mci_zx_prepare_hs400_tuning(struct dw_mci *host,
>>>> + struct mmc_ios *ios)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + /* config phase shift 90 */
>>>> + ret = dw_mci_zx_emmc_set_delay(host, 32, 0);
>>>
>>> It's always fixed to 32? What means 32?
>>
>> It means 90 degree shift for value 32. This configuration comes from
>> ZTE engineer as I do not have hardware for this tuning. Let's just
>> keep it with adding more comments till tuning is needed.
>>
>>>
>>>> + if (ret < 0)
>>>> + return -EIO;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int dw_mci_zx_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
>>>> +{
>>>> + struct dw_mci *host = slot->host;
>>>> +
>>>> + if (host->verid == 0x290a) /* emmc */
>>>> + return dw_mci_zx_emmc_execute_tuning(slot, opcode);
>>>
>>> I didn't know why you check host->verid is 2.90a..
>>> Is there any reason?
>> There are two version DW MMC IP on this SoC and different
>> configuration is needed for them. I do not have hardware for tuning
>> version 210A timing and will be added later.
>
> you means there are two IP version with same SoC?
>
> Best Regards,
> Jaehoon Chung
Right, two version IP exist on the same SoC.
>
>>
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int dw_mci_zx_parse_dt(struct dw_mci *host)
>>>> +{
>>>> + struct device_node *np = host->dev->of_node;
>>>> + struct device_node *node;
>>>> + struct dw_mci_zx_priv_data *priv;
>>>> + struct regmap *sysc_base;
>>>> + int ret;
>>>> +
>>>> + node = of_parse_phandle(np, "zte,aon-syscon", 0);
>>>> + if (node) {
>>>> + sysc_base = syscon_node_to_regmap(node);
>>>> + of_node_put(node);
>>>
>>> Use the syscon_regmap_lookup_by_phandle(). It's same behavior.
>> Will do.
>>>
>>>> +
>>>> + if (IS_ERR(sysc_base)) {
>>>> + ret = PTR_ERR(sysc_base);
>>>> + if (ret != -EPROBE_DEFER)
>>>> + dev_err(host->dev, "Can't get syscon: %d\n",
>>>> + ret);
>>>> + return ret;
>>>> + }
>>>> + }
>>>> +
>>>> + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
>>>> + if (!priv)
>>>> + return -ENOMEM;
>>>> + priv->sysc_base = sysc_base;
>>>
>>> Is there no case that sysc_base is NULL?
>>
>> sysc_base is needed only for eMMC. So it is NULL for SD/MMC cases, and
>> we can save memory for SD/MMC cases here :)
>>
>>>
>>>> + host->priv = priv;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static unsigned long zx_dwmmc_caps[3] = {
>>>> + MMC_CAP_CMD23,
>>>> + MMC_CAP_CMD23,
>>>> + MMC_CAP_CMD23,
>>>> +};
>>>> +
>>>> +static const struct dw_mci_drv_data zx_drv_data = {
>>>> + .caps = zx_dwmmc_caps,
>>>> + .execute_tuning = dw_mci_zx_execute_tuning,
>>>> + .prepare_hs400_tuning = dw_mci_zx_prepare_hs400_tuning,
>>>> + .parse_dt = dw_mci_zx_parse_dt,
>>>> +};
>>>> +
>>>> +static const struct of_device_id dw_mci_zx_match[] = {
>>>> + { .compatible = "zte,dw-mshc", .data = &zx_drv_data},
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, dw_mci_zx_match);
>>>> +
>>>> +static int dw_mci_zx_probe(struct platform_device *pdev)
>>>> +{
>>>> + const struct dw_mci_drv_data *drv_data;
>>>> + const struct of_device_id *match;
>>>> +
>>>> + match = of_match_node(dw_mci_zx_match, pdev->dev.of_node);
>>>> + drv_data = match->data;
>>>> +
>>>> + return dw_mci_pltfm_register(pdev, drv_data);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops dw_mci_zx_dev_pm_ops = {
>>>> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>>> + pm_runtime_force_resume)
>>>> + SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
>>>> + dw_mci_runtime_resume,
>>>> + NULL)
>>>> +};
>>>> +
>>>> +static struct platform_driver dw_mci_zx_pltfm_driver = {
>>>> + .probe = dw_mci_zx_probe,
>>>> + .remove = dw_mci_pltfm_remove,
>>>> + .driver = {
>>>> + .name = "dwmmc_zx",
>>>> + .of_match_table = dw_mci_zx_match,
>>>> + .pm = &dw_mci_zx_dev_pm_ops,
>>>> + },
>>>> +};
>>>> +
>>>> +module_platform_driver(dw_mci_zx_pltfm_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("ZTE emmc/sd driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> diff --git a/drivers/mmc/host/dw_mmc-zx.h b/drivers/mmc/host/dw_mmc-zx.h
>>>> new file mode 100644
>>>> index 0000000..b1aac52
>>>> --- /dev/null
>>>> +++ b/drivers/mmc/host/dw_mmc-zx.h
>>>> @@ -0,0 +1,23 @@
>>>> +#ifndef _DW_MMC_ZX_H_
>>>> +#define _DW_MMC_ZX_H_
>>>> +
>>>> +/* dll reg offset*/
>>>> +#define LB_AON_EMMC_CFG_REG0 0x1B0
>>>> +#define LB_AON_EMMC_CFG_REG1 0x1B4
>>>> +#define LB_AON_EMMC_CFG_REG2 0x1B8
>>>> +
>>>> +/* LB_AON_EMMC_CFG_REG0 register defines */
>>>> +#define PARA_DLL_START_POINT(x) (((x) & 0xFF) << 0)
>>>> +#define DLL_REG_SET BIT(8)
>>>> +#define PARA_DLL_LOCK_NUM(x) (((x) & 7) << 16)
>>>> +#define PARA_PHASE_DET_SEL(x) (((x) & 7) << 20)
>>>> +#define PARA_DLL_BYPASS_MODE BIT(23)
>>>> +#define PARA_HALF_CLK_MODE BIT(24)
>>>
>>> PAR_PHASE_DET_SEL/PARA_DLL_BYPASS_MODE_BIT/PARA_HALF_CLK_MODE are never used anywhere.
>>>
>>>> +
>>>> +/* LB_AON_EMMC_CFG_REG1 register defines */
>>>> +#define READ_DQS_DELAY(x) (((x) & 0x7F) << 0)
>>>> +#define READ_DQS_BYPASS_MODE BIT(7)
>>>> +#define CLK_SAMP_DELAY(x) (((x) & 0x7F) << 8)
>>>> +#define CLK_SAMP_BYPASS_MODE BIT(15)
>>>
>>> Also READ_DQS_BYPASS_MODe/CLK_SAMP_BYBASS_MODE didnt used anywhere.
>>>
>>>
>>> Hmm. These are not dwmmc host controller's register.
>>> So If you needs to add these defines..I think you needs to add dessriptions in more detail.
>>>
>>> At least..Which board's DLL reg offset.
>>>
>>> Best Regards,
>>> Jaehoon Chung
>>>
>>>> +
>>>> +#endif /* _DW_MMC_ZX_H_ */
>>>>
>>>
>>
>>
>>
>
^ permalink raw reply
* Re: [RFC 1/2] mmc: sdhci: dt: Add device tree properties sdhci-caps and sdhci-caps-mask
From: Jaehoon Chung @ 2016-10-31 22:13 UTC (permalink / raw)
To: Adrian Hunter, Ulf Hansson, Zach Brown
Cc: Rob Herring, Mark Rutland, linux-mmc, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <7bc4cb38-6864-c87e-390e-7f9f3f133dd6@intel.com>
On 10/31/2016 09:34 PM, Adrian Hunter wrote:
> On 31/10/16 13:59, Jaehoon Chung wrote:
>> On 10/28/2016 05:12 PM, Ulf Hansson wrote:
>>> On 25 October 2016 at 21:58, Zach Brown <zach.brown@ni.com> wrote:
>>>> On some systems the sdhci capabilty registers are incorrect for one
>>>> reason or another.
>>>>
>>>> The sdhci-caps-mask property specifies which bits in the registers
>>>> are incorrect and should be turned off before using sdhci-caps to turn
>>>> on bits.
>>>>
>>>> The sdhci-caps property specifies which bits should be turned on.
>>>>
>>>> Signed-off-by: Zach Brown <zach.brown@ni.com>
>>>> ---
>>>> Documentation/devicetree/bindings/mmc/mmc.txt | 7 +++++++
>>>> 1 file changed, 7 insertions(+)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt
>>>> index 8a37782..1415aa0 100644
>>>> --- a/Documentation/devicetree/bindings/mmc/mmc.txt
>>>> +++ b/Documentation/devicetree/bindings/mmc/mmc.txt
>>>
>>> The bindings in this document are common mmc DT bindings, not bindings
>>> specific to a mmc controller.
>>>
>>> So unless these bindings are applicable for another controller than
>>> sdhci, I suggest we create a new file to document these.
>>> How about Documentation/devicetree/bindings/mmc/sdhci.txt?
>>>
>>>> @@ -52,6 +52,13 @@ Optional properties:
>>>> - no-sdio: controller is limited to send sdio cmd during initialization
>>>> - no-sd: controller is limited to send sd cmd during initialization
>>>> - no-mmc: controller is limited to send mmc cmd during initialization
>>>> +- sdhci-caps-mask: The sdhci capabilities registers are incorrect. This 64bit
>>>
>>> /s/registers/register
>>>
>>> This applies to some more places below as well.
>>>
>>>> + property corresponds to the bits in the sdhci capabilty registers. If the bit
>>>> + is on in the mask then the bit is incorrect in the registers and should be
>>>> + turned off.
>>>> +- sdhci-caps: The sdhci capabilities registers are incorrect. This 64bit
>>>> + property corresponds to the bits in the sdhci capability registers. If the
>>>> + bit is on in the property then the bit should be on in the reigsters.
>>>
>>> /s/reigsters/register
>>>
>>>>
>>>> *NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
>>>> polarity properties, we have to fix the meaning of the "normal" and "inverted"
>>>> --
>>>> 2.7.4
>>>>
>>>
>>> Overall, I like this idea as it gives us good flexibility. Thus it
>>> should avoid us to having to add any further new similar "sdhci broken
>>> cap" DT binding. We could also decide to start deprecate some of the
>>> existing sdhci bindings, if we think that makes sense.
>>>
>>> The downside is that we get a "magic" hex value in the dts. Although,
>>> people could address this issue by providing some comments about what
>>> the bits it means in the dts files themselves.
>>
>> I think it's not good about getting "magic" hex value.
>> In my experience, it's too difficult what bits means and calculate..
>> Because some people who i know had already used like this.(locally..)
>>
>> It needs to consider this...otherwise..it should become really complex magic code.
>
> The bits we use are listed in sdhci.h and how we use them can be determined
> from the sdhci source code. Also, from the hardware perspective, there is
> the SDHCI specification. So what the bits mean is readily available.
>
> With regard to calculating the values, won't it be obvious from testing if
> they are wrong?
You're right. But I didn't see the real use case for this properties.
If it needs to add these properties, why didn't add codes relevant to these in device-tree?
Otherwise, this code should be dead code.
Best Regards,
Jaehoon Chung
>
>>
>> Best Regards,
>> Jaehoon Chung
>>
>>>
>>> Let's see what Rob thinks about this.
>>>
>>> Kind regards
>>> Uffe
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>>>
>>>
>>
>>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
^ permalink raw reply
* Re: Regression after "do not use CMD13 to get status after speed mode switch"
From: Adrian Hunter @ 2016-10-31 13:09 UTC (permalink / raw)
To: Ulf Hansson, Chaotian Jing
Cc: Linus Walleij, linux-mmc@vger.kernel.org,
linux-arm-msm@vger.kernel.org, Bjorn Andersson, Stephen Boyd,
Andy Gross
In-Reply-To: <CAPDyKFpQn+Es6RwiYp319B1KcUduQ_fa2ou6V_2TrQhEsgV4Jw@mail.gmail.com>
On 27/10/16 13:04, Ulf Hansson wrote:
> On 20 October 2016 at 09:06, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>> On 20 October 2016 at 04:22, Chaotian Jing <chaotian.jing@mediatek.com> wrote:
>>> On Wed, 2016-10-19 at 18:41 +0200, Ulf Hansson wrote:
>>>> Adrian, Linus,
>>>>
>>>> Thanks for looking into this and reporting!
>>>>
>>>> On 18 October 2016 at 15:23, Adrian Hunter <adrian.hunter@intel.com> wrote:
>>>>> On 18/10/16 11:36, Linus Walleij wrote:
>>>>>> On Mon, Oct 17, 2016 at 4:32 PM, Linus Walleij <linus.walleij@linaro.org> wrote:
>>>>>>
>>>>>>> Before this patch the eMMC is detected and all partitions enumerated
>>>>>>> immediately, but after the patch it doesn't come up at all, except
>>>>>>> sometimes, when it appears minutes (!) after boot, all of a sudden.
>>>>>>
>>>>>> FYI this is what it looks like when it eventually happens:
>>>>>> root@msm8660:/ [ 627.710175] mmc0: new high speed MMC card at address 0001
>>>>>> [ 627.711641] mmcblk0: mmc0:0001 SEM04G 3.69 GiB
>>>>>> [ 627.715485] mmcblk0boot0: mmc0:0001 SEM04G partition 1 1.00 MiB
>>>>>> [ 627.736654] mmcblk0boot1: mmc0:0001 SEM04G partition 2 1.00 MiB
>>>>>> [ 627.747397] mmcblk0rpmb: mmc0:0001 SEM04G partition 3 128 KiB
>>>>>> [ 627.756326] mmcblk0: p1 p2 p3 p4 < p5 p6 p7 p8 p9 p10 p11 p12 p13
>>>>>> p14 p15 p16 p17 p18 p19 p20 p21 >
>>>>>>
>>>>>> So after 627 seconds, a bit hard for users to wait this long for their
>>>>>> root filesystem.
>>>>>
>>>>> If the driver does not support busy detection and the eMMC card provides
>>>>> zero as the cmd6 generic timeout (which it may especially as cmd6 generic
>>>>> timeout wasn't added until eMMCv4.5), then __mmc_switch() defaults to
>>>>> waiting 10 minutes i.e.
>>>>>
>>>>> #define MMC_OPS_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */
>>>>
>>>> Urgh! Yes, I have verified that this is exactly what happens.
>>>>
>>>>>
>>>>> So removal of CMD13 polling for HS mode (as per commit
>>>>> 08573eaf1a70104f83fdbee9b84e5be03480e9ed) is going to be a problem for some
>>>>> combinations of eMMC cards and host drivers.
>>>>
>>>> I was looking in the __mmc_switch() function, it's just a pain to walk
>>>> trough it :-) So first out I decided to clean it up and factor out the
>>>> polling parts. I will post the patches first out tomorrow morning,
>>>> running some final test right now.
>>>>
>>>> Although, that of course doesn't solve our problem. As I see it we
>>>> only have a few options here.
>>>>
>>>> 1) In case when cmd6 generic timeout isn't available, let's assign
>>>> another empirically selected value.
>>>> 2) Use a specific timeout when switching to HS mode.
>>>> 3) Even if we deploy 1 (and 2), perhaps we still should allow polling
>>>> with CMD13 for switching to HS mode - unless it causes issues for some
>>>> cards/drivers combination?
>>>>
>>>> BTW, I already tried 2) and it indeed solves the problem, although
>>>> depending on the selected timeout, it might delay the card detection
>>>> to process.
>>>>
>>>> Thoughts?
>>>
>>> I just have a try of switching to HS mode with Hynix EMMC, the first
>>> CMD13 gets response of 0x900, but the EMMC is still pull-low DAT0. so
>>> that CMD13 cannot indicate current card status in this case.
>>
>> Thanks for sharing that. Okay, so clearly we have some cards that
>> don't supports polling with CMD13 when switching to HS mode.
>> One could of course add quirks for these kind of cards and do a fixed
>> delay for them, but then to find out which these cards are is going to
>> be hard.
>>
>> It seems like we are left with using a fixed delay. Any ideas of what
>> such delay should be? And should we have one specific for switch to
>> the various speed modes and a different one that overrides the CMD6
>> generic timout, when it doesn't exist?
>>
>
> Replying to my own earlier response, as I believe the problem could
> also be related to another old commit, see below.
>
> commit a27fbf2f067b0cd6f172c8b696b9a44c58bfaa7a
> Author: Seungwon Jeon <tgih.jun@samsung.com>
> Date: Wed Sep 4 21:21:05 2013 +0900
>
> mmc: add ignorance case for CMD13 CRC error
>
> While speed mode is changed, CMD13 cannot be guaranteed.
> According to the spec., it is not recommended to use CMD13
> to check the busy completion of the timing change.
> If CMD13 is used in this case, CRC error must be ignored.
>
> Signed-off-by: Seungwon Jeon <tgih.jun@samsung.com>
> Acked-by: Ulf Hansson <ulf.hansson@linaro.org>
> Signed-off-by: Chris Ball <cjb@laptop.org>
>
>
> The intent with this commit was not really correct. We don't want to
> ignore CRC errors, but instead we should *re-try* sending CMD13 once
> we get a CRC error.
>
> Unfortunate since this commit, instead we tell the host driver to
> *ignore* CRC errors and instead reads the status and returns 0
> (indicating success). In the mmc core, in __mmc_switch(), it will thus
> parse the status reply, even for a reply that might have been received
> with a CRC error. Not good!
I agree: ignoring CRC errors and then expecting the status in the response
to be correct doesn't make sense.
However, it raises the question of what to do if there are always CRC errors
e.g. if it only works without CRC errors once the mode and frequency are
changed in the host controller.
> I am wondering whether this actually is the main problem to why we
> think polling isn't working for some cases. And perhaps that was the
> original problem Chaotian was trying to solve?
>
> Thoughts?
Does Chaotian have a real problem since his driver has busy detection anyway?
^ permalink raw reply
* Re: [RFC 1/2] mmc: sdhci: dt: Add device tree properties sdhci-caps and sdhci-caps-mask
From: Adrian Hunter @ 2016-10-31 12:34 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson, Zach Brown
Cc: Rob Herring, Mark Rutland, linux-mmc,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <6031045c-0eb2-7bea-1efd-874dade0f009-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
On 31/10/16 13:59, Jaehoon Chung wrote:
> On 10/28/2016 05:12 PM, Ulf Hansson wrote:
>> On 25 October 2016 at 21:58, Zach Brown <zach.brown-acOepvfBmUk@public.gmane.org> wrote:
>>> On some systems the sdhci capabilty registers are incorrect for one
>>> reason or another.
>>>
>>> The sdhci-caps-mask property specifies which bits in the registers
>>> are incorrect and should be turned off before using sdhci-caps to turn
>>> on bits.
>>>
>>> The sdhci-caps property specifies which bits should be turned on.
>>>
>>> Signed-off-by: Zach Brown <zach.brown-acOepvfBmUk@public.gmane.org>
>>> ---
>>> Documentation/devicetree/bindings/mmc/mmc.txt | 7 +++++++
>>> 1 file changed, 7 insertions(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt
>>> index 8a37782..1415aa0 100644
>>> --- a/Documentation/devicetree/bindings/mmc/mmc.txt
>>> +++ b/Documentation/devicetree/bindings/mmc/mmc.txt
>>
>> The bindings in this document are common mmc DT bindings, not bindings
>> specific to a mmc controller.
>>
>> So unless these bindings are applicable for another controller than
>> sdhci, I suggest we create a new file to document these.
>> How about Documentation/devicetree/bindings/mmc/sdhci.txt?
>>
>>> @@ -52,6 +52,13 @@ Optional properties:
>>> - no-sdio: controller is limited to send sdio cmd during initialization
>>> - no-sd: controller is limited to send sd cmd during initialization
>>> - no-mmc: controller is limited to send mmc cmd during initialization
>>> +- sdhci-caps-mask: The sdhci capabilities registers are incorrect. This 64bit
>>
>> /s/registers/register
>>
>> This applies to some more places below as well.
>>
>>> + property corresponds to the bits in the sdhci capabilty registers. If the bit
>>> + is on in the mask then the bit is incorrect in the registers and should be
>>> + turned off.
>>> +- sdhci-caps: The sdhci capabilities registers are incorrect. This 64bit
>>> + property corresponds to the bits in the sdhci capability registers. If the
>>> + bit is on in the property then the bit should be on in the reigsters.
>>
>> /s/reigsters/register
>>
>>>
>>> *NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
>>> polarity properties, we have to fix the meaning of the "normal" and "inverted"
>>> --
>>> 2.7.4
>>>
>>
>> Overall, I like this idea as it gives us good flexibility. Thus it
>> should avoid us to having to add any further new similar "sdhci broken
>> cap" DT binding. We could also decide to start deprecate some of the
>> existing sdhci bindings, if we think that makes sense.
>>
>> The downside is that we get a "magic" hex value in the dts. Although,
>> people could address this issue by providing some comments about what
>> the bits it means in the dts files themselves.
>
> I think it's not good about getting "magic" hex value.
> In my experience, it's too difficult what bits means and calculate..
> Because some people who i know had already used like this.(locally..)
>
> It needs to consider this...otherwise..it should become really complex magic code.
The bits we use are listed in sdhci.h and how we use them can be determined
from the sdhci source code. Also, from the hardware perspective, there is
the SDHCI specification. So what the bits mean is readily available.
With regard to calculating the values, won't it be obvious from testing if
they are wrong?
>
> Best Regards,
> Jaehoon Chung
>
>>
>> Let's see what Rob thinks about this.
>>
>> Kind regards
>> Uffe
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
>> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>>
>>
>
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC 1/2] mmc: sdhci: dt: Add device tree properties sdhci-caps and sdhci-caps-mask
From: Jaehoon Chung @ 2016-10-31 11:59 UTC (permalink / raw)
To: Ulf Hansson, Zach Brown
Cc: Adrian Hunter, Rob Herring, Mark Rutland, linux-mmc,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <CAPDyKFpKoXd9yuX5vR-CACtXKGysmV0AqKnjxQ+hTGU+kgwceQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On 10/28/2016 05:12 PM, Ulf Hansson wrote:
> On 25 October 2016 at 21:58, Zach Brown <zach.brown-acOepvfBmUk@public.gmane.org> wrote:
>> On some systems the sdhci capabilty registers are incorrect for one
>> reason or another.
>>
>> The sdhci-caps-mask property specifies which bits in the registers
>> are incorrect and should be turned off before using sdhci-caps to turn
>> on bits.
>>
>> The sdhci-caps property specifies which bits should be turned on.
>>
>> Signed-off-by: Zach Brown <zach.brown-acOepvfBmUk@public.gmane.org>
>> ---
>> Documentation/devicetree/bindings/mmc/mmc.txt | 7 +++++++
>> 1 file changed, 7 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt
>> index 8a37782..1415aa0 100644
>> --- a/Documentation/devicetree/bindings/mmc/mmc.txt
>> +++ b/Documentation/devicetree/bindings/mmc/mmc.txt
>
> The bindings in this document are common mmc DT bindings, not bindings
> specific to a mmc controller.
>
> So unless these bindings are applicable for another controller than
> sdhci, I suggest we create a new file to document these.
> How about Documentation/devicetree/bindings/mmc/sdhci.txt?
>
>> @@ -52,6 +52,13 @@ Optional properties:
>> - no-sdio: controller is limited to send sdio cmd during initialization
>> - no-sd: controller is limited to send sd cmd during initialization
>> - no-mmc: controller is limited to send mmc cmd during initialization
>> +- sdhci-caps-mask: The sdhci capabilities registers are incorrect. This 64bit
>
> /s/registers/register
>
> This applies to some more places below as well.
>
>> + property corresponds to the bits in the sdhci capabilty registers. If the bit
>> + is on in the mask then the bit is incorrect in the registers and should be
>> + turned off.
>> +- sdhci-caps: The sdhci capabilities registers are incorrect. This 64bit
>> + property corresponds to the bits in the sdhci capability registers. If the
>> + bit is on in the property then the bit should be on in the reigsters.
>
> /s/reigsters/register
>
>>
>> *NOTE* on CD and WP polarity. To use common for all SD/MMC host controllers line
>> polarity properties, we have to fix the meaning of the "normal" and "inverted"
>> --
>> 2.7.4
>>
>
> Overall, I like this idea as it gives us good flexibility. Thus it
> should avoid us to having to add any further new similar "sdhci broken
> cap" DT binding. We could also decide to start deprecate some of the
> existing sdhci bindings, if we think that makes sense.
>
> The downside is that we get a "magic" hex value in the dts. Although,
> people could address this issue by providing some comments about what
> the bits it means in the dts files themselves.
I think it's not good about getting "magic" hex value.
In my experience, it's too difficult what bits means and calculate..
Because some people who i know had already used like this.(locally..)
It needs to consider this...otherwise..it should become really complex magic code.
Best Regards,
Jaehoon Chung
>
> Let's see what Rob thinks about this.
>
> Kind regards
> Uffe
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 1/3] mmc: replacing hardcoded value for runtime PM suspend
From: Alex Lemberg @ 2016-10-31 9:53 UTC (permalink / raw)
To: Shawn Lin, Ulf Hansson; +Cc: linux-mmc@vger.kernel.org, Avi Shchislowski
In-Reply-To: <9277d105-9603-c011-25c4-083590fb4709@rock-chips.com>
Great. Thanks for the information!
On 10/13/16, 11:30 AM, "Shawn Lin" <shawn.lin@rock-chips.com> wrote:
>在 2016/10/13 15:36, Ulf Hansson 写道:
>> On 4 September 2016 at 15:30, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
>>> Hi Shawn,
>>>
>>> On 9/2/16, 10:39 AM, "Shawn Lin" <shawn.lin@rock-chips.com> wrote:
>>>
>>>> Hi
>>>>
>>>> On 2016/9/1 22:24, alex lemberg wrote:
>>>>> Add MMC_AUTOSUSPEND_DELAY_MS define
>>>>
>>>> I'm thinking should we expose it to userspace
>>>> via sysfs, since I don't know why it should be 3ms, not 4ms, 5ms, etc.
>>>
>>> Agree, we can consider exposing it to the user space.
>>> The exposure to the user space is less critical to the current
>>> patchset, but we can do it as a “follow-up change” once its approved.
>>
>> No need for that, is already available for user-space. :-)
>>
>> Each device with runtime PM autosuspend enabled, allows userspace to
>> change the delay via the "autosuspend_delay_ms" sysfs file.
>
>Indeed, I just check it when enabling runtime PM for dw_mmc and could
>able to change the value of autosuspend_delay_ms.
>
>Thanks for sharing this.
>
>>
>> Kind regards
>> Uffe
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
>
>--
>Best Regards
>Shawn Lin
>
^ permalink raw reply
* Re: [PATCH 1/3] mmc: replacing hardcoded value for runtime PM suspend
From: Alex Lemberg @ 2016-10-31 9:53 UTC (permalink / raw)
To: Ulf Hansson; +Cc: linux-mmc, Avi Shchislowski
In-Reply-To: <CAPDyKFoAxiYWYOYZeYByXOwbK_k0Mgq_LCCqp3t2NJJmV4p1kQ@mail.gmail.com>
Thanks!
On 10/13/16, 10:36 AM, "Ulf Hansson" <ulf.hansson@linaro.org> wrote:
>On 1 September 2016 at 07:12, alex.lemberg <alex.lemberg@sandisk.com> wrote:
>> Add MMC_AUTOSUSPEND_DELAY_MS define
>>
>> Signed-off-by: alex.lemberg <alex.lemberg@sandisk.com>
>> ---
>> drivers/mmc/card/block.c | 3 ++-
>> include/linux/mmc/host.h | 2 ++
>> 2 files changed, 4 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
>> index 48a5dd7..f4386ed 100644
>> --- a/drivers/mmc/card/block.c
>> +++ b/drivers/mmc/card/block.c
>> @@ -2610,7 +2610,8 @@ static int mmc_blk_probe(struct mmc_card *card)
>> goto out;
>> }
>>
>> - pm_runtime_set_autosuspend_delay(&card->dev, 3000);
>> + pm_runtime_set_autosuspend_delay(&card->dev,
>> + MMC_RUNTIME_SUSPEND_DELAY_MS);
>> pm_runtime_use_autosuspend(&card->dev);
>>
>> /*
>> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
>> index aa4bfbf..8cb8218 100644
>> --- a/include/linux/mmc/host.h
>> +++ b/include/linux/mmc/host.h
>> @@ -22,6 +22,8 @@
>> #include <linux/mmc/mmc.h>
>> #include <linux/mmc/pm.h>
>>
>> +#define MMC_RUNTIME_SUSPEND_DELAY_MS 3000
>> +
>> struct mmc_ios {
>> unsigned int clock; /* clock rate */
>> unsigned short vdd;
>> --
>> 1.9.1
>>
>
>Actually, I don't mind having hardcoded values like this one directly
>in the code. Especially since it's used only at one single place and
>because it's not a magic number.
>
>Although, if really feel strong about this, I can apply it.
>
>Kind regards
>Uffe
^ permalink raw reply
* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree, Thomas Petazzoni, linux-arm-kernel,
Ziji Hu, Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai,
Ryan Gao, Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu,
Wilson Ding, Xueping Liu, Hilbert Zhang
Hello,
This the second version of the series adding support for the SDHCI
Xenon controller. It can be currently found on the Armada 37xx and the
Armada 7K/8K but will be also used in more Marvell SoC (and not only
the mvebu ones actually).
Some of the remarks had been taking into account since the first
version, according to Ziji Hu, here are the following chcanges:
"Changes in V2:
rebase on v4.9-rc2.
Re-write Xenon bindings. Ajust Xenon DT property naming.
Add a new DT property to indicate eMMC card type, instead of using
variable card_candidate.
Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
Add support to HS400 retuning."
I think the main open point which remains is about issuing commands
from the ->set_ios() callback (in patch 7).
Ulf, could you comment about it?
Thanks,
Gregory
Gregory CLEMENT (3):
arm64: dts: marvell: add eMMC support for Armada 37xx
arm64: dts: marvell: add sdhci support for Armada 7K/8K
arm64: configs: enable SDHCI driver for Xenon
Ziji Hu (7):
mmc: sdhci: Export sdhci_set_ios() from sdhci.c
mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
dt: bindings: Add bindings for Marvell Xenon SD Host Controller
mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +-
MAINTAINERS | 7 +-
arch/arm64/boot/dts/marvell/armada-3720-db.dts | 8 +-
arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 11 +-
arch/arm64/boot/dts/marvell/armada-7040-db.dts | 8 +-
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 9 +-
arch/arm64/configs/defconfig | 1 +-
drivers/mmc/host/Kconfig | 9 +-
drivers/mmc/host/Makefile | 3 +-
drivers/mmc/host/sdhci-xenon-phy.c | 1181 +++++++-
drivers/mmc/host/sdhci-xenon-phy.h | 157 +-
drivers/mmc/host/sdhci-xenon.c | 598 ++++-
drivers/mmc/host/sdhci-xenon.h | 159 +-
drivers/mmc/host/sdhci.c | 11 +-
drivers/mmc/host/sdhci.h | 4 +-
15 files changed, 2323 insertions(+), 4 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
create mode 100644 drivers/mmc/host/sdhci-xenon.c
create mode 100644 drivers/mmc/host/sdhci-xenon.h
base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
--
git-series 0.8.10
^ permalink raw reply
* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree, Thomas Petazzoni, linux-arm-kernel,
Ziji Hu, Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai,
Ryan Gao, Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu,
Wilson Ding, Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement@free-electrons.com>
From: Ziji Hu <huziji@marvell.com>
Export sdhci_start_signal_voltage_switch() from sdhci.c.
Thus vendor sdhci driver can implement its own signal voltage
switch routine.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
drivers/mmc/host/sdhci.c | 5 +++--
drivers/mmc/host/sdhci.h | 2 ++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ea06faf8a437..8e6e4e37e3b4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1832,8 +1832,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
spin_unlock_irqrestore(&host->lock, flags);
}
-static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
- struct mmc_ios *ios)
+int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
{
struct sdhci_host *host = mmc_priv(mmc);
u16 ctrl;
@@ -1925,6 +1925,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
return 0;
}
}
+EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch);
static int sdhci_card_busy(struct mmc_host *mmc)
{
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 37771de4cafa..cd18b6f19c3b 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
void sdhci_reset(struct sdhci_host *host, u8 mask);
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
+int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host);
--
git-series 0.8.10
^ permalink raw reply related
* [PATCH 10/10] arm64: configs: enable SDHCI driver for Xenon
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
This patch enables the driver for the SDHCI controller found on the
Marvell Armada 3700 and 7K/8K ARM64 SoCs.
Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+), 0 deletions(-)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0c1f1c..2d1f5ee62b18 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -353,6 +353,7 @@ CONFIG_MMC_DW=y
CONFIG_MMC_DW_EXYNOS=y
CONFIG_MMC_DW_K3=y
CONFIG_MMC_SUNXI=y
+CONFIG_MMC_SDHCI_XENON=y
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
--
git-series 0.8.10
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 9/10] arm64: dts: marvell: add sdhci support for Armada 7K/8K
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
Also enable it on the Armada 7040 DB board
Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
arch/arm64/boot/dts/marvell/armada-7040-db.dts | 8 ++++++++
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 9 +++++++++
2 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 070b589680c5..f8bdabdbd864 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,11 @@
&cpm_usb3_1 {
status = "okay";
};
+
+&sdhci0 {
+ status = "okay";
+ bus-width = <4>;
+ no-1-8-v;
+ non-removable;
+ marvell,xenon-emmc;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..174c41b24d4c 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -229,6 +229,15 @@
};
+ sdhci0: sdhci@6e0000 {
+ compatible = "marvell,xenon-sdhci";
+ reg = <0x6e0000 0x300>;
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "core";
+ clocks = <&cpm_syscon0 1 4>;
+ status = "disabled";
+ };
+
ap_syscon: system-controller@6f4000 {
compatible = "marvell,ap806-system-controller",
"syscon";
--
git-series 0.8.10
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 8/10] arm64: dts: marvell: add eMMC support for Armada 37xx
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.
Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
arch/arm64/boot/dts/marvell/armada-3720-db.dts | 8 ++++++++
arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 11 +++++++++++
2 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/arch/arm64/boot/dts/marvell/armada-3720-db.dts b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
index 1372e9a6aaa4..9107dd3e2a44 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,14 @@
status = "okay";
};
+&sdhci0 {
+ non-removable;
+ bus-width = <8>;
+ marvell,xenon-emmc;
+ marvell,pad-type = "fixed-1-8v";
+ status = "okay";
+};
+
/* CON31 */
&usb3 {
status = "okay";
diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
index c4762538ec01..0c4cafe92e66 100644
--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
@@ -161,6 +161,17 @@
};
};
+ sdhci0: sdhci@d8000 {
+ compatible = "marvell,armada-3700-sdhci",
+ "marvell,sdhci-xenon";
+ reg = <0xd8000 0x300
+ 0x17808 0x4>;
+ interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&nb_perih_clk 0>;
+ clock-names = "core";
+ status = "disabled";
+ };
+
sata: sata@e0000 {
compatible = "marvell,armada-3700-ahci";
reg = <0xe0000 0x2000>;
--
git-series 0.8.10
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree, Thomas Petazzoni, linux-arm-kernel,
Ziji Hu, Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai,
Ryan Gao, Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu,
Wilson Ding, Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement@free-electrons.com>
From: Ziji Hu <huziji@marvell.com>
Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
Three types of PHYs are supported.
Add support to multiple types of PHYs init and configuration.
Add register definitions of PHYs.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
MAINTAINERS | 2 +-
drivers/mmc/host/Makefile | 2 +-
drivers/mmc/host/sdhci-xenon-phy.c | 1181 +++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon-phy.h | 157 ++++-
drivers/mmc/host/sdhci-xenon.c | 4 +-
drivers/mmc/host/sdhci-xenon.h | 17 +-
6 files changed, 1361 insertions(+), 2 deletions(-)
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d92f4175574b..bb33286aeb48 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,7 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <huziji@marvell.com>
L: linux-mmc@vger.kernel.org
S: Supported
-F: drivers/mmc/host/sdhci-xenon.*
+F: drivers/mmc/host/sdhci-xenon*
F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 75eaf743486c..4f2854556ff7 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
endif
obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
-sdhci-xenon-driver-y += sdhci-xenon.o
+sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
new file mode 100644
index 000000000000..af32f8842e0b
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon-phy.c
@@ -0,0 +1,1181 @@
+/*
+ * PHY support for Xenon SDHC
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji@marvell.com>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/of_address.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+#include "sdhci-xenon.h"
+
+static const char * const phy_types[] = {
+ "sdh phy",
+ "emmc 5.0 phy",
+ "emmc 5.1 phy"
+};
+
+enum phy_type_enum {
+ SDH_PHY,
+ EMMC_5_0_PHY,
+ EMMC_5_1_PHY,
+ NR_PHY_TYPES
+};
+
+struct soc_pad_ctrl_table {
+ const char *soc;
+ void (*set_soc_pad)(struct sdhci_host *host,
+ unsigned char signal_voltage);
+};
+
+struct soc_pad_ctrl {
+ /* Register address of SOC PHY PAD ctrl */
+ void __iomem *reg;
+ /* SOC PHY PAD ctrl type */
+ enum soc_pad_ctrl_type pad_type;
+ /* SOC specific operation to set SOC PHY PAD */
+ void (*set_soc_pad)(struct sdhci_host *host,
+ unsigned char signal_voltage);
+};
+
+static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
+ .timing_adj = EMMC_5_0_PHY_TIMING_ADJUST,
+ .func_ctrl = EMMC_5_0_PHY_FUNC_CONTROL,
+ .pad_ctrl = EMMC_5_0_PHY_PAD_CONTROL,
+ .pad_ctrl2 = EMMC_5_0_PHY_PAD_CONTROL2,
+ .dll_ctrl = EMMC_5_0_PHY_DLL_CONTROL,
+ .logic_timing_adj = EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
+ .delay_mask = EMMC_5_0_PHY_FIXED_DELAY_MASK,
+ .dll_update = DLL_UPDATE_STROBE_5_0,
+};
+
+static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
+ .timing_adj = EMMC_PHY_TIMING_ADJUST,
+ .func_ctrl = EMMC_PHY_FUNC_CONTROL,
+ .pad_ctrl = EMMC_PHY_PAD_CONTROL,
+ .pad_ctrl2 = EMMC_PHY_PAD_CONTROL2,
+ .dll_ctrl = EMMC_PHY_DLL_CONTROL,
+ .logic_timing_adj = EMMC_PHY_LOGIC_TIMING_ADJUST,
+ .delay_mask = EMMC_PHY_FIXED_DELAY_MASK,
+ .dll_update = DLL_UPDATE,
+};
+
+static int xenon_delay_adj_test(struct mmc_card *card);
+
+/*
+ * eMMC PHY configuration and operations
+ */
+struct emmc_phy_params {
+ bool slow_mode;
+
+ u8 znr;
+ u8 zpr;
+
+ /* Nr of consecutive Sampling Points of a Valid Sampling Window */
+ u8 nr_tun_times;
+ /* Divider for calculating Tuning Step */
+ u8 tun_step_divider;
+
+ struct soc_pad_ctrl pad_ctrl;
+};
+
+static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card);
+static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card);
+static void xenon_emmc_phy_set(struct sdhci_host *host,
+ unsigned char timing);
+static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
+ unsigned char signal_voltage);
+
+static const struct xenon_phy_ops emmc_phy_ops = {
+ .strobe_delay_adj = xenon_emmc_phy_strobe_delay_adj,
+ .fix_sampl_delay_adj = xenon_emmc_phy_fix_sampl_delay_adj,
+ .phy_set = xenon_emmc_phy_set,
+ .set_soc_pad = xenon_emmc_set_soc_pad,
+};
+
+static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
+{
+ struct emmc_phy_params *params;
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ priv->phy_params = params;
+ priv->phy_ops = &emmc_phy_ops;
+ if (priv->phy_type == EMMC_5_0_PHY)
+ priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
+ else
+ priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
+
+ return 0;
+}
+
+static int xenon_emmc_phy_init(struct sdhci_host *host)
+{
+ u32 reg;
+ u32 wait, clock;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg |= PHY_INITIALIZAION;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+
+ /* Add duration of FC_SYNC_RST */
+ wait = ((reg >> FC_SYNC_RST_DURATION_SHIFT) &
+ FC_SYNC_RST_DURATION_MASK);
+ /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
+ wait += ((reg >> FC_SYNC_RST_EN_DURATION_SHIFT) &
+ FC_SYNC_RST_EN_DURATION_MASK);
+ /* Add duration of asserting FC_SYNC_EN */
+ wait += ((reg >> FC_SYNC_EN_DURATION_SHIFT) &
+ FC_SYNC_EN_DURATION_MASK);
+ /* Add duration of waiting for PHY */
+ wait += ((reg >> WAIT_CYCLE_BEFORE_USING_SHIFT) &
+ WAIT_CYCLE_BEFORE_USING_MASK);
+ /* 4 addtional bus clock and 4 AXI bus clock are required */
+ wait += 8;
+ wait <<= 20;
+
+ clock = host->clock;
+ if (!clock)
+ /* Use the possibly slowest bus frequency value */
+ clock = LOWEST_SDCLK_FREQ;
+ /* get the wait time */
+ wait /= clock;
+ wait++;
+ /* wait for host eMMC PHY init completes */
+ udelay(wait);
+
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= PHY_INITIALIZAION;
+ if (reg) {
+ dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
+ wait);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+#define ARMADA_3700_SOC_PAD_1_8V 0x1
+#define ARMADA_3700_SOC_PAD_3_3V 0x0
+
+static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+
+ if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
+ writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
+ } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
+ if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+ writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
+ else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+ writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
+ }
+}
+
+static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+
+ if (!params->pad_ctrl.reg)
+ return;
+
+ if (params->pad_ctrl.set_soc_pad)
+ params->pad_ctrl.set_soc_pad(host, signal_voltage);
+}
+
+static int emmc_phy_set_fix_sampl_delay(struct sdhci_host *host,
+ unsigned int delay,
+ bool invert,
+ bool delay_90_degree)
+{
+ u32 reg;
+ unsigned long flags;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ int ret = 0;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* Setup Sampling fix delay */
+ reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
+ reg &= ~phy_regs->delay_mask;
+ reg |= delay & phy_regs->delay_mask;
+ sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
+
+ if (priv->phy_type == EMMC_5_0_PHY) {
+ /* set 90 degree phase if necessary */
+ reg &= ~DELAY_90_DEGREE_MASK_EMMC5;
+ reg |= (delay_90_degree << DELAY_90_DEGREE_SHIFT_EMMC5);
+ sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
+ }
+
+ /* Disable SDCLK */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+
+ udelay(200);
+
+ if (priv->phy_type == EMMC_5_1_PHY) {
+ /* set 90 degree phase if necessary */
+ reg = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
+ reg &= ~ASYNC_DDRMODE_MASK;
+ reg |= (delay_90_degree << ASYNC_DDRMODE_SHIFT);
+ sdhci_writel(host, reg, EMMC_PHY_FUNC_CONTROL);
+ }
+
+ /* Setup Inversion of Sampling edge */
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= ~SAMPL_INV_QSP_PHASE_SELECT;
+ reg |= (invert << SAMPL_INV_QSP_PHASE_SELECT_SHIFT);
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+
+ /* Enable SD internal clock */
+ ret = enable_xenon_internal_clk(host);
+ if (ret)
+ goto out;
+
+ /* Enable SDCLK */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+
+ udelay(200);
+
+ /*
+ * Has to re-initialize eMMC PHY here to active PHY
+ * because later get status cmd will be issued.
+ */
+ ret = xenon_emmc_phy_init(host);
+
+out:
+ spin_unlock_irqrestore(&host->lock, flags);
+ return ret;
+}
+
+static int emmc_phy_do_fix_sampl_delay(struct sdhci_host *host,
+ struct mmc_card *card,
+ unsigned int delay,
+ bool invert, bool quarter)
+{
+ int ret;
+
+ emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
+
+ ret = xenon_delay_adj_test(card);
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "fail when sampling fix delay = %d, phase = %d degree\n",
+ delay, invert * 180 + quarter * 90);
+ return -1;
+ }
+ return 0;
+}
+
+static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ enum sampl_fix_delay_phase phase;
+ int idx, nr_pair;
+ int ret;
+ unsigned int delay;
+ unsigned int min_delay, max_delay;
+ bool invert, quarter;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ u32 coarse_step, fine_step;
+ const enum sampl_fix_delay_phase delay_edge[] = {
+ PHASE_0_DEGREE,
+ PHASE_180_DEGREE,
+ PHASE_90_DEGREE,
+ PHASE_270_DEGREE
+ };
+
+ coarse_step = phy_regs->delay_mask >> 1;
+ fine_step = coarse_step >> 2;
+
+ nr_pair = ARRAY_SIZE(delay_edge);
+
+ for (idx = 0; idx < nr_pair; idx++) {
+ phase = delay_edge[idx];
+ invert = (phase & 0x2) ? true : false;
+ quarter = (phase & 0x1) ? true : false;
+
+ /* increase delay value to get fix delay */
+ for (min_delay = 0;
+ min_delay <= phy_regs->delay_mask;
+ min_delay += coarse_step) {
+ ret = emmc_phy_do_fix_sampl_delay(host, card, min_delay,
+ invert, quarter);
+ if (!ret)
+ break;
+ }
+
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Fail to set Sampling Fixed Delay with phase = %d degree\n",
+ phase * 90);
+ continue;
+ }
+
+ for (max_delay = min_delay + fine_step;
+ max_delay < phy_regs->delay_mask;
+ max_delay += fine_step) {
+ ret = emmc_phy_do_fix_sampl_delay(host, card, max_delay,
+ invert, quarter);
+ if (ret) {
+ max_delay -= fine_step;
+ break;
+ }
+ }
+
+ if (!ret) {
+ ret = emmc_phy_do_fix_sampl_delay(host, card,
+ phy_regs->delay_mask,
+ invert, quarter);
+ if (!ret)
+ max_delay = phy_regs->delay_mask;
+ }
+
+ /*
+ * Sampling Fixed Delay line window should be large enough,
+ * thus the sampling point (the middle of the window)
+ * can work when environment varies.
+ * However, there is no clear conclusion how large the window
+ * should be.
+ */
+ if ((max_delay - min_delay) <=
+ EMMC_PHY_FIXED_DELAY_WINDOW_MIN) {
+ dev_info(mmc_dev(host->mmc),
+ "The window size %d with phase = %d degree is too small\n",
+ max_delay - min_delay, phase * 90);
+ continue;
+ }
+
+ delay = (min_delay + max_delay) / 2;
+ emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
+ dev_dbg(mmc_dev(host->mmc),
+ "sampling fix delay = %d with phase = %d degree\n",
+ delay, phase * 90);
+ return 0;
+ }
+
+ return -EIO;
+}
+
+static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
+{
+ u32 reg;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ u8 timeout;
+
+ if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
+ return -EINVAL;
+
+ reg = sdhci_readl(host, phy_regs->dll_ctrl);
+ if (reg & DLL_ENABLE)
+ return 0;
+
+ /* Enable DLL */
+ reg = sdhci_readl(host, phy_regs->dll_ctrl);
+ reg |= (DLL_ENABLE | DLL_FAST_LOCK);
+
+ /*
+ * Set Phase as 90 degree, which is most common value.
+ * Might set another value if necessary.
+ * The granularity is 1 degree.
+ */
+ reg &= ~((DLL_PHASE_MASK << DLL_PHSEL0_SHIFT) |
+ (DLL_PHASE_MASK << DLL_PHSEL1_SHIFT));
+ reg |= ((DLL_PHASE_90_DEGREE << DLL_PHSEL0_SHIFT) |
+ (DLL_PHASE_90_DEGREE << DLL_PHSEL1_SHIFT));
+
+ reg &= ~DLL_BYPASS_EN;
+ reg |= phy_regs->dll_update;
+ if (priv->phy_type == EMMC_5_1_PHY)
+ reg &= ~DLL_REFCLK_SEL;
+ sdhci_writel(host, reg, phy_regs->dll_ctrl);
+
+ /* Wait max 32 ms */
+ timeout = 32;
+ while (!(sdhci_readw(host, SDHC_SLOT_EXT_PRESENT_STATE) & LOCK_STATE)) {
+ if (!timeout) {
+ dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
+ return -ETIMEDOUT;
+ }
+ timeout--;
+ mdelay(1);
+ }
+ return 0;
+}
+
+static int __emmc_phy_config_tuning(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+ u32 reg, tuning_step;
+ int ret;
+ unsigned long flags;
+
+ if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
+ return -EINVAL;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ ret = xenon_emmc_phy_enable_dll(host);
+ if (ret) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return ret;
+ }
+
+ reg = sdhci_readl(host, SDHC_SLOT_DLL_CUR_DLY_VAL);
+ tuning_step = reg / params->tun_step_divider;
+ if (unlikely(tuning_step > TUNING_STEP_MASK)) {
+ dev_warn(mmc_dev(host->mmc),
+ "HS200 TUNING_STEP %d is larger than MAX value\n",
+ tuning_step);
+ tuning_step = TUNING_STEP_MASK;
+ }
+
+ reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
+ reg &= ~(TUN_CONSECUTIVE_TIMES_MASK << TUN_CONSECUTIVE_TIMES_SHIFT);
+ reg |= (params->nr_tun_times << TUN_CONSECUTIVE_TIMES_SHIFT);
+ reg &= ~(TUNING_STEP_MASK << TUNING_STEP_SHIFT);
+ reg |= (tuning_step << TUNING_STEP_SHIFT);
+ sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ return 0;
+}
+
+static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
+{
+ return __emmc_phy_config_tuning(host);
+}
+
+static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ u32 reg;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+
+ if (host->clock <= MMC_HIGH_52_MAX_DTR)
+ return;
+
+ dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ xenon_emmc_phy_enable_dll(host);
+
+ /* Enable SDHC Data Strobe */
+ reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+ reg |= ENABLE_DATA_STROBE;
+ sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
+
+ /* Set Data Strobe Pull down */
+ if (priv->phy_type == EMMC_5_0_PHY) {
+ reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
+ reg |= EMMC5_FC_QSP_PD;
+ reg &= ~EMMC5_FC_QSP_PU;
+ sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
+ } else {
+ reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
+ reg |= EMMC5_1_FC_QSP_PD;
+ reg &= ~EMMC5_1_FC_QSP_PU;
+ sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void __emmc_phy_disable_data_strobe(struct sdhci_host *host)
+{
+ u32 reg;
+
+ /* Disable SDHC Data Strobe */
+ reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+ reg &= ~ENABLE_DATA_STROBE;
+ sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
+}
+
+#define LOGIC_TIMING_VALUE 0x00AA8977
+
+static void xenon_emmc_phy_set(struct sdhci_host *host,
+ unsigned char timing)
+{
+ u32 reg;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct emmc_phy_params *params = priv->phy_params;
+ struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
+ struct mmc_card *card = priv->card_candidate;
+ unsigned long flags;
+
+ dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* Setup pad, set bit[28] and bits[26:24] */
+ reg = sdhci_readl(host, phy_regs->pad_ctrl);
+ reg |= (FC_DQ_RECEN | FC_CMD_RECEN | FC_QSP_RECEN | OEN_QSN);
+ /*
+ * All FC_XX_RECEIVCE should be set as CMOS Type
+ */
+ reg |= FC_ALL_CMOS_RECEIVER;
+ sdhci_writel(host, reg, phy_regs->pad_ctrl);
+
+ /* Set CMD and DQ Pull Up */
+ if (priv->phy_type == EMMC_5_0_PHY) {
+ reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
+ reg |= (EMMC5_FC_CMD_PU | EMMC5_FC_DQ_PU);
+ reg &= ~(EMMC5_FC_CMD_PD | EMMC5_FC_DQ_PD);
+ sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
+ } else {
+ reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
+ reg |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU);
+ reg &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD);
+ sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
+ }
+
+ if ((timing == MMC_TIMING_LEGACY) || !card)
+ goto phy_init;
+
+ /*
+ * FIXME: should depends on the specific board timing.
+ */
+ if ((timing == MMC_TIMING_MMC_HS400) ||
+ (timing == MMC_TIMING_MMC_HS200) ||
+ (timing == MMC_TIMING_UHS_SDR50) ||
+ (timing == MMC_TIMING_UHS_SDR104) ||
+ (timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_MMC_DDR52)) {
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= ~OUTPUT_QSN_PHASE_SELECT;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ }
+
+ /*
+ * If SDIO card, set SDIO Mode
+ * Otherwise, clear SDIO Mode and Slow Mode
+ */
+ if (mmc_card_sdio(card)) {
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg |= TIMING_ADJUST_SDIO_MODE;
+
+ if ((timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_UHS_SDR12) ||
+ (timing == MMC_TIMING_SD_HS) ||
+ (timing == MMC_TIMING_LEGACY))
+ reg |= TIMING_ADJUST_SLOW_MODE;
+
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ } else {
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg &= ~(TIMING_ADJUST_SDIO_MODE | TIMING_ADJUST_SLOW_MODE);
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ }
+
+ if (((timing == MMC_TIMING_UHS_SDR50) ||
+ (timing == MMC_TIMING_UHS_SDR25) ||
+ (timing == MMC_TIMING_UHS_SDR12) ||
+ (timing == MMC_TIMING_SD_HS) ||
+ (timing == MMC_TIMING_MMC_HS) ||
+ (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
+ reg = sdhci_readl(host, phy_regs->timing_adj);
+ reg |= TIMING_ADJUST_SLOW_MODE;
+ sdhci_writel(host, reg, phy_regs->timing_adj);
+ }
+
+ /*
+ * Set preferred ZNR and ZPR value
+ * The ZNR and ZPR value vary between different boards.
+ * Define them both in sdhci-xenon-emmc-phy.h.
+ */
+ reg = sdhci_readl(host, phy_regs->pad_ctrl2);
+ reg &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
+ reg |= ((params->znr << ZNR_SHIFT) | params->zpr);
+ sdhci_writel(host, reg, phy_regs->pad_ctrl2);
+
+ /*
+ * When setting EMMC_PHY_FUNC_CONTROL register,
+ * SD clock should be disabled
+ */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
+
+ reg = sdhci_readl(host, phy_regs->func_ctrl);
+ if ((timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_MMC_HS400) ||
+ (timing == MMC_TIMING_MMC_DDR52))
+ reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
+ else
+ reg &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
+ CMD_DDR_MODE);
+
+ if (timing == MMC_TIMING_MMC_HS400)
+ reg &= ~DQ_ASYNC_MODE;
+ else
+ reg |= DQ_ASYNC_MODE;
+ sdhci_writel(host, reg, phy_regs->func_ctrl);
+
+ /* Enable bus clock */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
+
+ if (timing == MMC_TIMING_MMC_HS400)
+ /* Hardware team recommend a value for HS400 */
+ sdhci_writel(host, LOGIC_TIMING_VALUE,
+ phy_regs->logic_timing_adj);
+ else
+ __emmc_phy_disable_data_strobe(host);
+
+phy_init:
+ xenon_emmc_phy_init(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
+}
+
+static int get_dt_pad_ctrl_data(struct sdhci_host *host,
+ struct device_node *np,
+ struct emmc_phy_params *params)
+{
+ int ret = 0;
+ const char *name;
+ struct resource iomem;
+
+ if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
+ params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
+ else
+ return 0;
+
+ if (of_address_to_resource(np, 1, &iomem)) {
+ dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
+ np->name);
+ return -EINVAL;
+ }
+
+ params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
+ &iomem);
+ if (IS_ERR(params->pad_ctrl.reg)) {
+ dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
+ np->name);
+ return PTR_ERR(params->pad_ctrl.reg);
+ }
+
+ ret = of_property_read_string(np, "marvell,pad-type", &name);
+ if (ret) {
+ dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
+ return ret;
+ }
+ if (!strcmp(name, "sd")) {
+ params->pad_ctrl.pad_type = SOC_PAD_SD;
+ } else if (!strcmp(name, "fixed-1-8v")) {
+ params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
+ } else {
+ dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
+ name);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int emmc_phy_parse_param_dt(struct sdhci_host *host,
+ struct device_node *np,
+ struct emmc_phy_params *params)
+{
+ u32 value;
+
+ if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode"))
+ params->slow_mode = true;
+ else
+ params->slow_mode = false;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value))
+ params->znr = value & ZNR_MASK;
+ else
+ params->znr = ZNR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value))
+ params->zpr = value & ZPR_MASK;
+ else
+ params->zpr = ZPR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun",
+ &value))
+ params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
+ else
+ params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider",
+ &value))
+ params->tun_step_divider = value & 0xFF;
+ else
+ params->tun_step_divider = TUNING_STEP_DIVIDER;
+
+ return get_dt_pad_ctrl_data(host, np, params);
+}
+
+/*
+ * SDH PHY configuration and operations
+ */
+static int xenon_sdh_phy_set_fix_sampl_delay(struct sdhci_host *host,
+ unsigned int delay, bool invert)
+{
+ u32 reg;
+ unsigned long flags;
+ int ret;
+
+ if (invert)
+ invert = 0x1;
+ else
+ invert = 0x0;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* Disable SDCLK */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+
+ udelay(200);
+
+ /* Setup Sampling fix delay */
+ reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
+ reg &= ~(SDH_PHY_FIXED_DELAY_MASK |
+ (0x1 << FORCE_SEL_INVERSE_CLK_SHIFT));
+ reg |= ((delay & SDH_PHY_FIXED_DELAY_MASK) |
+ (invert << FORCE_SEL_INVERSE_CLK_SHIFT));
+ sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
+
+ /* Enable SD internal clock */
+ ret = enable_xenon_internal_clk(host);
+
+ /* Enable SDCLK */
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+
+ udelay(200);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ return ret;
+}
+
+static int sdh_phy_do_fix_sampl_delay(struct sdhci_host *host,
+ struct mmc_card *card,
+ unsigned int delay, bool invert)
+{
+ int ret;
+
+ xenon_sdh_phy_set_fix_sampl_delay(host, delay, invert);
+
+ ret = xenon_delay_adj_test(card);
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "fail when sampling fix delay = %d, phase = %d degree\n",
+ delay, invert * 180);
+ return -1;
+ }
+ return 0;
+}
+
+#define SDH_PHY_COARSE_FIX_DELAY (SDH_PHY_FIXED_DELAY_MASK / 2)
+#define SDH_PHY_FINE_FIX_DELAY (SDH_PHY_COARSE_FIX_DELAY / 4)
+
+static int xenon_sdh_phy_fix_sampl_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ u32 reg;
+ bool dll_enable = false;
+ unsigned int min_delay, max_delay, delay;
+ const bool sampl_edge[] = {
+ false,
+ true,
+ };
+ int i, nr;
+ int ret;
+
+ if (host->clock > HIGH_SPEED_MAX_DTR) {
+ /* Enable DLL when SDCLK is higher than 50MHz */
+ reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_CTRL);
+ if (!(reg & SDH_PHY_ENABLE_DLL)) {
+ reg |= (SDH_PHY_ENABLE_DLL | SDH_PHY_FAST_LOCK_EN);
+ sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_CTRL);
+ mdelay(1);
+
+ reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_PHASE_SEL);
+ reg |= SDH_PHY_DLL_UPDATE_TUNING;
+ sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_PHASE_SEL);
+ }
+ dll_enable = true;
+ }
+
+ nr = dll_enable ? ARRAY_SIZE(sampl_edge) : 1;
+ for (i = 0; i < nr; i++) {
+ for (min_delay = 0; min_delay <= SDH_PHY_FIXED_DELAY_MASK;
+ min_delay += SDH_PHY_COARSE_FIX_DELAY) {
+ ret = sdh_phy_do_fix_sampl_delay(host, card, min_delay,
+ sampl_edge[i]);
+ if (!ret)
+ break;
+ }
+
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Fail to set Fixed Sampling Delay with %s edge\n",
+ sampl_edge[i] ? "negative" : "positive");
+ continue;
+ }
+
+ for (max_delay = min_delay + SDH_PHY_FINE_FIX_DELAY;
+ max_delay < SDH_PHY_FIXED_DELAY_MASK;
+ max_delay += SDH_PHY_FINE_FIX_DELAY) {
+ ret = sdh_phy_do_fix_sampl_delay(host, card, max_delay,
+ sampl_edge[i]);
+ if (ret) {
+ max_delay -= SDH_PHY_FINE_FIX_DELAY;
+ break;
+ }
+ }
+
+ if (!ret) {
+ delay = SDH_PHY_FIXED_DELAY_MASK;
+ ret = sdh_phy_do_fix_sampl_delay(host, card, delay,
+ sampl_edge[i]);
+ if (!ret)
+ max_delay = SDH_PHY_FIXED_DELAY_MASK;
+ }
+
+ if ((max_delay - min_delay) <= SDH_PHY_FIXED_DELAY_WINDOW_MIN) {
+ dev_info(mmc_dev(host->mmc),
+ "The window size %d with %s edge is too small\n",
+ max_delay - min_delay,
+ sampl_edge[i] ? "negative" : "positive");
+ continue;
+ }
+
+ delay = (min_delay + max_delay) / 2;
+ xenon_sdh_phy_set_fix_sampl_delay(host, delay, sampl_edge[i]);
+ dev_dbg(mmc_dev(host->mmc), "sampling fix delay = %d with %s edge\n",
+ delay, sampl_edge[i] ? "negative" : "positive");
+ return 0;
+ }
+ return -EIO;
+}
+
+static const struct xenon_phy_ops sdh_phy_ops = {
+ .fix_sampl_delay_adj = xenon_sdh_phy_fix_sampl_delay_adj,
+};
+
+static int alloc_sdh_phy(struct sdhci_xenon_priv *priv)
+{
+ priv->phy_params = NULL;
+ priv->phy_ops = &sdh_phy_ops;
+ return 0;
+}
+
+/*
+ * Common functions for all PHYs
+ */
+void xenon_soc_pad_ctrl(struct sdhci_host *host,
+ unsigned char signal_voltage)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ if (priv->phy_ops->set_soc_pad)
+ priv->phy_ops->set_soc_pad(host, signal_voltage);
+}
+
+static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
+{
+ int err;
+ u8 *ext_csd = NULL;
+
+ err = mmc_get_ext_csd(card, &ext_csd);
+ kfree(ext_csd);
+
+ return err;
+}
+
+static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = SD_IO_RW_DIRECT;
+ cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err)
+ return err;
+
+ if (cmd.resp[0] & R5_ERROR)
+ return -EIO;
+ if (cmd.resp[0] & R5_FUNCTION_NUMBER)
+ return -EINVAL;
+ if (cmd.resp[0] & R5_OUT_OF_RANGE)
+ return -ERANGE;
+ return 0;
+}
+
+static int __xenon_sd_delay_adj_test(struct mmc_card *card)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = MMC_SEND_STATUS;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ return err;
+}
+
+static int xenon_delay_adj_test(struct mmc_card *card)
+{
+ WARN_ON(!card);
+ WARN_ON(!card->host);
+
+ if (mmc_card_mmc(card))
+ return __xenon_emmc_delay_adj_test(card);
+ else if (mmc_card_sd(card))
+ return __xenon_sd_delay_adj_test(card);
+ else if (mmc_card_sdio(card))
+ return __xenon_sdio_delay_adj_test(card);
+ else
+ return -EINVAL;
+}
+
+static void xenon_phy_set(struct sdhci_host *host, unsigned char timing)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ if (priv->phy_ops->phy_set)
+ priv->phy_ops->phy_set(host, timing);
+}
+
+static void xenon_hs400_strobe_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ if (WARN_ON(!mmc_card_hs400(card)))
+ return;
+
+ /* Enable the DLL to automatically adjust HS400 strobe delay.
+ */
+ if (priv->phy_ops->strobe_delay_adj)
+ priv->phy_ops->strobe_delay_adj(host, card);
+}
+
+static int xenon_fix_sampl_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ if (priv->phy_ops->fix_sampl_delay_adj)
+ return priv->phy_ops->fix_sampl_delay_adj(host, card);
+
+ return 0;
+}
+
+/*
+ * xenon_delay_adj should not be called inside IRQ context,
+ * either Hard IRQ or Softirq.
+ */
+static int xenon_hs_delay_adj(struct sdhci_host *host,
+ struct mmc_card *card)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int ret = 0;
+
+ if (WARN_ON(host->clock <= DEFAULT_SDCLK_FREQ))
+ return -EINVAL;
+
+ if (mmc_card_hs400(card)) {
+ xenon_hs400_strobe_delay_adj(host, card);
+ return 0;
+ }
+
+ if (((priv->phy_type == EMMC_5_1_PHY) ||
+ (priv->phy_type == EMMC_5_0_PHY)) &&
+ (mmc_card_hs200(card) ||
+ (host->timing == MMC_TIMING_UHS_SDR104))) {
+ ret = xenon_emmc_phy_config_tuning(host);
+ if (!ret)
+ return 0;
+ }
+
+ ret = xenon_fix_sampl_delay_adj(host, card);
+ if (ret)
+ dev_err(mmc_dev(host->mmc), "fails sampling fixed delay adjustment\n");
+ return ret;
+}
+
+int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
+{
+ struct mmc_host *mmc = host->mmc;
+ struct mmc_card *card;
+ int ret = 0;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ if (!host->clock) {
+ priv->clock = 0;
+ return 0;
+ }
+
+ /*
+ * The timing, frequency or bus width is changed,
+ * better to set eMMC PHY based on current setting
+ * and adjust Xenon SDHC delay.
+ */
+ if ((host->clock == priv->clock) &&
+ (ios->bus_width == priv->bus_width) &&
+ (ios->timing == priv->timing))
+ return 0;
+
+ xenon_phy_set(host, ios->timing);
+
+ /* Update the record */
+ priv->bus_width = ios->bus_width;
+ /* Temp stage from HS200 to HS400 */
+ if (((priv->timing == MMC_TIMING_MMC_HS200) &&
+ (ios->timing == MMC_TIMING_MMC_HS)) ||
+ ((ios->timing == MMC_TIMING_MMC_HS) &&
+ (priv->clock > host->clock))) {
+ priv->timing = ios->timing;
+ priv->clock = host->clock;
+ return 0;
+ }
+ /*
+ * Skip temp stages from HS400 t0 HS200:
+ * from 200MHz to 52MHz in HS400
+ * from HS400 to HS DDR in 52MHz
+ * from HS DDR to HS in 52MHz
+ * from HS to HS200 in 52MHz
+ */
+ if (((priv->timing == MMC_TIMING_MMC_HS400) &&
+ ((host->clock == MMC_HIGH_52_MAX_DTR) ||
+ (ios->timing == MMC_TIMING_MMC_DDR52))) ||
+ ((priv->timing == MMC_TIMING_MMC_DDR52) &&
+ (ios->timing == MMC_TIMING_MMC_HS)) ||
+ ((ios->timing == MMC_TIMING_MMC_HS200) &&
+ (ios->clock == MMC_HIGH_52_MAX_DTR))) {
+ priv->timing = ios->timing;
+ priv->clock = host->clock;
+ return 0;
+ }
+ priv->timing = ios->timing;
+ priv->clock = host->clock;
+
+ /* Legacy mode is a special case */
+ if (ios->timing == MMC_TIMING_LEGACY)
+ return 0;
+
+ if (mmc->card)
+ card = mmc->card;
+ else
+ /*
+ * Only valid during initialization
+ * before mmc->card is set
+ */
+ card = priv->card_candidate;
+ if (unlikely(!card)) {
+ dev_warn(mmc_dev(mmc), "card is not present\n");
+ return -EINVAL;
+ }
+
+ if (host->clock > DEFAULT_SDCLK_FREQ)
+ ret = xenon_hs_delay_adj(host, card);
+ return ret;
+}
+
+static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
+ const char *phy_name)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int i, ret;
+
+ for (i = 0; i < NR_PHY_TYPES; i++) {
+ if (!strcmp(phy_name, phy_types[i])) {
+ priv->phy_type = i;
+ break;
+ }
+ }
+ if (i == NR_PHY_TYPES) {
+ dev_err(mmc_dev(host->mmc),
+ "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
+ phy_name);
+ priv->phy_type = EMMC_5_1_PHY;
+ }
+
+ if (priv->phy_type == SDH_PHY) {
+ return alloc_sdh_phy(priv);
+ } else if ((priv->phy_type == EMMC_5_0_PHY) ||
+ (priv->phy_type == EMMC_5_1_PHY)) {
+ ret = alloc_emmc_phy(priv);
+ if (ret)
+ return ret;
+ return emmc_phy_parse_param_dt(host, np, priv->phy_params);
+ }
+
+ return -EINVAL;
+}
+
+int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
+{
+ const char *phy_type = NULL;
+
+ if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type))
+ return add_xenon_phy(np, host, phy_type);
+
+ dev_err(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
+ return add_xenon_phy(np, host, "emmc 5.1 phy");
+}
diff --git a/drivers/mmc/host/sdhci-xenon-phy.h b/drivers/mmc/host/sdhci-xenon-phy.h
new file mode 100644
index 000000000000..4373c71d3b7b
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon-phy.h
@@ -0,0 +1,157 @@
+/* linux/drivers/mmc/host/sdhci-xenon-phy.h
+ *
+ * Author: Hu Ziji <huziji@marvell.com>
+ * Date: 2016-8-24
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+#ifndef SDHCI_XENON_PHY_H_
+#define SDHCI_XENON_PHY_H_
+
+#include <linux/types.h>
+#include "sdhci.h"
+
+/* Register base for eMMC PHY 5.0 Version */
+#define EMMC_5_0_PHY_REG_BASE 0x0160
+/* Register base for eMMC PHY 5.1 Version */
+#define EMMC_PHY_REG_BASE 0x0170
+
+#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE
+#define EMMC_5_0_PHY_TIMING_ADJUST EMMC_5_0_PHY_REG_BASE
+#define TIMING_ADJUST_SLOW_MODE BIT(29)
+#define TIMING_ADJUST_SDIO_MODE BIT(28)
+#define OUTPUT_QSN_PHASE_SELECT BIT(17)
+#define SAMPL_INV_QSP_PHASE_SELECT BIT(18)
+#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
+#define PHY_INITIALIZAION BIT(31)
+#define WAIT_CYCLE_BEFORE_USING_MASK 0xF
+#define WAIT_CYCLE_BEFORE_USING_SHIFT 12
+#define FC_SYNC_EN_DURATION_MASK 0xF
+#define FC_SYNC_EN_DURATION_SHIFT 8
+#define FC_SYNC_RST_EN_DURATION_MASK 0xF
+#define FC_SYNC_RST_EN_DURATION_SHIFT 4
+#define FC_SYNC_RST_DURATION_MASK 0xF
+#define FC_SYNC_RST_DURATION_SHIFT 0
+
+#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4)
+#define EMMC_5_0_PHY_FUNC_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x4)
+#define ASYNC_DDRMODE_MASK BIT(23)
+#define ASYNC_DDRMODE_SHIFT 23
+#define CMD_DDR_MODE BIT(16)
+#define DQ_DDR_MODE_SHIFT 8
+#define DQ_DDR_MODE_MASK 0xFF
+#define DQ_ASYNC_MODE BIT(4)
+
+#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8)
+#define EMMC_5_0_PHY_PAD_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x8)
+#define REC_EN_SHIFT 24
+#define REC_EN_MASK 0xF
+#define FC_DQ_RECEN BIT(24)
+#define FC_CMD_RECEN BIT(25)
+#define FC_QSP_RECEN BIT(26)
+#define FC_QSN_RECEN BIT(27)
+#define OEN_QSN BIT(28)
+#define AUTO_RECEN_CTRL BIT(30)
+#define FC_ALL_CMOS_RECEIVER 0xF000
+
+#define EMMC5_FC_QSP_PD BIT(18)
+#define EMMC5_FC_QSP_PU BIT(22)
+#define EMMC5_FC_CMD_PD BIT(17)
+#define EMMC5_FC_CMD_PU BIT(21)
+#define EMMC5_FC_DQ_PD BIT(16)
+#define EMMC5_FC_DQ_PU BIT(20)
+
+#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xC)
+#define EMMC5_1_FC_QSP_PD BIT(9)
+#define EMMC5_1_FC_QSP_PU BIT(25)
+#define EMMC5_1_FC_CMD_PD BIT(8)
+#define EMMC5_1_FC_CMD_PU BIT(24)
+#define EMMC5_1_FC_DQ_PD 0xFF
+#define EMMC5_1_FC_DQ_PU (0xFF << 16)
+
+#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10)
+#define EMMC_5_0_PHY_PAD_CONTROL2 (EMMC_5_0_PHY_REG_BASE + 0xC)
+#define ZNR_MASK 0x1F
+#define ZNR_SHIFT 8
+#define ZPR_MASK 0x1F
+/* Perferred ZNR and ZPR value vary between different boards.
+ * The specific ZNR and ZPR value should be defined here
+ * according to board actual timing.
+ */
+#define ZNR_DEF_VALUE 0xF
+#define ZPR_DEF_VALUE 0xF
+
+#define EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14)
+#define EMMC_5_0_PHY_DLL_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x10)
+#define DLL_ENABLE BIT(31)
+#define DLL_UPDATE_STROBE_5_0 BIT(30)
+#define DLL_REFCLK_SEL BIT(30)
+#define DLL_UPDATE BIT(23)
+#define DLL_PHSEL1_SHIFT 24
+#define DLL_PHSEL0_SHIFT 16
+#define DLL_PHASE_MASK 0x3F
+#define DLL_PHASE_90_DEGREE 0x1F
+#define DLL_FAST_LOCK BIT(5)
+#define DLL_GAIN2X BIT(3)
+#define DLL_BYPASS_EN BIT(0)
+
+#define EMMC_5_0_PHY_LOGIC_TIMING_ADJUST (EMMC_5_0_PHY_REG_BASE + 0x14)
+#define EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18)
+
+enum sampl_fix_delay_phase {
+ PHASE_0_DEGREE = 0x0,
+ PHASE_90_DEGREE = 0x1,
+ PHASE_180_DEGREE = 0x2,
+ PHASE_270_DEGREE = 0x3,
+};
+
+#define SDH_PHY_SLOT_DLL_CTRL (0x0138)
+#define SDH_PHY_ENABLE_DLL BIT(1)
+#define SDH_PHY_FAST_LOCK_EN BIT(5)
+
+#define SDH_PHY_SLOT_DLL_PHASE_SEL (0x013C)
+#define SDH_PHY_DLL_UPDATE_TUNING BIT(15)
+
+enum soc_pad_ctrl_type {
+ SOC_PAD_SD,
+ SOC_PAD_FIXED_1_8V,
+};
+
+/*
+ * List offset of PHY registers and some special register values
+ * in eMMC PHY 5.0 or eMMC PHY 5.1
+ */
+struct xenon_emmc_phy_regs {
+ /* Offset of Timing Adjust register */
+ u16 timing_adj;
+ /* Offset of Func Control register */
+ u16 func_ctrl;
+ /* Offset of Pad Control register */
+ u16 pad_ctrl;
+ /* Offset of Pad Control register */
+ u16 pad_ctrl2;
+ /* Offset of DLL Control register */
+ u16 dll_ctrl;
+ /* Offset of Logic Timing Adjust register */
+ u16 logic_timing_adj;
+ /* Max value of eMMC Fixed Sampling Delay */
+ u32 delay_mask;
+ /* DLL Update Enable bit */
+ u32 dll_update;
+};
+
+struct xenon_phy_ops {
+ void (*strobe_delay_adj)(struct sdhci_host *host,
+ struct mmc_card *card);
+ int (*fix_sampl_delay_adj)(struct sdhci_host *host,
+ struct mmc_card *card);
+ void (*phy_set)(struct sdhci_host *host, unsigned char timing);
+ void (*set_soc_pad)(struct sdhci_host *host,
+ unsigned char signal_voltage);
+};
+#endif
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
index 3ea059f2aaab..ee02014a2917 100644
--- a/drivers/mmc/host/sdhci-xenon.c
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -228,6 +228,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
spin_unlock_irqrestore(&host->lock, flags);
sdhci_set_ios(mmc, ios);
+ xenon_phy_adj(host, ios);
if (host->clock > DEFAULT_SDCLK_FREQ) {
spin_lock_irqsave(&host->lock, flags);
@@ -313,6 +314,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
*/
enable_xenon_internal_clk(host);
+ xenon_soc_pad_ctrl(host, ios->signal_voltage);
+
if (priv->emmc_slot)
return xenon_emmc_signal_voltage_switch(mmc, ios);
@@ -448,6 +451,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
}
+ err = xenon_phy_parse_dt(np, host);
return err;
}
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
index 4601d0a4b22f..e6ee47c227aa 100644
--- a/drivers/mmc/host/sdhci-xenon.h
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -15,6 +15,7 @@
#include <linux/mmc/card.h>
#include <linux/of.h>
#include "sdhci.h"
+#include "sdhci-xenon-phy.h"
/* Register Offset of SD Host Controller SOCP self-defined register */
#define SDHC_SYS_CFG_INFO 0x0104
@@ -76,6 +77,7 @@
#define MMC_TIMING_FAKE 0xFF
#define DEFAULT_SDCLK_FREQ (400000)
+#define LOWEST_SDCLK_FREQ (100000)
/* Xenon specific Mode Select value */
#define XENON_SDHCI_CTRL_HS200 0x5
@@ -99,6 +101,15 @@ struct sdhci_xenon_priv {
/* Whether this slot is for eMMC */
bool emmc_slot;
+ int phy_type;
+ /*
+ * Contains board-specific PHY parameters
+ * passed from device tree.
+ */
+ void *phy_params;
+ const struct xenon_phy_ops *phy_ops;
+ struct xenon_emmc_phy_regs *emmc_phy_regs;
+
/*
* When initializing card, Xenon has to determine card type and
* adjust Sampling Fixed delay for the speed mode in which
@@ -139,4 +150,10 @@ static inline int enable_xenon_internal_clk(struct sdhci_host *host)
return 0;
}
+
+int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
+int xenon_phy_parse_dt(struct device_node *np,
+ struct sdhci_host *host);
+void xenon_soc_pad_ctrl(struct sdhci_host *host,
+ unsigned char signal_voltage);
#endif
--
git-series 0.8.10
^ permalink raw reply related
* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
Add Xenon eMMC/SD/SDIO host controller core functionality.
Add Xenon specific intialization process.
Add Xenon specific mmc_host_ops APIs.
Add Xenon specific register definitions.
Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
Marvell Xenon SDHC conforms to SD Physical Layer Specification
Version 3.01 and is designed according to the guidelines provided
in the SD Host Controller Standard Specification Version 3.00.
Signed-off-by: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
MAINTAINERS | 1 +-
drivers/mmc/host/Kconfig | 9 +-
drivers/mmc/host/Makefile | 3 +-
drivers/mmc/host/sdhci-xenon.c | 594 ++++++++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon.h | 142 ++++++++-
5 files changed, 749 insertions(+), 0 deletions(-)
create mode 100644 drivers/mmc/host/sdhci-xenon.c
create mode 100644 drivers/mmc/host/sdhci-xenon.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 850a0afb0c8d..d92f4175574b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
L: linux-mmc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
S: Supported
+F: drivers/mmc/host/sdhci-xenon.*
F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f503a39a..85a53623526a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
Broadcom STB SoCs.
If unsure, say Y.
+
+config MMC_SDHCI_XENON
+ tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
+ depends on MMC_SDHCI && MMC_SDHCI_PLTFM
+ help
+ This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
+ If you have a machine with integrated Marvell Xenon SDHC IP,
+ say Y or M here.
+ If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf43184..75eaf743486c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG
endif
+
+obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
+sdhci-xenon-driver-y += sdhci-xenon.o
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
new file mode 100644
index 000000000000..3ea059f2aaab
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,594 @@
+/*
+ * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * Inspired by Jisheng Zhang <jszhang-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
+ * Special thanks to Video BG4 project team.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci.h"
+#include "sdhci-xenon.h"
+
+/* Set SDCLK-off-while-idle */
+static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
+ unsigned char slot_idx, bool enable)
+{
+ u32 reg;
+ u32 mask;
+
+ reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+ /* Get the bit shift basing on the slot index */
+ mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
+ if (enable)
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable/Disable the Auto Clock Gating function */
+static void xenon_set_acg(struct sdhci_host *host, bool enable)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+ if (enable)
+ reg &= ~AUTO_CLKGATE_DISABLE_MASK;
+ else
+ reg |= AUTO_CLKGATE_DISABLE_MASK;
+ sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable this slot */
+static void xenon_enable_slot(struct sdhci_host *host,
+ unsigned char slot_idx)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+ reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+ sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+
+ /*
+ * Manually set the flag which all the slots require,
+ * including SD, eMMC, SDIO
+ */
+ host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+/* Disable this slot */
+static void xenon_disable_slot(struct sdhci_host *host,
+ unsigned char slot_idx)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+ reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+ sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable Parallel Transfer Mode */
+static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
+ unsigned char slot_idx)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+ reg |= BIT(slot_idx);
+ sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+}
+
+static void xenon_slot_tuning_setup(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u32 reg;
+
+ /* Disable the Re-Tuning Request functionality */
+ reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
+ reg &= ~RETUNING_COMPATIBLE;
+ sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
+
+ /* Disable the Re-tuning Event Signal Enable */
+ reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+ reg &= ~SDHCI_INT_RETUNE;
+ sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
+
+ /* Force to use Tuning Mode 1 */
+ host->tuning_mode = SDHCI_TUNING_MODE_1;
+ /* Set re-tuning period */
+ host->tuning_count = 1 << (priv->tuning_count - 1);
+}
+
+/*
+ * Operations inside struct sdhci_ops
+ */
+/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
+static void sdhci_xenon_reset_exit(struct sdhci_host *host,
+ unsigned char slot_idx, u8 mask)
+{
+ /* Only SOFTWARE RESET ALL will clear the register setting */
+ if (!(mask & SDHCI_RESET_ALL))
+ return;
+
+ /* Disable tuning request and auto-retuning again */
+ xenon_slot_tuning_setup(host);
+
+ xenon_set_acg(host, true);
+
+ xenon_set_sdclk_off_idle(host, slot_idx, false);
+}
+
+static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ sdhci_reset(host, mask);
+ sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
+}
+
+/*
+ * Xenon defines different values for HS200 and SDR104
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+ unsigned int timing)
+{
+ u16 ctrl_2;
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ if (timing == MMC_TIMING_MMC_HS200)
+ ctrl_2 |= XENON_SDHCI_CTRL_HS200;
+ else if (timing == MMC_TIMING_UHS_SDR104)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+ else if (timing == MMC_TIMING_UHS_SDR12)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+ else if (timing == MMC_TIMING_UHS_SDR25)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+ else if (timing == MMC_TIMING_UHS_SDR50)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+ else if ((timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_MMC_DDR52))
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+ else if (timing == MMC_TIMING_MMC_HS400)
+ ctrl_2 |= XENON_SDHCI_CTRL_HS400;
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static const struct sdhci_ops sdhci_xenon_ops = {
+ .set_clock = sdhci_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = sdhci_xenon_reset,
+ .set_uhs_signaling = xenon_set_uhs_signaling,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+};
+
+static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
+ .ops = &sdhci_xenon_ops,
+ .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
+ SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+};
+
+/*
+ * Xenon Specific Operations in mmc_host_ops
+ */
+static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+ u32 reg;
+
+ /*
+ * HS400/HS200/eMMC HS doesn't have Preset Value register.
+ * However, sdhci_set_ios will read HS400/HS200 Preset register.
+ * Disable Preset Value register for HS400/HS200.
+ * eMMC HS with preset_enabled set will trigger a bug in
+ * get_preset_value().
+ */
+ spin_lock_irqsave(&host->lock, flags);
+ if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+ (ios->timing == MMC_TIMING_MMC_HS200) ||
+ (ios->timing == MMC_TIMING_MMC_HS)) {
+ host->preset_enabled = false;
+ host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+
+ reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+ sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
+ } else {
+ host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ sdhci_set_ios(mmc, ios);
+
+ if (host->clock > DEFAULT_SDCLK_FREQ) {
+ spin_lock_irqsave(&host->lock, flags);
+ xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
+ const unsigned char signal_voltage)
+{
+ u32 ctrl;
+ unsigned char voltage_code;
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+ voltage_code = EMMC_VCCQ_3_3V;
+ else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+ voltage_code = EMMC_VCCQ_1_8V;
+ else
+ return -EINVAL;
+
+ /*
+ * This host is for eMMC, XENON self-defined
+ * eMMC slot control register should be accessed
+ * instead of Host Control 2
+ */
+ ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+ ctrl &= ~EMMC_VCCQ_MASK;
+ ctrl |= voltage_code;
+ sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
+
+ /* There is no standard to determine this waiting period */
+ usleep_range(1000, 2000);
+
+ /* Check whether io voltage switch is done */
+ ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+ ctrl &= EMMC_VCCQ_MASK;
+ /*
+ * This bit is set only when regulator feeds back the voltage switch
+ * results to Xenon SDHC.
+ * However, in actaul implementation, regulator might not provide
+ * this feedback.
+ * Thus we shall not rely on this bit to determine if switch failed.
+ * If the bit is not set, just throw a message.
+ * Besides, error code should not be returned.
+ */
+ if (ctrl != voltage_code)
+ dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
+ return 0;
+}
+
+static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ unsigned char voltage = ios->signal_voltage;
+
+ if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
+ (voltage == MMC_SIGNAL_VOLTAGE_180))
+ return __emmc_signal_voltage_switch(mmc, voltage);
+
+ dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
+ voltage);
+ return -EINVAL;
+}
+
+static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ /*
+ * Before SD/SDIO set signal voltage, SD bus clock should be
+ * disabled. However, sdhci_set_clock will also disable the Internal
+ * clock in mmc_set_signal_voltage().
+ * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
+ * Thus here manually enable internal clock.
+ *
+ * After switch completes, it is unnecessary to disable internal clock,
+ * since keeping internal clock active obeys SD spec.
+ */
+ enable_xenon_internal_clk(host);
+
+ if (priv->emmc_slot)
+ return xenon_emmc_signal_voltage_switch(mmc, ios);
+
+ return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
+/*
+ * After determining which slot is used for SDIO,
+ * some additional task is required.
+ */
+static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u32 reg;
+ u8 slot_idx;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+ /* Link the card for delay adjustment */
+ priv->card_candidate = card;
+ /* Set tuning functionality of this slot */
+ xenon_slot_tuning_setup(host);
+
+ slot_idx = priv->slot_idx;
+ if (!mmc_card_sdio(card)) {
+ /* Clear SDIO Card Inserted indication */
+ reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+ reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+ sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+
+ if (mmc_card_mmc(card)) {
+ mmc->caps |= MMC_CAP_NONREMOVABLE;
+ if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
+ mmc->caps |= MMC_CAP_1_8V_DDR;
+ /*
+ * Force to clear BUS_TEST to
+ * skip bus_test_pre and bus_test_post
+ */
+ mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
+ mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
+ MMC_CAP2_PACKED_CMD;
+ if (mmc->caps & MMC_CAP_8_BIT_DATA)
+ mmc->caps2 |= MMC_CAP2_HS400_1_8V;
+ }
+ } else {
+ /*
+ * Set SDIO Card Inserted indication
+ * to inform that the current slot is for SDIO
+ */
+ reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+ reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+ sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+ }
+}
+
+static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (host->timing == MMC_TIMING_UHS_DDR50)
+ return 0;
+
+ return sdhci_execute_tuning(mmc, opcode);
+}
+
+static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
+{
+ host->mmc_host_ops.set_ios = xenon_set_ios;
+ host->mmc_host_ops.start_signal_voltage_switch =
+ xenon_start_signal_voltage_switch;
+ host->mmc_host_ops.init_card = xenon_init_card;
+ host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
+}
+
+static int xenon_probe_dt(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct mmc_host *mmc = host->mmc;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int err;
+ u32 slot_idx, nr_slot;
+ u32 tuning_count;
+ u32 reg;
+
+ /* Standard MMC property */
+ err = mmc_of_parse(mmc);
+ if (err)
+ return err;
+
+ /* Standard SDHCI property */
+ sdhci_get_of_property(pdev);
+
+ /*
+ * Xenon Specific property:
+ * emmc: explicitly indicate whether this slot is for eMMC
+ * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
+ * tun-count: the interval between re-tuning
+ * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
+ */
+ if (of_property_read_bool(np, "marvell,xenon-emmc"))
+ priv->emmc_slot = true;
+ else
+ priv->emmc_slot = false;
+
+ if (!of_property_read_u32(np, "marvell,xenon-slotno", &slot_idx)) {
+ nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+ nr_slot &= NR_SUPPORTED_SLOT_MASK;
+ if (unlikely(slot_idx > nr_slot)) {
+ dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
+ slot_idx, nr_slot);
+ return -EINVAL;
+ }
+ } else {
+ priv->slot_idx = 0x0;
+ }
+
+ if (!of_property_read_u32(np, "marvell,xenon-tun-count",
+ &tuning_count)) {
+ if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
+ dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
+ DEF_TUNING_COUNT);
+ tuning_count = DEF_TUNING_COUNT;
+ }
+ } else {
+ priv->tuning_count = DEF_TUNING_COUNT;
+ }
+
+ if (of_property_read_bool(np, "marvell,xenon-mask-conflict-err")) {
+ reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+ reg |= MASK_CMD_CONFLICT_ERROR;
+ sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+ }
+
+ return err;
+}
+
+static int xenon_slot_probe(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u8 slot_idx = priv->slot_idx;
+
+ /* Enable slot */
+ xenon_enable_slot(host, slot_idx);
+
+ /* Enable ACG */
+ xenon_set_acg(host, true);
+
+ /* Enable Parallel Transfer Mode */
+ xenon_enable_slot_parallel_tran(host, slot_idx);
+
+ priv->timing = MMC_TIMING_FAKE;
+ priv->clock = 0;
+
+ return 0;
+}
+
+static void xenon_slot_remove(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ u8 slot_idx = priv->slot_idx;
+
+ /* disable slot */
+ xenon_disable_slot(host, slot_idx);
+}
+
+static int sdhci_xenon_probe(struct platform_device *pdev)
+{
+ struct sdhci_pltfm_host *pltfm_host;
+ struct sdhci_host *host;
+ struct clk *clk, *axi_clk;
+ struct sdhci_xenon_priv *priv;
+ int err;
+
+ host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
+ sizeof(struct sdhci_xenon_priv));
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ pltfm_host = sdhci_priv(host);
+ priv = sdhci_pltfm_priv(pltfm_host);
+
+ xenon_set_acg(host, false);
+
+ /*
+ * Link Xenon specific mmc_host_ops function,
+ * to replace standard ones in sdhci_ops.
+ */
+ xenon_replace_mmc_host_ops(host);
+
+ clk = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Failed to setup input clk.\n");
+ err = PTR_ERR(clk);
+ goto free_pltfm;
+ }
+ clk_prepare_enable(clk);
+ pltfm_host->clk = clk;
+
+ /*
+ * Some SOCs require additional clock to
+ * manage AXI bus clock.
+ * It is optional.
+ */
+ axi_clk = devm_clk_get(&pdev->dev, "axi");
+ if (!IS_ERR(axi_clk)) {
+ clk_prepare_enable(axi_clk);
+ priv->axi_clk = axi_clk;
+ }
+
+ err = xenon_probe_dt(pdev);
+ if (err)
+ goto err_clk;
+
+ err = xenon_slot_probe(host);
+ if (err)
+ goto err_clk;
+
+ err = sdhci_add_host(host);
+ if (err)
+ goto remove_slot;
+
+ return 0;
+
+remove_slot:
+ xenon_slot_remove(host);
+err_clk:
+ clk_disable_unprepare(pltfm_host->clk);
+ if (!IS_ERR(axi_clk))
+ clk_disable_unprepare(axi_clk);
+free_pltfm:
+ sdhci_pltfm_free(pdev);
+ return err;
+}
+
+static int sdhci_xenon_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
+
+ xenon_slot_remove(host);
+
+ sdhci_remove_host(host, dead);
+
+ clk_disable_unprepare(pltfm_host->clk);
+ clk_disable_unprepare(priv->axi_clk);
+
+ sdhci_pltfm_free(pdev);
+
+ return 0;
+}
+
+static const struct of_device_id sdhci_xenon_dt_ids[] = {
+ { .compatible = "marvell,xenon-sdhci",},
+ { .compatible = "marvell,armada-3700-sdhci",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+ .driver = {
+ .name = "xenon-sdhci",
+ .of_match_table = sdhci_xenon_dt_ids,
+ .pm = &sdhci_pltfm_pmops,
+ },
+ .probe = sdhci_xenon_probe,
+ .remove = sdhci_xenon_remove,
+};
+
+module_platform_driver(sdhci_xenon_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
+MODULE_AUTHOR("Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
new file mode 100644
index 000000000000..4601d0a4b22f
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
+ * Date: 2016-8-24
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+#ifndef SDHCI_XENON_H_
+#define SDHCI_XENON_H_
+
+#include <linux/clk.h>
+#include <linux/mmc/card.h>
+#include <linux/of.h>
+#include "sdhci.h"
+
+/* Register Offset of SD Host Controller SOCP self-defined register */
+#define SDHC_SYS_CFG_INFO 0x0104
+#define SLOT_TYPE_SDIO_SHIFT 24
+#define SLOT_TYPE_EMMC_MASK 0xFF
+#define SLOT_TYPE_EMMC_SHIFT 16
+#define SLOT_TYPE_SD_SDIO_MMC_MASK 0xFF
+#define SLOT_TYPE_SD_SDIO_MMC_SHIFT 8
+#define NR_SUPPORTED_SLOT_MASK 0x7
+
+#define SDHC_SYS_OP_CTRL 0x0108
+#define AUTO_CLKGATE_DISABLE_MASK BIT(20)
+#define SDCLK_IDLEOFF_ENABLE_SHIFT 8
+#define SLOT_ENABLE_SHIFT 0
+
+#define SDHC_SYS_EXT_OP_CTRL 0x010C
+#define MASK_CMD_CONFLICT_ERROR BIT(8)
+
+#define SDHC_SLOT_OP_STATUS_CTRL 0x0128
+#define DELAY_90_DEGREE_MASK_EMMC5 BIT(7)
+#define DELAY_90_DEGREE_SHIFT_EMMC5 7
+#define EMMC_5_0_PHY_FIXED_DELAY_MASK 0x7F
+#define EMMC_PHY_FIXED_DELAY_MASK 0xFF
+#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN (EMMC_PHY_FIXED_DELAY_MASK >> 3)
+#define SDH_PHY_FIXED_DELAY_MASK 0x1FF
+#define SDH_PHY_FIXED_DELAY_WINDOW_MIN (SDH_PHY_FIXED_DELAY_MASK >> 4)
+
+#define TUN_CONSECUTIVE_TIMES_SHIFT 16
+#define TUN_CONSECUTIVE_TIMES_MASK 0x7
+#define TUN_CONSECUTIVE_TIMES 0x4
+#define TUNING_STEP_SHIFT 12
+#define TUNING_STEP_MASK 0xF
+#define TUNING_STEP_DIVIDER BIT(6)
+
+#define FORCE_SEL_INVERSE_CLK_SHIFT 11
+
+#define SDHC_SLOT_EMMC_CTRL 0x0130
+#define ENABLE_DATA_STROBE BIT(24)
+#define SET_EMMC_RSTN BIT(16)
+#define DISABLE_RD_DATA_CRC BIT(14)
+#define DISABLE_CRC_STAT_TOKEN BIT(13)
+#define EMMC_VCCQ_MASK 0x3
+#define EMMC_VCCQ_1_8V 0x1
+#define EMMC_VCCQ_3_3V 0x3
+
+#define SDHC_SLOT_RETUNING_REQ_CTRL 0x0144
+/* retuning compatible */
+#define RETUNING_COMPATIBLE 0x1
+
+#define SDHC_SLOT_EXT_PRESENT_STATE 0x014C
+#define LOCK_STATE 0x1
+
+#define SDHC_SLOT_DLL_CUR_DLY_VAL 0x0150
+
+/* Tuning Parameter */
+#define TMR_RETUN_NO_PRESENT 0xF
+#define DEF_TUNING_COUNT 0x9
+
+#define MMC_TIMING_FAKE 0xFF
+
+#define DEFAULT_SDCLK_FREQ (400000)
+
+/* Xenon specific Mode Select value */
+#define XENON_SDHCI_CTRL_HS200 0x5
+#define XENON_SDHCI_CTRL_HS400 0x6
+
+struct sdhci_xenon_priv {
+ /*
+ * The bus_width, timing, and clock fields in below
+ * record the current setting of Xenon SDHC.
+ * Driver will call a Sampling Fixed Delay Adjustment
+ * if any setting is changed.
+ */
+ unsigned char bus_width;
+ unsigned char timing;
+ unsigned char tuning_count;
+ unsigned int clock;
+ struct clk *axi_clk;
+
+ /* Slot idx */
+ u8 slot_idx;
+ /* Whether this slot is for eMMC */
+ bool emmc_slot;
+
+ /*
+ * When initializing card, Xenon has to determine card type and
+ * adjust Sampling Fixed delay for the speed mode in which
+ * DLL tuning is not support.
+ * However, at that time, card structure is not linked to mmc_host.
+ * Thus a card pointer is added here to provide
+ * the delay adjustment function with the card structure
+ * of the card during initialization.
+ *
+ * It is only valid during initialization after it is updated in
+ * xenon_init_card().
+ * Do not access this variable in normal transfers after
+ * initialization completes.
+ */
+ struct mmc_card *card_candidate;
+};
+
+static inline int enable_xenon_internal_clk(struct sdhci_host *host)
+{
+ u32 reg;
+ u8 timeout;
+
+ reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+ reg |= SDHCI_CLOCK_INT_EN;
+ sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+ /* Wait max 20 ms */
+ timeout = 20;
+ while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+ & SDHCI_CLOCK_INT_STABLE)) {
+ if (timeout == 0) {
+ pr_err("%s: Internal clock never stabilised.\n",
+ mmc_hostname(host->mmc));
+ return -ETIMEDOUT;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ return 0;
+}
+#endif
--
git-series 0.8.10
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Gregory CLEMENT,
Rob Herring, devicetree, Thomas Petazzoni, linux-arm-kernel,
Ziji Hu, Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai,
Ryan Gao, Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu,
Wilson Ding, Xueping Liu, Hilbert Zhang
In-Reply-To: <cover.86006f271b60cf7c0b4c5a51762a9dacca4c4718.1477911954.git-series.gregory.clement@free-electrons.com>
From: Ziji Hu <huziji@marvell.com>
Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.
Signed-off-by: Hu Ziji <huziji@marvell.com>
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
MAINTAINERS | 1 +-
2 files changed, 162 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
new file mode 100644
index 000000000000..0d2d139494d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
@@ -0,0 +1,161 @@
+Marvell's Xenon SDHCI Controller device tree bindings
+This file documents differences between the core mmc properties
+described by mmc.txt and the properties used by the Xenon implementation.
+
+A single Xenon IP can support multiple slots.
+Each slot acts as an independent SDHC. It owns independent resources, such
+as register sets clock and PHY.
+Each slot should have an independent device tree node.
+
+Required Properties:
+- compatible: should be one of the following
+ - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
+ Must provide a second register area and marvell,pad-type.
+ - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
+ Armada-3700.
+
+- clocks:
+ Array of clocks required for SDHCI.
+ Requires at least one for Xenon IP core.
+ Some SOCs require additional clock for AXI bus.
+
+- clock-names:
+ Array of names corresponding to clocks property.
+ The input clock for Xenon IP core should be named as "core".
+ The optional AXI clock should be named as "axi".
+
+- reg:
+ * For "marvell,xenon-sdhci", one register area for Xenon IP.
+
+ * For "marvell,armada-3700-sdhci", two register areas.
+ The first one for Xenon IP register. The second one for the Armada 3700 SOC
+ PHY PAD Voltage Control register.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+ Please also check property marvell,pad-type in below.
+
+Optional Properties:
+- marvell,xenon-slotno:
+ Indicate the corresponding bit index of current Xenon SDHC slot in
+ SDHC System Operation Control Register Bit[7:0].
+ Set/clear the corresponding bit to enable/disable current Xenon SDHC
+ slot.
+ If this property is not provided, Xenon IP should contain only one
+ slot.
+
+- marvell,xenon-phy-type:
+ Xenon support mutilple types of PHYs.
+ To select eMMC 5.1 PHY, set:
+ marvell,xenon-phy-type = "emmc 5.1 phy"
+ eMMC 5.1 PHY is the default choice if this property is not provided.
+ To select eMMC 5.0 PHY, set:
+ marvell,xenon-phy-type = "emmc 5.0 phy"
+ To select SDH PHY, set:
+ marvell,xenon-phy-type = "sdh phy"
+ Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
+ eMMC only.
+
+- marvell,xenon-phy-znr:
+ Set PHY ZNR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ valid range = [0:0x1F].
+ ZNR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-zpr:
+ Set PHY ZPR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ valid range = [0:0x1F].
+ ZPR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-nr-success-tun:
+ Set the number of required consecutive successful sampling points used to
+ identify a valid sampling window, in tuning process.
+ Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
+
+- marvell,xenon-phy-tun-step-divider:
+ Set the divider for calculating TUN_STEP.
+ Set as 64 by default if this property is not provided.
+
+- marvell,xenon-phy-slow-mode:
+ Force PHY into slow mode.
+ Only available when bus frequency lower than 50MHz in SDR mde.
+ Disabled by default. Please do not enable it unless it is necessary.
+
+- marvell,xenon-mask-conflict-err:
+ Mask Conflict Error alert on some SOC. Disabled by default.
+
+- marvell,xenon-tun-count:
+ Xenon SDHC SOC usually doesn't provide re-tuning counter in
+ Capabilities Register 3 Bit[11:8].
+ This property provides the re-tuning counter.
+ If this property is not set, default re-tuning counter will
+ be set as 0x9 in driver.
+
+- marvell,pad-type:
+ Type of Armada 3700 SOC PHY PAD Voltiage Controller register.
+ Only valid when "marvell,armada-3700-sdhci" is selected.
+ Two types: "sd" and "fixed-1-8v".
+ If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
+ switched to 1.8V when SD in UHS-I.
+ If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+
+Example:
+- For eMMC slot:
+
+ sdhci@aa0000 {
+ compatible = "marvell,xenon-sdhci";
+ reg = <0xaa0000 0x1000>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+ clocks = <&emmc_clk>, <&axi_clock>;
+ clock-names = "core", "axi";
+ bus-width = <8>;
+ marvell,xenon-emmc;
+ marvell,xenon-slotno = <0>;
+ marvell,xenon-phy-type = "emmc 5.1 phy";
+ marvell,xenon-tun-count = <11>;
+ };
+
+- For SD/SDIO slot:
+
+ sdhci@ab0000 {
+ compatible = "marvell,xenon-sdhci";
+ reg = <0xab0000 0x1000>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+ vqmmc-supply = <&sd_regulator>;
+ clocks = <&sdclk>;
+ clock-names = "core";
+ bus-width = <4>;
+ marvell,xenon-tun-count = <9>;
+ };
+
+- For eMMC slot with compatible "marvell,armada-3700-sdhci":
+
+ sdhci@aa0000 {
+ compatible = "marvell,armada-3700-sdhci";
+ reg = <0xaa0000 0x1000>,
+ <phy_addr 0x4>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+ clocks = <&emmcclk>;
+ clock-names = "core";
+ bus-width = <8>;
+ marvell,xenon-emmc;
+
+ marvell,pad-type = "fixed-1-8v";
+ };
+
+- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
+
+ sdhci@ab0000 {
+ compatible = "marvell,armada-3700-sdhci";
+ reg = <0xab0000 0x1000>,
+ <phy_addr 0x4>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+ vqmmc-supply = <&sd_regulator>;
+ clocks = <&sdclk>;
+ clock-names = "core";
+ bus-width = <4>;
+
+ marvell,pad-type = "sd";
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1a5c4c30ea24..850a0afb0c8d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <huziji@marvell.com>
L: linux-mmc@vger.kernel.org
S: Supported
+F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
L: linux-fbdev@vger.kernel.org
--
git-series 0.8.10
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox