// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018 Marvell International Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #define PCI_DEVICE_ID_OCTEONTX_SMI 0xA02B DECLARE_GLOBAL_DATA_PTR; enum octeontx_smi_mode { CLAUSE22 = 0, CLAUSE45 = 1, }; enum { SMI_OP_C22_WRITE = 0, SMI_OP_C22_READ = 1, SMI_OP_C45_ADDR = 0, SMI_OP_C45_WRITE = 1, SMI_OP_C45_PRIA = 2, SMI_OP_C45_READ = 3, }; union smi_x_clk { u64 u; struct smi_x_clk_s { int phase:8; int sample:4; int preamble:1; int clk_idle:1; int reserved_14_14:1; int sample_mode:1; int sample_hi:5; int reserved_21_23:3; int mode:1; } s; }; union smi_x_cmd { u64 u; struct smi_x_cmd_s { int reg_adr:5; int reserved_5_7:3; int phy_adr:5; int reserved_13_15:3; int phy_op:2; } s; }; union smi_x_wr_dat { u64 u; struct smi_x_wr_dat_s { unsigned int dat:16; int val:1; int pending:1; } s; }; union smi_x_rd_dat { u64 u; struct smi_x_rd_dat_s { unsigned int dat:16; int val:1; int pending:1; } s; }; union smi_x_en { u64 u; struct smi_x_en_s { int en:1; } s; }; #define SMI_X_RD_DAT 0x10ull #define SMI_X_WR_DAT 0x08ull #define SMI_X_CMD 0x00ull #define SMI_X_CLK 0x18ull #define SMI_X_EN 0x20ull struct octeontx_smi_priv { void __iomem *baseaddr; enum octeontx_smi_mode mode; }; #define MDIO_TIMEOUT 10000 void octeontx_smi_setmode(struct mii_dev *bus, enum octeontx_smi_mode mode) { struct octeontx_smi_priv *priv = bus->priv; union smi_x_clk smix_clk; smix_clk.u = readq(priv->baseaddr + SMI_X_CLK); smix_clk.s.mode = mode; smix_clk.s.preamble = mode == CLAUSE45; writeq(smix_clk.u, priv->baseaddr + SMI_X_CLK); priv->mode = mode; } int octeontx_c45_addr(struct mii_dev *bus, int addr, int devad, int regnum) { struct octeontx_smi_priv *priv = bus->priv; union smi_x_cmd smix_cmd; union smi_x_wr_dat smix_wr_dat; unsigned long timeout = MDIO_TIMEOUT; smix_wr_dat.u = 0; smix_wr_dat.s.dat = regnum; writeq(smix_wr_dat.u, priv->baseaddr + SMI_X_WR_DAT); smix_cmd.u = 0; smix_cmd.s.phy_op = SMI_OP_C45_ADDR; smix_cmd.s.phy_adr = addr; smix_cmd.s.reg_adr = devad; writeq(smix_cmd.u, priv->baseaddr + SMI_X_CMD); do { smix_wr_dat.u = readq(priv->baseaddr + SMI_X_WR_DAT); udelay(100); timeout--; } while (smix_wr_dat.s.pending && timeout); return timeout == 0; } int octeontx_phy_read(struct mii_dev *bus, int addr, int devad, int regnum) { struct octeontx_smi_priv *priv = bus->priv; union smi_x_cmd smix_cmd; union smi_x_rd_dat smix_rd_dat; unsigned long timeout = MDIO_TIMEOUT; int ret; enum octeontx_smi_mode mode = (devad < 0) ? CLAUSE22 : CLAUSE45; debug("RD: Mode: %u, baseaddr: %p, addr: %d, devad: %d, reg: %d\n", mode, priv->baseaddr, addr, devad, regnum); octeontx_smi_setmode(bus, mode); if (mode == CLAUSE45) { ret = octeontx_c45_addr(bus, addr, devad, regnum); debug("RD: ret: %u\n", ret); if (ret) return 0; } smix_cmd.u = 0; smix_cmd.s.phy_adr = addr; if (mode == CLAUSE45) { smix_cmd.s.reg_adr = devad; smix_cmd.s.phy_op = SMI_OP_C45_READ; } else { smix_cmd.s.reg_adr = regnum; smix_cmd.s.phy_op = SMI_OP_C22_READ; } writeq(smix_cmd.u, priv->baseaddr + SMI_X_CMD); do { smix_rd_dat.u = readq(priv->baseaddr + SMI_X_RD_DAT); udelay(10); timeout--; } while (smix_rd_dat.s.pending && timeout); debug("SMIX_RD_DAT: %lx\n", (unsigned long)smix_rd_dat.u); return smix_rd_dat.s.dat; } int octeontx_phy_write(struct mii_dev *bus, int addr, int devad, int regnum, u16 value) { struct octeontx_smi_priv *priv = bus->priv; union smi_x_cmd smix_cmd; union smi_x_wr_dat smix_wr_dat; unsigned long timeout = MDIO_TIMEOUT; int ret; enum octeontx_smi_mode mode = (devad < 0) ? CLAUSE22 : CLAUSE45; debug("WR: Mode: %u, baseaddr: %p, addr: %d, devad: %d, reg: %d\n", mode, priv->baseaddr, addr, devad, regnum); if (mode == CLAUSE45) { ret = octeontx_c45_addr(bus, addr, devad, regnum); debug("WR: ret: %u\n", ret); if (ret) return ret; } smix_wr_dat.u = 0; smix_wr_dat.s.dat = value; writeq(smix_wr_dat.u, priv->baseaddr + SMI_X_WR_DAT); smix_cmd.u = 0; smix_cmd.s.phy_adr = addr; if (mode == CLAUSE45) { smix_cmd.s.reg_adr = devad; smix_cmd.s.phy_op = SMI_OP_C45_WRITE; } else { smix_cmd.s.reg_adr = regnum; smix_cmd.s.phy_op = SMI_OP_C22_WRITE; } writeq(smix_cmd.u, priv->baseaddr + SMI_X_CMD); do { smix_wr_dat.u = readq(priv->baseaddr + SMI_X_WR_DAT); udelay(10); timeout--; } while (smix_wr_dat.s.pending && timeout); debug("SMIX_WR_DAT: %lx\n", (unsigned long)smix_wr_dat.u); return timeout == 0; } int octeontx_smi_reset(struct mii_dev *bus) { struct octeontx_smi_priv *priv = bus->priv; union smi_x_en smi_en; smi_en.s.en = 0; writeq(smi_en.u, priv->baseaddr + SMI_X_EN); smi_en.s.en = 1; writeq(smi_en.u, priv->baseaddr + SMI_X_EN); octeontx_smi_setmode(bus, CLAUSE22); return 0; } /* PHY XS initialization, primarily for RXAUI * */ int rxaui_phy_xs_init(struct mii_dev *bus, int phy_addr) { int reg; ulong start_time; int phy_id1, phy_id2; int oui, model_number; phy_id1 = octeontx_phy_read(bus, phy_addr, 1, 0x2); phy_id2 = octeontx_phy_read(bus, phy_addr, 1, 0x3); model_number = (phy_id2 >> 4) & 0x3F; debug("%s model %x\n", __func__, model_number); oui = phy_id1; oui <<= 6; oui |= (phy_id2 >> 10) & 0x3F; debug("%s oui %x\n", __func__, oui); switch (oui) { case 0x5016: if (model_number == 9) { debug("%s +\n", __func__); /* Perform hardware reset in XGXS control */ reg = octeontx_phy_read(bus, phy_addr, 4, 0x0); if ((reg & 0xffff) < 0) goto read_error; reg |= 0x8000; octeontx_phy_write(bus, phy_addr, 4, 0x0, reg); start_time = get_timer(0); do { reg = octeontx_phy_read(bus, phy_addr, 4, 0x0); if ((reg & 0xffff) < 0) goto read_error; } while ((reg & 0x8000) && get_timer(start_time) < 500); if (reg & 0x8000) { printf("HW reset for M88X3120 PHY failed"); printf("MII_BMCR: 0x%x\n", reg); return -1; } /* program 4.49155 with 0x5 */ octeontx_phy_write(bus, phy_addr, 4, 0xc003, 0x5); } break; default: break; } return 0; read_error: debug("M88X3120 PHY config read failed\n"); return -1; } int octeontx_smi_probe(struct udevice *dev) { pci_dev_t bdf = dm_pci_get_bdf(dev); struct octeontx_smi_priv *priv; struct mii_dev *bus; int ret, cnt = 0; ofnode subnode; u64 baseaddr; debug("SMI PCI device: %x\n", bdf); if (!dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0, 0, PCI_REGION_TYPE, PCI_REGION_MEM)) { printf("Failed to map PCI region for bdf %x\n", bdf); return -1; } dev_for_each_subnode(subnode, dev) { if (!ofnode_device_is_compatible(subnode, "cavium,thunder-8890-mdio")) continue; if (ofnode_read_u64(subnode, "reg", &baseaddr)) continue; bus = mdio_alloc(); priv = malloc(sizeof(*priv)); if (!bus || !priv) { printf("Failed to allocate OcteonTX MDIO bus # %u\n", dev_seq(dev)); return -1; } bus->read = octeontx_phy_read; bus->write = octeontx_phy_write; bus->reset = octeontx_smi_reset; bus->priv = priv; priv->mode = CLAUSE22; priv->baseaddr = (void __iomem *)baseaddr; debug("mdio base addr %p\n", priv->baseaddr); /* use given name or generate its own unique name */ snprintf(bus->name, MDIO_NAME_LEN, "smi%d", cnt++); ret = mdio_register(bus); if (ret) return ret; } return 0; } static const struct udevice_id octeontx_smi_ids[] = { { .compatible = "cavium,thunder-8890-mdio-nexus" }, {} }; U_BOOT_DRIVER(octeontx_smi) = { .name = "octeontx_smi", .id = UCLASS_MISC, .probe = octeontx_smi_probe, .of_match = octeontx_smi_ids, }; static struct pci_device_id octeontx_smi_supported[] = { { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_SMI) }, {} }; U_BOOT_PCI_DEVICE(octeontx_smi, octeontx_smi_supported);