Received: by 2002:a25:683:0:0:0:0:0 with SMTP id 125csp169016ybg; Thu, 11 Jun 2020 21:00:38 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzSmk3qMXpxTBR+9ShpuZgrVulnPhosUo7WDypws1c9UnB72R+n5YfnGMFlXt4wxaZQ4OWB X-Received: by 2002:a17:906:48d8:: with SMTP id d24mr11192388ejt.369.1591934437367; Thu, 11 Jun 2020 21:00:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1591934437; cv=none; d=google.com; s=arc-20160816; b=Fi14tJWrw8bnligFGqOumUgDLRmCpaBp8cJ/5yZ1ARuf+kumMACzrUVUyhJ8VyuqJ/ m5HK62yKoRAhOGb+tDkVKXhrqcXcl2bFhIYxbWfI41FpOSWuARB48xz3F3h/KVaraQL6 CtgM0WbK2CQYjp56K6Ys/tHqlw5D609OiB/MwGc0Qr1XReHfdirrkezw+yxdM/odqfjD IOCyLHbsgFjiD9T3MOTuwnvRk/3oWJsy1enX1WA+Fhyo4GnSHpkB/W6mZWd9RhDfexy2 bInPc5u99f8awpsFcuF1viGPSxEDa3VTrdxBjRnmLHMaK/jjZxMOmMx5iWsePuhTWw0f 785w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:ironport-sdr:ironport-sdr; bh=pm4dixGI96935duk0qKfmkYfNSs611SlwUoou65fCHw=; b=owgim1JhfFmBrgt7vrCAabWOFMGHhitSwWWizXYeggylz3e1+HKzf1aukEi2xrLj3S W2ITeoS5nrxsaNbOTH73GpGCnDuc8EDwaE1k4bOsHSahHqZAZLcwSW76wC1dLHftvR/4 Y3v9zbRhJSsIWtsftESUTU8iy0M9HZuyY+dQVtCFvVG9KQs6hHEiKKLMwkVYLZ0FHHR3 vIPZuaoHJnNczZuVK/cja0ApRu/J+LwFoF+7x/CBVyYGGv9YHdjlOsQshjUzbF/0J++6 uw4VljGlGYx/nsWniHrWSmw96+0U1gM9OyksP/5hV/zyeM1aqbP22AuOz8UWwNy+bZlA SGzg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=intel.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id s10si2943649ejf.167.2020.06.11.21.00.14; Thu, 11 Jun 2020 21:00:37 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=intel.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726405AbgFLD6J (ORCPT + 99 others); Thu, 11 Jun 2020 23:58:09 -0400 Received: from mga14.intel.com ([192.55.52.115]:35192 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726588AbgFLD6J (ORCPT ); Thu, 11 Jun 2020 23:58:09 -0400 IronPort-SDR: 1dHgZdtD3rqQRUVZ2phAfqEfcNQVeJtL5SOzSbijxNI3k3K17Iqk1/IZaADz86IDg12f7UJN+z 2XKNgujrA9Tw== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 11 Jun 2020 20:55:45 -0700 IronPort-SDR: hkMRRhKwYnOOPHrV535vHerMgzRopT+5NzJv/Tvd0ue64pX3drABNp89kYRrKuQX3tmTniJOrG uzwMvVMM1fDg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,501,1583222400"; d="scan'208";a="419330234" Received: from wwanmoha-ilbpg2.png.intel.com ([10.88.227.42]) by orsmga004.jf.intel.com with ESMTP; 11 Jun 2020 20:55:43 -0700 From: Wan Ahmad Zainie To: kishon@ti.com, vkoul@kernel.org, robh+dt@kernel.org Cc: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, andriy.shevchenko@intel.com, adrian.hunter@intel.com, wan.ahmad.zainie.wan.mohamad@intel.com Subject: [PATCH v4 2/2] phy: intel: Add Keem Bay eMMC PHY support Date: Fri, 12 Jun 2020 11:53:59 +0800 Message-Id: <20200612035359.14246-3-wan.ahmad.zainie.wan.mohamad@intel.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200612035359.14246-1-wan.ahmad.zainie.wan.mohamad@intel.com> References: <20200612035359.14246-1-wan.ahmad.zainie.wan.mohamad@intel.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add support for eMMC PHY on Intel Keem Bay SoC. Signed-off-by: Wan Ahmad Zainie --- drivers/phy/intel/Kconfig | 8 + drivers/phy/intel/Makefile | 1 + drivers/phy/intel/phy-keembay-emmc.c | 316 +++++++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 drivers/phy/intel/phy-keembay-emmc.c diff --git a/drivers/phy/intel/Kconfig b/drivers/phy/intel/Kconfig index 7b47682a4e0e..5f5497d1624a 100644 --- a/drivers/phy/intel/Kconfig +++ b/drivers/phy/intel/Kconfig @@ -22,3 +22,11 @@ config PHY_INTEL_EMMC select GENERIC_PHY help Enable this to support the Intel EMMC PHY + +config PHY_KEEMBAY_EMMC + tristate "Intel Keem Bay EMMC PHY Driver" + depends on OF + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Keem Bay EMMC PHY. diff --git a/drivers/phy/intel/Makefile b/drivers/phy/intel/Makefile index 233d530dadde..6566334e7b77 100644 --- a/drivers/phy/intel/Makefile +++ b/drivers/phy/intel/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PHY_INTEL_COMBO) += phy-intel-combo.o obj-$(CONFIG_PHY_INTEL_EMMC) += phy-intel-emmc.o +obj-$(CONFIG_PHY_KEEMBAY_EMMC) += phy-keembay-emmc.o diff --git a/drivers/phy/intel/phy-keembay-emmc.c b/drivers/phy/intel/phy-keembay-emmc.c new file mode 100644 index 000000000000..68a0e0be7d3c --- /dev/null +++ b/drivers/phy/intel/phy-keembay-emmc.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Keem Bay eMMC PHY driver + * Copyright (C) 2020 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* eMMC/SD/SDIO core/phy configuration registers */ +#define PHY_CFG_0 0x24 +#define SEL_DLY_TXCLK_MASK BIT(29) +#define SEL_DLY_TXCLK(x) (((x) << 29) & SEL_DLY_TXCLK_MASK) +#define OTAP_DLY_ENA_MASK BIT(27) +#define OTAP_DLY_ENA(x) (((x) << 27) & OTAP_DLY_ENA_MASK) +#define OTAP_DLY_SEL_MASK GENMASK(26, 23) +#define OTAP_DLY_SEL(x) (((x) << 23) & OTAP_DLY_SEL_MASK) +#define DLL_EN_MASK BIT(10) +#define DLL_EN(x) (((x) << 10) & DLL_EN_MASK) +#define PWR_DOWN_MASK BIT(0) +#define PWR_DOWN(x) (((x) << 0) & PWR_DOWN_MASK) + +#define PHY_CFG_2 0x2c +#define SEL_FREQ_MASK GENMASK(12, 10) +#define SEL_FREQ(x) (((x) << 10) & SEL_FREQ_MASK) + +#define PHY_STAT 0x40 +#define CAL_DONE_MASK BIT(6) +#define IS_CALDONE(x) ((x) & CAL_DONE_MASK) +#define DLL_RDY_MASK BIT(5) +#define IS_DLLRDY(x) ((x) & DLL_RDY_MASK) + +/* From ACS_eMMC51_16nFFC_RO1100_Userguide_v1p0.pdf p17 */ +#define FREQSEL_200M_170M 0x0 +#define FREQSEL_170M_140M 0x1 +#define FREQSEL_140M_110M 0x2 +#define FREQSEL_110M_80M 0x3 +#define FREQSEL_80M_50M 0x4 + +struct keembay_emmc_phy { + struct regmap *syscfg; + struct clk *emmcclk; +}; + +static const struct regmap_config keembay_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int keembay_emmc_phy_power(struct phy *phy, bool on_off) +{ + struct keembay_emmc_phy *priv = phy_get_drvdata(phy); + unsigned int caldone; + unsigned int dllrdy; + unsigned int freqsel; + unsigned int mhz; + int ret; + + /* + * Keep phyctrl_pdb and phyctrl_endll low to allow + * initialization of CALIO state M/C DFFs + */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK, + PWR_DOWN(0)); + if (ret) { + dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK, + DLL_EN(0)); + if (ret) { + dev_err(&phy->dev, "turn off the dll failed: %d\n", ret); + return ret; + } + + /* Already finish power off above */ + if (!on_off) + return 0; + + mhz = DIV_ROUND_CLOSEST(clk_get_rate(priv->emmcclk), 1000000); + if (mhz <= 200 && mhz >= 170) + freqsel = FREQSEL_200M_170M; + else if (mhz <= 170 && mhz >= 140) + freqsel = FREQSEL_170M_140M; + else if (mhz <= 140 && mhz >= 110) + freqsel = FREQSEL_140M_110M; + else if (mhz <= 110 && mhz >= 80) + freqsel = FREQSEL_110M_80M; + else if (mhz <= 80 && mhz >= 50) + freqsel = FREQSEL_80M_50M; + else + freqsel = 0x0; + + if (mhz < 50 || mhz > 200) + dev_warn(&phy->dev, "Unsupported rate: %d MHz\n", mhz); + + /* + * According to the user manual, calpad calibration + * cycle takes more than 2us without the minimal recommended + * value, so we may need a little margin here + */ + udelay(5); + + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK, + PWR_DOWN(1)); + if (ret) { + dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret); + return ret; + } + + /* + * According to the user manual, it asks driver to wait 5us for + * calpad busy trimming. However it is documented that this value is + * PVT(A.K.A. process, voltage and temperature) relevant, so some + * failure cases are found which indicates we should be more tolerant + * to calpad busy trimming. + */ + ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT, + caldone, IS_CALDONE(caldone), + 0, 50); + if (ret) { + dev_err(&phy->dev, "caldone failed, ret=%d\n", ret); + return ret; + } + + /* Set the frequency of the DLL operation */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_2, SEL_FREQ_MASK, + SEL_FREQ(freqsel)); + if (ret) { + dev_err(&phy->dev, "set the frequency of dll failed:%d\n", ret); + return ret; + } + + /* Turn on the DLL */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK, + DLL_EN(1)); + if (ret) { + dev_err(&phy->dev, "turn on the dll failed: %d\n", ret); + return ret; + } + + /* + * We turned on the DLL even though the rate was 0 because we the + * clock might be turned on later. ...but we can't wait for the DLL + * to lock when the rate is 0 because it will never lock with no + * input clock. + * + * Technically we should be checking the lock later when the clock + * is turned on, but for now we won't. + */ + if (mhz == 0) + return 0; + + /* + * After enabling analog DLL circuits docs say that we need 10.2 us if + * our source clock is at 50 MHz and that lock time scales linearly + * with clock speed. If we are powering on the PHY and the card clock + * is super slow (like 100kHz) this could take as long as 5.1 ms as + * per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms + * hopefully we won't be running at 100 kHz, but we should still make + * sure we wait long enough. + * + * NOTE: There appear to be corner cases where the DLL seems to take + * extra long to lock for reasons that aren't understood. In some + * extreme cases we've seen it take up to over 10ms (!). We'll be + * generous and give it 50ms. + */ + ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT, + dllrdy, IS_DLLRDY(dllrdy), + 0, 50 * USEC_PER_MSEC); + if (ret) { + dev_err(&phy->dev, "dllrdy failed, ret=%d\n", ret); + return ret; + } + + return ret; +} + +static int keembay_emmc_phy_init(struct phy *phy) +{ + struct keembay_emmc_phy *priv = phy_get_drvdata(phy); + + /* + * We purposely get the clock here and not in probe to avoid the + * circular dependency problem. We expect: + * - PHY driver to probe + * - SDHCI driver to start probe + * - SDHCI driver to register it's clock + * - SDHCI driver to get the PHY + * - SDHCI driver to init the PHY + * + * The clock is optional, so upon any error just return it like + * any other error to user. + */ + priv->emmcclk = clk_get_optional(&phy->dev, "emmcclk"); + + return PTR_ERR_OR_ZERO(priv->emmcclk); +} + +static int keembay_emmc_phy_exit(struct phy *phy) +{ + struct keembay_emmc_phy *priv = phy_get_drvdata(phy); + + clk_put(priv->emmcclk); + + return 0; +}; + +static int keembay_emmc_phy_power_on(struct phy *phy) +{ + struct keembay_emmc_phy *priv = phy_get_drvdata(phy); + int ret; + + /* Delay chain based txclk: enable */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, SEL_DLY_TXCLK_MASK, + SEL_DLY_TXCLK(1)); + if (ret) { + dev_err(&phy->dev, "ERROR: delay chain txclk set: %d\n", ret); + return ret; + } + + /* Output tap delay: enable */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_ENA_MASK, + OTAP_DLY_ENA(1)); + if (ret) { + dev_err(&phy->dev, "ERROR: output tap delay set: %d\n", ret); + return ret; + } + + /* Output tap delay */ + ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_SEL_MASK, + OTAP_DLY_SEL(2)); + if (ret) { + dev_err(&phy->dev, "ERROR: output tap delay select: %d\n", ret); + return ret; + } + + /* Power up eMMC phy analog blocks */ + return keembay_emmc_phy_power(phy, true); +} + +static int keembay_emmc_phy_power_off(struct phy *phy) +{ + /* Power down eMMC phy analog blocks */ + return keembay_emmc_phy_power(phy, false); +} + +static const struct phy_ops ops = { + .init = keembay_emmc_phy_init, + .exit = keembay_emmc_phy_exit, + .power_on = keembay_emmc_phy_power_on, + .power_off = keembay_emmc_phy_power_off, + .owner = THIS_MODULE, +}; + +static int keembay_emmc_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct keembay_emmc_phy *priv; + struct phy *generic_phy; + struct phy_provider *phy_provider; + void __iomem *base; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->syscfg = devm_regmap_init_mmio(dev, base, &keembay_regmap_config); + if (IS_ERR(priv->syscfg)) + return PTR_ERR(priv->syscfg); + + generic_phy = devm_phy_create(dev, np, &ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id keembay_emmc_phy_dt_ids[] = { + { .compatible = "intel,keembay-emmc-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, keembay_emmc_phy_dt_ids); + +static struct platform_driver keembay_emmc_phy_driver = { + .probe = keembay_emmc_phy_probe, + .driver = { + .name = "keembay-emmc-phy", + .of_match_table = keembay_emmc_phy_dt_ids, + }, +}; +module_platform_driver(keembay_emmc_phy_driver); + +MODULE_AUTHOR("Wan Ahmad Zainie "); +MODULE_DESCRIPTION("Intel Keem Bay eMMC PHY driver"); +MODULE_LICENSE("GPL v2"); -- 2.17.1