* [PATCH v3 03/11] ARM: shmobile: r8a7743: basic SoC support
From: Simon Horman @ 2016-10-08 2:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <2345971.ESZSzve5jF@avalon>
On Fri, Oct 07, 2016 at 11:33:33AM +0300, Laurent Pinchart wrote:
> Hi Simon,
>
>
> On Friday 07 Oct 2016 12:15:37 Simon Horman wrote:
> > On Thu, Oct 06, 2016 at 12:37:08AM +0300, Sergei Shtylyov wrote:
> > > Add minimal support for the RZ/G1M (R8A7743) SoC.
> > >
> > > Based on the original (and large) patch by Dmitry Shifrin
> > > <dmitry.shifrin@cogentembedded.com>.
> > >
> > > Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
> > > Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
> >
> > Thanks, I have queued this up.
>
> I'd like to see this patch rebased on top of "[PATCH] ARM: shmobile:
> Consolidate R8A779[234] machine definitions".
I'm happy to drop this patch if that is the desired outcome of
the discussion in this sub-thread.
^ permalink raw reply
* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Shawn Lin @ 2016-10-08 2:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9a660a813af58ad7c108fd06289513302426c9fb.1475853198.git-series.gregory.clement@free-electrons.com>
Hi,
? 2016/10/7 23:22, Gregory CLEMENT ??:
> 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.
>
You can overwtite this callback within your driver itself.
That is what other sdhci variant drivers did, so patch 1-3 are
unnecessary.
> Signed-off-by: Hu Ziji <huziji@marvell.com>
> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.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 d4bb818c52d5..2250ea22231f 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -1828,8 +1828,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;
> @@ -1921,6 +1921,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 21dc80b8ae3d..c38ab65b9a97 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -687,6 +687,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);
>
--
Best Regards
Shawn Lin
^ permalink raw reply
* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Shawn Lin @ 2016-10-08 2:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <e08ffb085d02a784b28456ac47fa4dc6540a9139.1475853198.git-series.gregory.clement@free-electrons.com>
? 2016/10/7 23:22, Gregory CLEMENT ??:
> 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>
> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
> ---
> MAINTAINERS | 1 +-
> drivers/mmc/host/Makefile | 2 +-
> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
> 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, 1321 insertions(+), 1 deletion(-)
> 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 859420e5dfd3..b5673c2ee5f2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji@marvell.com>
> L: linux-mmc at vger.kernel.org
> S: Supported
> F: drivers/mmc/host/sdhci-xenon.*
> +F: drivers/mmc/host/sdhci-xenon-phy.*
drivers/mmc/host/sdhci-xenon* shoube enough
> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.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..4eb8fea1bec9
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
Well, it's legit to use phy API and move your phy
operations to PHY subsystem. :)
> @@ -0,0 +1,1141 @@
> +/*
> + * 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);
> +}
> +
> +#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);
> +
> + if ((timing == MMC_TIMING_UHS_DDR50) ||
> + (timing == MMC_TIMING_MMC_HS400) ||
> + (timing == MMC_TIMING_MMC_DDR52)) {
> + reg = sdhci_readl(host, phy_regs->func_ctrl);
> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
> + sdhci_writel(host, reg, phy_regs->func_ctrl);
> + }
> +
> + if (timing == MMC_TIMING_MMC_HS400) {
> + reg = sdhci_readl(host, phy_regs->func_ctrl);
> + 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);
> +
> +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, "xenon,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, "xenon,phy-slow-mode"))
> + params->slow_mode = true;
> + else
> + params->slow_mode = false;
> +
> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
> + params->znr = value & ZNR_MASK;
> + else
> + params->znr = ZNR_DEF_VALUE;
> +
> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
> + params->zpr = value & ZPR_MASK;
> + else
> + params->zpr = ZPR_DEF_VALUE;
> +
> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
> + else
> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
> +
> + if (!of_property_read_u32(np, "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;
> + }
> + priv->timing = ios->timing;
> + priv->clock = host->clock;
> +
> + /* Legacy mode is a special case */
> + if (ios->timing == MMC_TIMING_LEGACY)
> + return 0;
> +
> + 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, "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 03ba183494d3..4d7d871544fc 100644
> --- a/drivers/mmc/host/sdhci-xenon.c
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -224,6 +224,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);
> @@ -309,6 +310,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->card_candidate) {
> if (mmc_card_mmc(priv->card_candidate))
> return xenon_emmc_signal_voltage_switch(mmc, ios);
> @@ -453,6 +456,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 c2370493fbe8..06e5261a563c 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
> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
> /* Slot idx */
> u8 slot_idx;
>
> + 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.
> @@ -131,4 +142,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
>
--
Best Regards
Shawn Lin
^ permalink raw reply
* [PATCH v2 2/2] ARM: dts: rockchip: Add rk3066 MK808 board
From: Shawn Lin @ 2016-10-08 3:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <2320435.WKGs9crzlB@phil>
? 2016/10/7 18:01, Heiko Stuebner ??:
> Am Freitag, 7. Oktober 2016, 10:29:48 CEST schrieb Shawn Lin:
>> Hi Pawe? ,
>>
>> On 2016/10/7 1:38, Pawe? Jarosz wrote:
>>> MK808 is a tv stick which has rockchip rk3066 CPU inside, two usb ports
>>> - host and otg, micro sd card slot and onboard wifi RK901.
>>>
>>> Signed-off-by: Pawe? Jarosz <paweljarosz3691@gmail.com>
>>> ---
>>>
>>> Changes in v2:
>>> - included Heiko sugestion.
>>>
>>> Documentation/devicetree/bindings/arm/rockchip.txt | 4 +
>>> arch/arm/boot/dts/Makefile | 1 +
>>> arch/arm/boot/dts/rk3066a-mk808.dts | 184
>>> +++++++++++++++++++++ 3 files changed, 189 insertions(+)
>>> create mode 100644 arch/arm/boot/dts/rk3066a-mk808.dts
>>>
>>> diff --git a/Documentation/devicetree/bindings/arm/rockchip.txt
>>> b/Documentation/devicetree/bindings/arm/rockchip.txt index
>>> 55f388f..c09595b 100644
>>> --- a/Documentation/devicetree/bindings/arm/rockchip.txt
>>> +++ b/Documentation/devicetree/bindings/arm/rockchip.txt
>>> @@ -17,6 +17,10 @@ Rockchip platforms device tree bindings
>>>
>>> Required root node properties:
>>> - compatible = "chipspark,rayeager-px2", "rockchip,rk3066a";
>>>
>>> +- Rikomagic MK808 v1 board:
>>> + Required root node properties:
>>> + - compatible = "rikomagic,mk808", "rockchip,rk3066a";
>>> +
>>>
>>> - Radxa Rock board:
>>> Required root node properties:
>>> - compatible = "radxa,rock", "rockchip,rk3188";
>>>
>>> diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
>>> index befcd26..f19cc1d 100644
>>> --- a/arch/arm/boot/dts/Makefile
>>> +++ b/arch/arm/boot/dts/Makefile
>>> @@ -639,6 +639,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += \
>>>
>>> rk3036-kylin.dtb \
>>> rk3066a-bqcurie2.dtb \
>>> rk3066a-marsboard.dtb \
>>>
>>> + rk3066a-mk808.dtb \
>>>
>>> rk3066a-rayeager.dtb \
>>> rk3188-radxarock.dtb \
>>> rk3228-evb.dtb \
>>>
>>> diff --git a/arch/arm/boot/dts/rk3066a-mk808.dts
>>> b/arch/arm/boot/dts/rk3066a-mk808.dts new file mode 100644
>>> index 0000000..2878562
>>> --- /dev/null
>>> +++ b/arch/arm/boot/dts/rk3066a-mk808.dts
>>> @@ -0,0 +1,184 @@
>>> +/*
>>> + * Copyright (c) 2016 Pawe? Jarosz <paweljarosz3691@gmail.com>
>>> + *
>>> + * This file is dual-licensed: you can use it either under the terms
>>> + * of the GPL or the X11 license, at your option. Note that this dual
>>> + * licensing only applies to this file, and not this project as a
>>> + * whole.
>>> + *
>>> + * a) This file 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.
>>> + *
>>> + * This file is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + * Or, alternatively,
>>> + *
>>> + * b) Permission is hereby granted, free of charge, to any person
>>> + * obtaining a copy of this software and associated documentation
>>> + * files (the "Software"), to deal in the Software without
>>> + * restriction, including without limitation the rights to use,
>>> + * copy, modify, merge, publish, distribute, sublicense, and/or
>>> + * sell copies of the Software, and to permit persons to whom the
>>> + * Software is furnished to do so, subject to the following
>>> + * conditions:
>>> + *
>>> + * The above copyright notice and this permission notice shall be
>>> + * included in all copies or substantial portions of the Software.
>>> + *
>>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
>>> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
>>> + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
>>> + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
>>> + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
>>> + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
>>> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>>> + * OTHER DEALINGS IN THE SOFTWARE.
>>> + */
>>> +
>>> +/dts-v1/;
>>> +#include "rk3066a.dtsi"
>>> +
>>> +/ {
>>> + model = "Rikomagic MK808";
>>> + compatible = "rikomagic,mk808", "rockchip,rk3066a";
>>> +
>>> + chosen {
>>> + stdout-path = "serial2:115200n8";
>>> + };
>>> +
>>> + memory at 60000000 {
>>> + device_type = "memory";
>>> + reg = <0x60000000 0x40000000>;
>>> + };
>>> +
>>> + gpio-leds {
>>> + compatible = "gpio-leds";
>>> +
>>> + blue {
>>> + label = "mk808:blue:power";
>>> + gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
>>> + default-state = "off";
>>> + linux,default-trigger = "default-on";
>>> + };
>>> + };
>>> +
>>> + mmc_pwrseq: mmc-pwrseq {
>>> + compatible = "mmc-pwrseq-simple";
>>> + pinctrl-names = "default";
>>> + pinctrl-0 = <&sdmmc_pwr>;
>>
>> sd slot does not contain a reset pin. So the power pin should be
>> enough. just add sdmmc_pwr to mmc0's pinctrl-0 and let mmc driver
>> control it should be enough. Typically pwrseq is for emmc and sdio.
>> We don't need a pwerseq to control power for sd slot..
>>
>> But it seems sdmmc_pwr is a GPIO, but not functional port.
>> So I am interesting that why MK808 board doesn't use the mmc
>> controller's default power pin but chosing another gpio, so finally
>> we have to add thses code for your DT.
>
> Actually, we always model the sd-mmc pwr-pin as gpio-based regulator (see
> sdmmc-regulator for example in the rk3066a-rayeager.dts), and specifiy this
> regulator as vmmc. That way the mmc core really can control the power.
yes it did. There are two ways for us to control vmmc properly.
If we use gpio-based regulator or PMIC, mmc core take over it
to control the power correctly. But if we use the dafault functional
port, namely SDMMC_PWREN, the dw_mmc driver could handle the control
when doing set_ios.. Both of these two could work fine. But I always
chose the latter one to simplify the dts.
Anyway, I won't insist on it if it looks okay to you. :)
>
>
> Heiko
>
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip
>
--
Best Regards
Shawn Lin
^ permalink raw reply
* [v12, 0/8] Fix eSDHC host version register bug
From: Y.B. Lu @ 2016-10-08 3:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474441040-11946-1-git-send-email-yangbo.lu@nxp.com>
Hi Uffe, Arnd and Scott,
Any comments on this latest patcheset?
Could we consider to merge it if no any other changes needed?
:)
Thanks.
Best regards,
Yangbo Lu
> -----Original Message-----
> From: Y.B. Lu
> Sent: Monday, September 26, 2016 11:15 AM
> To: linux-mmc at vger.kernel.org; ulf.hansson at linaro.org; Scott Wood; Arnd
> Bergmann
> Cc: linuxppc-dev at lists.ozlabs.org; devicetree at vger.kernel.org; linux-arm-
> kernel at lists.infradead.org; linux-kernel at vger.kernel.org; linux-
> clk at vger.kernel.org; linux-i2c at vger.kernel.org; iommu at lists.linux-
> foundation.org; netdev at vger.kernel.org; Mark Rutland; Rob Herring;
> Russell King; Jochen Friedrich; Joerg Roedel; Claudiu Manoil; Bhupesh
> Sharma; Qiang Zhao; Kumar Gala; Santosh Shilimkar; Leo Li; X.B. Xie; M.H.
> Lian
> Subject: RE: [v12, 0/8] Fix eSDHC host version register bug
>
> Any comments about this version patchset ?
>
> :)
>
>
> > -----Original Message-----
> > From: Yangbo Lu [mailto:yangbo.lu at nxp.com]
> > Sent: Wednesday, September 21, 2016 2:57 PM
> > To: linux-mmc at vger.kernel.org; ulf.hansson at linaro.org; Scott Wood;
> > Arnd Bergmann
> > Cc: linuxppc-dev at lists.ozlabs.org; devicetree at vger.kernel.org;
> > linux-arm- kernel at lists.infradead.org; linux-kernel at vger.kernel.org;
> > linux- clk at vger.kernel.org; linux-i2c at vger.kernel.org;
> > iommu at lists.linux- foundation.org; netdev at vger.kernel.org; Mark
> > Rutland; Rob Herring; Russell King; Jochen Friedrich; Joerg Roedel;
> > Claudiu Manoil; Bhupesh Sharma; Qiang Zhao; Kumar Gala; Santosh
> Shilimkar; Leo Li; X.B. Xie; M.H.
> > Lian; Y.B. Lu
> > Subject: [v12, 0/8] Fix eSDHC host version register bug
> >
> > This patchset is used to fix a host version register bug in the T4240-
> > R1.0-R2.0 eSDHC controller. To match the SoC version and revision, 10
> > previous version patchsets had tried many methods but all of them were
> > rejected by reviewers.
> > Such as
> > - dts compatible method
> > - syscon method
> > - ifdef PPC method
> > - GUTS driver getting SVR method
> > Anrd suggested a soc_device_match method in v10, and this is the only
> > available method left now. This v11 patchset introduces the
> > soc_device_match interface in soc driver.
> >
> > The first six patches of Yangbo are to add the GUTS driver. This is
> > used to register a soc device which contain soc version and revision
> > information.
> > The other two patches introduce the soc_device_match method in soc
> > driver and apply it on esdhc driver to fix this bug.
> >
> > Arnd Bergmann (1):
> > base: soc: introduce soc_device_match() interface
> >
> > Yangbo Lu (7):
> > dt: bindings: update Freescale DCFG compatible
> > ARM64: dts: ls2080a: add device configuration node
> > dt: bindings: move guts devicetree doc out of powerpc directory
> > powerpc/fsl: move mpc85xx.h to include/linux/fsl
> > soc: fsl: add GUTS driver for QorIQ platforms
> > MAINTAINERS: add entry for Freescale SoC drivers
> > mmc: sdhci-of-esdhc: fix host version for T4240-R1.0-R2.0
> >
> > Documentation/devicetree/bindings/arm/fsl.txt | 6 +-
> > .../bindings/{powerpc => soc}/fsl/guts.txt | 3 +
> > MAINTAINERS | 11 +-
> > arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi | 6 +
> > arch/powerpc/kernel/cpu_setup_fsl_booke.S | 2 +-
> > arch/powerpc/sysdev/fsl_pci.c | 2 +-
> > drivers/base/Kconfig | 1 +
> > drivers/base/soc.c | 66 ++++++
> > drivers/clk/clk-qoriq.c | 3 +-
> > drivers/i2c/busses/i2c-mpc.c | 2 +-
> > drivers/iommu/fsl_pamu.c | 3 +-
> > drivers/mmc/host/Kconfig | 1 +
> > drivers/mmc/host/sdhci-of-esdhc.c | 20 ++
> > drivers/net/ethernet/freescale/gianfar.c | 2 +-
> > drivers/soc/Kconfig | 2 +-
> > drivers/soc/fsl/Kconfig | 19 ++
> > drivers/soc/fsl/Makefile | 1 +
> > drivers/soc/fsl/guts.c | 257
> > +++++++++++++++++++++
> > include/linux/fsl/guts.h | 125 ++++++----
> > .../asm/mpc85xx.h => include/linux/fsl/svr.h | 4 +-
> > include/linux/sys_soc.h | 3 +
> > 21 files changed, 478 insertions(+), 61 deletions(-) rename
> > Documentation/devicetree/bindings/{powerpc => soc}/fsl/guts.txt (91%)
> > create mode 100644 drivers/soc/fsl/Kconfig create mode 100644
> > drivers/soc/fsl/guts.c rename arch/powerpc/include/asm/mpc85xx.h =>
> > include/linux/fsl/svr.h (97%)
> >
> > --
> > 2.1.0.27.g96db324
^ permalink raw reply
* [PATCH 1/5] rpmsg: smd: Reduce restrictions when finding channel
From: Bjorn Andersson @ 2016-10-08 4:23 UTC (permalink / raw)
To: linux-arm-kernel
SMD channels are created by the remotes in "opening" state, but
sometimes as we close and try to reopen them they linger in closing
state.
Following the search for a matching channel the create_ept() will verify
that the channel is in a suitable state, so we can lax the restrictions
of the search function to work around above difference in behaviour.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
drivers/rpmsg/qcom_smd.c | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c
index 06fef2b4c814..92efa74a0024 100644
--- a/drivers/rpmsg/qcom_smd.c
+++ b/drivers/rpmsg/qcom_smd.c
@@ -820,20 +820,13 @@ qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
struct qcom_smd_channel *channel;
struct qcom_smd_channel *ret = NULL;
unsigned long flags;
- unsigned state;
spin_lock_irqsave(&edge->channels_lock, flags);
list_for_each_entry(channel, &edge->channels, list) {
- if (strcmp(channel->name, name))
- continue;
-
- state = GET_RX_CHANNEL_INFO(channel, state);
- if (state != SMD_CHANNEL_OPENING &&
- state != SMD_CHANNEL_OPENED)
- continue;
-
- ret = channel;
- break;
+ if (!strcmp(channel->name, name)) {
+ ret = channel;
+ break;
+ }
}
spin_unlock_irqrestore(&edge->channels_lock, flags);
--
2.5.0
^ permalink raw reply related
* [PATCH 2/5] rpmsg: Introduce a driver override mechanism
From: Bjorn Andersson @ 2016-10-08 4:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475900595-8375-1-git-send-email-bjorn.andersson@linaro.org>
Similar to other subsystems it's useful to provide a mechanism to force
a specific driver match on a device, so introduce this.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
drivers/rpmsg/rpmsg_core.c | 3 +++
include/linux/rpmsg.h | 2 ++
2 files changed, 5 insertions(+)
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index b6ea9ffa7381..087d4db896c8 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -315,6 +315,9 @@ static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
const struct rpmsg_device_id *ids = rpdrv->id_table;
unsigned int i;
+ if (rpdev->driver_override)
+ return !strcmp(rpdev->driver_override, drv->name);
+
if (ids)
for (i = 0; ids[i].name[0]; i++)
if (rpmsg_id_match(rpdev, &ids[i]))
diff --git a/include/linux/rpmsg.h b/include/linux/rpmsg.h
index 452d393cc8dd..7ad6c205f110 100644
--- a/include/linux/rpmsg.h
+++ b/include/linux/rpmsg.h
@@ -64,6 +64,7 @@ struct rpmsg_channel_info {
* rpmsg_device - device that belong to the rpmsg bus
* @dev: the device struct
* @id: device id (used to match between rpmsg drivers and devices)
+ * @driver_override: driver name to force a match
* @src: local address
* @dst: destination address
* @ept: the rpmsg endpoint of this channel
@@ -72,6 +73,7 @@ struct rpmsg_channel_info {
struct rpmsg_device {
struct device dev;
struct rpmsg_device_id id;
+ char *driver_override;
u32 src;
u32 dst;
struct rpmsg_endpoint *ept;
--
2.5.0
^ permalink raw reply related
* [PATCH 3/5] rpmsg: Support drivers without primary endpoint
From: Bjorn Andersson @ 2016-10-08 4:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475900595-8375-1-git-send-email-bjorn.andersson@linaro.org>
Some types of rpmsg drivers does not have a primary endpoint to tie
their existence upon, but wishes to create and destroy endpoints
dynamically, e.g. based on user interactions.
Allow rpmsg drivers to omit a driver callback to signal this case and
make the probe path not create a primary endpoint in this case.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
drivers/rpmsg/rpmsg_core.c | 32 ++++++++++++++++++--------------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index 087d4db896c8..7561941ba413 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -347,27 +347,30 @@ static int rpmsg_dev_probe(struct device *dev)
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
struct rpmsg_channel_info chinfo = {};
- struct rpmsg_endpoint *ept;
+ struct rpmsg_endpoint *ept = NULL;
int err;
- strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
- chinfo.src = rpdev->src;
- chinfo.dst = RPMSG_ADDR_ANY;
+ if (rpdrv->callback) {
+ strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
+ chinfo.src = rpdev->src;
+ chinfo.dst = RPMSG_ADDR_ANY;
- ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
- if (!ept) {
- dev_err(dev, "failed to create endpoint\n");
- err = -ENOMEM;
- goto out;
- }
+ ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
+ if (!ept) {
+ dev_err(dev, "failed to create endpoint\n");
+ err = -ENOMEM;
+ goto out;
+ }
- rpdev->ept = ept;
- rpdev->src = ept->addr;
+ rpdev->ept = ept;
+ rpdev->src = ept->addr;
+ }
err = rpdrv->probe(rpdev);
if (err) {
dev_err(dev, "%s: failed: %d\n", __func__, err);
- rpmsg_destroy_ept(ept);
+ if (ept)
+ rpmsg_destroy_ept(ept);
goto out;
}
@@ -388,7 +391,8 @@ static int rpmsg_dev_remove(struct device *dev)
rpdrv->remove(rpdev);
- rpmsg_destroy_ept(rpdev->ept);
+ if (rpdev->ept)
+ rpmsg_destroy_ept(rpdev->ept);
return err;
}
--
2.5.0
^ permalink raw reply related
* [PATCH 4/5] rpmsg: Driver for user space endpoint interface
From: Bjorn Andersson @ 2016-10-08 4:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475900595-8375-1-git-send-email-bjorn.andersson@linaro.org>
This driver allows rpmsg instances to expose access to rpmsg endpoints
to user space processes. It provides a control interface, allowing
userspace to export endpoints and an endpoint interface for each exposed
endpoint.
The implementation is based on prior art by Texas Instrument, Google,
PetaLogix and was derived from a FreeRTOS performance statistics driver
written by Michal Simek.
The control interface provides a "create endpoint" ioctl, which is fed a
name, source and destination address. The three values are used to
create the endpoint, in a backend-specific way, and a rpmsg endpoint
device is created - with the three parameters are available in sysfs for
udev usage.
E.g. to create an endpoint device for one of the Qualcomm SMD channel
related to DIAG one would issue:
struct rpmsg_endpoint_info info = { "DIAG_CNTL", 0, 0 };
int fd = open("/dev/rpmsg_ctrl0", O_RDWR);
ioctl(fd, RPMSG_CREATE_EPT_IOCTL, &info);
Each created endpoint device shows up as an individual character device
in /dev, allowing permission to be controlled on a per-endpoint basis.
The rpmsg endpoint will be created and destroyed following the opening
and closing of the endpoint device, allowing rpmsg backends to open and
close the physical channel, if supported by the wire protocol.
Cc: Marek Novak <marek.novak@nxp.com>
Cc: Matteo Sartori <matteo.sartori@t3lab.it>
Cc: Michal Simek <monstr@monstr.eu>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
Documentation/ioctl/ioctl-number.txt | 1 +
drivers/rpmsg/Makefile | 2 +-
drivers/rpmsg/rpmsg_char.c | 576 +++++++++++++++++++++++++++++++++++
drivers/rpmsg/rpmsg_internal.h | 2 +
include/uapi/linux/rpmsg.h | 35 +++
5 files changed, 615 insertions(+), 1 deletion(-)
create mode 100644 drivers/rpmsg/rpmsg_char.c
create mode 100644 include/uapi/linux/rpmsg.h
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index 81c7f2bb7daf..08244bea5048 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -321,6 +321,7 @@ Code Seq#(hex) Include File Comments
0xB1 00-1F PPPoX <mailto:mostrows@styx.uwaterloo.ca>
0xB3 00 linux/mmc/ioctl.h
0xB4 00-0F linux/gpio.h <mailto:linux-gpio@vger.kernel.org>
+0xB5 00-0F uapi/linux/rpmsg.h <mailto:linux-remoteproc@vger.kernel.org>
0xC0 00-0F linux/usb/iowarrior.h
0xCA 00-0F uapi/misc/cxl.h
0xCA 80-8F uapi/scsi/cxlflash_ioctl.h
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index ae9c9132cf76..5daf1209b77d 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1,3 +1,3 @@
-obj-$(CONFIG_RPMSG) += rpmsg_core.o
+obj-$(CONFIG_RPMSG) += rpmsg_core.o rpmsg_char.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c
new file mode 100644
index 000000000000..a398a63e8d44
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_char.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 2016, Linaro Ltd.
+ * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2012, PetaLogix
+ * Copyright (c) 2011, Texas Instruments, Inc.
+ * Copyright (c) 2011, Google, Inc.
+ *
+ * Based on rpmsg performance statistics driver by Michal Simek, which in turn
+ * was based on TI & Google OMX rpmsg driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/rpmsg.h>
+
+#include "rpmsg_internal.h"
+
+#define RPMSG_DEV_MAX 256
+
+static dev_t rpmsg_major;
+static struct class *rpmsg_class;
+
+static DEFINE_IDA(rpmsg_ctrl_ida);
+static DEFINE_IDA(rpmsg_ept_ida);
+static DEFINE_IDA(rpmsg_minor_ida);
+
+#define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev)
+#define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev)
+
+#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
+#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
+
+struct rpmsg_ctrldev {
+ struct rpmsg_device *rpdev;
+ struct cdev cdev;
+ struct device dev;
+};
+
+struct rpmsg_eptdev {
+ struct device dev;
+ struct cdev cdev;
+
+ struct rpmsg_device *rpdev;
+ struct rpmsg_channel_info chinfo;
+
+ struct mutex ept_lock;
+ struct rpmsg_endpoint *ept;
+
+ spinlock_t queue_lock;
+ struct sk_buff_head queue;
+ wait_queue_head_t readq;
+};
+
+static int rpmsg_eptdev_destroy(struct rpmsg_eptdev *eptdev);
+
+
+static int rpmsg_cdev_register(struct device *dev,
+ struct cdev *cdev,
+ const struct file_operations *fops,
+ dev_t *assigned_devt)
+{
+ dev_t devt;
+ int ret;
+
+ ret = ida_simple_get(&rpmsg_minor_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ devt = MKDEV(MAJOR(rpmsg_major), ret);
+
+ cdev_init(cdev, fops);
+ cdev->owner = THIS_MODULE;
+ ret = cdev_add(cdev, devt, 1);
+ if (ret < 0) {
+ dev_err(dev, "cdev_add failed: %d\n", ret);
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(devt));
+ return ret;
+ }
+
+ *assigned_devt = devt;
+ return 0;
+}
+
+static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,
+ void *priv, u32 addr)
+{
+ struct rpmsg_eptdev *eptdev = priv;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ memcpy(skb_put(skb, len), buf, len);
+
+ spin_lock(&eptdev->queue_lock);
+ skb_queue_tail(&eptdev->queue, skb);
+ spin_unlock(&eptdev->queue_lock);
+
+ /* wake up any blocking processes, waiting for new data */
+ wake_up_interruptible(&eptdev->readq);
+
+ return 0;
+}
+
+static int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
+ struct rpmsg_endpoint *ept;
+ struct rpmsg_device *rpdev = eptdev->rpdev;
+ struct device *dev = &eptdev->dev;
+
+ get_device(dev);
+
+ ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
+ if (!ept) {
+ dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);
+ put_device(dev);
+ return -EINVAL;
+ }
+
+ eptdev->ept = ept;
+ filp->private_data = eptdev;
+
+ return 0;
+}
+
+static int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
+ struct device *dev = &eptdev->dev;
+ struct sk_buff *skb;
+
+ /* Close the endpoint, if it's not already destroyed by the parent */
+ if (eptdev->ept)
+ rpmsg_destroy_ept(eptdev->ept);
+
+ /* Discard all SKBs */
+ while (!skb_queue_empty(&eptdev->queue)) {
+ skb = skb_dequeue(&eptdev->queue);
+ kfree_skb(skb);
+ }
+
+ put_device(dev);
+
+ return 0;
+}
+
+static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rpmsg_eptdev *eptdev = fp->private_data;
+
+ if (cmd != RPMSG_DESTROY_EPT_IOCTL)
+ return -EINVAL;
+
+ return rpmsg_eptdev_destroy(eptdev);
+}
+
+static ssize_t rpmsg_eptdev_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct rpmsg_eptdev *eptdev = filp->private_data;
+ unsigned long flags;
+ struct sk_buff *skb;
+ int use;
+
+ spin_lock_irqsave(&eptdev->queue_lock, flags);
+
+ /* Wait for data in the queue */
+ if (skb_queue_empty(&eptdev->queue)) {
+ spin_unlock_irqrestore(&eptdev->queue_lock, flags);
+
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ /* Wait until we get data or the endpoint goes away */
+ if (wait_event_interruptible(eptdev->readq,
+ !skb_queue_empty(&eptdev->queue) ||
+ !eptdev->ept))
+ return -ERESTARTSYS;
+
+ /* We lost the endpoint while waiting */
+ if (!eptdev->ept)
+ return -EPIPE;
+
+ spin_lock_irqsave(&eptdev->queue_lock, flags);
+ }
+
+ skb = skb_dequeue(&eptdev->queue);
+ if (!skb)
+ return -EFAULT;
+
+ spin_unlock_irqrestore(&eptdev->queue_lock, flags);
+
+ use = min_t(size_t, count, skb->len);
+ if (copy_to_user(buf, skb->data, use))
+ use = -EFAULT;
+
+ kfree_skb(skb);
+
+ return use;
+}
+
+static ssize_t rpmsg_eptdev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct rpmsg_eptdev *eptdev = filp->private_data;
+ void *kbuf;
+ int ret;
+
+ kbuf = kzalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, buf, count)) {
+ ret = -EFAULT;
+ goto free_kbuf;
+ }
+
+ if (mutex_lock_interruptible(&eptdev->ept_lock)) {
+ ret = -ERESTARTSYS;
+ goto free_kbuf;
+ }
+
+ if (!eptdev->ept) {
+ ret = -EPIPE;
+ goto unlock_eptdev;
+ }
+
+ if (filp->f_flags & O_NONBLOCK)
+ ret = rpmsg_trysend(eptdev->ept, kbuf, count);
+ else
+ ret = rpmsg_send(eptdev->ept, kbuf, count);
+
+unlock_eptdev:
+ mutex_unlock(&eptdev->ept_lock);
+
+free_kbuf:
+ kfree(kbuf);
+ return ret;
+}
+
+static const struct file_operations rpmsg_eptdev_fops = {
+ .owner = THIS_MODULE,
+ .open = rpmsg_eptdev_open,
+ .release = rpmsg_eptdev_release,
+ .read = rpmsg_eptdev_read,
+ .write = rpmsg_eptdev_write,
+ .unlocked_ioctl = rpmsg_eptdev_ioctl,
+};
+
+static ssize_t name_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", eptdev->chinfo.name);
+}
+static DEVICE_ATTR_RO(name);
+
+static ssize_t src_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", eptdev->chinfo.src);
+}
+static DEVICE_ATTR_RO(src);
+
+static ssize_t dst_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", eptdev->chinfo.dst);
+}
+static DEVICE_ATTR_RO(dst);
+
+static struct attribute *rpmsg_eptdev_attrs[] = {
+ &dev_attr_name.attr,
+ &dev_attr_src.attr,
+ &dev_attr_dst.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(rpmsg_eptdev);
+
+static void rpmsg_eptdev_release_device(struct device *dev)
+{
+ struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
+
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(eptdev->dev.devt));
+ kfree(eptdev);
+}
+
+static int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev,
+ struct rpmsg_channel_info chinfo)
+{
+ struct rpmsg_device *rpdev = ctrldev->rpdev;
+ struct rpmsg_eptdev *eptdev;
+ struct device *dev;
+ int ret;
+ int id;
+
+ eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL);
+ if (!eptdev)
+ return -ENOMEM;
+
+ eptdev->rpdev = rpdev;
+ eptdev->chinfo = chinfo;
+
+ mutex_init(&eptdev->ept_lock);
+ spin_lock_init(&eptdev->queue_lock);
+ skb_queue_head_init(&eptdev->queue);
+ init_waitqueue_head(&eptdev->readq);
+
+ id = ida_simple_get(&rpmsg_ept_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ kfree(eptdev);
+ return id;
+ }
+
+ dev = &eptdev->dev;
+ device_initialize(dev);
+ dev->class = rpmsg_class;
+ dev->id = id;
+ dev->parent = &ctrldev->dev;
+ dev->release = rpmsg_eptdev_release_device;
+ dev->groups = rpmsg_eptdev_groups;
+ dev_set_name(dev, "rpmsg%d", id);
+ dev_set_drvdata(dev, eptdev);
+
+ ret = rpmsg_cdev_register(dev, &eptdev->cdev,
+ &rpmsg_eptdev_fops, &dev->devt);
+ if (ret) {
+ dev_err(dev, "cdev_add failed: %d\n", ret);
+ goto out;
+ }
+
+ ret = device_add(dev);
+ if (ret) {
+ dev_err(dev, "device_register failed: %d\n", ret);
+ goto out;
+ }
+
+out:
+ if (ret < 0)
+ put_device(dev);
+
+ return ret;
+}
+
+static int rpmsg_eptdev_destroy(struct rpmsg_eptdev *eptdev)
+{
+ struct rpmsg_endpoint *ept = eptdev->ept;
+
+ mutex_lock(&eptdev->ept_lock);
+ eptdev->ept = NULL;
+ mutex_unlock(&eptdev->ept_lock);
+
+ rpmsg_destroy_ept(ept);
+
+ /* wake up any blocking processes */
+ wake_up_interruptible(&eptdev->readq);
+
+ cdev_del(&eptdev->cdev);
+ device_del(&eptdev->dev);
+ put_device(&eptdev->dev);
+
+ return 0;
+}
+
+static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
+
+ get_device(&ctrldev->rpdev->dev);
+ filp->private_data = ctrldev;
+
+ return 0;
+}
+
+static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
+
+ put_device(&ctrldev->rpdev->dev);
+
+ return 0;
+}
+
+static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rpmsg_ctrldev *ctrldev = fp->private_data;
+ void __user *argp = (void __user *)arg;
+ struct rpmsg_endpoint_info eptinfo;
+ struct rpmsg_channel_info chinfo;
+
+ if (cmd != RPMSG_CREATE_EPT_IOCTL)
+ return -EINVAL;
+
+ if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
+ return -EFAULT;
+
+ memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
+ chinfo.name[RPMSG_NAME_SIZE-1] = '\0';
+ chinfo.src = eptinfo.src;
+ chinfo.dst = eptinfo.dst;
+
+ return rpmsg_eptdev_create(ctrldev, chinfo);
+};
+
+static const struct file_operations rpmsg_ctrldev_fops = {
+ .owner = THIS_MODULE,
+ .open = rpmsg_ctrldev_open,
+ .release = rpmsg_ctrldev_release,
+ .unlocked_ioctl = rpmsg_ctrldev_ioctl,
+};
+
+static void rpmsg_chrdev_release_device(struct device *dev)
+{
+ struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
+
+ ida_simple_remove(&rpmsg_ctrl_ida, MINOR(dev->devt));
+ cdev_del(&ctrldev->cdev);
+ kfree(ctrldev);
+}
+
+static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_ctrldev *ctrldev;
+ struct device *dev;
+ int ret;
+ int id;
+
+ ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
+ if (!ctrldev)
+ return -ENOMEM;
+
+ dev = &ctrldev->dev;
+
+ ctrldev->rpdev = rpdev;
+
+ id = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ kfree(ctrldev);
+ return id;
+ }
+
+ device_initialize(dev);
+ dev->parent = &rpdev->dev;
+ dev->class = rpmsg_class;
+ dev->release = rpmsg_chrdev_release_device;
+ dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", id);
+
+ ret = rpmsg_cdev_register(dev, &ctrldev->cdev,
+ &rpmsg_ctrldev_fops, &dev->devt);
+ if (ret < 0) {
+ put_device(dev);
+ return ret;
+ }
+
+ ret = device_add(dev);
+ if (ret) {
+ dev_err(&rpdev->dev, "device_register failed: %d\n", ret);
+ put_device(dev);
+ }
+
+ dev_set_drvdata(&rpdev->dev, ctrldev);
+
+ return ret;
+}
+
+static int _rpmsg_eptdev_destroy(struct device *dev, void *data)
+{
+ struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
+
+ return rpmsg_eptdev_destroy(eptdev);
+}
+
+static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
+ int ret;
+
+ /* Destroy all endpoints */
+ ret = device_for_each_child(&ctrldev->dev, NULL, _rpmsg_eptdev_destroy);
+ if (ret)
+ dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
+
+ device_del(&ctrldev->dev);
+ put_device(&ctrldev->dev);
+}
+
+static struct rpmsg_driver rpmsg_chrdev_driver = {
+ .probe = rpmsg_chrdev_probe,
+ .remove = rpmsg_chrdev_remove,
+ .drv = {
+ .name = "rpmsg_chrdev",
+ },
+};
+
+/**
+ * rpmsg_chrdev_register_device() - register chrdev device based on rpdev
+ * @rpdev: prepared rpdev to be used for creating endpoints
+ *
+ * This function wraps rpmsg_register_device() preparing the rpdev for use as
+ * basis for the rpmsg chrdev.
+ */
+int rpmsg_chrdev_register_device(struct rpmsg_device *rpdev)
+{
+ strcpy(rpdev->id.name, "rpmsg_chrdev");
+ rpdev->driver_override = "rpmsg_chrdev";
+
+ return rpmsg_register_device(rpdev);
+}
+EXPORT_SYMBOL(rpmsg_chrdev_register_device);
+
+static int rpmsg_char_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg");
+ if (ret < 0) {
+ pr_err("rpmsg: failed to allocate char dev region\n");
+ return ret;
+ }
+
+ rpmsg_class = class_create(THIS_MODULE, "rpmsg");
+ if (IS_ERR(rpmsg_class)) {
+ pr_err("failed to create rpmsg class\n");
+ ret = PTR_ERR(rpmsg_class);
+ goto unregister_chrdev;
+ }
+
+ ret = register_rpmsg_driver(&rpmsg_chrdev_driver);
+ if (ret < 0) {
+ pr_err("rpmsgchr: failed to register rpmsg driver\n");
+ goto destroy_class;
+ }
+
+ return 0;
+
+destroy_class:
+ class_destroy(rpmsg_class);
+
+unregister_chrdev:
+ unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+
+ return ret;
+}
+postcore_initcall(rpmsg_char_init);
+
+static void rpmsg_chrdev_exit(void)
+{
+ unregister_rpmsg_driver(&rpmsg_chrdev_driver);
+ unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+}
+module_exit(rpmsg_chrdev_exit);
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index 8075a20f919b..53d300eacc1c 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -79,4 +79,6 @@ int rpmsg_unregister_device(struct device *parent,
struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo);
+int rpmsg_chrdev_register_device(struct rpmsg_device *rpdev);
+
#endif
diff --git a/include/uapi/linux/rpmsg.h b/include/uapi/linux/rpmsg.h
new file mode 100644
index 000000000000..dedc226e0d3f
--- /dev/null
+++ b/include/uapi/linux/rpmsg.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _UAPI_RPMSG_H_
+#define _UAPI_RPMSG_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * struct rpmsg_endpoint_info - endpoint info representation
+ * @name: name of service
+ * @src: local address
+ * @dst: destination address
+ */
+struct rpmsg_endpoint_info {
+ char name[32];
+ __u32 src;
+ __u32 dst;
+};
+
+#define RPMSG_CREATE_EPT_IOCTL _IOW(0xb5, 0x1, struct rpmsg_endpoint_info)
+#define RPMSG_DESTROY_EPT_IOCTL _IO(0xb5, 0x2)
+
+#endif
--
2.5.0
^ permalink raw reply related
* [PATCH 5/5] rpmsg: smd: Register rpmsg user space interface for edges
From: Bjorn Andersson @ 2016-10-08 4:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475900595-8375-1-git-send-email-bjorn.andersson@linaro.org>
Create and register a rpmsg device for use with the rpmsg user space
interface, allowing user space to access SMD channels.
Also provide the "rpmsg_name" device attribute to expose the edge name
in sysfs, allowing the user to write udev rules for specific rpmsg
devices and their children.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
---
drivers/rpmsg/qcom_smd.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c
index 92efa74a0024..86856b2b558a 100644
--- a/drivers/rpmsg/qcom_smd.c
+++ b/drivers/rpmsg/qcom_smd.c
@@ -983,6 +983,20 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel)
return rpmsg_register_device(rpdev);
}
+static int qcom_smd_create_chrdev(struct qcom_smd_edge *edge)
+{
+ struct qcom_smd_device *qsdev;
+
+ qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
+ if (!qsdev)
+ return -ENOMEM;
+
+ qsdev->edge = edge;
+ qsdev->rpdev.ops = &qcom_smd_device_ops;
+ qsdev->rpdev.dev.parent = &edge->dev;
+ return rpmsg_chrdev_register_device(&qsdev->rpdev);
+}
+
/*
* Allocate the qcom_smd_channel object for a newly found smd channel,
* retrieving and validating the smem items involved.
@@ -1284,6 +1298,21 @@ static void qcom_smd_edge_release(struct device *dev)
kfree(edge);
}
+static ssize_t rpmsg_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qcom_smd_edge *edge = to_smd_edge(dev);
+
+ return sprintf(buf, "%s\n", edge->of_node->name);
+}
+static DEVICE_ATTR_RO(rpmsg_name);
+
+static struct attribute *qcom_smd_edge_attrs[] = {
+ &dev_attr_rpmsg_name.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(qcom_smd_edge);
+
/**
* qcom_smd_register_edge() - register an edge based on an device_node
* @parent: parent device for the edge
@@ -1305,6 +1334,7 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
edge->dev.parent = parent;
edge->dev.release = qcom_smd_edge_release;
+ edge->dev.groups = qcom_smd_edge_groups;
dev_set_name(&edge->dev, "%s:%s", dev_name(parent), node->name);
ret = device_register(&edge->dev);
if (ret) {
@@ -1318,6 +1348,12 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
goto unregister_dev;
}
+ ret = qcom_smd_create_chrdev(edge);
+ if (ret) {
+ dev_err(&edge->dev, "failed to register chrdev for edge\n");
+ goto unregister_dev;
+ }
+
schedule_work(&edge->scan_work);
return edge;
--
2.5.0
^ permalink raw reply related
* [PATCH 2/3] arm64: hw_breakpoint: Handle inexact watchpoint addresses
From: Pratyush Anand @ 2016-10-08 5:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAJt8pk9WOhebSXpbahbbLscqJcY1OOqHiA_9hHC0zmHruRPxWQ@mail.gmail.com>
Hi Pavel,
On Fri, Oct 7, 2016 at 10:54 PM, Pavel Labath <labath@google.com> wrote:
> On 7 October 2016 at 09:38, Pratyush Anand <panand@redhat.com> wrote:
>>
>>
>> IIUC, then you see an issue when an address watched is not the base
>> address accessed by the instruction. For example, if an address 'a+8'
>> is watched and an instruction accesses instruction from a to a +16. I
>> tried to reproduce the issue with mustang using your test-case in
>> patch3 (after couple of syntax modifcations for resolving compilation
>> issue with gcc). All the test case did pass with existing code in
>> v4.8. I noticed that, watchpoint exception is generated if any of the
>> sub-location accessed from a single instruction is watched, provided
>> watchdpoint watches either a byte, half word, word or double word
>> from the base.
>>
>>
>> So, either I must be missing something or the problem is not related
>> to all arm64 platform.
>
>
> Hello Pratyush,
>
> Thank you for looking into this.
>
> The thing is, I have observed different behavior here depending on the
> exact hardware used. I don't have the exact parameters with me now,
> but I can look it up next week.
>
> The thing is that the spec is imprecise about what exact address the
> hardware can report for the watchpoint hit. I presume that is
> deliberate to give some leeway to implementers. The spec says the
> address can be anywhere in the range from the lowest memory address
> accessed by the instruction to the highest address watched by the
> watchpoint,
I think, my patches should be able to take care of the above condition.
> but most hardware seems to be stricter than that and
> return an address that fits inside the watched range.
>
> On chip 1, I observed the behavior where the hardware would
> consistently report an address out of range of the watchpoint and we
> would just spin it in a loop.
>
> On chip 2, I observed the behavior where the hardware would report an
> out-of-range address for the first two dozen (~) iterations, after
> which it would "give up" and report an address that we were happy
> with. I don't really have an explanation for this - I can only assume
> that some external event like a reschedule to a different core caused
> some internal state of the hardware to be reset and cause it to report
> a different (better?) address instead. In the case where this was
> happening, it had no observable effects on userspace - it did not see
> the fact that we had re-executed the offending instruction a dozen
> times and as far as it was concerned, the watchpoint functionality
> worked perfectly. You can check whether this is happening in your case
> by instrumenting the code to print the reported address whenever it
> enters `watchpoint_handler`.
>
Yes, I had done that. In my case it was always starting memory address
accessed by the instruction. e.g. if a strb writes to 0x400023, I
would get addr=0x400023 in watchpoint_handler(), if a str writes to
0x400020-0x400027, I would get addr=0x400020.
> (I am sorry about the test errors. I was compiling the test case with
> an android gcc - I'll make sure to check it with a vanilla linux gcc
> also.)
>
your test cases are helpful. I think, I would use them to build
further test cases.
> >
>> However, I did notice that it does not work if we watch an address
>> which is at some offset from address programmed. For example, it works
>> when byte_mask is 0x3, but it does not work if byte_mask if 0x2 (which
>> is supported by hardware).
>>
>> I do have some patches to resolve that.
>>
>> https://github.com/pratyushanand/linux/commits/perf/upstream_arm64_devel
>>
>> I will send them for review comment after some testing.
>
> I am looking forward to these patches - they were the next on my list
> to look into after I got this resolved. :)
>
> However: Are sure about 0x2 not being a valid byte mask? According to
It is a valid mask indeed, and you should be able to use all those
mask after my patches.
> my reading of the armv8 spec (section D7.3.11, "DBGWCR<n>_EL1, Debug
> Watchpoint Control Registers, n = 0 - 15") it should be fine.
> ====
> The valid values for BAS are 0b0000000, or a binary number all of
> whose set bits are contiguous. All other values are reserved and must
> not be used by software.
> ====
> So, 0x2 (as well as 0x6, 0xC, 0xE) should be fine as it has a
> contiguous sequence of set bit(s). I haven't tried yet whether any
> hardware actually handles that correctly, but I was certainly hoping
> we would be able to watch more precise memory regions.
I have tested couple of them, and I think my patches should help with
using any contiguous bit mask acceptable by spec.
~Pratyush
^ permalink raw reply
* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Ziji Hu @ 2016-10-08 6:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9bfe6e9a-c06e-481a-b5ea-af58820093bf@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:40, Shawn Lin wrote:
> Hi,
>
> ? 2016/10/7 23:22, Gregory CLEMENT ??:
>> 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.
>>
>
> You can overwtite this callback within your driver itself.
> That is what other sdhci variant drivers did, so patch 1-3 are
> unnecessary.
Thanks a lot for your reply.
Our SDHC driver just requests some pre- and post- operations besides common standard SDHC functions.
Overwriting those common functions costs too much.
Thank you.
Best regards,
Hu Ziji
>
>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.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 d4bb818c52d5..2250ea22231f 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -1828,8 +1828,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;
>> @@ -1921,6 +1921,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 21dc80b8ae3d..c38ab65b9a97 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -687,6 +687,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);
>>
>
>
^ permalink raw reply
* [PATCH] drm/virtio: kconfig: Fixup white space.
From: Peter Griffin @ 2016-10-08 7:55 UTC (permalink / raw)
To: linux-arm-kernel
Use tabs instead of spaces.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
drivers/gpu/drm/virtio/Kconfig | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/virtio/Kconfig b/drivers/gpu/drm/virtio/Kconfig
index e1afc3d..81d1807 100644
--- a/drivers/gpu/drm/virtio/Kconfig
+++ b/drivers/gpu/drm/virtio/Kconfig
@@ -1,10 +1,10 @@
config DRM_VIRTIO_GPU
tristate "Virtio GPU driver"
depends on DRM && VIRTIO
- select DRM_KMS_HELPER
- select DRM_TTM
+ select DRM_KMS_HELPER
+ select DRM_TTM
help
This is the virtual GPU driver for virtio. It can be used with
- QEMU based VMMs (like KVM or Xen).
+ QEMU based VMMs (like KVM or Xen).
If unsure say M.
--
1.9.1
^ permalink raw reply related
* [PATCH v7 0/8] power: add power sequence library
From: Peter Chen @ 2016-10-08 8:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20160926062649.GB25395@b29397-desktop>
On Mon, Sep 26, 2016 at 02:26:49PM +0800, Peter Chen wrote:
> On Tue, Sep 20, 2016 at 11:36:39AM +0800, Peter Chen wrote:
> > Hi all,
> >
> > This is a follow-up for my last power sequence framework patch set [1].
> > According to Rob Herring and Ulf Hansson's comments[2]. The kinds of
> > power sequence instances will be added at postcore_initcall, the match
> > criteria is compatible string first, if the compatible string is not
> > matched between dts and library, it will try to use generic power sequence.
> >
> > The host driver just needs to call of_pwrseq_on/of_pwrseq_off
> > if only one power sequence instance is needed, for more power sequences
> > are used, using of_pwrseq_on_list/of_pwrseq_off_list instead (eg, USB hub driver).
> >
> > In future, if there are special power sequence requirements, the special
> > power sequence library can be created.
> >
> > This patch set is tested on i.mx6 sabresx evk using a dts change, I use
> > two hot-plug devices to simulate this use case, the related binding
> > change is updated at patch [1/6], The udoo board changes were tested
> > using my last power sequence patch set.[3]
> >
> > Except for hard-wired MMC and USB devices, I find the USB ULPI PHY also
> > need to power on itself before it can be found by ULPI bus.
> >
> > [1] http://www.spinics.net/lists/linux-usb/msg142755.html
> > [2] http://www.spinics.net/lists/linux-usb/msg143106.html
> > [3] http://www.spinics.net/lists/linux-usb/msg142815.html
> >
> > Changes for v7:
> > - Create kinds of power sequence instance at postcore_initcall, and match
> > the instance with node using compatible string, the beneit of this is
> > the host driver doesn't need to consider which pwrseq instance needs
> > to be used, and pwrseq core will match it, however, it eats some memories
> > if less power sequence instances are used. [Patch 2/8]
> > - Add pwrseq_compatible_sample.c to test match pwrseq using device_id. [Patch 2/8]
> > - Fix the comments Vaibhav Hiremath adds for error path for clock and do not
> > use device_node for parameters at pwrseq_on. [Patch 2/8]
> > - Simplify the caller to use power sequence, follows Alan's commnets [Patch 4/8]
> > - Tested three pwrseq instances together using both specific compatible string and
> > generic libraries.
> >
>
> Hi Vaibhav, would you please test if this series can support your case,
> you can add one instance like pwrseq_compatible_sample? If you are busy
> now, but think this series can satisfy your requirement, please ack it.
> I will delete pwrseq_compatible_sample, and only submit the generic one
> at next revision, you can add it later, thanks.
>
ping...
Vaibhav, if you have no more comments, I will delete the
pwrseq_compatible_sample, and send v8.
Peter
> Peter
>
> > Changes for v6:
> > - Add Matthias Kaehlcke's Reviewed-by and Tested-by. (patch [2/6])
> > - Change chipidea core of_node assignment for coming user. (patch [5/6])
> > - Applies Joshua Clayton's three dts changes for two boards,
> > the USB device's reg has only #address-cells, but without #size-cells.
> >
> > Changes for v5:
> > - Delete pwrseq_register/pwrseq_unregister, which is useless currently
> > - Fix the linker error when the pwrseq user is compiled as module
> >
> > Changes for v4:
> > - Create the patch on next-20160722
> > - Fix the of_node is not NULL after chipidea driver is unbinded [Patch 5/6]
> > - Using more friendly wait method for reset gpio [Patch 2/6]
> > - Support multiple input clocks [Patch 2/6]
> > - Add Rob Herring's ack for DT changes
> > - Add Joshua Clayton's Tested-by
> >
> > Changes for v3:
> > - Delete "power-sequence" property at binding-doc, and change related code
> > at both library and user code.
> > - Change binding-doc example node name with Rob's comments
> > - of_get_named_gpio_flags only gets the gpio, but without setting gpio flags,
> > add additional code request gpio with proper gpio flags
> > - Add Philipp Zabel's Ack and MAINTAINER's entry
> >
> > Changes for v2:
> > - Delete "pwrseq" prefix and clock-names for properties at dt binding
> > - Should use structure not but its pointer for kzalloc
> > - Since chipidea core has no of_node, let core's of_node equals glue
> > layer's at core's probe
> >
> > Joshua Clayton (2):
> > ARM: dts: imx6qdl: Enable usb node children with <reg>
> > ARM: dts: imx6q-evi: Fix onboard hub reset line
> >
> > Peter Chen (6):
> > binding-doc: power: pwrseq-generic: add binding doc for generic power
> > sequence library
> > power: add power sequence library
> > binding-doc: usb: usb-device: add optional properties for power
> > sequence
> > usb: core: add power sequence handling for USB devices
> > usb: chipidea: let chipidea core device of_node equal's glue layer
> > device of_node
> > ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property
> >
> > .../bindings/power/pwrseq/pwrseq-generic.txt | 48 ++++++
> > .../devicetree/bindings/usb/usb-device.txt | 10 +-
> > MAINTAINERS | 9 +
> > arch/arm/boot/dts/imx6q-evi.dts | 25 +--
> > arch/arm/boot/dts/imx6qdl-udoo.dtsi | 26 ++-
> > arch/arm/boot/dts/imx6qdl.dtsi | 6 +
> > drivers/power/Kconfig | 1 +
> > drivers/power/Makefile | 1 +
> > drivers/power/pwrseq/Kconfig | 45 +++++
> > drivers/power/pwrseq/Makefile | 3 +
> > drivers/power/pwrseq/core.c | 190 +++++++++++++++++++++
> > drivers/power/pwrseq/pwrseq_compatible_sample.c | 178 +++++++++++++++++++
> > drivers/power/pwrseq/pwrseq_generic.c | 177 +++++++++++++++++++
> > drivers/usb/chipidea/core.c | 27 ++-
> > drivers/usb/core/hub.c | 41 ++++-
> > drivers/usb/core/hub.h | 1 +
> > include/linux/power/pwrseq.h | 73 ++++++++
> > 17 files changed, 820 insertions(+), 41 deletions(-)
> > create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
> > create mode 100644 drivers/power/pwrseq/Kconfig
> > create mode 100644 drivers/power/pwrseq/Makefile
> > create mode 100644 drivers/power/pwrseq/core.c
> > create mode 100644 drivers/power/pwrseq/pwrseq_compatible_sample.c
> > create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
> > create mode 100644 include/linux/power/pwrseq.h
> >
> > --
> > 2.7.4
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> > the body of a message to majordomo at vger.kernel.org
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>
> --
>
> Best Regards,
> Peter Chen
--
Best Regards,
Peter Chen
^ permalink raw reply
* [PATCH] clk: core: add __init decoration for CLK_OF_DECLARE_DRIVER function
From: Shawn Guo @ 2016-10-08 8:59 UTC (permalink / raw)
To: linux-arm-kernel
The new introduced macro CLK_OF_DECLARE_DRIVER is usually used to
declare clock driver init functions, which are mostly decorated with
__init. Add __init decoration for CLK_OF_DECLARE_DRIVER function to
avoid causing section mismatch warnings on client clock drivers.
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
include/linux/clk-provider.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index af596381fa0f..a428aec36ace 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -785,7 +785,7 @@ struct clk_hw_onecell_data {
* routines, one at of_clk_init(), and one at platform device probe
*/
#define CLK_OF_DECLARE_DRIVER(name, compat, fn) \
- static void name##_of_clk_init_driver(struct device_node *np) \
+ static void __init name##_of_clk_init_driver(struct device_node *np) \
{ \
of_node_clear_flag(np, OF_POPULATED); \
fn(np); \
--
1.9.1
^ permalink raw reply related
* [PATCH] Specify all interrupts for the GPIO controller.
From: Robert Jarzmik @ 2016-10-08 9:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475489412-9758-1-git-send-email-vijaykumar@zilogic.com>
Vijay Kumar B <vijaykumar@zilogic.com> writes:
> The PXA GPIO controller has 3 interrupt outputs, this needs to be
> indicated in the DTS file. Without this mainstone's CPLD interrupt 0
> will not be raised to the processor.
>
> Signed-off-by: Vijay Kumar B. <vijaykumar@zilogic.com>
> Reviewed-by: Deepak S. <deepak@zilogic.com>
> ---
> arch/arm/boot/dts/pxa2xx.dtsi | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm/boot/dts/pxa2xx.dtsi b/arch/arm/boot/dts/pxa2xx.dtsi
> index 5e5af07..9ca2e5b 100644
> --- a/arch/arm/boot/dts/pxa2xx.dtsi
> +++ b/arch/arm/boot/dts/pxa2xx.dtsi
> @@ -54,8 +54,8 @@
> reg = <0x40e00000 0x10000>;
> gpio-controller;
> #gpio-cells = <0x2>;
> - interrupts = <10>;
> - interrupt-names = "gpio_mux";
> + interrupts = <8 9 10>;
> + interrupt-names = "gpio0", "gpio1", "gpio_mux";
> interrupt-controller;
> #interrupt-cells = <0x2>;
> ranges;
Hi Vijay,
A patch was already submitted in september for this in there :
https://mail-archive.com/linux-kernel at vger.kernel.org/msg1239445.html
Cheers.
--
Robert
^ permalink raw reply
* [PATCH] ARM: pxa: pxa_cplds: honor probe deferral
From: Robert Jarzmik @ 2016-10-08 9:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474874488-26533-1-git-send-email-robert.jarzmik@free.fr>
Robert Jarzmik <robert.jarzmik@free.fr> writes:
> The method to acquire the input interrupt was working in a platform data
> based board, but was failing in a device-tree one when the gpio
> controller was probed after pxa_cplds.
>
> Use platform_get_irq() to benefit from the probe deferral
> mechanism. Moreover, as seen in dm9000.c development, platform_get_irq()
> doesn't honor the irq type IO resource (ie. edge rising for example),
> and it must be passed again at irq request in a not device-tree build,
> hence the irq_get_trigger_type() call.
>
> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Applied to pxa/for-next.
Cheers.
--
Robert
^ permalink raw reply
* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Ziji Hu @ 2016-10-08 9:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <25146173-d98b-f346-b333-4d7466960496@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:44, Shawn Lin wrote:
> ? 2016/10/7 23:22, Gregory CLEMENT ??:
>> 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>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> ---
>> MAINTAINERS | 1 +-
>> drivers/mmc/host/Makefile | 2 +-
>> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
>> 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, 1321 insertions(+), 1 deletion(-)
>> 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 859420e5dfd3..b5673c2ee5f2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji@marvell.com>
>> L: linux-mmc at vger.kernel.org
>> S: Supported
>> F: drivers/mmc/host/sdhci-xenon.*
>> +F: drivers/mmc/host/sdhci-xenon-phy.*
>
> drivers/mmc/host/sdhci-xenon* shoube enough
>
>> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.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..4eb8fea1bec9
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
>
> Well, it's legit to use phy API and move your phy
> operations to PHY subsystem. :)
>
Actually we tried to put the PHY code into Linux PHY framework.
But it cannot fit in Linux common PHY framework.
Our Xenon SDHC PHY register is a part of Xenon SDHC register set.
Besides, during MMC initialization, MMC sequence has to call several PHY functions to complete timing setting.
In those PHY setting functions, they have to access SDHC register and know current MMC setting, such as bus width, clock frequency and speed mode.
As a result, we have to implement PHY under MMC directory.
Thank you.
Best regards,
Hu Ziji
>> @@ -0,0 +1,1141 @@
>> +/*
>> + * 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);
>> +}
>> +
>> +#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);
>> +
>> + if ((timing == MMC_TIMING_UHS_DDR50) ||
>> + (timing == MMC_TIMING_MMC_HS400) ||
>> + (timing == MMC_TIMING_MMC_DDR52)) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>> + }
>> +
>> + if (timing == MMC_TIMING_MMC_HS400) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + 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);
>> +
>> +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, "xenon,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, "xenon,phy-slow-mode"))
>> + params->slow_mode = true;
>> + else
>> + params->slow_mode = false;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
>> + params->znr = value & ZNR_MASK;
>> + else
>> + params->znr = ZNR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
>> + params->zpr = value & ZPR_MASK;
>> + else
>> + params->zpr = ZPR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
>> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
>> + else
>> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
>> +
>> + if (!of_property_read_u32(np, "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;
>> + }
>> + priv->timing = ios->timing;
>> + priv->clock = host->clock;
>> +
>> + /* Legacy mode is a special case */
>> + if (ios->timing == MMC_TIMING_LEGACY)
>> + return 0;
>> +
>> + 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, "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 03ba183494d3..4d7d871544fc 100644
>> --- a/drivers/mmc/host/sdhci-xenon.c
>> +++ b/drivers/mmc/host/sdhci-xenon.c
>> @@ -224,6 +224,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);
>> @@ -309,6 +310,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->card_candidate) {
>> if (mmc_card_mmc(priv->card_candidate))
>> return xenon_emmc_signal_voltage_switch(mmc, ios);
>> @@ -453,6 +456,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 c2370493fbe8..06e5261a563c 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
>> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
>> /* Slot idx */
>> u8 slot_idx;
>>
>> + 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.
>> @@ -131,4 +142,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
>>
>
>
^ permalink raw reply
* [PATCH] ARM: dts: pxa: fix gpio0 and gpio1 interrupts
From: Robert Jarzmik @ 2016-10-08 9:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474874531-26703-1-git-send-email-robert.jarzmik@free.fr>
Robert Jarzmik <robert.jarzmik@free.fr> writes:
> Since gpio-pxa was redesigned to differenciate gpio0, gpio1 and the
> gpio-mux interrupt as in the hardware IP, the device-tree description
> should be amended so that interrupts from gpio0 and gpio1 can be mapped
> to consumers.
>
> This is especially true on lubbock and mainstone devices where gpio0 is
> multiplexed on pxa_cplds for ethernet, sa1111, usb udc, and other
> devices.
>
> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Applied to pxa/dt.
Cheers.
--
Robert
^ permalink raw reply
* [PATCH 1/3] nvmem: add NXP LPC18xx OTP driver
From: Joachim Eastwood @ 2016-10-08 10:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <8ad4d5fb-0672-fbd7-6076-a91c89dee132@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:44, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim, Srinivas,
>
> On 13.09.2016 19:12, Joachim Eastwood wrote:
>> Add simple read only driver for the internal OTP (One Time Programmable)
>> memory found on all NXP LPC18xx and LPC43xx devices.
>>
>> The OTP memory is split into 4 banks each with 4 32-bits word. Some of
>> the banks contain predefined data while others are for general purpose
>> and user programmable via the OTP API in ROM. Note that writing to the
>> OTP memory is not yet supported.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>> ---
>
> please feel free to add my
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
Thanks for testing!
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH 3/3] ARM: dts: lpc18xx: add boot rom node
From: Joachim Eastwood @ 2016-10-08 10:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <7d4f9e48-e93a-db9e-fba0-e1b64e7056d7@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:50, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim,
>
> On 13.09.2016 22:51, Joachim Eastwood wrote:
>> Add node for the boot ROM found on all NXP LPC18xx/43xx devices.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>> ---
>> arch/arm/boot/dts/lpc18xx.dtsi | 8 ++++++++
>> 1 file changed, 8 insertions(+)
>>
>> diff --git a/arch/arm/boot/dts/lpc18xx.dtsi b/arch/arm/boot/dts/lpc18xx.dtsi
>> index 631e6f6c..9f94f3e 100644
>> --- a/arch/arm/boot/dts/lpc18xx.dtsi
>> +++ b/arch/arm/boot/dts/lpc18xx.dtsi
>> @@ -81,6 +81,14 @@
>> status = "disabled";
>> };
>>
>> + boot_rom: firmware at 10400000 {
>> + compatible = "nxp,lpc1850-boot-rom";
>> + reg = <0x10400000 0x10000>;
>> + syscon = <&creg>;
>> + nvmem-cells = <&part_id>;
>
> nitpicking, this change has a compile time dependency on yours
> "nvmem: add lpc18xx OTP memory driver" series, I haven't noticed
> this info stated, but it may be an overlook on my part.
It's not really a hard dependency and I will ensure that the DTS
changes comes in the correct order.
>> + nvmem-cell-names = "PartID";
>> + };
>> +
>> dmac: dma-controller at 40002000 {
>> compatible = "arm,pl080", "arm,primecell";
>> arm,primecell-periphid = <0x00041080>;
>>
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
Thanks for testing and going through the changes.
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH 1/3] firmware: add lpc18xx boot rom driver
From: Joachim Eastwood @ 2016-10-08 10:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9fec6e3b-fcd9-2251-c4c9-5b3f4fdc3c70@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:42, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim,
>
> On 13.09.2016 22:51, Joachim Eastwood wrote:
>> Firmware driver for the boot ROM found on all NXP LPC18xx/43xx
>> devices. This driver makes it possible to retrieve device specific
>> information from either the ROM via API calls or from OTP memory.
>>
>> The boot ROM contains several APIs for on-chip devices. Note that not
>> all APIs are available on all devices. The API to retrieve device
>> information and internal Flash programming (IAP) is only available on
>> devices with Flash. Flashless devices retrieve device information from
>> OTP memory. The CHIPID register in CREG (syscon) is used to check if
>> IAP is available.
>>
>> For now this driver is only used to expose device information via a
>> 'SoC device'. Linux API for the IAP and OTP will be added later. These
>> two APIs will be used by a Flash MTD driver and a OTP NVMEM driver to
>> program the memory.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>
> please feel free to add to the series my
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
>
> I tested the change on a board powered by LPC4357.
Thanks!
> Nevertheless I have some nitpicks for your consideration.
>
> And the first one is that I'm not totally convinced that drivers/firmware
> is the proper place for the driver, I tend to think that it might be
> a good time to create drivers/soc/nxp/
As the driver stand now, I agree with you. But I do plan to add
firmware functionally later for Flash programming which will be used
for a MTD driver and OTP programming for the OTP nvmem driver. This is
also stated in the cover letter.
See this patch for the Flash ROM API:
https://github.com/manabian/linux-lpc/commit/f33b03e7b47515a1d48b42f8bb1476ad5c67025b
The reason why I want to merge a simple driver now is:
i) The driver useful for SoC identification
ii ) It's easier to review and the Flash API is really a separate thing
iii ) The Flash API can be reviewed/merged together with its user (MTD-driver)
>> ---
>> drivers/firmware/Kconfig | 12 ++
>> drivers/firmware/Makefile | 1 +
>> drivers/firmware/nxp_lpc_boot_rom.c | 411 ++++++++++++++++++++++++++++++++++++
>> 3 files changed, 424 insertions(+)
>> create mode 100644 drivers/firmware/nxp_lpc_boot_rom.c
>>
>
> [snip]
>
>> +/* LPC18xx/43xx CREG (syscon) defines */
>> +#define LPC18XX_CREG_CHIPID 0x200
>> +#define LPC18XX_FLASH_CHIPID0 0x4284e02b
>> +#define LPC18XX_FLASH_CHIPID1 0x7284e02b
>> +#define LPC18XX_FLASHLESS_CHIPID0 0x5284e02b
>> +#define LPC18XX_FLASHLESS_CHIPID1 0x6284e02b
>> +#define LPC43XX_FLASH_CHIPID0 0x4906002b
>> +#define LPC43XX_FLASH_CHIPID1 0x7906002b
>> +#define LPC43XX_FLASHLESS_CHIPID0 0x5906002b
>> +#define LPC43XX_FLASHLESS_CHIPID1 0x6906002b
>> +
>> +#define NXP_PART_LPC(_num, _id0, _id1, _sz0, _sz1) \
>> + { \
>> + .name = "LPC"#_num, \
>> + .id[0] = _id0, .id[1] = _id1, \
>> + .flash_size[0] = _sz0 * 1024, \
>> + .flash_size[1] = _sz1 * 1024, \
>> + }
>> +
>> +
>
> checkpatch complains:
>
> CHECK: Please don't use multiple blank lines
> #145: FILE: drivers/firmware/nxp_lpc_boot_rom.c:75:
I'll fix up.
>
>> +struct nxp_lpc_part {
>> + const char *name;
>> + u16 flash_size[2];
>
> flash_size[2] is set by NXP_PART_LPC() macro but unused, however
> in my build environment (W=1 and sparse checks) I get tons of
> legitimate compile time warnings:
flash_size is currently unused, but will be used by the MTD via the
Flash ROM API. I only kept in this patch to avoid the churn later, but
if it causes warnings I'll remove it.
> drivers/firmware/nxp_lpc_boot_rom.c:93:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:93:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:94:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:94:9: warning: cast truncates bits from constant value (80000 becomes 0)
> ....
>
> drivers/firmware/nxp_lpc_boot_rom.c:93:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> NXP_PART_LPC(1857, 0xf001d830, 0x00, 512, 512),
> ^
> drivers/firmware/nxp_lpc_boot_rom.c:93:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> drivers/firmware/nxp_lpc_boot_rom.c:94:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> NXP_PART_LPC(18S57, 0xf001d860, 0x00, 512, 512),
> ^
> ....
>
> and so on.
>
> Please consider either to store flash size in kB or change storage type to u32.
This was a last minute change and I didn't notice before sending it. I
have already fixed this up in my tree.
>> + u32 id[2];
>> +};
>> +
>> +static const struct nxp_lpc_part nxp_lpc_parts[] = {
>> + /* LPC18xx Flashless parts */
>> + NXP_PART_LPC(1850, 0xf000d830, 0x00, 0, 0),
>> + NXP_PART_LPC(18S50, 0xf000d860, 0x00, 0, 0),
>> + NXP_PART_LPC(1830, 0xf000da30, 0x00, 0, 0),
>> + NXP_PART_LPC(18S30, 0xf000da60, 0x00, 0, 0),
>> + NXP_PART_LPC(1820, 0xf00adb3c, 0x00, 0, 0),
>> + NXP_PART_LPC(18S20, 0xf00adb6c, 0x00, 0, 0),
>> + NXP_PART_LPC(1810, 0xf00b5b3f, 0x00, 0, 0),
>> + NXP_PART_LPC(18S10, 0xf00b5b6f, 0x00, 0, 0),
>> + /* LPC18xx Flash parts */
>> + NXP_PART_LPC(1857, 0xf001d830, 0x00, 512, 512),
>> + NXP_PART_LPC(18S57, 0xf001d860, 0x00, 512, 512),
>> + NXP_PART_LPC(1853, 0xf001d830, 0x44, 256, 256),
>> + NXP_PART_LPC(1837, 0xf001da30, 0x00, 512, 512),
>> + NXP_PART_LPC(18S37, 0xf001d860, 0x00, 512, 512),
>> + NXP_PART_LPC(1833, 0xf001da30, 0x44, 256, 256),
>> + NXP_PART_LPC(1827, 0xf001db3c, 0x00, 512, 512),
>> + NXP_PART_LPC(1825, 0xf001db3c, 0x22, 384, 384),
>> + NXP_PART_LPC(1823, 0xf00bdb3c, 0x44, 256, 256),
>> + NXP_PART_LPC(1822, 0xf00bdb3c, 0x80, 512, 0),
>> + NXP_PART_LPC(1817, 0xf001db3f, 0x00, 512, 512),
>> + NXP_PART_LPC(1815, 0xf001db3f, 0x22, 384, 384),
>> + NXP_PART_LPC(1813, 0xf00bdb3f, 0x44, 256, 256),
>> + NXP_PART_LPC(1812, 0xf00bdb3f, 0x80, 512, 0),
>> + /* LPC43xx Flashless parts */
>> + NXP_PART_LPC(4370, 0x00000030, 0x00, 0, 0), /* LBGA256 */
>> + NXP_PART_LPC(4370, 0x00000230, 0x00, 0, 0), /* TFBGA100 */
>> + NXP_PART_LPC(43S70, 0x00000060, 0x00, 0, 0),
>> + NXP_PART_LPC(4350, 0xa0000830, 0x00, 0, 0),
>> + NXP_PART_LPC(43S50, 0xa0000860, 0x00, 0, 0),
>> + NXP_PART_LPC(4330, 0xa0000a30, 0x00, 0, 0),
>> + NXP_PART_LPC(43S30, 0xa0000a60, 0x00, 0, 0),
>> + NXP_PART_LPC(4320, 0xa000cb3c, 0x00, 0, 0),
>> + NXP_PART_LPC(43S20, 0xa000cb6c, 0x00, 0, 0),
>> + NXP_PART_LPC(4310, 0xa00acb3f, 0x00, 0, 0),
>> + /* LPC43xx parts with Flash */
>> + NXP_PART_LPC(4367, 0x8001c030, 0x00, 512, 512),
>> + NXP_PART_LPC(43S67, 0x8001c060, 0x00, 512, 512),
>> + NXP_PART_LPC(4357, 0xa001c830, 0x00, 512, 512),
>> + NXP_PART_LPC(43S57, 0xa001c860, 0x00, 512, 512), /* LBGA256 */
>> + NXP_PART_LPC(43S57, 0xa001ca60, 0x00, 512, 512), /* LQFP208 */
>> + NXP_PART_LPC(4353, 0xa001c830, 0x44, 256, 256),
>> + NXP_PART_LPC(4337, 0xa001ca30, 0x00, 512, 512),
>> + NXP_PART_LPC(43S37, 0xa001ca60, 0x00, 512, 512),
>> + NXP_PART_LPC(4333, 0xa001ca30, 0x44, 256, 256),
>> + NXP_PART_LPC(4327, 0xa001cb3c, 0x00, 512, 512),
>> + NXP_PART_LPC(4325, 0xa001cb3c, 0x22, 384, 384),
>> + NXP_PART_LPC(4323, 0xa00bcb3c, 0x44, 256, 256),
>> + NXP_PART_LPC(4322, 0xa00bcb3c, 0x80, 512, 0),
>> + NXP_PART_LPC(4317, 0xa001cb3f, 0x00, 512, 512),
>> + NXP_PART_LPC(4315, 0xa001cb3f, 0x22, 384, 384),
>> + NXP_PART_LPC(4313, 0xa00bcb3f, 0x44, 256, 256),
>> + NXP_PART_LPC(4312, 0xa00bcb3f, 0x80, 512, 0),
>> +};
>> +
>> +struct iap_rom {
>> + void (*entry)(u32 *, u32 *);
>> +};
>
> Here it might be simpler to declare a typedef instead of a struct.
This is done using a struct to keep it consistent with the other ROM
APIs that will be added later. The IAP is kinda special since it only
has one function while most other APIs have several functions.
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH] clk: nxp: clk-lpc18xx-ccu: Unmap region obtained by of_iomap
From: Joachim Eastwood @ 2016-10-08 10:58 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474367987-15808-1-git-send-email-arvind.yadav.cs@gmail.com>
Hi Arvind,
On 20 September 2016 at 12:39, Arvind Yadav <arvind.yadav.cs@gmail.com> wrote:
> From: Arvind Yadav <arvind.yadav.cs@gmail.com>
>
> Free memory mapping, if lpc18xx_ccu_init is not successful.
>
> Signed-off-by: Arvind Yadav <arvind.yadav.cs@gmail.com>
Acked-by: Joachim Eastwood <manabian@gmail.com>
One comment below:
> ---
> drivers/clk/nxp/clk-lpc18xx-ccu.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/clk/nxp/clk-lpc18xx-ccu.c b/drivers/clk/nxp/clk-lpc18xx-ccu.c
> index f7136b9..27781b4 100644
> --- a/drivers/clk/nxp/clk-lpc18xx-ccu.c
> +++ b/drivers/clk/nxp/clk-lpc18xx-ccu.c
> @@ -277,12 +277,15 @@ static void __init lpc18xx_ccu_init(struct device_node *np)
> }
>
> clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
> - if (!clk_data)
> + if (!clk_data) {
> + iounmap(reg_base);
> return;
> + }
You could also move the kzalloc() at the beginning of the function to
save a few lines. I am fine either way.
>
> clk_data->num = of_property_count_strings(np, "clock-names");
> clk_data->name = kcalloc(clk_data->num, sizeof(char *), GFP_KERNEL);
> if (!clk_data->name) {
> + iounmap(reg_base);
> kfree(clk_data);
> return;
> }
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH v10 00/11] Add support for FDMA DMA controller and slim core rproc found on STi chipsets
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
Hi Vinod and Bjorn,
This patchset adds support for the Flexible Direct Memory Access (FDMA) core
found on STi chipsets from STMicroelectronics. The FDMA is a slim core CPU
with a dedicated firmware. It is a general purpose DMA controller supporting
16 independent channels and data can be moved from memory to memory or between
memory and paced latency critical real time targets.
[..]
V10 series updates remoteproc kconfig subsystem to use 'depends on' rather
than select for the remoteproc core. Remoteproc client drivers now need to
'depends on' for the core part.
This avoids the Kconfig recursive dependency errors and reliance on the DRM
patch included with the v9 series. Patches are also included for enabling the
remoteproc core in the multi_v7 defconfig. All patches for DRM subsystem have
been removed.
Note there is no longer a depedency with the DRM_VIRTIO_GPU driver in this series,
only remoteproc and dmaengine subsystems.
regards,
Peter.
Changes since v9:
- Update remoteproc Kconfig to 'depends on'
- Add remoteproc core to multi_v7 defconfig
- Remove drm patches
- rebase v4.8
Changes since v8:
- Add MODULE_ALIAS (Vinod)
- devm_kzalloc to devm_kcalloc (Vinod)
- quisce tasklet initialised by vchan_init() (Vinod)
- Don't make SLIM rproc user selectable (Bjorn)
- slim_rproc: Ensure clocks enabled before firmware load (Peter)
- Various code style nits / commit message change (Lee)
- Separate patch for '\n' kconfig removal (Vinod)
- Fix clock bug when booting without clk_ignore_unused param
Changes since v7:
- Rebase on v4.8-rc3 (Peter)
- Double check that len is <= mem[i].size (Bjorn)
- Remove if (!fw) checks (Bjorn)
- Omit reference from fw_ops struct (Bjorn)
- Call rproc_del() before rproc_put() (Bjorn)
- Fix some odd line breaks (Bjorn)
- Rename SLIM_* and slim_* with st prefix (Bjorn)
Changes since v6:
- Fix recursive Kconfig warning (Patrice)
- Fix various Intel zero day compiler warnings
- Remove all changes related to late firmware load (now relies on initramfs) (Peter)
Changes since v5:
- Remove optional rsc table and go back to dummy resource table (Bjorn)
- Retry rproc_fw_config_virtio() from rproc_boot() if previously failed (Peter)
- Fixup some kbuild warnings
- rebase on v4.7-rc5
- Remove some patches which were merged via ASoC tree
Changes since v4:
- Make rsc table optional in remoteproc (Peter)
- Various fixups to STi audio DT nodes (Arnaud)
- Bulk rename of xp70 to slim (Peter)
- Update my email to @linaro.org (Lee)
- rebase on v4.7-rc2 (Peter)
- Don't make ST_SLIM_REMOTEPROC user selectable (Bjorn)
- -EPROBE_DEFER if rproc_boot fails when allocating DMA channel (Arnaud / Peter)
- Drop some unnecessary headers (Vinod / Bjorn)
- Change to writel now we have several mappings (Bjorn)
- Remove io_res, rproc, and some unused structure fields / #define (Bjorn)
- put clks in error path, also put clks before rproc_put() (Bjorn)
- Make enum less generic (Bjorn)
- Make slim_rproc_alloc() return a st_slim_rproc reference (Bjorn)
- Alphabetical naming in Kconfig & Makefile (Vinod)
- Add FDMA prefix to REQ_CTRL* (Vinod)
- Print ret on some error paths (Vinod)
- Add some acked-by (Peter)
Changes since v3:
- Remove elf loading code from fdma driver (Vinod)
- Remove fdma_ prefix for clock names (Arnd)
- Make _xlate use dma_get_any_channel rather than request_channel (Arnd)
- Make a common function for _prep_ routines (Vinod)
- Make driver depend on COMPILE_TEST (Arnd)
- Remove unnecessary st_fdma_filter_fn (Arnd)
- Enable FDMA as a module (Arnd)
- Drop fdma_ clock prefix (Arnd)
- Fix description as well as example for st, prefix (Arnd)
- Remove string concatenation from fdma register macros to ease grep'ability (Arnd)
- Add a XP70 rproc driver for ELF firmware loading and start/stop control (Peter)
- Add myself as a author of the driver (Peter)
Changes since v2:
- Change to dma-controller (Arnd)
- Remove platform data header file and simplifiy code (Arnd)
- Remove FW_LOADER_USER_HELPER_FALLBACK and rework firmware loading to device config (Vinod)
- Use SET_RUNTIME_PM_OPS helpers (Vinod)
- Remove fdma-id dt prop and use compatibles to generate different fdma firmware names (Arnd / Lee)
- Add sti-asoc-card DT nodes and pinmux config for uniperif player & reader (Peter)
- Update sti-asoc-card DT binding documentation (Peter)
- Enable STi audio drivers in multi_v7_defconfig (Peter)
Changes since v1:
- split into smaller patches for easier / faster review (Vinod)
- new fill_hw_mode() with common code (Vinod)
- new config_reqctrl() called from *_prep() instead of device_config cb (Vinod)
- fdma-xbar support removed (Peter)
- rework firmware name mechanism so fwname isn't in DT (Peter / Lee)
- st_fdma_seg_to_mem can be static (Paul)
- EXPORT_SYMBOL st_fdma_filter_fn not required (Paul)
- s/channel/channels (vinod)
- better describe "Must be <3>" (vinod)
- sizeof(*ehdr) (vinod)
- print values on error debug (vinod)
- empty line (Vinod)
- Update to -EIO (Vinod)
- Make st_fdma tristate (Paul)
- Remove __exit tag from .remove (Maxime)
- Update MAINTAINERS rule to fdma* (Lee)
- Unit address should match reg property (Lee)
Peter Griffin (11):
remoteproc: st_slim_rproc: add a slimcore rproc driver
MAINTAINERS: Add st slim core rproc driver to STi section.
remoteproc: Update Kconfig setup to 'depends on REMOTEPROC'
dmaengine: st_fdma: Add STMicroelectronics FDMA DT binding
documentation
dmaengine: st_fdma: Add STMicroelectronics FDMA driver header file
dmaengine: st_fdma: Add STMicroelectronics FDMA engine driver support
MAINTAINERS: Add FDMA driver files to STi section.
ARM: multi_v7_defconfig: Enable remoteproc core
ARM: multi_v7_defconfig: Enable st_remoteproc driver.
ARM: multi_v7_defconfig: Enable STi FDMA driver
ARM: multi_v7_defconfig: Enable STi and simple-card drivers.
Documentation/devicetree/bindings/dma/st_fdma.txt | 87 +++
MAINTAINERS | 3 +
arch/arm/configs/multi_v7_defconfig | 6 +
drivers/dma/Kconfig | 13 +
drivers/dma/Makefile | 1 +
drivers/dma/st_fdma.c | 899 ++++++++++++++++++++++
drivers/dma/st_fdma.h | 249 ++++++
drivers/remoteproc/Kconfig | 23 +-
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/st_slim_rproc.c | 364 +++++++++
include/linux/remoteproc/st_slim_rproc.h | 58 ++
11 files changed, 1696 insertions(+), 8 deletions(-)
create mode 100644 Documentation/devicetree/bindings/dma/st_fdma.txt
create mode 100644 drivers/dma/st_fdma.c
create mode 100644 drivers/dma/st_fdma.h
create mode 100644 drivers/remoteproc/st_slim_rproc.c
create mode 100644 include/linux/remoteproc/st_slim_rproc.h
--
1.9.1
^ permalink raw reply
* [PATCH v10 01/11] remoteproc: st_slim_rproc: add a slimcore rproc driver
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
slim core is used as a basis for many IPs in the STi
chipsets such as fdma and demux. To avoid duplicating
the elf loading code in each device driver a slim
rproc driver has been created.
This driver is designed to be used by other device drivers
such as fdma, or demux whose IP is based around a slim core.
The device driver can call slim_rproc_alloc() to allocate
a slim rproc and slim_rproc_put() when finished.
This driver takes care of ioremapping the slim
registers (dmem, imem, slimcore, peripherals), whose offsets
and sizes can change between IP's. It also obtains and enables
any clocks used by the device. This approach avoids having
a double mapping of the registers as slim_rproc does not register
its own platform device. It also maps well to device tree
abstraction as it allows us to have one dt node for the whole
device.
All of the generic rproc elf loading code can be reused, and
we provide start() stop() hooks to start and stop the slim
core once the firmware has been loaded. This has been tested
successfully with fdma driver.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
drivers/remoteproc/Kconfig | 4 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/st_slim_rproc.c | 364 +++++++++++++++++++++++++++++++
include/linux/remoteproc/st_slim_rproc.h | 58 +++++
4 files changed, 427 insertions(+)
create mode 100644 drivers/remoteproc/st_slim_rproc.c
create mode 100644 include/linux/remoteproc/st_slim_rproc.h
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 1a8bf76a..a7bedc6 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -100,4 +100,8 @@ config ST_REMOTEPROC
processor framework.
This can be either built-in or a loadable module.
+config ST_SLIM_REMOTEPROC
+ tristate
+ select REMOTEPROC
+
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 92d3758..db1dae7 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o
obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
+obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
diff --git a/drivers/remoteproc/st_slim_rproc.c b/drivers/remoteproc/st_slim_rproc.c
new file mode 100644
index 0000000..1484e97
--- /dev/null
+++ b/drivers/remoteproc/st_slim_rproc.c
@@ -0,0 +1,364 @@
+/*
+ * SLIM core rproc driver
+ *
+ * Copyright (C) 2016 STMicroelectronics
+ *
+ * Author: Peter Griffin <peter.griffin@linaro.org>
+ *
+ * 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/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+#include <linux/remoteproc/st_slim_rproc.h>
+#include "remoteproc_internal.h"
+
+/* SLIM core registers */
+#define SLIM_ID_OFST 0x0
+#define SLIM_VER_OFST 0x4
+
+#define SLIM_EN_OFST 0x8
+#define SLIM_EN_RUN BIT(0)
+
+#define SLIM_CLK_GATE_OFST 0xC
+#define SLIM_CLK_GATE_DIS BIT(0)
+#define SLIM_CLK_GATE_RESET BIT(2)
+
+#define SLIM_SLIM_PC_OFST 0x20
+
+/* DMEM registers */
+#define SLIM_REV_ID_OFST 0x0
+#define SLIM_REV_ID_MIN_MASK GENMASK(15, 8)
+#define SLIM_REV_ID_MIN(id) ((id & SLIM_REV_ID_MIN_MASK) >> 8)
+#define SLIM_REV_ID_MAJ_MASK GENMASK(23, 16)
+#define SLIM_REV_ID_MAJ(id) ((id & SLIM_REV_ID_MAJ_MASK) >> 16)
+
+
+/* peripherals registers */
+#define SLIM_STBUS_SYNC_OFST 0xF88
+#define SLIM_STBUS_SYNC_DIS BIT(0)
+
+#define SLIM_INT_SET_OFST 0xFD4
+#define SLIM_INT_CLR_OFST 0xFD8
+#define SLIM_INT_MASK_OFST 0xFDC
+
+#define SLIM_CMD_CLR_OFST 0xFC8
+#define SLIM_CMD_MASK_OFST 0xFCC
+
+static const char *mem_names[ST_SLIM_MEM_MAX] = {
+ [ST_SLIM_DMEM] = "dmem",
+ [ST_SLIM_IMEM] = "imem",
+};
+
+static int slim_clk_get(struct st_slim_rproc *slim_rproc, struct device *dev)
+{
+ int clk, err;
+
+ for (clk = 0; clk < ST_SLIM_MAX_CLK; clk++) {
+ slim_rproc->clks[clk] = of_clk_get(dev->of_node, clk);
+ if (IS_ERR(slim_rproc->clks[clk])) {
+ err = PTR_ERR(slim_rproc->clks[clk]);
+ if (err == -EPROBE_DEFER)
+ goto err_put_clks;
+ slim_rproc->clks[clk] = NULL;
+ break;
+ }
+ }
+
+ return 0;
+
+err_put_clks:
+ while (--clk >= 0)
+ clk_put(slim_rproc->clks[clk]);
+
+ return err;
+}
+
+static void slim_clk_disable(struct st_slim_rproc *slim_rproc)
+{
+ int clk;
+
+ for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++)
+ clk_disable_unprepare(slim_rproc->clks[clk]);
+}
+
+static int slim_clk_enable(struct st_slim_rproc *slim_rproc)
+{
+ int clk, ret;
+
+ for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++) {
+ ret = clk_prepare_enable(slim_rproc->clks[clk]);
+ if (ret)
+ goto err_disable_clks;
+ }
+
+ return 0;
+
+err_disable_clks:
+ while (--clk >= 0)
+ clk_disable_unprepare(slim_rproc->clks[clk]);
+
+ return ret;
+}
+
+/*
+ * Remoteproc slim specific device handlers
+ */
+static int slim_rproc_start(struct rproc *rproc)
+{
+ struct device *dev = &rproc->dev;
+ struct st_slim_rproc *slim_rproc = rproc->priv;
+ unsigned long hw_id, hw_ver, fw_rev;
+ u32 val;
+
+ /* disable CPU pipeline clock & reset CPU pipeline */
+ val = SLIM_CLK_GATE_DIS | SLIM_CLK_GATE_RESET;
+ writel(val, slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
+
+ /* disable SLIM core STBus sync */
+ writel(SLIM_STBUS_SYNC_DIS, slim_rproc->peri + SLIM_STBUS_SYNC_OFST);
+
+ /* enable cpu pipeline clock */
+ writel(!SLIM_CLK_GATE_DIS,
+ slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
+
+ /* clear int & cmd mailbox */
+ writel(~0U, slim_rproc->peri + SLIM_INT_CLR_OFST);
+ writel(~0U, slim_rproc->peri + SLIM_CMD_CLR_OFST);
+
+ /* enable all channels cmd & int */
+ writel(~0U, slim_rproc->peri + SLIM_INT_MASK_OFST);
+ writel(~0U, slim_rproc->peri + SLIM_CMD_MASK_OFST);
+
+ /* enable cpu */
+ writel(SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST);
+
+ hw_id = readl_relaxed(slim_rproc->slimcore + SLIM_ID_OFST);
+ hw_ver = readl_relaxed(slim_rproc->slimcore + SLIM_VER_OFST);
+
+ fw_rev = readl(slim_rproc->mem[ST_SLIM_DMEM].cpu_addr +
+ SLIM_REV_ID_OFST);
+
+ dev_info(dev, "fw rev:%ld.%ld on SLIM %ld.%ld\n",
+ SLIM_REV_ID_MAJ(fw_rev), SLIM_REV_ID_MIN(fw_rev),
+ hw_id, hw_ver);
+
+ return 0;
+}
+
+static int slim_rproc_stop(struct rproc *rproc)
+{
+ struct st_slim_rproc *slim_rproc = rproc->priv;
+ u32 val;
+
+ /* mask all (cmd & int) channels */
+ writel(0UL, slim_rproc->peri + SLIM_INT_MASK_OFST);
+ writel(0UL, slim_rproc->peri + SLIM_CMD_MASK_OFST);
+
+ /* disable cpu pipeline clock */
+ writel(SLIM_CLK_GATE_DIS, slim_rproc->slimcore + SLIM_CLK_GATE_OFST);
+
+ writel(!SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST);
+
+ val = readl(slim_rproc->slimcore + SLIM_EN_OFST);
+ if (val & SLIM_EN_RUN)
+ dev_warn(&rproc->dev, "Failed to disable SLIM");
+
+ dev_dbg(&rproc->dev, "slim stopped\n");
+
+ return 0;
+}
+
+static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
+{
+ struct st_slim_rproc *slim_rproc = rproc->priv;
+ void *va = NULL;
+ int i;
+
+ for (i = 0; i < ST_SLIM_MEM_MAX; i++) {
+ if (da != slim_rproc->mem[i].bus_addr)
+ continue;
+
+ if (len <= slim_rproc->mem[i].size) {
+ /* __force to make sparse happy with type conversion */
+ va = (__force void *)slim_rproc->mem[i].cpu_addr;
+ break;
+ }
+ }
+
+ dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%p\n", da, len, va);
+
+ return va;
+}
+
+static struct rproc_ops slim_rproc_ops = {
+ .start = slim_rproc_start,
+ .stop = slim_rproc_stop,
+ .da_to_va = slim_rproc_da_to_va,
+};
+
+/*
+ * Firmware handler operations: sanity, boot address, load ...
+ */
+
+static struct resource_table empty_rsc_tbl = {
+ .ver = 1,
+ .num = 0,
+};
+
+static struct resource_table *slim_rproc_find_rsc_table(struct rproc *rproc,
+ const struct firmware *fw,
+ int *tablesz)
+{
+ *tablesz = sizeof(empty_rsc_tbl);
+ return &empty_rsc_tbl;
+}
+
+static struct rproc_fw_ops slim_rproc_fw_ops = {
+ .find_rsc_table = slim_rproc_find_rsc_table,
+};
+
+/**
+ * st_slim_rproc_alloc() - allocate and initialise slim rproc
+ * @pdev: Pointer to the platform_device struct
+ * @fw_name: Name of firmware for rproc to use
+ *
+ * Function for allocating and initialising a slim rproc for use by
+ * device drivers whose IP is based around the SLIM core. It
+ * obtains and enables any clocks required by the SLIM core and also
+ * ioremaps the various IO.
+ *
+ * Returns st_slim_rproc pointer or PTR_ERR() on error.
+ */
+
+struct st_slim_rproc *st_slim_rproc_alloc(struct platform_device *pdev,
+ char *fw_name)
+{
+ struct device *dev = &pdev->dev;
+ struct st_slim_rproc *slim_rproc;
+ struct device_node *np = dev->of_node;
+ struct rproc *rproc;
+ struct resource *res;
+ int err, i;
+ const struct rproc_fw_ops *elf_ops;
+
+ if (!fw_name)
+ return ERR_PTR(-EINVAL);
+
+ if (!of_device_is_compatible(np, "st,slim-rproc"))
+ return ERR_PTR(-EINVAL);
+
+ rproc = rproc_alloc(dev, np->name, &slim_rproc_ops,
+ fw_name, sizeof(*slim_rproc));
+ if (!rproc)
+ return ERR_PTR(-ENOMEM);
+
+ rproc->has_iommu = false;
+
+ slim_rproc = rproc->priv;
+ slim_rproc->rproc = rproc;
+
+ elf_ops = rproc->fw_ops;
+ /* Use some generic elf ops */
+ slim_rproc_fw_ops.load = elf_ops->load;
+ slim_rproc_fw_ops.sanity_check = elf_ops->sanity_check;
+
+ rproc->fw_ops = &slim_rproc_fw_ops;
+
+ /* get imem and dmem */
+ for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ mem_names[i]);
+
+ slim_rproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(slim_rproc->mem[i].cpu_addr)) {
+ dev_err(&pdev->dev, "devm_ioremap_resource failed\n");
+ err = PTR_ERR(slim_rproc->mem[i].cpu_addr);
+ goto err;
+ }
+ slim_rproc->mem[i].bus_addr = res->start;
+ slim_rproc->mem[i].size = resource_size(res);
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slimcore");
+ slim_rproc->slimcore = devm_ioremap_resource(dev, res);
+ if (IS_ERR(slim_rproc->slimcore)) {
+ dev_err(&pdev->dev, "failed to ioremap slimcore IO\n");
+ err = PTR_ERR(slim_rproc->slimcore);
+ goto err;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "peripherals");
+ slim_rproc->peri = devm_ioremap_resource(dev, res);
+ if (IS_ERR(slim_rproc->peri)) {
+ dev_err(&pdev->dev, "failed to ioremap peripherals IO\n");
+ err = PTR_ERR(slim_rproc->peri);
+ goto err;
+ }
+
+ err = slim_clk_get(slim_rproc, dev);
+ if (err)
+ goto err;
+
+ err = slim_clk_enable(slim_rproc);
+ if (err) {
+ dev_err(dev, "Failed to enable clocks\n");
+ goto err_clk_put;
+ }
+
+ /* Register as a remoteproc device */
+ err = rproc_add(rproc);
+ if (err) {
+ dev_err(dev, "registration of slim remoteproc failed\n");
+ goto err_clk_dis;
+ }
+
+ return slim_rproc;
+
+err_clk_dis:
+ slim_clk_disable(slim_rproc);
+err_clk_put:
+ for (i = 0; i < ST_SLIM_MAX_CLK && slim_rproc->clks[i]; i++)
+ clk_put(slim_rproc->clks[i]);
+err:
+ rproc_put(rproc);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(st_slim_rproc_alloc);
+
+/**
+ * st_slim_rproc_put() - put slim rproc resources
+ * @slim_rproc: Pointer to the st_slim_rproc struct
+ *
+ * Function for calling respective _put() functions on slim_rproc resources.
+ *
+ */
+void st_slim_rproc_put(struct st_slim_rproc *slim_rproc)
+{
+ int clk;
+
+ if (!slim_rproc)
+ return;
+
+ slim_clk_disable(slim_rproc);
+
+ for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++)
+ clk_put(slim_rproc->clks[clk]);
+
+ rproc_del(slim_rproc->rproc);
+ rproc_put(slim_rproc->rproc);
+}
+EXPORT_SYMBOL(st_slim_rproc_put);
+
+MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>");
+MODULE_DESCRIPTION("STMicroelectronics SLIM core rproc driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/remoteproc/st_slim_rproc.h b/include/linux/remoteproc/st_slim_rproc.h
new file mode 100644
index 0000000..4155556
--- /dev/null
+++ b/include/linux/remoteproc/st_slim_rproc.h
@@ -0,0 +1,58 @@
+/*
+ * SLIM core rproc driver header
+ *
+ * Copyright (C) 2016 STMicroelectronics
+ *
+ * Author: Peter Griffin <peter.griffin@linaro.org>
+ *
+ * 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 _ST_REMOTEPROC_SLIM_H
+#define _ST_REMOTEPROC_SLIM_H
+
+#define ST_SLIM_MEM_MAX 2
+#define ST_SLIM_MAX_CLK 4
+
+enum {
+ ST_SLIM_DMEM,
+ ST_SLIM_IMEM,
+};
+
+/**
+ * struct st_slim_mem - slim internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @bus_addr: Bus address used to access the memory region
+ * @size: Size of the memory region
+ */
+struct st_slim_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ size_t size;
+};
+
+/**
+ * struct st_slim_rproc - SLIM slim core
+ * @rproc: rproc handle
+ * @mem: slim memory information
+ * @slimcore: slim slimcore regs
+ * @peri: slim peripheral regs
+ * @clks: slim clocks
+ */
+struct st_slim_rproc {
+ struct rproc *rproc;
+ struct st_slim_mem mem[ST_SLIM_MEM_MAX];
+ void __iomem *slimcore;
+ void __iomem *peri;
+
+ /* st_slim_rproc private */
+ struct clk *clks[ST_SLIM_MAX_CLK];
+};
+
+struct st_slim_rproc *st_slim_rproc_alloc(struct platform_device *pdev,
+ char *fw_name);
+void st_slim_rproc_put(struct st_slim_rproc *slim_rproc);
+
+#endif
--
1.9.1
^ 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