From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7FD77C54F4C for ; Sat, 25 Jul 2026 04:51:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:Reply-To:List-Subscribe:List-Help: List-Post:List-Archive:List-Unsubscribe:List-Id:Cc:To:In-Reply-To:References: Message-Id:MIME-Version:Subject:Date:From:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=nFVNTAJP/pXtjiYblcU/jxvbgzhsy3NWhANEsil17iQ=; b=aAcp96GROn3mL4 qYWmT+71Yu/NCUG3pWowrbfJmle9X9dEcjtUrLjJ/InYH5d8mz3Hf5j9gJ5G8Kf75JClS5Wivb0sq Q+GHl0QUsvgiMcEEGfKlBxAzeUODOZhrBMao+2mww0KprrSJJjDH1NUPGiL+Y9GGzLIxa/nlpbc9l tScXKo920RaECA/hLEm5PK6rpdkGNCFFPj7LSdqauszKuMmdLQnClBGOMjgMFOqP7kGw67z71kWMl HNli+ic48wYWHiAVr03wT2lOMawFk5epDL7/Et2P/NRGdI22PAH7fHnzMiKiK0RnidcY36+bLoyRa k0s9EdjeI3/XlaWwHEPg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1wnUMB-000000000X6-49Te; Sat, 25 Jul 2026 04:51:28 +0000 Received: from sea.source.kernel.org ([2600:3c0a:e001:78e:0:1991:8:25]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1wnUM9-000000000TC-3eGG; Sat, 25 Jul 2026 04:51:25 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sea.source.kernel.org (Postfix) with ESMTP id BEC6545154; Sat, 25 Jul 2026 04:51:24 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPS id 6FA50C4AF15; Sat, 25 Jul 2026 04:51:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784955084; bh=hHU0+fPqo778U/j9OhQauMQ6TdfWYasuKZDposbCTYs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=pgr3LJOlyEPUFu1D0ZKn5Z5jDdcNj1iLJ/qMjh4FDTQ5Jhh4bHKy2UBU723LlHpNV 6UiDby/2UJeapf5cjKl8k4+bhYrvsfjF0KCdmOwOxNcCK5QirNN4oFkVJIHPxlzDqW fHyTEvZ/ge8F7tUvzPX0QAF6Y1BW31zouDWy7FBryFSK5b5RxI9CABlYSH1UAOxokj ZqSI470OuD/r6YFcZZwNNSQ4F+gQawE2swTThSVEJivo4ibs9LLMnqgEY3OHtNnTbT gjwjOVPQA0aOaHXZxbPof8wxcOK6bp9t50HmhQJew1VvTqcFGtBkNGQRw8YgB5bX61 2+SLZtgTkouOQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4CF27C5321A; Sat, 25 Jul 2026 04:51:24 +0000 (UTC) From: Cody Kang via B4 Relay Date: Sat, 25 Jul 2026 00:51:14 -0400 Subject: [PATCH RESEND 05/17] phy: spacemit: add Innosilicon DP TX PHY driver MIME-Version: 1.0 Message-Id: <20260725-k3-display-v1-5-6de34d80e86c@gmail.com> References: <20260725-k3-display-v1-0-6de34d80e86c@gmail.com> In-Reply-To: <20260725-k3-display-v1-0-6de34d80e86c@gmail.com> To: David Airlie , Simona Vetter , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Yixun Lan , Vinod Koul , Neil Armstrong , Haylen Chu , Michael Turquette , Stephen Boyd , Brian Masney , Philipp Zabel , Paul Walmsley , Palmer Dabbelt , Albert Ou , Alexandre Ghiti Cc: dri-devel@lists.freedesktop.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, spacemit@lists.linux.dev, linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org, linux-clk@vger.kernel.org, Cody Kang X-Mailer: b4 0.15.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784955080; l=31305; i=codykang.hk@gmail.com; s=codykang; h=from:subject:message-id; bh=HOLYzUwIscX6QiFa09LTVUU69+Md5E/F7luFw9IK3BI=; b=bj0HOCCFar4ayu5pnfecFOSKvWEgjwKUg9UBQSbTjBjsz8PS+sb5XmTrRkfJp/lla543HeOYB SJO8HnWkkioAufFKbR19U9Ia+kz0qrSB3t2oFAfo4z5FMks0QzyPd8l X-Developer-Key: i=codykang.hk@gmail.com; a=ed25519; pk=Osn6/cs9Su/mHHw4P19cmVc3v2FMQfiWahjsN0vEkuk= X-Endpoint-Received: by B4 Relay for codykang.hk@gmail.com/codykang with auth_id=895 X-Original-From: Cody Kang X-BeenThere: linux-phy@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Linux Phy Mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: codykang.hk@gmail.com Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "linux-phy" Errors-To: linux-phy-bounces+linux-phy=archiver.kernel.org@lists.infradead.org From: Cody Kang The PHY's registers are interleaved into its parent DP controller's MMIO window rather than a window of its own, so it probes as a child of the controller, carries no reg or reset, and reaches its registers through the controller's regmap. It registers its pixel PLL as a clock provider. That lets the display controller ask for a mode's pixel rate through clk_set_rate(), and it lets the APMU pixel-clock mux parent to the PHY through the clock framework. Without it the controller and PHY would have to know each other's registers. Signed-off-by: Cody Kang --- drivers/phy/spacemit/Kconfig | 13 + drivers/phy/spacemit/Makefile | 1 + drivers/phy/spacemit/phy-k3-inno-dp.c | 948 ++++++++++++++++++++++++++++++++++ 3 files changed, 962 insertions(+) diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig index 50b0005acf66..098f33998f73 100644 --- a/drivers/phy/spacemit/Kconfig +++ b/drivers/phy/spacemit/Kconfig @@ -23,3 +23,16 @@ config PHY_SPACEMIT_K1_USB2 help Enable this to support K1 USB 2.0 PHY driver. This driver takes care of enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. + +config PHY_SPACEMIT_K3_INNO_DP + tristate "SpacemiT K3 Innosilicon DisplayPort PHY driver" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + select GENERIC_PHY + select REGMAP + help + Enable support for the Innosilicon DisplayPort transmit PHY + integrated in the SpacemiT K3 SoC. The PHY shares its MMIO window + with its parent DP/eDP controller (drivers/gpu/drm/spacemit/) and + exposes lane / rate / vswing / pre-emphasis control through the + generic PHY framework. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile index a821a21d6142..960c5827f5e5 100644 --- a/drivers/phy/spacemit/Makefile +++ b/drivers/phy/spacemit/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o +obj-$(CONFIG_PHY_SPACEMIT_K3_INNO_DP) += phy-k3-inno-dp.o diff --git a/drivers/phy/spacemit/phy-k3-inno-dp.c b/drivers/phy/spacemit/phy-k3-inno-dp.c new file mode 100644 index 000000000000..9dd62edb7a5a --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-inno-dp.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SpacemiT K3 Innosilicon DisplayPort PHY driver. + * + * Copyright (C) 2025-2026 SpacemiT Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Offsets are into the parent controller's MMIO window */ +#define DPTX_PHY_CTRL 0x0100 +#define DPTX_PHY_CTRL_XMIT_EN GENMASK(20, 17) +#define DPTX_PHY_CTRL_NUM_LANES GENMASK(6, 5) +#define DPTX_PHY_CTRL_RATE GENMASK(1, 0) + +/* One 6-bit slot per lane; only the low 4 bits of each slot are used. */ +#define DPTX_PHY_LANE_TRIM 0x0104 +#define DPTX_PHY_LANE_BITS_PER 6 +#define DPTX_PHY_LANE_FIELD_MASK 0xf /* preemp[1:0] | vswing[1:0] */ +#define DPTX_PHY_LANE_VSWING_SHIFT 2 /* within the lane field */ + +/* MPLL is the link PLL (lane rate) */ +#define DPTX_ANA_MPLL 0x0180 +#define DPTX_ANA_MPLL_FBDIV_LBIT GENMASK(31, 24) +#define DPTX_ANA_MPLL_FBDIV_HBIT GENMASK(19, 16) +#define DPTX_ANA_MPLL_PREDIV GENMASK(13, 8) +#define DPTX_ANA_MPLL_LOCKED BIT(7) +#define DPTX_ANA_MPLL_DACPD BIT(5) +#define DPTX_ANA_MPLL_DSMPD BIT(4) +#define DPTX_ANA_MPLL_PD BIT(0) + +#define DPTX_ANA_MPLL_FRAC 0x0184 +#define DPTX_ANA_MPLL_FRAC_LBIT GENMASK(23, 16) +#define DPTX_ANA_MPLL_FRAC_MBIT GENMASK(15, 8) +#define DPTX_ANA_MPLL_FRAC_HBIT GENMASK(7, 0) + +#define DPTX_ANA_MPLL_DIV 0x0188 +#define DPTX_ANA_MPLL_VCOCLK_DIV8_EN BIT(16) +#define DPTX_ANA_MPLL_POSTDIVEN BIT(11) +#define DPTX_ANA_MPLL_POSTDIV GENMASK(10, 8) + +/* PREPLL is the pixel PLL exposed as a clock. */ +#define DPTX_ANA_PREPLL 0x0190 +#define DPTX_ANA_PREPLL_FBDIV2_LBIT GENMASK(31, 24) +#define DPTX_ANA_PREPLL_FBDIV2_HBIT GENMASK(19, 16) +#define DPTX_ANA_PREPLL_PREDIV GENMASK(13, 8) +#define DPTX_ANA_PREPLL_LOCKED BIT(7) +#define DPTX_ANA_PREPLL_DACPD BIT(5) +#define DPTX_ANA_PREPLL_DSMPD BIT(4) +#define DPTX_ANA_PREPLL_PCLK_NORMAL BIT(1) +#define DPTX_ANA_PREPLL_PD BIT(0) + +#define DPTX_ANA_PREPLL_DIV 0x0194 +#define DPTX_ANA_PREPLL_PRECLK_DIVM GENMASK(17, 16) +#define DPTX_ANA_PREPLL_PRECLK_DIVAUX GENMASK(12, 8) + +#define DPTX_ANA_PREPLL_CTRL 0x0198 +#define DPTX_ANA_PREPLL_PCLKDIV5_EN BIT(31) +#define DPTX_ANA_PREPLL_PCLK_DIVAUX GENMASK(28, 24) +#define DPTX_ANA_PREPLL_LOCK_BYPEN BIT(17) +#define DPTX_ANA_PREPLL_HDMI_EN BIT(9) +#define DPTX_ANA_PREPLL_DP_EN BIT(8) + +#define DPTX_ANA_PREPLL_FRAC 0x019c +#define DPTX_ANA_PREPLL_FRAC2_LBIT GENMASK(23, 16) +#define DPTX_ANA_PREPLL_FRAC2_MBIT GENMASK(15, 8) +#define DPTX_ANA_PREPLL_FRAC2_HBIT GENMASK(7, 0) + +#define DPTX_ANA_TX_CTRL 0x01a0 +#define DPTX_ANA_MPLL_CLKDIV_16M GENMASK(13, 8) + +/* PRECLK_DIVM register value -> divide factor */ +static const u8 k3_inno_dp_divm_factors[] = { 1, 2, 3, 5 }; + +#define DPTX_VCO_MIN_KHZ 1000000 +#define DPTX_VCO_MAX_KHZ 3000000 +#define DPTX_PLL_FRAC_MOD (1 << 24) +#define DPTX_PLL_ERR_TOLERANCE 10 + +struct k3_inno_dp_phy { + struct device *dev; + struct regmap *regmap; + struct phy *phy; + u8 lanes; /* tracks last set_lanes */ + + struct clk_hw pxclk_hw; /* the PREPLL's pixel clock */ + unsigned long pxclk_rate; /* last programmed PREPLL rate */ + u32 ref_clk_khz; /* PLL reference */ +}; + +static int k3_inno_dp_phy_power_on(struct phy *phy) +{ + struct k3_inno_dp_phy *p = phy_get_drvdata(phy); + u32 lane_en; + u32 val; + int ret; + + switch (p->lanes) { + case 1: + lane_en = 0x1; + break; + case 2: + lane_en = 0x3; + break; + case 4: + default: + lane_en = 0xf; + break; + } + + ret = regmap_write_bits(p->regmap, DPTX_ANA_MPLL, DPTX_ANA_MPLL_PD, 0); + if (ret) + return ret; + ret = regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, + DPTX_ANA_PREPLL_PD, 0); + if (ret) + return ret; + usleep_range(2000, 4000); + + ret = regmap_write_bits(p->regmap, DPTX_PHY_CTRL, + DPTX_PHY_CTRL_XMIT_EN, + FIELD_PREP(DPTX_PHY_CTRL_XMIT_EN, lane_en)); + if (ret) + return ret; + usleep_range(2000, 4000); + + ret = regmap_read_poll_timeout(p->regmap, DPTX_ANA_MPLL, val, + val & DPTX_ANA_MPLL_LOCKED, + 2000, 10 * 1000); + if (ret) { + dev_err(p->dev, "DP PHY core PLL lock timed out\n"); + return ret; + } + + ret = regmap_read_poll_timeout(p->regmap, DPTX_ANA_PREPLL, val, + val & DPTX_ANA_PREPLL_LOCKED, + 2000, 10 * 1000); + if (ret) + dev_err(p->dev, "DP PHY pixel PLL lock timed out\n"); + + return ret; +} + +static int k3_inno_dp_phy_power_off(struct phy *phy) +{ + struct k3_inno_dp_phy *p = phy_get_drvdata(phy); + + regmap_write_bits(p->regmap, DPTX_PHY_CTRL, DPTX_PHY_CTRL_XMIT_EN, 0); + usleep_range(2000, 4000); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_PD, + DPTX_ANA_PREPLL_PD); + usleep_range(2000, 4000); + + return 0; +} + +static int k3_inno_dp_phy_set_lanes(struct k3_inno_dp_phy *p, u8 lanes) +{ + u32 val; + int ret; + + switch (lanes) { + case 1: + val = 0; + break; + case 2: + val = 1; + break; + case 4: + val = 2; + break; + default: + return -EINVAL; + } + + ret = regmap_write_bits(p->regmap, DPTX_PHY_CTRL, + DPTX_PHY_CTRL_NUM_LANES, + FIELD_PREP(DPTX_PHY_CTRL_NUM_LANES, val)); + if (ret) + return ret; + + p->lanes = lanes; + return 0; +} + +struct k3_inno_dp_mpll_cfg { + unsigned int link_rate; /* Mbps per lane */ + u8 prediv; + u16 fbdiv; + u32 frac; + u8 postdiv; + u8 postdiv_en; + u8 frac_pd; + u8 vcoclk_div8_en; + u8 clk_16mdiv; +}; + +static const struct k3_inno_dp_mpll_cfg k3_inno_dp_mpll_cfgs[] = { + { + .link_rate = 1620, + .prediv = 0x02, + .fbdiv = 0x87, + .frac = 0, + .postdiv = 0, + .postdiv_en = 1, + .frac_pd = 0x3, + .vcoclk_div8_en = 1, + .clk_16mdiv = 12, + }, { + .link_rate = 2700, + .prediv = 0x02, + .fbdiv = 0xe1, + .frac = 0, + .postdiv = 0, + .postdiv_en = 1, + .frac_pd = 0x3, + .vcoclk_div8_en = 1, + .clk_16mdiv = 21, + }, { + .link_rate = 5400, + .prediv = 0x02, + .fbdiv = 0xe1, + .frac = 0, + .postdiv = 0, + .postdiv_en = 0, + .frac_pd = 0x3, + .vcoclk_div8_en = 0, + .clk_16mdiv = 42, + }, +}; + +static void k3_inno_dp_phy_program_mpll(struct k3_inno_dp_phy *p, + const struct k3_inno_dp_mpll_cfg *cfg) +{ + struct regmap *rm = p->regmap; + + /* + * Shared with an unwired HDMI path: select DP first, otherwise the + * PLLs still lock but nothing reaches the lanes and AUX times out. + */ + regmap_write_bits(rm, DPTX_ANA_PREPLL_CTRL, DPTX_ANA_PREPLL_DP_EN, + DPTX_ANA_PREPLL_DP_EN); + regmap_write_bits(rm, DPTX_ANA_PREPLL_CTRL, DPTX_ANA_PREPLL_HDMI_EN, 0); + + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_PD, + FIELD_PREP(DPTX_ANA_MPLL_PD, 1)); + usleep_range(2000, 4000); + + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_PREDIV, + FIELD_PREP(DPTX_ANA_MPLL_PREDIV, cfg->prediv)); + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_FBDIV_LBIT, + FIELD_PREP(DPTX_ANA_MPLL_FBDIV_LBIT, cfg->fbdiv & 0xff)); + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_FBDIV_HBIT, + FIELD_PREP(DPTX_ANA_MPLL_FBDIV_HBIT, (cfg->fbdiv >> 8) & 0xf)); + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_DACPD, + FIELD_PREP(DPTX_ANA_MPLL_DACPD, (cfg->frac_pd >> 1) & 0x1)); + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_DSMPD, + FIELD_PREP(DPTX_ANA_MPLL_DSMPD, cfg->frac_pd & 0x1)); + + regmap_write_bits(rm, DPTX_ANA_MPLL_FRAC, DPTX_ANA_MPLL_FRAC_LBIT, + FIELD_PREP(DPTX_ANA_MPLL_FRAC_LBIT, cfg->frac & 0xff)); + regmap_write_bits(rm, DPTX_ANA_MPLL_FRAC, DPTX_ANA_MPLL_FRAC_MBIT, + FIELD_PREP(DPTX_ANA_MPLL_FRAC_MBIT, (cfg->frac >> 8) & 0xff)); + regmap_write_bits(rm, DPTX_ANA_MPLL_FRAC, DPTX_ANA_MPLL_FRAC_HBIT, + FIELD_PREP(DPTX_ANA_MPLL_FRAC_HBIT, (cfg->frac >> 16) & 0xff)); + + regmap_write_bits(rm, DPTX_ANA_MPLL_DIV, DPTX_ANA_MPLL_POSTDIV, + FIELD_PREP(DPTX_ANA_MPLL_POSTDIV, cfg->postdiv)); + regmap_write_bits(rm, DPTX_ANA_MPLL_DIV, DPTX_ANA_MPLL_POSTDIVEN, + FIELD_PREP(DPTX_ANA_MPLL_POSTDIVEN, cfg->postdiv_en)); + regmap_write_bits(rm, DPTX_ANA_MPLL_DIV, DPTX_ANA_MPLL_VCOCLK_DIV8_EN, + FIELD_PREP(DPTX_ANA_MPLL_VCOCLK_DIV8_EN, cfg->vcoclk_div8_en)); + + regmap_write_bits(rm, DPTX_ANA_TX_CTRL, DPTX_ANA_MPLL_CLKDIV_16M, + FIELD_PREP(DPTX_ANA_MPLL_CLKDIV_16M, cfg->clk_16mdiv)); + + regmap_write_bits(rm, DPTX_ANA_PREPLL_CTRL, DPTX_ANA_PREPLL_LOCK_BYPEN, + FIELD_PREP(DPTX_ANA_PREPLL_LOCK_BYPEN, 1)); + + regmap_write_bits(rm, DPTX_ANA_MPLL, DPTX_ANA_MPLL_PD, + FIELD_PREP(DPTX_ANA_MPLL_PD, 0)); + usleep_range(2000, 4000); +} + +static int k3_inno_dp_phy_set_rate(struct k3_inno_dp_phy *p, + unsigned int link_rate) +{ + const struct k3_inno_dp_mpll_cfg *cfg = NULL; + unsigned int i; + u32 sel; + + switch (link_rate) { + case 1620: + sel = 0; + break; + case 2700: + sel = 1; + break; + case 5400: + sel = 2; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(k3_inno_dp_mpll_cfgs); i++) { + if (k3_inno_dp_mpll_cfgs[i].link_rate == link_rate) { + cfg = &k3_inno_dp_mpll_cfgs[i]; + break; + } + } + if (!cfg) + return -EINVAL; + + k3_inno_dp_phy_program_mpll(p, cfg); + + return regmap_write_bits(p->regmap, DPTX_PHY_CTRL, + DPTX_PHY_CTRL_RATE, + FIELD_PREP(DPTX_PHY_CTRL_RATE, sel)); +} + +static int k3_inno_dp_phy_set_voltages(struct k3_inno_dp_phy *p, + struct phy_configure_opts_dp *opts) +{ + u8 lane; + int ret; + + for (lane = 0; lane < opts->lanes; lane++) { + unsigned int shift = lane * DPTX_PHY_LANE_BITS_PER; + u32 mask = DPTX_PHY_LANE_FIELD_MASK << shift; + u32 val = (((opts->voltage[lane] & 0x3) << DPTX_PHY_LANE_VSWING_SHIFT) | + (opts->pre[lane] & 0x3)) << shift; + + ret = regmap_write_bits(p->regmap, DPTX_PHY_LANE_TRIM, mask, + val); + if (ret) + return ret; + } + + return 0; +} + +static int k3_inno_dp_phy_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct k3_inno_dp_phy *p = phy_get_drvdata(phy); + int ret; + + if (opts->dp.set_lanes) { + ret = k3_inno_dp_phy_set_lanes(p, opts->dp.lanes); + if (ret) + return ret; + } + + if (opts->dp.set_rate) { + ret = k3_inno_dp_phy_set_rate(p, opts->dp.link_rate); + if (ret) + return ret; + } + + if (opts->dp.set_voltages) { + ret = k3_inno_dp_phy_set_voltages(p, &opts->dp); + if (ret) + return ret; + } + + return 0; +} + +static u32 k3_inno_dp_div64(u64 *n, u32 base) +{ + return do_div(*n, base); +} + +static int k3_inno_dp_better_cfg(bool new_valid, bool new_is_int, u8 new_pre, u32 new_vco, + bool best_valid, bool best_is_int, u8 best_pre, u32 best_vco) +{ + if (!new_valid) + return 0; + if (!best_valid) + return 1; + + if (new_is_int && !best_is_int) + return 1; + if (!new_is_int && best_is_int) + return 0; + + if (new_pre < best_pre) + return 1; + if (new_pre > best_pre) + return 0; + + if (new_vco > best_vco) + return 1; + + return 0; +} + +struct k3_inno_dp_prepll_cfg { + u32 target_pclk_khz; + u32 vco_freq_khz; + u8 prediv; + u16 fbdiv; + u32 frac_pd; + u32 frac; + u8 div5_en; + u8 divm; + u8 divaux; + u8 divp; + u32 actual_pclk_khz; + bool valid; +}; + +static const struct k3_inno_dp_prepll_cfg k3_inno_dp_prepll_cfgs[] = { + { 614400, 2460000, 0x05, 512, 0x3, 0x0, 0x0, 0x1, 0x01, 0x1, 614400, true }, + { 594000, 2376000, 0x01, 99, 0x3, 0x0, 0x0, 0x1, 0x01, 0x1, 594000, true }, + { 551040, 2760000, 0x05, 574, 0x3, 0x0, 0x1, 0x0, 0x00, 0x0, 551040, true }, + { 533250, 2130000, 0x08, 711, 0x3, 0x0, 0x0, 0x1, 0x01, 0x1, 533250, true }, + { 443250, 1770000, 0x08, 591, 0x3, 0x0, 0x0, 0x1, 0x01, 0x1, 443250, true }, + { 375000, 3000000, 0x01, 125, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 375000, true }, + { 372000, 2980000, 0x01, 124, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 372000, true }, + { 348500, 2790000, 0x06, 697, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 348500, true }, + { 307200, 1540000, 0x01, 64, 0x3, 0x0, 0x1, 0x0, 0x00, 0x0, 307200, true }, + { 297000, 2376000, 0x01, 99, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 297000, true }, + { 280000, 1680000, 0x01, 70, 0x3, 0x0, 0x0, 0x2, 0x01, 0x1, 280000, true }, + { 277440, 2770000, 0x05, 578, 0x3, 0x0, 0x0, 0x3, 0x01, 0x1, 277440, true }, + { 245760, 2457600, 0x05, 512, 0x3, 0x0, 0x0, 0x3, 0x01, 0x1, 245760, true }, + { 241500, 1932000, 0x02, 161, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 241500, true }, + { 236000, 2830000, 0x01, 118, 0x3, 0x0, 0x0, 0x0, 0x06, 0x1, 236000, true }, + { 204800, 2048000, 0x03, 256, 0x3, 0x0, 0x0, 0x3, 0x01, 0x1, 204800, true }, + { 193250, 2320000, 0x08, 773, 0x3, 0x0, 0x0, 0x0, 0x06, 0x1, 193250, true }, + { 189000, 1510000, 0x01, 63, 0x3, 0x0, 0x0, 0x0, 0x04, 0x1, 189000, true }, + { 187500, 3000000, 0x01, 125, 0x3, 0x0, 0x0, 0x0, 0x08, 0x1, 187500, true }, + { 162000, 2592000, 0x01, 108, 0x3, 0x0, 0x0, 0x0, 0x08, 0x1, 162000, true }, + { 156000, 2810000, 0x01, 117, 0x3, 0x0, 0x0, 0x0, 0x09, 0x1, 156000, true }, + { 150000, 3000000, 0x01, 125, 0x3, 0x0, 0x0, 0x0, 0x0a, 0x1, 150000, true }, + { 148500, 2376000, 0x01, 99, 0x3, 0x0, 0x0, 0x0, 0x08, 0x1, 148500, true }, + { 146000, 1750000, 0x01, 73, 0x3, 0x0, 0x0, 0x0, 0x06, 0x1, 146000, true }, + { 142860, 2860000, 0x14, 2381, 0x3, 0x0, 0x0, 0x0, 0x0a, 0x1, 142860, true }, + { 140000, 2520000, 0x01, 105, 0x3, 0x0, 0x0, 0x0, 0x09, 0x1, 140000, true }, + { 138500, 2220000, 0x03, 277, 0x3, 0x0, 0x0, 0x0, 0x08, 0x1, 138500, true }, + { 122000, 2930000, 0x01, 122, 0x3, 0x0, 0x0, 0x0, 0x0c, 0x1, 122000, true }, + { 121750, 2920000, 0x04, 487, 0x3, 0x0, 0x0, 0x0, 0x0c, 0x1, 121750, true }, + { 108000, 2810000, 0x01, 117, 0x3, 0x0, 0x0, 0x0, 0x0d, 0x1, 108000, true }, + { 106500, 1700000, 0x01, 71, 0x3, 0x0, 0x0, 0x0, 0x08, 0x1, 106500, true }, + { 83500, 2000000, 0x02, 167, 0x3, 0x0, 0x0, 0x0, 0x0c, 0x1, 83500, true }, + { 79500, 2540000, 0x01, 106, 0x3, 0x0, 0x0, 0x0, 0x10, 0x1, 79500, true }, + { 75000, 3000000, 0x01, 125, 0x3, 0x0, 0x0, 0x0, 0x14, 0x1, 75000, true }, + { 74250, 2376000, 0x01, 99, 0x3, 0x0, 0x0, 0x0, 0x10, 0x1, 74250, true }, + { 65000, 1560000, 0x01, 65, 0x3, 0x0, 0x0, 0x0, 0x0c, 0x1, 65000, true }, + { 40000, 2880000, 0x01, 120, 0x3, 0x0, 0x0, 0x0, 0x12, 0x2, 40000, true }, + { 27000, 2810000, 0x01, 117, 0x3, 0x0, 0x0, 0x0, 0x1a, 0x2, 27000, true }, + { 25600, 2300000, 0x01, 96, 0x3, 0x0, 0x0, 0x0, 0x0f, 0x3, 25600, true }, + { 25200, 2520000, 0x01, 105, 0x3, 0x0, 0x0, 0x0, 0x19, 0x2, 25200, true }, +}; + +static u64 k3_inno_dp_abs_diff(u64 a, u64 b) +{ + return (a > b) ? (a - b) : (b - a); +} + +static u32 k3_inno_dp_pll_rate_khz(u8 pre, u16 fb, u32 frac, + u32 ref_clk_khz, u32 total_div) +{ + u64 vco_hz; + u64 ref_hz = (u64)ref_clk_khz * 1000; + u64 int_part; + u64 frac_part; + + /* Integer part: (Ref * FB) / Pre */ + int_part = ref_hz * fb; + int_part += (pre / 2); + k3_inno_dp_div64(&int_part, pre); + + /* Fractional part: (Ref * Frac) / (Pre * 2^24) */ + frac_part = ref_hz * frac; + + frac_part += (pre / 2); + k3_inno_dp_div64(&frac_part, pre); + + frac_part += (DPTX_PLL_FRAC_MOD / 2); + k3_inno_dp_div64(&frac_part, DPTX_PLL_FRAC_MOD); + + vco_hz = int_part + frac_part; + + /* Final Rate = VCO / total_div */ + vco_hz += (total_div / 2); + k3_inno_dp_div64(&vco_hz, total_div); + + vco_hz += 500; + k3_inno_dp_div64(&vco_hz, 1000); + + return (u32)vco_hz; +} + +static int k3_inno_dp_prepll_div_total(const struct k3_inno_dp_prepll_cfg *cfg, + u32 *div_total) +{ + if (!cfg || !div_total || !cfg->valid) + return -EINVAL; + + /* div5_en fixes div_total at 5; divp is unused on this path */ + if (cfg->div5_en) { + *div_total = 5; + return 0; + } + + if (!cfg->divp) + return -EINVAL; + + if (cfg->divaux == 1) { + if (cfg->divm >= ARRAY_SIZE(k3_inno_dp_divm_factors)) + return -EINVAL; + + *div_total = 2 * k3_inno_dp_divm_factors[cfg->divm] * cfg->divp; + return 0; + } + + if (cfg->divaux < 2) + return -EINVAL; + + *div_total = 2 * cfg->divaux * cfg->divp; + return 0; +} + +static bool k3_inno_dp_prepll_cfg_matches(const struct k3_inno_dp_prepll_cfg *cfg, + u32 target_pclk_khz, + u32 ref_clk_khz) +{ + u32 div_total; + u32 actual_pclk_khz; + + if (k3_inno_dp_prepll_div_total(cfg, &div_total)) + return false; + + actual_pclk_khz = k3_inno_dp_pll_rate_khz(cfg->prediv, cfg->fbdiv, + cfg->frac, ref_clk_khz, + div_total); + + if (cfg->actual_pclk_khz && + k3_inno_dp_abs_diff(actual_pclk_khz, + cfg->actual_pclk_khz) > DPTX_PLL_ERR_TOLERANCE) + return false; + + return k3_inno_dp_abs_diff(actual_pclk_khz, + target_pclk_khz) <= DPTX_PLL_ERR_TOLERANCE; +} + +static const struct k3_inno_dp_prepll_cfg *k3_inno_dp_find_prepll_cfg(u32 pclk_khz, + u32 ref_clk_khz) +{ + const struct k3_inno_dp_prepll_cfg *best_match = NULL; + u32 min_diff = 0xFFFFFFFF; + int num_configs = ARRAY_SIZE(k3_inno_dp_prepll_cfgs); + + for (int i = 0; i < num_configs; i++) { + u32 current_target = k3_inno_dp_prepll_cfgs[i].target_pclk_khz; + u32 diff = k3_inno_dp_abs_diff(pclk_khz, current_target); + + if (diff == 0 && k3_inno_dp_prepll_cfg_matches(&k3_inno_dp_prepll_cfgs[i], + pclk_khz, ref_clk_khz)) { + return &k3_inno_dp_prepll_cfgs[i]; + } + + if ((pclk_khz / 100) == (current_target / 100) && + k3_inno_dp_prepll_cfg_matches(&k3_inno_dp_prepll_cfgs[i], + pclk_khz, ref_clk_khz)) { + return &k3_inno_dp_prepll_cfgs[i]; + } + + if (diff < min_diff && diff < 500 && + k3_inno_dp_prepll_cfg_matches(&k3_inno_dp_prepll_cfgs[i], + pclk_khz, ref_clk_khz)) { + min_diff = diff; + best_match = &k3_inno_dp_prepll_cfgs[i]; + } + } + + return best_match; +} + +static int k3_inno_dp_solve_frac(u32 target_vco_khz, u32 ref_clk_khz, + u8 *best_pre, u16 *best_fb, u32 *best_frac) +{ + u64 min_err = ~0ULL; + int found = 0; + bool best_is_int = false; + int pre; + + for (pre = 1; pre <= 63; pre++) { + u64 ref_clk_hz = (u64)ref_clk_khz * 1000; + u64 target_vco_hz = (u64)target_vco_khz * 1000; + + /* + * Calculate required multiplier: Mult = (TargetVCO * Pre) / Ref + */ + u64 num = target_vco_hz * pre; + u64 den = ref_clk_hz; + u64 remainder; + u64 fb_val; + u64 frac_val; + u64 actual_vco; + u64 diff; + bool current_is_int; + + fb_val = num; + remainder = k3_inno_dp_div64(&fb_val, (u32)den); + + if (fb_val > 4095) + continue; + + /* Frac = (Remainder * 2^24 + Ref/2) / Ref */ + frac_val = remainder * DPTX_PLL_FRAC_MOD; + frac_val += (den / 2); + k3_inno_dp_div64(&frac_val, (u32)den); + + if (frac_val > 0xFFFFFF) + frac_val = 0xFFFFFF; + + /* VCO = (Ref * FB / Pre) + (Ref * Frac / (Pre * 2^24)) */ + { + u64 vco_int, vco_frac; + + vco_int = ref_clk_hz * fb_val; + k3_inno_dp_div64(&vco_int, pre); + vco_frac = ref_clk_hz * frac_val; + k3_inno_dp_div64(&vco_frac, pre); + k3_inno_dp_div64(&vco_frac, DPTX_PLL_FRAC_MOD); + + actual_vco = vco_int + vco_frac; + } + + diff = k3_inno_dp_abs_diff(actual_vco, target_vco_hz); + current_is_int = (frac_val == 0); + + if (diff < min_err) { + min_err = diff; + *best_pre = pre; + *best_fb = (u16)fb_val; + *best_frac = (u32)frac_val; + best_is_int = current_is_int; + found = 1; + } else if (diff == min_err) { + if (current_is_int && !best_is_int) { + *best_pre = pre; + *best_fb = (u16)fb_val; + *best_frac = (u32)frac_val; + best_is_int = true; + found = 1; + } + } + } + + return found ? 0 : -EINVAL; +} + +/* divaux must be 1 for the DivM path and > 1 for the DivAux path. */ +static void k3_inno_dp_try_prepll(u32 target_pclk_khz, u32 ref_clk_khz, + u32 div_total, u8 div5_en, u8 divaux, + u8 divm, u8 divp, + struct k3_inno_dp_prepll_cfg *best) +{ + struct k3_inno_dp_prepll_cfg curr = {0}; + u32 target_vco = target_pclk_khz * div_total; + u32 actual_pclk; + u32 frac; + u16 fb; + u8 pre; + + if (target_vco < DPTX_VCO_MIN_KHZ || target_vco > DPTX_VCO_MAX_KHZ) + return; + + if (k3_inno_dp_solve_frac(target_vco, ref_clk_khz, &pre, &fb, &frac)) + return; + + actual_pclk = k3_inno_dp_pll_rate_khz(pre, fb, frac, ref_clk_khz, + div_total); + if (k3_inno_dp_abs_diff(actual_pclk, target_pclk_khz) > DPTX_PLL_ERR_TOLERANCE) + return; + + curr.valid = true; + curr.vco_freq_khz = target_vco; + curr.actual_pclk_khz = actual_pclk; + curr.prediv = pre; + curr.fbdiv = fb; + curr.frac = frac; + curr.frac_pd = (frac == 0) ? 3 : 0; + curr.div5_en = div5_en; + curr.divaux = divaux; + curr.divm = divm; + curr.divp = divp; + + if (k3_inno_dp_better_cfg(curr.valid, curr.frac == 0, curr.prediv, + curr.vco_freq_khz, best->valid, + best->frac == 0, best->prediv, + best->vco_freq_khz)) + *best = curr; +} + +static int k3_inno_dp_solve_prepll(u32 target_pclk_khz, u32 ref_clk_khz, + struct k3_inno_dp_prepll_cfg *cfg) +{ + struct k3_inno_dp_prepll_cfg best = {0}; + int pclk_div; + int i; + + /* Strategy 1: Div5 path (VCO = PCLK * 5) */ + k3_inno_dp_try_prepll(target_pclk_khz, ref_clk_khz, 5, 1, 0, 0, 0, + &best); + + for (pclk_div = 1; pclk_div <= 31; pclk_div++) { + /* Strategy 2: DivM path */ + for (i = 0; i < ARRAY_SIZE(k3_inno_dp_divm_factors); i++) + k3_inno_dp_try_prepll(target_pclk_khz, ref_clk_khz, + 2 * k3_inno_dp_divm_factors[i] * pclk_div, 0, 1, + i, pclk_div, &best); + + /* Strategy 3: DivAux path */ + for (i = 2; i <= 31; i++) + k3_inno_dp_try_prepll(target_pclk_khz, ref_clk_khz, + 2 * i * pclk_div, + 0, i, 0, pclk_div, &best); + } + + if (!best.valid) + return -EINVAL; + + *cfg = best; + return 0; +} + +static void k3_inno_dp_program_prepll(struct k3_inno_dp_phy *p, + const struct k3_inno_dp_prepll_cfg *cfg) +{ + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_PD, + FIELD_PREP(DPTX_ANA_PREPLL_PD, 1)); + usleep_range(2000, 4000); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_PREDIV, + FIELD_PREP(DPTX_ANA_PREPLL_PREDIV, cfg->prediv)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, + DPTX_ANA_PREPLL_FBDIV2_LBIT, + FIELD_PREP(DPTX_ANA_PREPLL_FBDIV2_LBIT, cfg->fbdiv & 0xFF)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, + DPTX_ANA_PREPLL_FBDIV2_HBIT, + FIELD_PREP(DPTX_ANA_PREPLL_FBDIV2_HBIT, (cfg->fbdiv >> 8) & 0xF)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_DACPD, + FIELD_PREP(DPTX_ANA_PREPLL_DACPD, (cfg->frac_pd >> 1) & 0x1)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_DSMPD, + FIELD_PREP(DPTX_ANA_PREPLL_DSMPD, cfg->frac_pd & 0x1)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_FRAC, + DPTX_ANA_PREPLL_FRAC2_LBIT, + FIELD_PREP(DPTX_ANA_PREPLL_FRAC2_LBIT, cfg->frac & 0xFF)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_FRAC, + DPTX_ANA_PREPLL_FRAC2_MBIT, + FIELD_PREP(DPTX_ANA_PREPLL_FRAC2_MBIT, (cfg->frac >> 8) & 0xFF)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_FRAC, + DPTX_ANA_PREPLL_FRAC2_HBIT, + FIELD_PREP(DPTX_ANA_PREPLL_FRAC2_HBIT, (cfg->frac >> 16) & 0xFF)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_DIV, + DPTX_ANA_PREPLL_PRECLK_DIVM, + FIELD_PREP(DPTX_ANA_PREPLL_PRECLK_DIVM, cfg->divm)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_DIV, + DPTX_ANA_PREPLL_PRECLK_DIVAUX, + FIELD_PREP(DPTX_ANA_PREPLL_PRECLK_DIVAUX, cfg->divaux)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_CTRL, + DPTX_ANA_PREPLL_PCLKDIV5_EN, + FIELD_PREP(DPTX_ANA_PREPLL_PCLKDIV5_EN, cfg->div5_en)); + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL_CTRL, + DPTX_ANA_PREPLL_PCLK_DIVAUX, + FIELD_PREP(DPTX_ANA_PREPLL_PCLK_DIVAUX, cfg->divp)); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, + DPTX_ANA_PREPLL_PCLK_NORMAL, + FIELD_PREP(DPTX_ANA_PREPLL_PCLK_NORMAL, 1)); + usleep_range(2000, 4000); + + regmap_write_bits(p->regmap, DPTX_ANA_PREPLL, DPTX_ANA_PREPLL_PD, + FIELD_PREP(DPTX_ANA_PREPLL_PD, 0)); + usleep_range(2000, 4000); +} + +#define to_k3_inno_dp_phy_clk(_hw) \ + container_of(_hw, struct k3_inno_dp_phy, pxclk_hw) + +static unsigned long k3_inno_dp_pxclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct k3_inno_dp_phy *p = to_k3_inno_dp_phy_clk(hw); + + return p->pxclk_rate; +} + +static int k3_inno_dp_pxclk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct k3_inno_dp_phy *p = to_k3_inno_dp_phy_clk(hw); + const struct k3_inno_dp_prepll_cfg *cfg; + struct k3_inno_dp_prepll_cfg solved; + u32 khz = div_u64(req->rate, 1000); + int ret; + + cfg = k3_inno_dp_find_prepll_cfg(khz, p->ref_clk_khz); + if (cfg) { + req->rate = (unsigned long)cfg->actual_pclk_khz * 1000; + return 0; + } + + ret = k3_inno_dp_solve_prepll(khz, p->ref_clk_khz, &solved); + if (ret) + return ret; + + req->rate = (unsigned long)solved.actual_pclk_khz * 1000; + + return 0; +} + +static int k3_inno_dp_pxclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct k3_inno_dp_phy *p = to_k3_inno_dp_phy_clk(hw); + const struct k3_inno_dp_prepll_cfg *cfg; + struct k3_inno_dp_prepll_cfg solved; + u32 khz = div_u64(rate, 1000); + int ret; + + cfg = k3_inno_dp_find_prepll_cfg(khz, p->ref_clk_khz); + if (!cfg) { + ret = k3_inno_dp_solve_prepll(khz, p->ref_clk_khz, &solved); + if (ret) { + dev_err(p->dev, "no PLL setting for %lu Hz\n", rate); + return ret; + } + cfg = &solved; + } + + k3_inno_dp_program_prepll(p, cfg); + p->pxclk_rate = (unsigned long)cfg->actual_pclk_khz * 1000; + + return 0; +} + +static const struct clk_ops k3_inno_dp_pxclk_ops = { + .recalc_rate = k3_inno_dp_pxclk_recalc_rate, + .determine_rate = k3_inno_dp_pxclk_determine_rate, + .set_rate = k3_inno_dp_pxclk_set_rate, +}; + +static const struct phy_ops k3_inno_dp_phy_ops = { + .power_on = k3_inno_dp_phy_power_on, + .power_off = k3_inno_dp_phy_power_off, + .configure = k3_inno_dp_phy_configure, + .owner = THIS_MODULE, +}; + +static int k3_inno_dp_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct k3_inno_dp_phy *p; + struct phy_provider *provider; + struct clk_init_data init = {}; + int ret; + + p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->dev = dev; + + p->regmap = dev_get_regmap(dev->parent, "dp"); + if (!p->regmap) + return dev_err_probe(dev, -ENODEV, + "parent DP regmap not available\n"); + + p->phy = devm_phy_create(dev, dev->of_node, &k3_inno_dp_phy_ops); + if (IS_ERR(p->phy)) + return dev_err_probe(dev, PTR_ERR(p->phy), + "failed to create DP PHY\n"); + + phy_set_drvdata(p->phy, p); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "failed to register PHY provider\n"); + + /* PHY takes a 24 MHz reference clock */ + p->ref_clk_khz = 24000; + + /* The APMU pixel-clock mux references this clock by name. */ + of_property_read_string(dev->of_node, "clock-output-names", &init.name); + if (!init.name) + init.name = dev_name(dev); + init.ops = &k3_inno_dp_pxclk_ops; + init.parent_names = NULL; + init.num_parents = 0; + init.flags = 0; + p->pxclk_hw.init = &init; + + ret = devm_clk_hw_register(dev, &p->pxclk_hw); + if (ret) + return dev_err_probe(dev, ret, + "failed to register pixel clock\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &p->pxclk_hw); + if (ret) + return dev_err_probe(dev, ret, + "failed to add pixel clock provider\n"); + + return 0; +} + +static const struct of_device_id k3_inno_dp_phy_of_match[] = { + { .compatible = "spacemit,k3-inno-dp-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, k3_inno_dp_phy_of_match); + +static struct platform_driver k3_inno_dp_phy_driver = { + .probe = k3_inno_dp_phy_probe, + .driver = { + .name = "spacemit-k3-inno-dp-phy", + .of_match_table = k3_inno_dp_phy_of_match, + }, +}; +module_platform_driver(k3_inno_dp_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 Innosilicon DisplayPort PHY driver"); +MODULE_AUTHOR("Cody Kang "); +MODULE_LICENSE("GPL"); -- 2.43.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy