blob: d2e2d50cb28fde3e7a9d1720d4dd02d957529ada [file] [log] [blame]
/*
* 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);
}