Received: by 2002:a05:6902:102b:0:0:0:0 with SMTP id x11csp1045273ybt; Wed, 1 Jul 2020 17:13:33 -0700 (PDT) X-Google-Smtp-Source: ABdhPJylZx7Fn4HaASRMmCqR/8dywht2QtgEvjBgWCHINokGRY4MyGAg2CG9G1ovxOCYudDgs4I7 X-Received: by 2002:aa7:da56:: with SMTP id w22mr33115723eds.124.1593648813167; Wed, 01 Jul 2020 17:13:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1593648813; cv=none; d=google.com; s=arc-20160816; b=nzU2rpH8hzAPyMgSm53JFZ0QHlfSuu0NAnlUN7KNFEEBsKvTXn096F51I+owaIRyYl ZOCjQetf9PnZCk1JC1nSQtWfDctZY4jLfDi1p0E440UjtQ2WRDzPOOTsrBFOy4zwqe9S toiVcdR64dR2Pc4tuBzag3YLJ//FaDeh+pIK0ExDwcu8GBHsV0PL2DiRrjutbSgTmRWa Ha2XSauIHIaDttIF40mQnIqTXO2zMX8SGYL9mMNw0bye4mURnf7FQESNYCYuz4y5PN0a AGB7t7T3/I+ymz7svSbQhaoljSXdgV6EZE7KeAqN4C4aKVif+EPyDKZMxW65mYEQ6+qc 2skA== 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=itsCA/j1vUaXMaeq8pbtVWn4OQXc+7a5EUPGnUQUMfk=; b=sTSXrTDPxmcuX1oZF5fE87N/W6e8sq5q8NLWVUqHmOC+jsNL3Xpbpq7km3lCbhytq3 NHMpZc7fbzar/TfNkRRgPlHwl5/yu/IboVNsexWh5uV2Cs4kKuZxKJjIMD6PuwDv6BMf E7PQAMSGDRIW5AlUl7g7Fmg2rPxkd8vpg6rjZpAxTSzxgUrvmD6saQ7vfb43XfTtv7eH Escx5Bbdrb7gQ+xfoTwRQW9JdN4zUgiDjbG1KsIfJRtBv8XZyQOp8ZFxEb68Ho92Zxi1 UtO+LbcYoa4nwPM8GEujXYwfudnmyWf8mGAAsH4+lSIBB+25nxYX+sk1nzvE5JOBpqIn wPLA== 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 cm12si4602552edb.333.2020.07.01.17.13.10; Wed, 01 Jul 2020 17:13:33 -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 S1728050AbgGBALg (ORCPT + 99 others); Wed, 1 Jul 2020 20:11:36 -0400 Received: from mga03.intel.com ([134.134.136.65]:60622 "EHLO mga03.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727811AbgGBALe (ORCPT ); Wed, 1 Jul 2020 20:11:34 -0400 IronPort-SDR: 07UqMuJqX/DOjhl9axr1tjq1a6HaS5tlUiaLhhHOp9+KburGPKG6GZ4qI0YzYx8BxuPvQtPV4t NO8H4rXdDCLQ== X-IronPort-AV: E=McAfee;i="6000,8403,9669"; a="146734373" X-IronPort-AV: E=Sophos;i="5.75,302,1589266800"; d="scan'208";a="146734373" X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga103.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 01 Jul 2020 17:11:32 -0700 IronPort-SDR: Mncba7ksVP/Ab9VlH86zTt8uFbw0OeEbMTO2rq6jN8RWuKKWXU5fxfw3gi6GSBa8ZneLuwWcpt tnF05i4nD3bg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.75,302,1589266800"; d="scan'208";a="313944385" Received: from wwanmoha-ilbpg2.png.intel.com ([10.88.227.42]) by fmsmga002.fm.intel.com with ESMTP; 01 Jul 2020 17:11:30 -0700 From: Wan Ahmad Zainie To: kishon@ti.com, vkoul@kernel.org, robh+dt@kernel.org, andriy.shevchenko@intel.com Cc: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, wan.ahmad.zainie.wan.mohamad@intel.com, sureshkumar.mp@intel.com, lakshmi.bai.raja.subramanian@intel.com Subject: [PATCH v6 2/2] phy: intel: Add Keem Bay eMMC PHY support Date: Thu, 2 Jul 2020 08:09:34 +0800 Message-Id: <20200702000934.3258-3-wan.ahmad.zainie.wan.mohamad@intel.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200702000934.3258-1-wan.ahmad.zainie.wan.mohamad@intel.com> References: <20200702000934.3258-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 | 12 + drivers/phy/intel/Makefile | 1 + drivers/phy/intel/phy-keembay-emmc.c | 314 +++++++++++++++++++++++++++ 3 files changed, 327 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..8ddda4fb95d2 100644 --- a/drivers/phy/intel/Kconfig +++ b/drivers/phy/intel/Kconfig @@ -22,3 +22,15 @@ 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 ARM64 || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + select REGMAP_MMIO + help + Choose this option if you have an Intel Keem Bay SoC. + + To compile this driver as a module, choose M here: the module + will be called phy-keembay-emmc. 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..66b5931a749a --- /dev/null +++ b/drivers/phy/intel/phy-keembay-emmc.c @@ -0,0 +1,314 @@ +// 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; +} + +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