mirror of
https://github.com/Ysurac/openmptcprouter.git
synced 2025-03-09 15:40:20 +00:00
445 lines
14 KiB
Diff
445 lines
14 KiB
Diff
From 10d9fcf2545d9acef2281a215e7e92d6c7e88f85 Mon Sep 17 00:00:00 2001
|
|
From: Ulf Hansson <ulf.hansson@linaro.org>
|
|
Date: Thu, 29 Oct 2020 09:57:16 +0800
|
|
Subject: [PATCH 345/697] mmc: brcmstb: add support for BCM2712
|
|
|
|
BCM2712 has an SD Express capable SDHCI implementation and uses
|
|
the SDIO CFG register block present on other STB chips.
|
|
|
|
Add plumbing for SD Express handover and BCM2712-specific functions.
|
|
|
|
Due to the common bus infrastructure between BCM2711 and BCM2712,
|
|
the driver also needs to implement 32-bit IO accessors.
|
|
|
|
mmc: brcmstb: override card presence if broken-cd is set
|
|
|
|
Not just if the card is declared as nonremovable.
|
|
|
|
sdhci: brcmstb: align SD express switchover with SD spec v8.00
|
|
|
|
Part 1 of the Physical specification, figure 3-24, details the switch
|
|
sequence for cards initially probed as SD. Add a missing check for DAT2
|
|
level after switching VDD2 on.
|
|
|
|
sdhci: brcmstb: clean up SD Express probe and error handling
|
|
|
|
Refactor to avoid spurious error messages in dmesg if the requisite SD
|
|
Express DT nodes aren't present.
|
|
|
|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
|
|
|
|
mmc: sdhci-brcmstb: only use the delay line PHY for tuneable speeds
|
|
|
|
The MMC core has a 200MHz core clock which allows the use of DDR50 and
|
|
below without incremental phase tuning. SDR50/SDR104 and the EMMC HS200
|
|
speeds require tuning.
|
|
|
|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
|
|
|
|
mmc: sdhci-brcmstb: remove 32-bit accessors for BCM2712
|
|
|
|
The reason for adding these are lost to the mists of time (and for a
|
|
previous chip revision). Removing these accessors appears to have no ill
|
|
effect on production chips, so get rid of the unnecessary RMW cycles.
|
|
|
|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
|
|
|
|
drivers: mmc: sdhci-brcmstb: fix usage of SD_PIN_SEL on BCM2712
|
|
|
|
The SDIO_CFG register SD_PIN_SEL conflates two settings - whether eMMC
|
|
HS or SD UHS timings are applied to the interface, and whether or not
|
|
the card-detect line is functional. SD_PIN_SEL can only be changed when
|
|
the SD clock isn't running, so add a bcm2712-specific clock setup.
|
|
|
|
Toggling SD_PIN_SEL at runtime means the integrated card-detect feature
|
|
can't be used, so this controller needs a cd-gpios property.
|
|
|
|
Also fix conditionals for usage of the delay-line PHY - no-1-8-v will
|
|
imply no bits set in hsemmc_mask or uhs_mask, so remove it.
|
|
|
|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
|
|
|
|
drivers: sdhci-brcmstb: set CQE timer clock frequency
|
|
|
|
CQHCI keeps track of tags in flight with internal timers, so the clock
|
|
frequency driving the timer needs to be specified. The config registers
|
|
default to 0 (100kHz) which means timeouts will be significantly shorter
|
|
than they should be. Assume the timer clock comes from the controller
|
|
base clock.
|
|
|
|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
|
|
---
|
|
drivers/mmc/host/Kconfig | 1 +
|
|
drivers/mmc/host/sdhci-brcmstb.c | 235 +++++++++++++++++++++++++++++--
|
|
2 files changed, 227 insertions(+), 9 deletions(-)
|
|
|
|
--- a/drivers/mmc/host/Kconfig
|
|
+++ b/drivers/mmc/host/Kconfig
|
|
@@ -1070,6 +1070,7 @@ config MMC_SDHCI_BRCMSTB
|
|
depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST
|
|
depends on MMC_SDHCI_PLTFM
|
|
select MMC_CQHCI
|
|
+ select OF_DYNAMIC
|
|
default ARCH_BRCMSTB || BMIPS_GENERIC
|
|
help
|
|
This selects support for the SDIO/SD/MMC Host Controller on
|
|
--- a/drivers/mmc/host/sdhci-brcmstb.c
|
|
+++ b/drivers/mmc/host/sdhci-brcmstb.c
|
|
@@ -12,6 +12,8 @@
|
|
#include <linux/of.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/delay.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
|
|
#include "sdhci-cqhci.h"
|
|
#include "sdhci-pltfm.h"
|
|
@@ -28,29 +30,38 @@
|
|
|
|
#define BRCMSTB_PRIV_FLAGS_HAS_CQE BIT(0)
|
|
#define BRCMSTB_PRIV_FLAGS_GATE_CLOCK BIT(1)
|
|
+#define BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS BIT(2)
|
|
|
|
#define SDHCI_ARASAN_CQE_BASE_ADDR 0x200
|
|
|
|
#define SDIO_CFG_CQ_CAPABILITY 0x4c
|
|
-#define SDIO_CFG_CQ_CAPABILITY_FMUL GENMASK(13, 12)
|
|
+#define SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT 12
|
|
|
|
#define SDIO_CFG_CTRL 0x0
|
|
#define SDIO_CFG_CTRL_SDCD_N_TEST_EN BIT(31)
|
|
#define SDIO_CFG_CTRL_SDCD_N_TEST_LEV BIT(30)
|
|
|
|
+#define SDIO_CFG_SD_PIN_SEL 0x44
|
|
+#define SDIO_CFG_SD_PIN_SEL_MASK 0x3
|
|
+#define SDIO_CFG_SD_PIN_SEL_SD BIT(1)
|
|
+#define SDIO_CFG_SD_PIN_SEL_MMC BIT(0)
|
|
+
|
|
#define SDIO_CFG_MAX_50MHZ_MODE 0x1ac
|
|
#define SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE BIT(31)
|
|
#define SDIO_CFG_MAX_50MHZ_MODE_ENABLE BIT(0)
|
|
|
|
-#define MMC_CAP_HSE_MASK (MMC_CAP2_HSX00_1_2V | MMC_CAP2_HSX00_1_8V)
|
|
-/* Select all SD UHS type I SDR speed above 50MB/s */
|
|
-#define MMC_CAP_UHS_I_SDR_MASK (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104)
|
|
-
|
|
struct sdhci_brcmstb_priv {
|
|
void __iomem *cfg_regs;
|
|
unsigned int flags;
|
|
struct clk *base_clk;
|
|
u32 base_freq_hz;
|
|
+ struct regulator *sde_1v8;
|
|
+ struct device_node *sde_pcie;
|
|
+ void *__iomem sde_ioaddr;
|
|
+ void *__iomem sde_ioaddr2;
|
|
+ struct pinctrl *pinctrl;
|
|
+ struct pinctrl_state *pins_default;
|
|
+ struct pinctrl_state *pins_sdex;
|
|
};
|
|
|
|
struct brcmstb_match_priv {
|
|
@@ -141,6 +152,42 @@ static void sdhci_brcmstb_hs400es(struct
|
|
writel(reg, host->ioaddr + SDHCI_VENDOR);
|
|
}
|
|
|
|
+static void sdhci_bcm2712_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
+{
|
|
+ u16 clk;
|
|
+ u32 reg;
|
|
+ bool is_emmc_rate = false;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ host->mmc->actual_clock = 0;
|
|
+
|
|
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
|
|
+
|
|
+ switch (host->mmc->ios.timing) {
|
|
+ case MMC_TIMING_MMC_HS400:
|
|
+ case MMC_TIMING_MMC_HS200:
|
|
+ case MMC_TIMING_MMC_DDR52:
|
|
+ case MMC_TIMING_MMC_HS:
|
|
+ is_emmc_rate = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
|
|
+ reg &= ~SDIO_CFG_SD_PIN_SEL_MASK;
|
|
+ if (is_emmc_rate)
|
|
+ reg |= SDIO_CFG_SD_PIN_SEL_MMC;
|
|
+ else
|
|
+ reg |= SDIO_CFG_SD_PIN_SEL_SD;
|
|
+ writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
|
|
+
|
|
+ if (clock == 0)
|
|
+ return;
|
|
+
|
|
+ clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock);
|
|
+ sdhci_enable_clk(host, clk);
|
|
+}
|
|
+
|
|
static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
{
|
|
u16 clk;
|
|
@@ -156,6 +203,17 @@ static void sdhci_brcmstb_set_clock(stru
|
|
sdhci_enable_clk(host, clk);
|
|
}
|
|
|
|
+static void sdhci_brcmstb_set_power(struct sdhci_host *host, unsigned char mode,
|
|
+ unsigned short vdd)
|
|
+{
|
|
+ if (!IS_ERR(host->mmc->supply.vmmc)) {
|
|
+ struct mmc_host *mmc = host->mmc;
|
|
+
|
|
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
|
|
+ }
|
|
+ sdhci_set_power_noreg(host, mode, vdd);
|
|
+}
|
|
+
|
|
static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host,
|
|
unsigned int timing)
|
|
{
|
|
@@ -190,12 +248,16 @@ static void sdhci_brcmstb_cfginit_2712(s
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
|
|
u32 reg;
|
|
+ u32 uhs_mask = (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104);
|
|
+ u32 hsemmc_mask = (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR |
|
|
+ MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V);
|
|
+ u32 base_clk_mhz;
|
|
|
|
/*
|
|
* If we support a speed that requires tuning,
|
|
* then select the delay line PHY as the clock source.
|
|
*/
|
|
- if ((host->mmc->caps & MMC_CAP_UHS_I_SDR_MASK) || (host->mmc->caps2 & MMC_CAP_HSE_MASK)) {
|
|
+ if ((host->mmc->caps & uhs_mask) || (host->mmc->caps2 & hsemmc_mask)) {
|
|
reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE);
|
|
reg &= ~SDIO_CFG_MAX_50MHZ_MODE_ENABLE;
|
|
reg |= SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE;
|
|
@@ -210,6 +272,109 @@ static void sdhci_brcmstb_cfginit_2712(s
|
|
reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN;
|
|
writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CTRL);
|
|
}
|
|
+
|
|
+ /* Guesstimate the timer frequency (controller base clock) */
|
|
+ base_clk_mhz = max_t(u32, clk_get_rate(pltfm_host->clk) / (1000 * 1000), 1);
|
|
+ reg = (3 << SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT) | base_clk_mhz;
|
|
+ writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CQ_CAPABILITY);
|
|
+}
|
|
+
|
|
+static int bcm2712_init_sd_express(struct sdhci_host *host, struct mmc_ios *ios)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
|
|
+ struct device *dev = host->mmc->parent;
|
|
+ u32 ctrl_val;
|
|
+ u32 present_state;
|
|
+ int ret;
|
|
+
|
|
+ if (!brcmstb_priv->sde_ioaddr || !brcmstb_priv->sde_ioaddr2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!brcmstb_priv->pinctrl)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Turn off the SD clock first */
|
|
+ sdhci_set_clock(host, 0);
|
|
+
|
|
+ /* Disable SD DAT0-3 pulls */
|
|
+ pinctrl_select_state(brcmstb_priv->pinctrl, brcmstb_priv->pins_sdex);
|
|
+
|
|
+ ctrl_val = readl(brcmstb_priv->sde_ioaddr);
|
|
+ dev_dbg(dev, "ctrl_val 1 %08x\n", ctrl_val);
|
|
+
|
|
+ /* Tri-state the SD pins */
|
|
+ ctrl_val |= 0x1ff8;
|
|
+ writel(ctrl_val, brcmstb_priv->sde_ioaddr);
|
|
+ dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
|
|
+ /* Let voltages settle */
|
|
+ udelay(100);
|
|
+
|
|
+ /* Enable the PCIe sideband pins */
|
|
+ ctrl_val &= ~0x6000;
|
|
+ writel(ctrl_val, brcmstb_priv->sde_ioaddr);
|
|
+ dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
|
|
+ /* Let voltages settle */
|
|
+ udelay(100);
|
|
+
|
|
+ /* Turn on the 1v8 VDD2 regulator */
|
|
+ ret = regulator_enable(brcmstb_priv->sde_1v8);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Wait for Tpvcrl */
|
|
+ msleep(1);
|
|
+
|
|
+ /* Sample DAT2 (CLKREQ#) - if low, card is in PCIe mode */
|
|
+ present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
|
+ present_state = (present_state & SDHCI_DATA_LVL_MASK) >> SDHCI_DATA_LVL_SHIFT;
|
|
+ dev_dbg(dev, "state = 0x%08x\n", present_state);
|
|
+
|
|
+ if (present_state & BIT(2)) {
|
|
+ dev_err(dev, "DAT2 still high, abandoning SDex switch\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* Turn on the LCPLL PTEST mux */
|
|
+ ctrl_val = readl(brcmstb_priv->sde_ioaddr2 + 20); // misc5
|
|
+ ctrl_val &= ~(0x7 << 7);
|
|
+ ctrl_val |= 3 << 7;
|
|
+ writel(ctrl_val, brcmstb_priv->sde_ioaddr2 + 20);
|
|
+ dev_dbg(dev, "misc 5->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2 + 20));
|
|
+
|
|
+ /* PTEST diff driver enable */
|
|
+ ctrl_val = readl(brcmstb_priv->sde_ioaddr2);
|
|
+ ctrl_val |= BIT(21);
|
|
+ writel(ctrl_val, brcmstb_priv->sde_ioaddr2);
|
|
+
|
|
+ dev_dbg(dev, "misc 0->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2));
|
|
+
|
|
+ /* Wait for more than the minimum Tpvpgl time */
|
|
+ msleep(100);
|
|
+
|
|
+ if (brcmstb_priv->sde_pcie) {
|
|
+ struct of_changeset changeset;
|
|
+ static struct property okay_property = {
|
|
+ .name = "status",
|
|
+ .value = "okay",
|
|
+ .length = 5,
|
|
+ };
|
|
+
|
|
+ /* Enable the pcie controller */
|
|
+ of_changeset_init(&changeset);
|
|
+ ret = of_changeset_update_property(&changeset,
|
|
+ brcmstb_priv->sde_pcie,
|
|
+ &okay_property);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "%s: failed to update property - %d\n", __func__,
|
|
+ ret);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ ret = of_changeset_apply(&changeset);
|
|
+ }
|
|
+
|
|
+ dev_dbg(dev, "%s -> %d\n", __func__, ret);
|
|
+ return ret;
|
|
}
|
|
|
|
static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc)
|
|
@@ -245,11 +410,12 @@ static struct sdhci_ops sdhci_brcmstb_op
|
|
};
|
|
|
|
static struct sdhci_ops sdhci_brcmstb_ops_2712 = {
|
|
- .set_clock = sdhci_set_clock,
|
|
- .set_power = sdhci_set_power_and_bus_voltage,
|
|
+ .set_clock = sdhci_bcm2712_set_clock,
|
|
+ .set_power = sdhci_brcmstb_set_power,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.reset = sdhci_reset,
|
|
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
|
+ .init_sd_express = bcm2712_init_sd_express,
|
|
};
|
|
|
|
static struct sdhci_ops sdhci_brcmstb_ops_7216 = {
|
|
@@ -267,6 +433,8 @@ static struct sdhci_ops sdhci_brcmstb_op
|
|
};
|
|
|
|
static const struct brcmstb_match_priv match_priv_2712 = {
|
|
+ .flags = BRCMSTB_MATCH_FLAGS_USE_CARD_BUSY,
|
|
+ .hs400es = sdhci_brcmstb_hs400es,
|
|
.cfginit = sdhci_brcmstb_cfginit_2712,
|
|
.ops = &sdhci_brcmstb_ops_2712,
|
|
};
|
|
@@ -372,6 +540,8 @@ static int sdhci_brcmstb_probe(struct pl
|
|
struct sdhci_brcmstb_priv *priv;
|
|
u32 actual_clock_mhz;
|
|
struct sdhci_host *host;
|
|
+ struct resource *iomem;
|
|
+ bool no_pinctrl = false;
|
|
struct clk *clk;
|
|
struct clk *base_clk = NULL;
|
|
int res;
|
|
@@ -394,12 +564,19 @@ static int sdhci_brcmstb_probe(struct pl
|
|
return PTR_ERR(host);
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
+ pltfm_host->clk = clk;
|
|
+
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
if (device_property_read_bool(&pdev->dev, "supports-cqe")) {
|
|
priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE;
|
|
match_priv->ops->irq = sdhci_brcmstb_cqhci_irq;
|
|
}
|
|
|
|
+ priv->sde_pcie = of_parse_phandle(pdev->dev.of_node,
|
|
+ "sde-pcie", 0);
|
|
+ if (priv->sde_pcie)
|
|
+ priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
|
|
+
|
|
/* Map in the non-standard CFG registers */
|
|
priv->cfg_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL);
|
|
if (IS_ERR(priv->cfg_regs)) {
|
|
@@ -412,6 +589,43 @@ static int sdhci_brcmstb_probe(struct pl
|
|
if (res)
|
|
goto err;
|
|
|
|
+ priv->sde_1v8 = devm_regulator_get_optional(&pdev->dev, "sde-1v8");
|
|
+ if (IS_ERR(priv->sde_1v8))
|
|
+ priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
|
|
+
|
|
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
|
+ if (iomem) {
|
|
+ priv->sde_ioaddr = devm_ioremap_resource(&pdev->dev, iomem);
|
|
+ if (IS_ERR(priv->sde_ioaddr))
|
|
+ priv->sde_ioaddr = NULL;
|
|
+ }
|
|
+
|
|
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 3);
|
|
+ if (iomem) {
|
|
+ priv->sde_ioaddr2 = devm_ioremap_resource(&pdev->dev, iomem);
|
|
+ if (IS_ERR(priv->sde_ioaddr2))
|
|
+ priv->sde_ioaddr = NULL;
|
|
+ }
|
|
+
|
|
+ priv->pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
+ if (IS_ERR(priv->pinctrl)) {
|
|
+ no_pinctrl = true;
|
|
+ }
|
|
+ priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default");
|
|
+ if (IS_ERR(priv->pins_default)) {
|
|
+ dev_dbg(&pdev->dev, "No pinctrl default state\n");
|
|
+ no_pinctrl = true;
|
|
+ }
|
|
+ priv->pins_sdex = pinctrl_lookup_state(priv->pinctrl, "sd-express");
|
|
+ if (IS_ERR(priv->pins_sdex)) {
|
|
+ dev_dbg(&pdev->dev, "No pinctrl sd-express state\n");
|
|
+ no_pinctrl = true;
|
|
+ }
|
|
+ if (no_pinctrl || !priv->sde_ioaddr || !priv->sde_ioaddr2) {
|
|
+ priv->pinctrl = NULL;
|
|
+ priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
|
|
+ }
|
|
+
|
|
/*
|
|
* Automatic clock gating does not work for SD cards that may
|
|
* voltage switch so only enable it for non-removable devices.
|
|
@@ -428,6 +642,10 @@ static int sdhci_brcmstb_probe(struct pl
|
|
(host->mmc->caps2 & MMC_CAP2_HS400_ES))
|
|
host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es;
|
|
|
|
+ if (host->ops->init_sd_express &&
|
|
+ (priv->flags & BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS))
|
|
+ host->mmc->caps2 |= MMC_CAP2_SD_EXP;
|
|
+
|
|
if (match_priv->cfginit)
|
|
match_priv->cfginit(host);
|
|
|
|
@@ -481,7 +699,6 @@ add_host:
|
|
if (res)
|
|
goto err;
|
|
|
|
- pltfm_host->clk = clk;
|
|
return res;
|
|
|
|
err:
|