From nobody Fri Sep 20 08:58:42 2024 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 83780C4332F for ; Tue, 12 Dec 2023 03:47:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345907AbjLLDra (ORCPT ); Mon, 11 Dec 2023 22:47:30 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39274 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229473AbjLLDr3 (ORCPT ); Mon, 11 Dec 2023 22:47:29 -0500 Received: from pidgin.makrotopia.org (pidgin.makrotopia.org [185.142.180.65]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CC671DC; Mon, 11 Dec 2023 19:47:34 -0800 (PST) Received: from local by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.96.2) (envelope-from ) id 1rCtjt-0002rO-21; Tue, 12 Dec 2023 03:47:22 +0000 Date: Tue, 12 Dec 2023 03:47:18 +0000 From: Daniel Golle To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chunfeng Yun , Vinod Koul , Kishon Vijay Abraham I , Felix Fietkau , John Crispin , Sean Wang , Mark Lee , Lorenzo Bianconi , Matthias Brugger , AngeloGioacchino Del Regno , Andrew Lunn , Heiner Kallweit , Russell King , Alexander Couzens , Daniel Golle , Qingfang Deng , SkyLake Huang , Philipp Zabel , netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mediatek@lists.infradead.org, linux-phy@lists.infradead.org Subject: [RFC PATCH net-next v3 3/8] net: pcs: pcs-mtk-lynxi: add platform driver for MT7988 Message-ID: <8aa905080bdb6760875d62cb3b2b41258837f80e.1702352117.git.daniel@makrotopia.org> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Introduce a proper platform MFD driver for the LynxI (H)SGMII PCS which is going to initially be used for the MT7988 SoC. Signed-off-by: Daniel Golle --- drivers/net/pcs/pcs-mtk-lynxi.c | 226 ++++++++++++++++++++++++++++-- include/linux/pcs/pcs-mtk-lynxi.h | 11 ++ 2 files changed, 226 insertions(+), 11 deletions(-) diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynx= i.c index 8501dd365279b..e06dd7db66144 100644 --- a/drivers/net/pcs/pcs-mtk-lynxi.c +++ b/drivers/net/pcs/pcs-mtk-lynxi.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2018-2019 MediaTek Inc. -/* A library for MediaTek SGMII circuit +/* A library and platform driver for the MediaTek LynxI SGMII circuit * * Author: Sean Wang * Author: Alexander Couzens @@ -8,11 +8,17 @@ * */ =20 +#include #include +#include +#include #include +#include #include #include +#include #include +#include =20 /* SGMII subsystem config registers */ /* BMCR (low 16) BMSR (high 16) */ @@ -65,6 +71,8 @@ #define SGMII_PN_SWAP_MASK GENMASK(1, 0) #define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1)) =20 +#define MTK_NETSYS_V3_AMA_RGC3 0x128 + /* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassoc= iated * data * @regmap: The register map pointing at the range used to = setup @@ -74,15 +82,29 @@ * @interface: Currently configured interface mode * @pcs: Phylink PCS structure * @flags: Flags indicating hardware properties + * @rstc: Reset controller + * @sgmii_sel: SGMII Register Clock + * @sgmii_rx: SGMII RX Clock + * @sgmii_tx: SGMII TX Clock + * @node: List node */ struct mtk_pcs_lynxi { struct regmap *regmap; + struct device *dev; u32 ana_rgc3; phy_interface_t interface; struct phylink_pcs pcs; u32 flags; + struct reset_control *rstc; + struct clk *sgmii_sel; + struct clk *sgmii_rx; + struct clk *sgmii_tx; + struct list_head node; }; =20 +static LIST_HEAD(mtk_pcs_lynxi_instances); +static DEFINE_MUTEX(instance_mutex); + static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs) { return container_of(pcs, struct mtk_pcs_lynxi, pcs); @@ -102,6 +124,17 @@ static void mtk_pcs_lynxi_get_state(struct phylink_pcs= *pcs, FIELD_GET(SGMII_LPA, adv)); } =20 +static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs) +{ + if (!mpcs->rstc) + return; + + reset_control_assert(mpcs->rstc); + udelay(100); + reset_control_deassert(mpcs->rstc); + mdelay(1); +} + static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_= mode, phy_interface_t interface, const unsigned long *advertising, @@ -147,6 +180,7 @@ static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs= , unsigned int neg_mode, SGMII_PHYA_PWD); =20 /* Reset SGMII PCS state */ + mtk_sgmii_reset(mpcs); regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0, SGMII_SW_RESET); =20 @@ -233,10 +267,29 @@ static void mtk_pcs_lynxi_link_up(struct phylink_pcs = *pcs, } } =20 +static int mtk_pcs_lynxi_enable(struct phylink_pcs *pcs) +{ + struct mtk_pcs_lynxi *mpcs =3D pcs_to_mtk_pcs_lynxi(pcs); + + if (mpcs->sgmii_tx && mpcs->sgmii_rx) { + clk_prepare_enable(mpcs->sgmii_rx); + clk_prepare_enable(mpcs->sgmii_tx); + } + + return 0; +} + static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs) { struct mtk_pcs_lynxi *mpcs =3D pcs_to_mtk_pcs_lynxi(pcs); =20 + regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD); + + if (mpcs->sgmii_tx && mpcs->sgmii_rx) { + clk_disable_unprepare(mpcs->sgmii_tx); + clk_disable_unprepare(mpcs->sgmii_rx); + } + mpcs->interface =3D PHY_INTERFACE_MODE_NA; } =20 @@ -246,11 +299,12 @@ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops= =3D { .pcs_an_restart =3D mtk_pcs_lynxi_restart_an, .pcs_link_up =3D mtk_pcs_lynxi_link_up, .pcs_disable =3D mtk_pcs_lynxi_disable, + .pcs_enable =3D mtk_pcs_lynxi_enable, }; =20 -struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, - struct regmap *regmap, u32 ana_rgc3, - u32 flags) +static struct phylink_pcs *mtk_pcs_lynxi_init(struct device *dev, struct r= egmap *regmap, + u32 ana_rgc3, u32 flags, + struct mtk_pcs_lynxi *prealloc) { struct mtk_pcs_lynxi *mpcs; u32 id, ver; @@ -258,29 +312,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct devic= e *dev, =20 ret =3D regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id); if (ret < 0) - return NULL; + return ERR_PTR(ret); =20 if (id !=3D SGMII_LYNXI_DEV_ID) { dev_err(dev, "unknown PCS device id %08x\n", id); - return NULL; + return ERR_PTR(-ENODEV); } =20 ret =3D regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver); if (ret < 0) - return NULL; + return ERR_PTR(ret); =20 ver =3D FIELD_GET(SGMII_DEV_VERSION, ver); if (ver !=3D 0x1) { dev_err(dev, "unknown PCS device version %04x\n", ver); - return NULL; + return ERR_PTR(-ENODEV); } =20 dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id, ver); =20 - mpcs =3D kzalloc(sizeof(*mpcs), GFP_KERNEL); - if (!mpcs) - return NULL; + if (prealloc) { + mpcs =3D prealloc; + } else { + mpcs =3D kzalloc(sizeof(*mpcs), GFP_KERNEL); + if (!mpcs) + return ERR_PTR(-ENOMEM); + }; =20 mpcs->ana_rgc3 =3D ana_rgc3; mpcs->regmap =3D regmap; @@ -291,6 +349,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct device= *dev, mpcs->interface =3D PHY_INTERFACE_MODE_NA; =20 return &mpcs->pcs; +}; + +struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, + struct regmap *regmap, u32 ana_rgc3, + u32 flags) +{ + return mtk_pcs_lynxi_init(dev, regmap, ana_rgc3, flags, NULL); } EXPORT_SYMBOL(mtk_pcs_lynxi_create); =20 @@ -303,4 +368,143 @@ void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs) } EXPORT_SYMBOL(mtk_pcs_lynxi_destroy); =20 +static int mtk_pcs_lynxi_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct device_node *np =3D dev->of_node; + struct mtk_pcs_lynxi *mpcs; + struct phylink_pcs *pcs; + struct regmap *regmap; + u32 flags =3D 0; + + mpcs =3D devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL); + if (!mpcs) + return -ENOMEM; + + mpcs->dev =3D dev; + regmap =3D syscon_node_to_regmap(np->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + if (of_property_read_bool(np->parent, "mediatek,pnswap")) + flags |=3D MTK_SGMII_FLAG_PN_SWAP; + + mpcs->rstc =3D of_reset_control_get_shared(np->parent, NULL); + if (IS_ERR(mpcs->rstc)) + return PTR_ERR(mpcs->rstc); + + reset_control_deassert(mpcs->rstc); + mpcs->sgmii_sel =3D devm_clk_get_enabled(dev, "sgmii_sel"); + if (IS_ERR(mpcs->sgmii_sel)) + return PTR_ERR(mpcs->sgmii_sel); + + mpcs->sgmii_rx =3D devm_clk_get(dev, "sgmii_rx"); + if (IS_ERR(mpcs->sgmii_rx)) + return PTR_ERR(mpcs->sgmii_rx); + + mpcs->sgmii_tx =3D devm_clk_get(dev, "sgmii_tx"); + if (IS_ERR(mpcs->sgmii_tx)) + return PTR_ERR(mpcs->sgmii_tx); + + pcs =3D mtk_pcs_lynxi_init(dev, regmap, (uintptr_t)of_device_get_match_da= ta(dev), + flags, mpcs); + if (IS_ERR(pcs)) + return PTR_ERR(pcs); + + regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, SGMII_PHYA_PWD); + + platform_set_drvdata(pdev, mpcs); + + mutex_lock(&instance_mutex); + list_add_tail(&mpcs->node, &mtk_pcs_lynxi_instances); + mutex_unlock(&instance_mutex); + + return 0; +} + +static int mtk_pcs_lynxi_remove(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct mtk_pcs_lynxi *cur, *tmp; + + mutex_lock(&instance_mutex); + list_for_each_entry_safe(cur, tmp, &mtk_pcs_lynxi_instances, node) + if (cur->dev =3D=3D dev) { + list_del(&cur->node); + kfree(cur); + break; + } + mutex_unlock(&instance_mutex); + + return 0; +} + +static const struct of_device_id mtk_pcs_lynxi_of_match[] =3D { + { .compatible =3D "mediatek,mt7988-sgmii", .data =3D (void *)MTK_NETSYS_V= 3_AMA_RGC3 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mtk_pcs_lynxi_of_match); + +struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_no= de *np) +{ + struct platform_device *pdev; + struct mtk_pcs_lynxi *mpcs; + + if (!np) + return NULL; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + if (!of_match_node(mtk_pcs_lynxi_of_match, np)) + return ERR_PTR(-EINVAL); + + pdev =3D of_find_device_by_node(np); + if (!pdev || !platform_get_drvdata(pdev)) { + if (pdev) + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + mpcs =3D platform_get_drvdata(pdev); + device_link_add(dev, mpcs->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + + return &mpcs->pcs; +} +EXPORT_SYMBOL(mtk_pcs_lynxi_get); + +void mtk_pcs_lynxi_put(struct phylink_pcs *pcs) +{ + struct mtk_pcs_lynxi *cur, *mpcs =3D NULL; + + if (!pcs) + return; + + mutex_lock(&instance_mutex); + list_for_each_entry(cur, &mtk_pcs_lynxi_instances, node) + if (pcs =3D=3D &cur->pcs) { + mpcs =3D cur; + break; + } + mutex_unlock(&instance_mutex); + + if (WARN_ON(!mpcs)) + return; + + put_device(mpcs->dev); +} +EXPORT_SYMBOL(mtk_pcs_lynxi_put); + +static struct platform_driver mtk_pcs_lynxi_driver =3D { + .driver =3D { + .name =3D "mtk-pcs-lynxi", + .of_match_table =3D mtk_pcs_lynxi_of_match, + }, + .probe =3D mtk_pcs_lynxi_probe, + .remove =3D mtk_pcs_lynxi_remove, +}; +module_platform_driver(mtk_pcs_lynxi_driver); + +MODULE_AUTHOR("Daniel Golle "); +MODULE_DESCRIPTION("MediaTek LynxI HSGMII PCS"); MODULE_LICENSE("GPL"); diff --git a/include/linux/pcs/pcs-mtk-lynxi.h b/include/linux/pcs/pcs-mtk-= lynxi.h index be3b4ab32f4a7..2d44e951229c7 100644 --- a/include/linux/pcs/pcs-mtk-lynxi.h +++ b/include/linux/pcs/pcs-mtk-lynxi.h @@ -10,4 +10,15 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct device *= dev, struct regmap *regmap, u32 ana_rgc3, u32 flags); void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs); + +#if IS_ENABLED(CONFIG_PCS_MTK_LYNXI) +struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, struct device_no= de *np); +void mtk_pcs_lynxi_put(struct phylink_pcs *pcs); +#else +static inline struct phylink_pcs *mtk_pcs_lynxi_get(struct device *dev, st= ruct device_node *np) +{ + return NULL; +} +static inline void mtk_pcs_lynxi_put(struct phylink_pcs *pcs) { } +#endif /* IS_ENABLED(CONFIG_PCS_MTK_LYNXI) */ #endif --=20 2.43.0