| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2018 Marvell International Ltd. |
| */ |
| |
| #include <dm.h> |
| #include <malloc.h> |
| #include <miiphy.h> |
| #include <misc.h> |
| #include <pci.h> |
| #include <pci_ids.h> |
| #include <phy.h> |
| #include <asm/global_data.h> |
| #include <asm/io.h> |
| #include <linux/ctype.h> |
| #include <linux/delay.h> |
| |
| #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_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); |