// SPDX-License-Identifier: GPL-2.0+ /* * QE UEC ethernet phy controller driver * * based on phy parts of drivers/qe/uec.c and drivers/qe/uec_phy.c * from NXP * * Copyright (C) 2020 Heiko Schocher */ #include #include #include #include #include #include #include #include "dm_qe_uec.h" struct qe_uec_mdio_priv { struct ucc_mii_mng *base; }; static int qe_uec_mdio_read(struct udevice *dev, int addr, int devad, int reg) { struct qe_uec_mdio_priv *priv = dev_get_priv(dev); struct ucc_mii_mng *regs = priv->base; u32 tmp_reg; u16 value; debug("%s: regs: %p addr: %x devad: %x reg: %x\n", __func__, regs, addr, devad, reg); /* Setting up the MII management Address Register */ tmp_reg = ((u32)addr << MIIMADD_PHY_ADDRESS_SHIFT) | reg; out_be32(®s->miimadd, tmp_reg); /* clear MII management command cycle */ out_be32(®s->miimcom, 0); sync(); /* Perform an MII management read cycle */ out_be32(®s->miimcom, MIIMCOM_READ_CYCLE); /* Wait till MII management write is complete */ while ((in_be32(®s->miimind)) & (MIIMIND_NOT_VALID | MIIMIND_BUSY)) ; /* Read MII management status */ value = (u16)in_be32(®s->miimstat); if (value == 0xffff) return -EINVAL; return value; }; static int qe_uec_mdio_write(struct udevice *dev, int addr, int devad, int reg, u16 value) { struct qe_uec_mdio_priv *priv = dev_get_priv(dev); struct ucc_mii_mng *regs = priv->base; u32 tmp_reg; debug("%s: regs: %p addr: %x devad: %x reg: %x val: %x\n", __func__, regs, addr, devad, reg, value); /* Stop the MII management read cycle */ out_be32(®s->miimcom, 0); /* Setting up the MII management Address Register */ tmp_reg = ((u32)addr << MIIMADD_PHY_ADDRESS_SHIFT) | reg; out_be32(®s->miimadd, tmp_reg); /* Setting up the MII management Control Register with the value */ out_be32(®s->miimcon, (u32)value); sync(); /* Wait till MII management write is complete */ while ((in_be32(®s->miimind)) & MIIMIND_BUSY) ; return 0; }; static const struct mdio_ops qe_uec_mdio_ops = { .read = qe_uec_mdio_read, .write = qe_uec_mdio_write, }; static int qe_uec_mdio_probe(struct udevice *dev) { struct qe_uec_mdio_priv *priv = dev_get_priv(dev); fdt_size_t base; ofnode node; u32 num = 0; int ret = -ENODEV; priv->base = dev_read_addr_ptr(dev); base = (fdt_size_t)priv->base; /* * idea from linux: * drivers/net/ethernet/freescale/fsl_pq_mdio.c * * Find the UCC node that controls the given MDIO node * * For some reason, the QE MDIO nodes are not children of the UCC * devices that control them. Therefore, we need to scan all UCC * nodes looking for the one that encompases the given MDIO node. * We do this by comparing physical addresses. The 'start' and * 'end' addresses of the MDIO node are passed, and the correct * UCC node will cover the entire address range. */ node = ofnode_by_compatible(ofnode_null(), "ucc_geth"); while (ofnode_valid(node)) { fdt_size_t size; fdt_addr_t addr; addr = ofnode_get_addr_index(node, 0); ret = ofnode_get_addr_size_index(node, 0, &size); if (addr == FDT_ADDR_T_NONE) { node = ofnode_by_compatible(node, "ucc_geth"); continue; } /* check if priv->base in start end */ if (base > addr && base < (addr + size)) { ret = ofnode_read_u32(node, "cell-index", &num); if (ret) ret = ofnode_read_u32(node, "device-id", &num); break; } node = ofnode_by_compatible(node, "ucc_geth"); } if (ret) { printf("%s: no cell-index nor device-id found!", __func__); return ret; } /* Setup MII master clock source */ qe_set_mii_clk_src(num - 1); return 0; } static const struct udevice_id qe_uec_mdio_ids[] = { { .compatible = "fsl,ucc-mdio" }, { } }; U_BOOT_DRIVER(mvmdio) = { .name = "qe_uec_mdio", .id = UCLASS_MDIO, .of_match = qe_uec_mdio_ids, .probe = qe_uec_mdio_probe, .ops = &qe_uec_mdio_ops, .priv_auto = sizeof(struct qe_uec_mdio_priv), };