root/drivers/net/ethernet/mediatek/mtk_eth_path.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018-2019 MediaTek Inc.

/* A library for configuring path from GMAC/GDM to target PHY
 *
 * Author: Sean Wang <sean.wang@mediatek.com>
 *
 */

#include <linux/phy.h>
#include <linux/regmap.h>

#include "mtk_eth_soc.h"

struct mtk_eth_muxc {
        const char      *name;
        u64             cap_bit;
        int             (*set_path)(struct mtk_eth *eth, u64 path);
};

static const char *mtk_eth_path_name(u64 path)
{
        switch (path) {
        case MTK_ETH_PATH_GMAC1_RGMII:
                return "gmac1_rgmii";
        case MTK_ETH_PATH_GMAC1_TRGMII:
                return "gmac1_trgmii";
        case MTK_ETH_PATH_GMAC1_SGMII:
                return "gmac1_sgmii";
        case MTK_ETH_PATH_GMAC2_RGMII:
                return "gmac2_rgmii";
        case MTK_ETH_PATH_GMAC2_SGMII:
                return "gmac2_sgmii";
        case MTK_ETH_PATH_GMAC2_2P5GPHY:
                return "gmac2_2p5gphy";
        case MTK_ETH_PATH_GMAC2_GEPHY:
                return "gmac2_gephy";
        case MTK_ETH_PATH_GDM1_ESW:
                return "gdm1_esw";
        default:
                return "unknown path";
        }
}

static int set_mux_gdm1_to_gmac1_esw(struct mtk_eth *eth, u64 path)
{
        bool updated = true;
        u32 mask, set, reg;

        switch (path) {
        case MTK_ETH_PATH_GMAC1_SGMII:
                mask = ~(u32)MTK_MUX_TO_ESW;
                set = 0;
                break;
        case MTK_ETH_PATH_GDM1_ESW:
                mask = ~(u32)MTK_MUX_TO_ESW;
                set = MTK_MUX_TO_ESW;
                break;
        default:
                updated = false;
                break;
        }

        if (mtk_is_netsys_v3_or_greater(eth))
                reg = MTK_MAC_MISC_V3;
        else
                reg = MTK_MAC_MISC;

        if (updated)
                mtk_m32(eth, mask, set, reg);

        dev_dbg(eth->dev, "path %s in %s updated = %d\n",
                mtk_eth_path_name(path), __func__, updated);

        return 0;
}

static int set_mux_gmac2_gmac0_to_gephy(struct mtk_eth *eth, u64 path)
{
        unsigned int val = 0;
        bool updated = true;

        switch (path) {
        case MTK_ETH_PATH_GMAC2_GEPHY:
                val = ~(u32)GEPHY_MAC_SEL;
                break;
        default:
                updated = false;
                break;
        }

        if (updated)
                regmap_update_bits(eth->infra, INFRA_MISC2, GEPHY_MAC_SEL, val);

        dev_dbg(eth->dev, "path %s in %s updated = %d\n",
                mtk_eth_path_name(path), __func__, updated);

        return 0;
}

static int set_mux_u3_gmac2_to_qphy(struct mtk_eth *eth, u64 path)
{
        unsigned int val = 0, mask = 0, reg = 0;
        bool updated = true;

        switch (path) {
        case MTK_ETH_PATH_GMAC2_SGMII:
                if (MTK_HAS_CAPS(eth->soc->caps, MTK_U3_COPHY_V2)) {
                        reg = USB_PHY_SWITCH_REG;
                        val = SGMII_QPHY_SEL;
                        mask = QPHY_SEL_MASK;
                } else {
                        reg = INFRA_MISC2;
                        val = CO_QPHY_SEL;
                        mask = val;
                }
                break;
        default:
                updated = false;
                break;
        }

        if (updated)
                regmap_update_bits(eth->infra, reg, mask, val);

        dev_dbg(eth->dev, "path %s in %s updated = %d\n",
                mtk_eth_path_name(path), __func__, updated);

        return 0;
}

static int set_mux_gmac2_to_2p5gphy(struct mtk_eth *eth, u64 path)
{
        int ret;

        if (path == MTK_ETH_PATH_GMAC2_2P5GPHY) {
                ret = regmap_clear_bits(eth->ethsys, ETHSYS_SYSCFG0,
                                        SYSCFG0_SGMII_GMAC2_V2);
                if (ret)
                        return ret;

                /* Setup mux to 2p5g PHY */
                ret = regmap_clear_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX,
                                        MUX_G2_USXGMII_SEL);
                if (ret)
                        return ret;

                dev_dbg(eth->dev, "path %s in %s updated\n",
                        mtk_eth_path_name(path), __func__);
        }

        return 0;
}

