| /* |
| * Copyright 2017 NXP |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/netdevice.h> |
| #include <linux/phy.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #ifdef CONFIG_ARCH_MXC_ARM64 |
| #include <soc/imx8/sc/sci.h> |
| #endif |
| #include "fec.h" |
| |
| #define PHY_ID_AR8031 0x004dd074 |
| |
| #define IMX8QM_FUSE_MAC0_WORD0 452 |
| #define IMX8QM_FUSE_MAC0_WORD1 453 |
| #define IMX8QM_FUSE_MAC1_WORD0 454 |
| #define IMX8QM_FUSE_MAC1_WORD1 455 |
| #define IMX8QXP_FUSE_MAC0_WORD0 708 |
| #define IMX8QXP_FUSE_MAC0_WORD1 709 |
| #define IMX8QXP_FUSE_MAC1_WORD0 710 |
| #define IMX8QXP_FUSE_MAC1_WORD1 711 |
| #define IMX8M_OCOTP_MAC_ADDR0_OFF 0x640 |
| #define IMX8M_OCOTP_MAC_ADDR1_OFF 0x650 |
| |
| enum imx_soc_type { |
| IMX8QM_FUSE = 0, |
| IMX8QXP_FUSE, |
| }; |
| |
| struct imx_fuse_mac_addr { |
| u32 fuse_mac0_word0; |
| u32 fuse_mac0_word1; |
| u32 fuse_mac1_word0; |
| u32 fuse_mac1_word1; |
| }; |
| |
| static struct imx_fuse_mac_addr imx8_fuse_mapping[] = { |
| { |
| .fuse_mac0_word0 = IMX8QM_FUSE_MAC0_WORD0, |
| .fuse_mac0_word1 = IMX8QM_FUSE_MAC0_WORD1, |
| .fuse_mac1_word0 = IMX8QM_FUSE_MAC1_WORD0, |
| .fuse_mac1_word1 = IMX8QM_FUSE_MAC1_WORD1, |
| }, { |
| .fuse_mac0_word0 = IMX8QXP_FUSE_MAC0_WORD0, |
| .fuse_mac0_word1 = IMX8QXP_FUSE_MAC0_WORD1, |
| .fuse_mac1_word0 = IMX8QXP_FUSE_MAC1_WORD0, |
| .fuse_mac1_word1 = IMX8QXP_FUSE_MAC1_WORD1, |
| }, { |
| /* sentinel */ |
| } |
| }; |
| |
| static int ar8031_phy_fixup(struct phy_device *dev) |
| { |
| u16 val; |
| |
| /* Set RGMII IO voltage to 1.8V */ |
| phy_write(dev, 0x1d, 0x1f); |
| phy_write(dev, 0x1e, 0x8); |
| |
| /* Disable phy AR8031 SmartEEE function */ |
| phy_write(dev, 0xd, 0x3); |
| phy_write(dev, 0xe, 0x805d); |
| phy_write(dev, 0xd, 0x4003); |
| val = phy_read(dev, 0xe); |
| val &= ~(0x1 << 8); |
| phy_write(dev, 0xe, val); |
| |
| /* Introduce tx clock delay */ |
| phy_write(dev, 0x1d, 0x5); |
| phy_write(dev, 0x1e, 0x100); |
| |
| return 0; |
| } |
| |
| void fec_enet_register_fixup(struct net_device *ndev) |
| { |
| struct fec_enet_private *fep = netdev_priv(ndev); |
| int err; |
| |
| if (!IS_BUILTIN(CONFIG_PHYLIB)) |
| return; |
| |
| if (fep->fixups & FEC_QUIRK_AR8031_FIXUP) { |
| static int ar8031_registered = 0; |
| |
| if (ar8031_registered) |
| return; |
| err = phy_register_fixup_for_uid(PHY_ID_AR8031, 0xffffffef, |
| ar8031_phy_fixup); |
| if (err) |
| netdev_info(ndev, "Cannot register PHY board fixup\n"); |
| ar8031_registered = 1; |
| } |
| } |
| |
| int of_fec_enet_parse_fixup(struct device_node *np) |
| { |
| int fixups = 0; |
| |
| if (of_get_property(np, "fsl,ar8031-phy-fixup", NULL)) |
| fixups |= FEC_QUIRK_AR8031_FIXUP; |
| |
| return fixups; |
| } |
| |
| static void imx8mq_get_mac_from_fuse(int dev_id, unsigned char *mac) |
| { |
| struct device_node *ocotp_np; |
| void __iomem *base; |
| u32 value; |
| |
| ocotp_np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-ocotp"); |
| if (!ocotp_np) { |
| pr_warn("failed to find ocotp node\n"); |
| return; |
| } |
| |
| base = of_iomap(ocotp_np, 0); |
| if (!base) { |
| pr_warn("failed to map ocotp\n"); |
| goto put_ocotp_node; |
| } |
| |
| value = readl_relaxed(base + IMX8M_OCOTP_MAC_ADDR1_OFF); |
| mac[0] = (value >> 8); |
| mac[1] = value; |
| |
| value = readl_relaxed(base + IMX8M_OCOTP_MAC_ADDR0_OFF); |
| mac[2] = value >> 24; |
| mac[3] = value >> 16; |
| mac[4] = value >> 8; |
| mac[5] = value; |
| |
| put_ocotp_node: |
| of_node_put(ocotp_np); |
| } |
| |
| #ifdef CONFIG_ARCH_MXC_ARM64 |
| static void imx8qm_get_mac_from_fuse(int dev_id, unsigned char *mac, |
| struct imx_fuse_mac_addr *fuse_mapping) |
| { |
| uint32_t mu_id; |
| sc_ipc_t ipc_handle; |
| sc_err_t sc_err = SC_ERR_NONE; |
| uint32_t val1 = 0, val2 = 0; |
| uint32_t word1, word2; |
| |
| sc_err = sc_ipc_getMuID(&mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC MAC fuse: Get MU ID failed\n"); |
| return; |
| } |
| |
| sc_err = sc_ipc_open(&ipc_handle, mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC MAC fuse: Open MU channel failed\n"); |
| return; |
| } |
| |
| if (dev_id == 0) { |
| word1 = fuse_mapping->fuse_mac0_word0; |
| word2 = fuse_mapping->fuse_mac0_word1; |
| } else { |
| word1 = fuse_mapping->fuse_mac1_word0; |
| word2 = fuse_mapping->fuse_mac1_word1; |
| } |
| |
| sc_err = sc_misc_otp_fuse_read(ipc_handle, word1, &val1); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC MAC fuse %d read error: %d\n", word1, sc_err); |
| sc_ipc_close(ipc_handle); |
| return; |
| } |
| |
| sc_err = sc_misc_otp_fuse_read(ipc_handle, word2, &val2); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC MAC fuse %d read error: %d\n", word2, sc_err); |
| sc_ipc_close(ipc_handle); |
| return; |
| } |
| |
| mac[0] = val1; |
| mac[1] = val1 >> 8; |
| mac[2] = val1 >> 16; |
| mac[3] = val1 >> 24; |
| mac[4] = val2; |
| mac[5] = val2 >> 8; |
| |
| sc_ipc_close(ipc_handle); |
| } |
| |
| static void imx8qm_ipg_stop_enable(int dev_id, bool enabled) |
| { |
| uint32_t mu_id; |
| sc_ipc_t ipc_handle; |
| sc_err_t sc_err = SC_ERR_NONE; |
| uint32_t rsrc_id, val; |
| |
| sc_err = sc_ipc_getMuID(&mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC ipg stop: Get MU ID failed\n"); |
| return; |
| } |
| |
| sc_err = sc_ipc_open(&ipc_handle, mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| pr_err("FEC ipg stop: Open MU channel failed\n"); |
| return; |
| } |
| |
| if (dev_id == 0) |
| rsrc_id = SC_R_ENET_0; |
| else |
| rsrc_id = SC_R_ENET_1; |
| |
| val = enabled ? 1 : 0; |
| sc_err = sc_misc_set_control(ipc_handle, rsrc_id, SC_C_IPG_STOP, val); |
| if (sc_err != SC_ERR_NONE) |
| pr_err("FEC ipg stop set error: %d\n", sc_err); |
| |
| sc_ipc_close(ipc_handle); |
| } |
| #else |
| static void imx8qm_get_mac_from_fuse(int dev_id, unsigned char *mac, |
| struct imx_fuse_mac_addr *fuse_mapping) {} |
| static void imx8qm_ipg_stop_enable(int dev_id, bool enabled) {} |
| #endif |
| |
| void fec_enet_get_mac_from_fuse(struct device_node *np, unsigned char *mac) |
| { |
| int idx; |
| |
| if (!np) |
| return; |
| |
| idx = of_alias_get_id(np, "ethernet"); |
| if (idx < 0) |
| idx = 0; |
| |
| if (of_machine_is_compatible("fsl,imx8qm")) |
| imx8qm_get_mac_from_fuse(idx, mac, |
| &imx8_fuse_mapping[IMX8QM_FUSE]); |
| else if (of_machine_is_compatible("fsl,imx8qxp")) |
| imx8qm_get_mac_from_fuse(idx, mac, |
| &imx8_fuse_mapping[IMX8QXP_FUSE]); |
| else if (of_machine_is_compatible("fsl,imx8mq") || |
| of_machine_is_compatible("fsl,imx8mm")) |
| imx8mq_get_mac_from_fuse(idx, mac); |
| } |
| |
| void fec_enet_ipg_stop_misc_set(struct device_node *np, bool enabled) |
| { |
| int idx; |
| |
| if (!np) |
| return; |
| |
| idx = of_alias_get_id(np, "ethernet"); |
| if (idx < 0) |
| idx = 0; |
| |
| if (of_machine_is_compatible("fsl,imx8qm") || |
| of_machine_is_compatible("fsl,imx8qxp")) |
| imx8qm_ipg_stop_enable(idx, enabled); |
| } |