Received: by 2002:a25:4158:0:0:0:0:0 with SMTP id o85csp3896338yba; Tue, 16 Apr 2019 23:33:45 -0700 (PDT) X-Google-Smtp-Source: APXvYqw9wjyByA3d7iGW0U3vwFYmb1//pfpE97JvEJrJx1rewaenhJYHnJFNYmXA4srLYCzl2E8O X-Received: by 2002:a65:625a:: with SMTP id q26mr82127587pgv.68.1555482824967; Tue, 16 Apr 2019 23:33:44 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1555482824; cv=none; d=google.com; s=arc-20160816; b=tOafME/kOOiskR2V+/Jn0jTRhNO4PuMjQVIQqJJgSP43q2Mncg30NUPIS9WwGpedkM 7vrxkpg2nOqW2FsXz2JCycFsCcA42c5i+CjOpbl4dsdIqrzINNPRZbzLUA4dbgHezzKB BOD6jYZ3ktBz6h0wcMXUWGDffbTwgN2S0w6JGrDu0up5Njb26xJZJnbVFEZSUS03gTEk 1KEd62l0PCEYn1nCgu7qqKsu65z6efMLyo0acgcVaSnxKb18n224Kd8KtNFlPEe/mu3T eHyR/S1TQArVEjeB8EO0oWT9s8S4CICdCJdDSXM+POXMlYAZFOQuyUqli8mMvUt4KVMv 4Ztw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding :content-language:in-reply-to:mime-version:user-agent:date :message-id:from:references:to:subject:dkim-signature; bh=UaCgWO4p5Ym1hrO5dUpgMk451fqwqG8SpKiabaXGG/c=; b=wzd/YFuOgxiOqt7+l9J0iWrdT0rsiUjs0ej4D38Uk4TASQPB+EJ1Sfp0VysPJK3tmo DzwRSN91NfyVQVCd7Ed2Vq31h0tISpD+KI8x1/WNpHuJEL5+LK1IV21HSuTi5G3jI/To UAA3k3fLlRLdM6Fs3tyauM1ST8bJyrEO5G7jHhg2p4DX+FzkN3nbJz4P+RghYpzNQr6j 3hWLhuctAUIH1ppBBeboizeYY4+ceTBtQ+eFJ7EgdgxZJd0rfhsU8kq4WIv3TSK/bK/r MORoadPK4Fl7zUrmEaBEnVLSXbCbtTOoqkrzyrxCGSc8WMj4BiMCqV3+dJ4TgTuMSowA 3eKw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=Lw6I8q9t; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h185si11901294pge.368.2019.04.16.23.33.29; Tue, 16 Apr 2019 23:33:44 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=Lw6I8q9t; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730042AbfDQGcQ (ORCPT + 99 others); Wed, 17 Apr 2019 02:32:16 -0400 Received: from fllv0015.ext.ti.com ([198.47.19.141]:51592 "EHLO fllv0015.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727013AbfDQGcQ (ORCPT ); Wed, 17 Apr 2019 02:32:16 -0400 Received: from fllv0034.itg.ti.com ([10.64.40.246]) by fllv0015.ext.ti.com (8.15.2/8.15.2) with ESMTP id x3H6VUSD108797; Wed, 17 Apr 2019 01:31:31 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ti.com; s=ti-com-17Q1; t=1555482691; bh=UaCgWO4p5Ym1hrO5dUpgMk451fqwqG8SpKiabaXGG/c=; h=Subject:To:References:From:Date:In-Reply-To; b=Lw6I8q9tbMs3TV6ZpGacu44dBAOmumyVFlwqBolnS7fj2WNDuT+VgNPXn3bmMfl9J DixBvnQCmXxG1aSm2j8T/ZaqgcA2n41XxOCHilAhKV0+3dnqz99YBsu8GdPGZu7Lvz AC6zCFbgD4vmI1ZAgqYGX81guBgT+ipb9MxenjBI= Received: from DLEE112.ent.ti.com (dlee112.ent.ti.com [157.170.170.23]) by fllv0034.itg.ti.com (8.15.2/8.15.2) with ESMTPS id x3H6VUv8000687 (version=TLSv1.2 cipher=AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 17 Apr 2019 01:31:30 -0500 Received: from DLEE103.ent.ti.com (157.170.170.33) by DLEE112.ent.ti.com (157.170.170.23) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1713.5; Wed, 17 Apr 2019 01:31:30 -0500 Received: from fllv0040.itg.ti.com (10.64.41.20) by DLEE103.ent.ti.com (157.170.170.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1713.5 via Frontend Transport; Wed, 17 Apr 2019 01:31:30 -0500 Received: from [172.24.190.233] (ileax41-snat.itg.ti.com [10.172.224.153]) by fllv0040.itg.ti.com (8.15.2/8.15.2) with ESMTP id x3H6VKfs058504; Wed, 17 Apr 2019 01:31:21 -0500 Subject: Re: [PATCH v8 2/2] phy: Add driver for mixel mipi dphy found on NXP's i.MX8 SoCs To: =?UTF-8?Q?Guido_G=c3=bcnther?= , Rob Herring , Mark Rutland , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , NXP Linux Team , Thierry Reding , =?UTF-8?Q?Andreas_F=c3=a4rber?= , Martin Blumenstingl , Heiko Stuebner , Johan Hovold , Lucas Stach , Abel Vesa , Li Jun , , , , , Robert Chiras , Sam Ravnborg , Maxime Ripard References: <78f5a30d1c86d23686c9ce3e7e9144403faf2bec.1554113192.git.agx@sigxcpu.org> From: Kishon Vijay Abraham I Message-ID: Date: Wed, 17 Apr 2019 12:00:14 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.6.1 MIME-Version: 1.0 In-Reply-To: <78f5a30d1c86d23686c9ce3e7e9144403faf2bec.1554113192.git.agx@sigxcpu.org> Content-Type: text/plain; charset="utf-8" Content-Language: en-US Content-Transfer-Encoding: 8bit X-EXCLAIMER-MD-CONFIG: e1e8a2fd-e40a-4ac6-ac9b-f7e9cc9ee180 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hi Maxime, On 01/04/19 3:43 PM, Guido Günther wrote: > This adds support for the Mixel DPHY as found on i.MX8 CPUs but since > this is an IP core it will likely be found on others in the future. So > instead of adding this to the nwl host driver make it a generic PHY > driver. > > The driver supports the i.MX8MQ. Support for i.MX8QM and i.MX8QXP can be > added once the necessary system controller bits are in via > mixel_dphy_devdata. > > Co-authored-by: Robert Chiras > Signed-off-by: Guido Günther This driver uses the new interface that you introduced. Can you review this patch and give your Acked-by? Thanks Kishon > --- > drivers/phy/freescale/Kconfig | 11 + > drivers/phy/freescale/Makefile | 1 + > .../phy/freescale/phy-fsl-imx8-mipi-dphy.c | 506 ++++++++++++++++++ > 3 files changed, 518 insertions(+) > create mode 100644 drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c > > diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig > index 832670b4952b..a111b130f9d2 100644 > --- a/drivers/phy/freescale/Kconfig > +++ b/drivers/phy/freescale/Kconfig > @@ -3,3 +3,14 @@ config PHY_FSL_IMX8MQ_USB > depends on OF && HAS_IOMEM > select GENERIC_PHY > default ARCH_MXC && ARM64 > + > +config PHY_MIXEL_MIPI_DPHY > + tristate "Mixel MIPI DSI PHY support" > + depends on OF && HAS_IOMEM > + select GENERIC_PHY > + select GENERIC_PHY_MIPI_DPHY > + select REGMAP_MMIO > + default ARCH_MXC && ARM64 > + help > + Enable this to add support for the Mixel DSI PHY as found > + on NXP's i.MX8 family of SOCs. > diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile > index dc2b3f1f2f80..07491c926a2c 100644 > --- a/drivers/phy/freescale/Makefile > +++ b/drivers/phy/freescale/Makefile > @@ -1 +1,2 @@ > obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o > +obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o > diff --git a/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c b/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c > new file mode 100644 > index 000000000000..2f4f4afd1cf5 > --- /dev/null > +++ b/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c > @@ -0,0 +1,506 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2017,2018 NXP > + * Copyright 2019 Purism SPC > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* DPHY registers */ > +#define DPHY_PD_DPHY 0x00 > +#define DPHY_M_PRG_HS_PREPARE 0x04 > +#define DPHY_MC_PRG_HS_PREPARE 0x08 > +#define DPHY_M_PRG_HS_ZERO 0x0c > +#define DPHY_MC_PRG_HS_ZERO 0x10 > +#define DPHY_M_PRG_HS_TRAIL 0x14 > +#define DPHY_MC_PRG_HS_TRAIL 0x18 > +#define DPHY_PD_PLL 0x1c > +#define DPHY_TST 0x20 > +#define DPHY_CN 0x24 > +#define DPHY_CM 0x28 > +#define DPHY_CO 0x2c > +#define DPHY_LOCK 0x30 > +#define DPHY_LOCK_BYP 0x34 > +#define DPHY_REG_BYPASS_PLL 0x4C > + > +#define MBPS(x) ((x) * 1000000) > + > +#define DATA_RATE_MAX_SPEED MBPS(1500) > +#define DATA_RATE_MIN_SPEED MBPS(80) > + > +#define PLL_LOCK_SLEEP 10 > +#define PLL_LOCK_TIMEOUT 1000 > + > +#define CN_BUF 0xcb7a89c0 > +#define CO_BUF 0x63 > +#define CM(x) ( \ > + ((x) < 32)?0xe0|((x)-16) : \ > + ((x) < 64)?0xc0|((x)-32) : \ > + ((x) < 128)?0x80|((x)-64) : \ > + ((x) - 128)) > +#define CN(x) (((x) == 1)?0x1f : (((CN_BUF)>>((x)-1))&0x1f)) > +#define CO(x) ((CO_BUF)>>(8-(x))&0x3) > + > +/* PHY power on is active low */ > +#define PWR_ON 0 > +#define PWR_OFF 1 > + > +enum mixel_dphy_devtype { > + MIXEL_IMX8MQ, > +}; > + > +struct mixel_dphy_devdata { > + u8 reg_tx_rcal; > + u8 reg_auto_pd_en; > + u8 reg_rxlprp; > + u8 reg_rxcdrp; > + u8 reg_rxhs_settle; > +}; > + > +static const struct mixel_dphy_devdata mixel_dphy_devdata[] = { > + [MIXEL_IMX8MQ] = { > + .reg_tx_rcal = 0x38, > + .reg_auto_pd_en = 0x3c, > + .reg_rxlprp = 0x40, > + .reg_rxcdrp = 0x44, > + .reg_rxhs_settle = 0x48, > + }, > +}; > + > +struct mixel_dphy_cfg { > + /* DPHY PLL parameters */ > + u32 cm; > + u32 cn; > + u32 co; > + /* DPHY register values */ > + u8 mc_prg_hs_prepare; > + u8 m_prg_hs_prepare; > + u8 mc_prg_hs_zero; > + u8 m_prg_hs_zero; > + u8 mc_prg_hs_trail; > + u8 m_prg_hs_trail; > + u8 rxhs_settle; > +}; > + > +struct mixel_dphy_priv { > + struct mixel_dphy_cfg cfg; > + struct regmap *regs; > + struct clk *phy_ref_clk; > + const struct mixel_dphy_devdata *devdata; > +}; > + > +static const struct regmap_config mixel_dphy_regmap_config = { > + .reg_bits = 8, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = DPHY_REG_BYPASS_PLL, > + .name = "mipi-dphy", > +}; > + > +static int phy_write(struct phy *phy, u32 value, unsigned int reg) > +{ > + struct mixel_dphy_priv *priv = phy_get_drvdata(phy); > + int ret; > + > + ret = regmap_write(priv->regs, reg, value); > + if (ret < 0) > + dev_err(&phy->dev, "Failed to write DPHY reg %d: %d", reg, ret); > + return ret; > +} > + > +/* > + * Find a ratio close to the desired one using continued fraction > + * approximation ending either at exact match or maximum allowed > + * nominator, denominator. > + */ > +static void get_best_ratio(u32 *pnum, u32 *pdenom, unsigned int max_n, > + unsigned int max_d) > +{ > + u32 a = *pnum; > + u32 b = *pdenom; > + u32 c; > + u32 n[] = {0, 1}; > + u32 d[] = {1, 0}; > + u32 whole; > + unsigned int i = 1; > + > + while (b) { > + i ^= 1; > + whole = a / b; > + n[i] += (n[i ^ 1] * whole); > + d[i] += (d[i ^ 1] * whole); > + if ((n[i] > max_n) || (d[i] > max_d)) { > + i ^= 1; > + break; > + } > + c = a - (b * whole); > + a = b; > + b = c; > + } > + *pnum = n[i]; > + *pdenom = d[i]; > +} > + > +static int mixel_dphy_config_from_opts(struct phy *phy, > + struct phy_configure_opts_mipi_dphy *dphy_opts, > + struct mixel_dphy_cfg *cfg) > +{ > + struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent); > + unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk); > + u32 lp_t, numerator, denominator; > + unsigned long long tmp; > + u32 n; > + int i; > + > + if (dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED || > + dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED) > + return -EINVAL; > + > + numerator = dphy_opts->hs_clk_rate; > + denominator = ref_clk; > + get_best_ratio(&numerator, &denominator, 255, 256); > + if (!numerator || !denominator) { > + dev_err(&phy->dev, "Invalid %d/%d for %ld/%ld\n", > + numerator, denominator, > + dphy_opts->hs_clk_rate, ref_clk); > + return -EINVAL; > + } > + > + while ((numerator < 16) && (denominator <= 128)) { > + numerator <<= 1; > + denominator <<= 1; > + } > + /* > + * CM ranges between 16 and 255 > + * CN ranges between 1 and 32 > + * CO is power of 2: 1, 2, 4, 8 > + */ > + i = __ffs(denominator); > + if (i > 3) > + i = 3; > + cfg->cn = denominator >> i; > + cfg->co = 1 << i; > + cfg->cm = numerator; > + > + if (cfg->cm < 16 || cfg->cm > 255 || > + cfg->cn < 1 || cfg->cn > 32 || > + cfg->co < 1 || cfg->co > 8) { > + dev_err(&phy->dev, "Invalid CM/CN/CO values: %u/%u/%u\n", > + cfg->cm, cfg->cn, cfg->co); > + dev_err(&phy->dev, "for hs_clk/ref_clk=%ld/%ld ⩰ %d/%d\n", > + dphy_opts->hs_clk_rate, ref_clk, > + numerator, denominator); > + return -EINVAL; > + } > + > + dev_dbg(&phy->dev, "hs_clk/ref_clk=%ld/%ld ⩰ %d/%d\n", > + dphy_opts->hs_clk_rate, ref_clk, numerator, denominator); > + > + /* LP clock period */ > + tmp = 1000000000000LL; > + do_div(tmp, dphy_opts->lp_clk_rate); /* ps */ > + if (tmp > ULONG_MAX) > + return -EINVAL; > + > + lp_t = tmp; > + dev_dbg(&phy->dev, "LP clock %lu, period: %u ps\n", > + dphy_opts->lp_clk_rate, lp_t); > + > + /* hs_prepare: in lp clock periods */ > + if (2 * dphy_opts->hs_prepare > 5 * lp_t) { > + dev_err(&phy->dev, > + "hs_prepare (%u) > 2.5 * lp clock period (%u)", > + dphy_opts->hs_prepare, lp_t); > + return -EINVAL; > + } > + /* 00: lp_t, 01: 1.5 * lp_t, 10: 2 * lp_t, 11: 2.5 * lp_t */ > + if (dphy_opts->hs_prepare < lp_t) { > + n = 0; > + } else { > + tmp = 2 * (dphy_opts->hs_prepare - lp_t); > + do_div(tmp, lp_t); > + n = tmp; > + } > + cfg->m_prg_hs_prepare = n; > + > + /* clk_prepare: in lp clock periods */ > + if (2 * dphy_opts->clk_prepare > 3 * lp_t) { > + dev_err(&phy->dev, > + "clk_prepare (%u) > 1.5 * lp clock period (%u)", > + dphy_opts->clk_prepare, lp_t); > + return -EINVAL; > + } > + /* 00: lp_t, 01: 1.5 * lp_t */ > + cfg->mc_prg_hs_prepare = dphy_opts->clk_prepare > lp_t ? 1 : 0; > + > + /* hs_zero: formula from NXP BSP */ > + n = (144 * (dphy_opts->hs_clk_rate / 1000000) - 47500) / 10000; > + cfg->m_prg_hs_zero = n < 1 ? 1 : n; > + > + /* clk_zero: formula from NXP BSP */ > + n = (34 * (dphy_opts->hs_clk_rate / 1000000) - 2500) / 1000; > + cfg->mc_prg_hs_zero = n < 1 ? 1 : n; > + > + /* clk_trail, hs_trail: formula from NXP BSP */ > + n = (103 * (dphy_opts->hs_clk_rate / 1000000) + 10000) / 10000; > + if (n > 15) > + n = 15; > + if (n < 1) > + n = 1; > + cfg->m_prg_hs_trail = n; > + cfg->mc_prg_hs_trail = n; > + > + /* rxhs_settle: formula from NXP BSP */ > + if (dphy_opts->hs_clk_rate < MBPS(80)) > + cfg->rxhs_settle = 0x0d; > + else if (dphy_opts->hs_clk_rate < MBPS(90)) > + cfg->rxhs_settle = 0x0c; > + else if (dphy_opts->hs_clk_rate < MBPS(125)) > + cfg->rxhs_settle = 0x0b; > + else if (dphy_opts->hs_clk_rate < MBPS(150)) > + cfg->rxhs_settle = 0x0a; > + else if (dphy_opts->hs_clk_rate < MBPS(225)) > + cfg->rxhs_settle = 0x09; > + else if (dphy_opts->hs_clk_rate < MBPS(500)) > + cfg->rxhs_settle = 0x08; > + else > + cfg->rxhs_settle = 0x07; > + > + dev_dbg(&phy->dev, "hs_prepare: %u, clk_prepare: %u, " > + "hs_prepare: %u, clk_prepare: %u, " > + "hs_trail: %u, clk_trail: %u, " > + "rxhs_settle: %u\n", > + cfg->m_prg_hs_prepare, cfg->mc_prg_hs_prepare, > + cfg->m_prg_hs_zero, cfg->mc_prg_hs_zero, > + cfg->m_prg_hs_trail, cfg->mc_prg_hs_trail, > + cfg->rxhs_settle); > + > + return 0; > +} > + > +static void mixel_phy_set_hs_timings(struct phy *phy) > +{ > + struct mixel_dphy_priv *priv = phy_get_drvdata(phy); > + > + phy_write(phy, priv->cfg.m_prg_hs_prepare, DPHY_M_PRG_HS_PREPARE); > + phy_write(phy, priv->cfg.mc_prg_hs_prepare, DPHY_MC_PRG_HS_PREPARE); > + phy_write(phy, priv->cfg.m_prg_hs_zero, DPHY_M_PRG_HS_ZERO); > + phy_write(phy, priv->cfg.mc_prg_hs_zero, DPHY_MC_PRG_HS_ZERO); > + phy_write(phy, priv->cfg.m_prg_hs_trail, DPHY_M_PRG_HS_TRAIL); > + phy_write(phy, priv->cfg.mc_prg_hs_trail, DPHY_MC_PRG_HS_TRAIL); > + phy_write(phy, priv->cfg.rxhs_settle, priv->devdata->reg_rxhs_settle); > +} > + > +static int mixel_dphy_set_pll_params(struct phy *phy) > +{ > + struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent); > + > + if (priv->cfg.cm < 16 || priv->cfg.cm > 255 || > + priv->cfg.cn < 1 || priv->cfg.cn > 32 || > + priv->cfg.co < 1 || priv->cfg.co > 8) { > + dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n", > + priv->cfg.cm, priv->cfg.cn, priv->cfg.co); > + return -EINVAL; > + } > + dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n", > + priv->cfg.cm, priv->cfg.cn, priv->cfg.co); > + phy_write(phy, CM(priv->cfg.cm), DPHY_CM); > + phy_write(phy, CN(priv->cfg.cn), DPHY_CN); > + phy_write(phy, CO(priv->cfg.co), DPHY_CO); > + return 0; > +} > + > +static int mixel_dphy_configure(struct phy *phy, union phy_configure_opts *opts) > +{ > + struct mixel_dphy_priv *priv = phy_get_drvdata(phy); > + struct mixel_dphy_cfg cfg = { 0 }; > + int ret; > + > + ret = mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); > + if (ret) > + return ret; > + > + /* Update the configuration */ > + memcpy(&priv->cfg, &cfg, sizeof(struct mixel_dphy_cfg)); > + > + phy_write(phy, 0x00, DPHY_LOCK_BYP); > + phy_write(phy, 0x01, priv->devdata->reg_tx_rcal); > + phy_write(phy, 0x00, priv->devdata->reg_auto_pd_en); > + phy_write(phy, 0x02, priv->devdata->reg_rxlprp); > + phy_write(phy, 0x02, priv->devdata->reg_rxcdrp); > + phy_write(phy, 0x25, DPHY_TST); > + > + mixel_phy_set_hs_timings(phy); > + ret = mixel_dphy_set_pll_params(phy); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int mixel_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, > + union phy_configure_opts *opts) > +{ > + struct mixel_dphy_cfg cfg = { 0 }; > + > + if (mode != PHY_MODE_MIPI_DPHY) > + return -EINVAL; > + > + return mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); > +} > + > + > +static int mixel_dphy_init(struct phy *phy) > +{ > + phy_write(phy, PWR_OFF, DPHY_PD_PLL); > + phy_write(phy, PWR_OFF, DPHY_PD_DPHY); > + > + return 0; > +} > + > + > +static int mixel_dphy_exit(struct phy *phy) > +{ > + phy_write(phy, 0, DPHY_CM); > + phy_write(phy, 0, DPHY_CN); > + phy_write(phy, 0, DPHY_CO); > + > + return 0; > +} > + > + > +static int mixel_dphy_power_on(struct phy *phy) > +{ > + struct mixel_dphy_priv *priv = phy_get_drvdata(phy); > + u32 locked; > + int ret; > + > + ret = clk_prepare_enable(priv->phy_ref_clk); > + if (ret < 0) > + return ret; > + > + phy_write(phy, PWR_ON, DPHY_PD_PLL); > + ret = regmap_read_poll_timeout(priv->regs, DPHY_LOCK, locked, > + locked, PLL_LOCK_SLEEP, > + PLL_LOCK_TIMEOUT); > + if (ret < 0) { > + dev_err(&phy->dev, "Could not get DPHY lock (%d)!\n", ret); > + goto clock_disable; > + } > + phy_write(phy, PWR_ON, DPHY_PD_DPHY); > + > + return 0; > +clock_disable: > + clk_disable_unprepare(priv->phy_ref_clk); > + return ret; > +} > + > +static int mixel_dphy_power_off(struct phy *phy) > +{ > + struct mixel_dphy_priv *priv = phy_get_drvdata(phy); > + > + phy_write(phy, PWR_OFF, DPHY_PD_PLL); > + phy_write(phy, PWR_OFF, DPHY_PD_DPHY); > + > + clk_disable_unprepare(priv->phy_ref_clk); > + > + return 0; > +} > + > + > +static const struct phy_ops mixel_dphy_phy_ops = { > + .init = mixel_dphy_init, > + .exit = mixel_dphy_exit, > + .power_on = mixel_dphy_power_on, > + .power_off = mixel_dphy_power_off, > + .configure = mixel_dphy_configure, > + .validate = mixel_dphy_validate, > + .owner = THIS_MODULE, > +}; > + > +static const struct of_device_id mixel_dphy_of_match[] = { > + { .compatible = "fsl,imx8mq-mipi-dphy", > + .data = &mixel_dphy_devdata[MIXEL_IMX8MQ] }, > + { /* sentinel */ }, > +}; > +MODULE_DEVICE_TABLE(of, mixel_dphy_of_match); > + > +static int mixel_dphy_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + struct phy_provider *phy_provider; > + struct mixel_dphy_priv *priv; > + struct resource *res; > + struct phy *phy; > + void __iomem *regs; > + > + if (!np) > + return -ENODEV; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->devdata = of_device_get_match_data(&pdev->dev); > + if (!priv->devdata) > + return -EINVAL; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + regs = devm_ioremap_resource(dev, res); > + if (IS_ERR(regs)) { > + dev_err(dev, "Couldn't map the DPHY registers\n"); > + return PTR_ERR(regs); > + } > + > + priv->regs = devm_regmap_init_mmio(&pdev->dev, regs, > + &mixel_dphy_regmap_config); > + if (IS_ERR(priv->regs)) { > + dev_err(dev, "Couldn't create the DPHY regmap\n"); > + return PTR_ERR(priv->regs); > + } > + > + priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref"); > + if (IS_ERR(priv->phy_ref_clk)) { > + dev_err(dev, "No phy_ref clock found\n"); > + return PTR_ERR(priv->phy_ref_clk); > + } > + dev_dbg(dev, "phy_ref clock rate: %lu\n", > + clk_get_rate(priv->phy_ref_clk)); > + > + dev_set_drvdata(dev, priv); > + > + phy = devm_phy_create(dev, np, &mixel_dphy_phy_ops); > + if (IS_ERR(phy)) { > + dev_err(dev, "Failed to create phy %ld\n", PTR_ERR(phy)); > + return PTR_ERR(phy); > + } > + phy_set_drvdata(phy, priv); > + > + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); > + > + return PTR_ERR_OR_ZERO(phy_provider); > +} > + > +static struct platform_driver mixel_dphy_driver = { > + .probe = mixel_dphy_probe, > + .driver = { > + .name = "mixel-mipi-dphy", > + .of_match_table = mixel_dphy_of_match, > + } > +}; > +module_platform_driver(mixel_dphy_driver); > + > +MODULE_AUTHOR("NXP Semiconductor"); > +MODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver"); > +MODULE_LICENSE("GPL v2"); >