/* * (C) Copyright 2014 * Dirk Eibach, Guntermann & Drunck GmbH, eibach@gdsys.de * * SPDX-License-Identifier: GPL-2.0+ */ #include #include enum { MIICMD_SET, MIICMD_MODIFY, MIICMD_VERIFY_VALUE, MIICMD_WAIT_FOR_VALUE, }; struct mii_setupcmd { u8 token; u8 reg; u16 data; u16 mask; u32 timeout; }; /* * verify we are talking to a 88e1518 */ struct mii_setupcmd verify_88e1518[] = { { MIICMD_SET, 22, 0x0000 }, { MIICMD_VERIFY_VALUE, 2, 0x0141, 0xffff }, { MIICMD_VERIFY_VALUE, 3, 0x0dd0, 0xfff0 }, }; /* * workaround for erratum mentioned in 88E1518 release notes */ struct mii_setupcmd fixup_88e1518[] = { { MIICMD_SET, 22, 0x00ff }, { MIICMD_SET, 17, 0x214b }, { MIICMD_SET, 16, 0x2144 }, { MIICMD_SET, 17, 0x0c28 }, { MIICMD_SET, 16, 0x2146 }, { MIICMD_SET, 17, 0xb233 }, { MIICMD_SET, 16, 0x214d }, { MIICMD_SET, 17, 0xcc0c }, { MIICMD_SET, 16, 0x2159 }, { MIICMD_SET, 22, 0x00fb }, { MIICMD_SET, 7, 0xc00d }, { MIICMD_SET, 22, 0x0000 }, }; /* * default initialization: * - set RGMII receive timing to "receive clock transition when data stable" * - set RGMII transmit timing to "transmit clock internally delayed" * - set RGMII output impedance target to 78,8 Ohm * - run output impedance calibration * - set autonegotiation advertise to 1000FD only */ struct mii_setupcmd default_88e1518[] = { { MIICMD_SET, 22, 0x0002 }, { MIICMD_MODIFY, 21, 0x0030, 0x0030 }, { MIICMD_MODIFY, 25, 0x0000, 0x0003 }, { MIICMD_MODIFY, 24, 0x8000, 0x8000 }, { MIICMD_WAIT_FOR_VALUE, 24, 0x4000, 0x4000, 2000 }, { MIICMD_SET, 22, 0x0000 }, { MIICMD_MODIFY, 4, 0x0000, 0x01e0 }, { MIICMD_MODIFY, 9, 0x0200, 0x0300 }, }; /* * turn off CLK125 for PHY daughterboard */ struct mii_setupcmd ch1fix_88e1518[] = { { MIICMD_SET, 22, 0x0002 }, { MIICMD_MODIFY, 16, 0x0006, 0x0006 }, { MIICMD_SET, 22, 0x0000 }, }; /* * perform copper software reset */ struct mii_setupcmd swreset_88e1518[] = { { MIICMD_SET, 22, 0x0000 }, { MIICMD_MODIFY, 0, 0x8000, 0x8000 }, { MIICMD_WAIT_FOR_VALUE, 0, 0x0000, 0x8000, 2000 }, }; /* * special one for 88E1514: * Force SGMII to Copper mode */ struct mii_setupcmd mii_to_copper_88e1514[] = { { MIICMD_SET, 22, 0x0012 }, { MIICMD_MODIFY, 20, 0x0001, 0x0007 }, { MIICMD_MODIFY, 20, 0x8000, 0x8000 }, { MIICMD_SET, 22, 0x0000 }, }; /* * turn off SGMII auto-negotiation */ struct mii_setupcmd sgmii_autoneg_off_88e1518[] = { { MIICMD_SET, 22, 0x0001 }, { MIICMD_MODIFY, 0, 0x0000, 0x1000 }, { MIICMD_MODIFY, 0, 0x8000, 0x8000 }, { MIICMD_SET, 22, 0x0000 }, }; /* * invert LED2 polarity */ struct mii_setupcmd invert_led2_88e1514[] = { { MIICMD_SET, 22, 0x0003 }, { MIICMD_MODIFY, 17, 0x0030, 0x0010 }, { MIICMD_SET, 22, 0x0000 }, }; static int process_setupcmd(const char *bus, unsigned char addr, struct mii_setupcmd *setupcmd) { int res; u8 reg = setupcmd->reg; u16 data = setupcmd->data; u16 mask = setupcmd->mask; u32 timeout = setupcmd->timeout; u16 orig_data; unsigned long start; debug("mii %s:%u reg %2u ", bus, addr, reg); switch (setupcmd->token) { case MIICMD_MODIFY: res = miiphy_read(bus, addr, reg, &orig_data); if (res) break; debug("is %04x. (value %04x mask %04x) ", orig_data, data, mask); data = (orig_data & ~mask) | (data & mask); /* fallthrough */ case MIICMD_SET: debug("=> %04x\n", data); res = miiphy_write(bus, addr, reg, data); break; case MIICMD_VERIFY_VALUE: res = miiphy_read(bus, addr, reg, &orig_data); if (res) break; if ((orig_data & mask) != (data & mask)) res = -1; debug("(value %04x mask %04x) == %04x? %s\n", data, mask, orig_data, res ? "FAIL" : "PASS"); break; case MIICMD_WAIT_FOR_VALUE: res = -1; start = get_timer(0); while ((res != 0) && (get_timer(start) < timeout)) { res = miiphy_read(bus, addr, reg, &orig_data); if (res) continue; if ((orig_data & mask) != (data & mask)) res = -1; } debug("(value %04x mask %04x) == %04x? %s after %lu ms\n", data, mask, orig_data, res ? "FAIL" : "PASS", get_timer(start)); break; default: res = -1; break; } return res; } static int process_setup(const char *bus, unsigned char addr, struct mii_setupcmd *setupcmd, unsigned int count) { int res = 0; unsigned int k; for (k = 0; k < count; ++k) { res = process_setupcmd(bus, addr, &setupcmd[k]); if (res) { printf("mii cmd %u on bus %s addr %u failed, aborting setup\n", setupcmd[k].token, bus, addr); break; } } return res; } int setup_88e1518(const char *bus, unsigned char addr) { int res; res = process_setup(bus, addr, verify_88e1518, ARRAY_SIZE(verify_88e1518)); if (res) return res; res = process_setup(bus, addr, fixup_88e1518, ARRAY_SIZE(fixup_88e1518)); if (res) return res; res = process_setup(bus, addr, default_88e1518, ARRAY_SIZE(default_88e1518)); if (res) return res; if (addr) { res = process_setup(bus, addr, ch1fix_88e1518, ARRAY_SIZE(ch1fix_88e1518)); if (res) return res; } res = process_setup(bus, addr, swreset_88e1518, ARRAY_SIZE(swreset_88e1518)); if (res) return res; return 0; } int setup_88e1514(const char *bus, unsigned char addr) { int res; res = process_setup(bus, addr, verify_88e1518, ARRAY_SIZE(verify_88e1518)); if (res) return res; res = process_setup(bus, addr, fixup_88e1518, ARRAY_SIZE(fixup_88e1518)); if (res) return res; res = process_setup(bus, addr, mii_to_copper_88e1514, ARRAY_SIZE(mii_to_copper_88e1514)); if (res) return res; res = process_setup(bus, addr, sgmii_autoneg_off_88e1518, ARRAY_SIZE(sgmii_autoneg_off_88e1518)); if (res) return res; res = process_setup(bus, addr, invert_led2_88e1514, ARRAY_SIZE(invert_led2_88e1514)); if (res) return res; res = process_setup(bus, addr, default_88e1518, ARRAY_SIZE(default_88e1518)); if (res) return res; if (addr) { res = process_setup(bus, addr, ch1fix_88e1518, ARRAY_SIZE(ch1fix_88e1518)); if (res) return res; } res = process_setup(bus, addr, swreset_88e1518, ARRAY_SIZE(swreset_88e1518)); if (res) return res; return 0; }