On Wed, Dec 06, 2023 at 05:51:34PM +0000, Russell King (Oracle) wrote:
> On Wed, Dec 06, 2023 at 01:44:17AM +0000, Daniel Golle wrote:
> > +struct phylink_pcs *mtk_pcs_lynxi_select_pcs(struct device_node *np, phy_interface_t mode)
> > +{
> > + 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 = of_find_device_by_node(np);
> > + if (!pdev || !platform_get_drvdata(pdev)) {
> > + if (pdev)
> > + put_device(&pdev->dev);
> > + return ERR_PTR(-EPROBE_DEFER);
> > + }
> > +
> > + mpcs = platform_get_drvdata(pdev);
> > + put_device(&pdev->dev);
> > +
> > + return &mpcs->pcs;
> > +}
> > +EXPORT_SYMBOL(mtk_pcs_lynxi_select_pcs);
>
> If you're going to play games like this, then you must mark the driver
> with .suppress_bind_attrs = true to remove the bind/unbind attributes
> in userspace that could wreak havoc with the above - because there is
> _nothing_ that guarantees that the memory you're returning from this
> function will remain intact. Basically, it's racy.
Ack, I've set .suppress_bind_attrs = true in the usxgmii driver but
forgot to add it here.
> Also, I'm not sure I approve of using the "select_pcs" suffix (I
> haven't spotted _where_ you use this, but returning EPROBE_DEFER to
> phylink's mac_select_pcs() method doesn't do anything to defer any
> probe, so that's an entirely misleading error code.
EPROBE_DEFER is handled when the function is called by mtk_add_mac()
during probe of the Ethernet driver -- which we do want to postpone
in case the PCS hasn't been probed yet as at this point that's the
best we can do without adding lots of intrastructure to dynamically
attach the PCS later on...
But true, later the function is being called by mac_select_pcs() and
what ever it returns is returned to the caller of mac_select_pcs().
If you think it's better to return ENODEV (or EAGAIN?) I can change
that -- from what I could tell, the only error which receives special
handling by phylink is -EOPNOTSUPP, everything else just gets passed-
through to the callers.
> If we are going to have device drivers for PCS, then we need to
> seriously think about how we look up PCS and return the phylink_pcs
> pointer - and also how we handle the PCS device going away. None of
> that should be coded into _any_ PCS driver.
I agree -- just wasn't up to design and implement all that at once.
@@ -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 <sean.wang@mediatek.com>
* Author: Alexander Couzens <lynxis@fe80.eu>
@@ -8,11 +8,16 @@
*
*/
+#include <linux/clk.h>
#include <linux/mdio.h>
+#include <linux/mfd/syscon.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/phylink.h>
+#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
/* SGMII subsystem config registers */
/* BMCR (low 16) BMSR (high 16) */
@@ -65,6 +70,8 @@
#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
+#define MTK_NETSYS_V3_AMA_RGC3 0x128
+
/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
* data
* @regmap: The register map pointing at the range used to setup
@@ -81,6 +88,10 @@ struct mtk_pcs_lynxi {
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;
};
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
@@ -102,6 +113,17 @@ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
FIELD_GET(SGMII_LPA, adv));
}
+static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
+{
+ if (!mpcs->rstc)
+ return;
+
+ reset_control_assert(mpcs->rstc);
+ usleep_range(100, 500);
+ reset_control_deassert(mpcs->rstc);
+ mdelay(10);
+}
+
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
@@ -147,6 +169,7 @@ static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
SGMII_PHYA_PWD);
/* Reset SGMII PCS state */
+ mtk_sgmii_reset(mpcs);
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
@@ -233,10 +256,29 @@ static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs,
}
}
+static int mtk_pcs_lynxi_enable(struct phylink_pcs *pcs)
+{
+ struct mtk_pcs_lynxi *mpcs = 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 = pcs_to_mtk_pcs_lynxi(pcs);
+ 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 = PHY_INTERFACE_MODE_NA;
}
@@ -246,11 +288,12 @@ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
+ .pcs_enable = mtk_pcs_lynxi_enable,
};
-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 regmap *regmap,
+ u32 ana_rgc3, u32 flags,
+ struct mtk_pcs_lynxi *prealloc)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
@@ -258,29 +301,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
- return NULL;
+ return ERR_PTR(ret);
if (id != SGMII_LYNXI_DEV_ID) {
dev_err(dev, "unknown PCS device id %08x\n", id);
- return NULL;
+ return ERR_PTR(-ENODEV);
}
ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
if (ret < 0)
- return NULL;
+ return ERR_PTR(ret);
ver = FIELD_GET(SGMII_DEV_VERSION, ver);
if (ver != 0x1) {
dev_err(dev, "unknown PCS device version %04x\n", ver);
- return NULL;
+ return ERR_PTR(-ENODEV);
}
dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
ver);
- mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
- if (!mpcs)
- return NULL;
+ if (prealloc) {
+ mpcs = prealloc;
+ } else {
+ mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
+ if (!mpcs)
+ return ERR_PTR(-ENOMEM);
+ };
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
@@ -291,6 +338,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
mpcs->interface = PHY_INTERFACE_MODE_NA;
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);
@@ -303,4 +357,98 @@ void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
+static int mtk_pcs_lynxi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct mtk_pcs_lynxi *mpcs;
+ struct phylink_pcs *pcs;
+ struct regmap *regmap;
+ u32 flags = 0;
+
+ mpcs = devm_kzalloc(dev, sizeof(*mpcs), GFP_KERNEL);
+ if (!mpcs)
+ return -ENOMEM;
+
+ regmap = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ if (of_property_read_bool(np->parent, "mediatek,pnswap"))
+ flags |= MTK_SGMII_FLAG_PN_SWAP;
+
+ mpcs->rstc = 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 = devm_clk_get_enabled(dev, "sgmii_sel");
+ if (IS_ERR(mpcs->sgmii_sel))
+ return PTR_ERR(mpcs->sgmii_sel);
+
+ mpcs->sgmii_rx = devm_clk_get(dev, "sgmii_rx");
+ if (IS_ERR(mpcs->sgmii_rx))
+ return PTR_ERR(mpcs->sgmii_rx);
+
+ mpcs->sgmii_tx = devm_clk_get(dev, "sgmii_tx");
+ if (IS_ERR(mpcs->sgmii_tx))
+ return PTR_ERR(mpcs->sgmii_tx);
+
+ pcs = mtk_pcs_lynxi_init(dev, regmap, (uintptr_t)of_device_get_match_data(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);
+
+ return 0;
+}
+
+static const struct of_device_id mtk_pcs_lynxi_of_match[] = {
+ { .compatible = "mediatek,mt7988-sgmii", .data = (void *)MTK_NETSYS_V3_AMA_RGC3 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_pcs_lynxi_of_match);
+
+struct phylink_pcs *mtk_pcs_lynxi_select_pcs(struct device_node *np, phy_interface_t mode)
+{
+ 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 = of_find_device_by_node(np);
+ if (!pdev || !platform_get_drvdata(pdev)) {
+ if (pdev)
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ mpcs = platform_get_drvdata(pdev);
+ put_device(&pdev->dev);
+
+ return &mpcs->pcs;
+}
+EXPORT_SYMBOL(mtk_pcs_lynxi_select_pcs);
+
+static struct platform_driver mtk_pcs_lynxi_driver = {
+ .driver = {
+ .name = "mtk-pcs-lynxi",
+ .of_match_table = mtk_pcs_lynxi_of_match,
+ },
+ .probe = mtk_pcs_lynxi_probe,
+};
+module_platform_driver(mtk_pcs_lynxi_driver);
+
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_DESCRIPTION("MediaTek LynxI HSGMII PCS");
MODULE_LICENSE("GPL");
@@ -10,4 +10,5 @@ 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);
+struct phylink_pcs *mtk_pcs_lynxi_select_pcs(struct device_node *np, phy_interface_t mode);
#endif