static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, u64 path)
{
        unsigned int val = 0;
        bool updated = true;

        switch (path) {
        case MTK_ETH_PATH_GMAC1_SGMII:
                val = SYSCFG0_SGMII_GMAC1;
                break;
        case MTK_ETH_PATH_GMAC2_SGMII:
                val = SYSCFG0_SGMII_GMAC2;
                break;
        case MTK_ETH_PATH_GMAC1_RGMII:
        case MTK_ETH_PATH_GMAC2_RGMII:
                regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
                val &= SYSCFG0_SGMII_MASK;

                if ((path == MTK_GMAC1_RGMII && val == SYSCFG0_SGMII_GMAC1) ||
                    (path == MTK_GMAC2_RGMII && val == SYSCFG0_SGMII_GMAC2))
                        val = 0;
                else
                        updated = false;
                break;
        default:
                updated = false;
                break;
        }

        if (updated)
                regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
                                   SYSCFG0_SGMII_MASK, val);

        dev_dbg(eth->dev, "path %s in %s updated = %d\n",
                mtk_eth_path_name(path), __func__, updated);

        return 0;
}

static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
{
        unsigned int val = 0;
        bool updated = true;

        regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);

        switch (path) {
        case MTK_ETH_PATH_GMAC1_SGMII:
                val |= SYSCFG0_SGMII_GMAC1_V2;
                break;
        case MTK_ETH_PATH_GMAC2_GEPHY:
                val &= ~(u32)SYSCFG0_SGMII_GMAC2_V2;
                break;
        case MTK_ETH_PATH_GMAC2_SGMII:
                val |= SYSCFG0_SGMII_GMAC2_V2;
                break;
        default:
                updated = false;
        }

        if (updated)
                regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
                                   SYSCFG0_SGMII_MASK, val);

        dev_dbg(eth->dev, "path %s in %s updated = %d\n",
                mtk_eth_path_name(path), __func__, updated);

        return 0;
}

static const struct mtk_eth_muxc mtk_eth_muxc[] = {
        {
                .name = "mux_gdm1_to_gmac1_esw",
                .cap_bit = MTK_ETH_MUX_GDM1_TO_GMAC1_ESW,
                .set_path = set_mux_gdm1_to_gmac1_esw,
        }, {
                .name = "mux_gmac2_gmac0_to_gephy",
                .cap_bit = MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY,
                .set_path = set_mux_gmac2_gmac0_to_gephy,
        }, {
                .name = "mux_u3_gmac2_to_qphy",
                .cap_bit = MTK_ETH_MUX_U3_GMAC2_TO_QPHY,
                .set_path = set_mux_u3_gmac2_to_qphy,
        }, {
                .name = "mux_gmac2_to_2p5gphy",
                .cap_bit = MTK_ETH_MUX_GMAC2_TO_2P5GPHY,
                .set_path = set_mux_gmac2_to_2p5gphy,
        }, {
                .name = "mux_gmac1_gmac2_to_sgmii_rgmii",
                .cap_bit = MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII,
                .set_path = set_mux_gmac1_gmac2_to_sgmii_rgmii,
        }, {
                .name = "mux_gmac12_to_gephy_sgmii",
                .cap_bit = MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII,
                .set_path = set_mux_gmac12_to_gephy_sgmii,
        },
};

static int mtk_eth_mux_setup(struct mtk_eth *eth, u64 path)
{
        int i, err = 0;

        if (!MTK_HAS_CAPS(eth->soc->caps, path)) {
                dev_err(eth->dev, "path %s isn't support on the SoC\n",
                        mtk_eth_path_name(path));
                return -EINVAL;
        }

        if (!MTK_HAS_CAPS(eth->soc->caps, MTK_MUX))
                return 0;

        /* Setup MUX in path fabric */
        for (i = 0; i < ARRAY_SIZE(mtk_eth_muxc); i++) {
                if (MTK_HAS_CAPS(eth->soc->caps, mtk_eth_muxc[i].cap_bit)) {
                        err = mtk_eth_muxc[i].set_path(eth, path);
                        if (err)
                                goto out;
                } else {
                        dev_dbg(eth->dev, "mux %s isn't present on the SoC\n",
                                mtk_eth_muxc[i].name);
                }
        }

out:
        return err;
}

int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id)
{
        u64 path;

        path = (mac_id == 0) ?  MTK_ETH_PATH_GMAC1_SGMII :
                                MTK_ETH_PATH_GMAC2_SGMII;

        /* Setup proper MUXes along the path */
        return mtk_eth_mux_setup(eth, path);
}

int mtk_gmac_2p5gphy_path_setup(struct mtk_eth *eth, int mac_id)
{
        u64 path = 0;

        if (mac_id == MTK_GMAC2_ID)
                path = MTK_ETH_PATH_GMAC2_2P5GPHY;

        if (!path)
                return -EINVAL;

        /* Setup proper MUXes along the path */
        return mtk_eth_mux_setup(eth, path);
}

int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id)
{
        u64 path = 0;

        if (mac_id == 1)
                path = MTK_ETH_PATH_GMAC2_GEPHY;

        if (!path)
                return -EINVAL;

        /* Setup proper MUXes along the path */
        return mtk_eth_mux_setup(eth, path);
}

int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id)
{
        u64 path;

        path = (mac_id == 0) ?  MTK_ETH_PATH_GMAC1_RGMII :
                                MTK_ETH_PATH_GMAC2_RGMII;

        /* Setup proper MUXes along the path */
        return mtk_eth_mux_setup(eth, path);
}