// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018 Marvell International Ltd. * Author: Ken Ma */ #include #include #include #include #include #include #include #include #include #include #define MVMDIO_SMI_DATA_SHIFT 0 #define MVMDIO_SMI_PHY_ADDR_SHIFT 16 #define MVMDIO_SMI_PHY_REG_SHIFT 21 #define MVMDIO_SMI_READ_OPERATION BIT(26) #define MVMDIO_SMI_WRITE_OPERATION 0 #define MVMDIO_SMI_READ_VALID BIT(27) #define MVMDIO_SMI_BUSY BIT(28) #define MVMDIO_XSMI_MGNT_REG 0x0 #define MVMDIO_XSMI_PHYADDR_SHIFT 16 #define MVMDIO_XSMI_DEVADDR_SHIFT 21 #define MVMDIO_XSMI_WRITE_OPERATION (0x5 << 26) #define MVMDIO_XSMI_READ_OPERATION (0x7 << 26) #define MVMDIO_XSMI_READ_VALID BIT(29) #define MVMDIO_XSMI_BUSY BIT(30) #define MVMDIO_XSMI_ADDR_REG 0x8 enum mvmdio_bus_type { BUS_TYPE_SMI, BUS_TYPE_XSMI }; struct mvmdio_priv { void *mdio_base; enum mvmdio_bus_type type; }; static int mvmdio_smi_read(struct udevice *dev, int addr, int devad, int reg) { struct mvmdio_priv *priv = dev_get_priv(dev); u32 val; int ret; if (devad != MDIO_DEVAD_NONE) return -EOPNOTSUPP; ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; writel(((addr << MVMDIO_SMI_PHY_ADDR_SHIFT) | (reg << MVMDIO_SMI_PHY_REG_SHIFT) | MVMDIO_SMI_READ_OPERATION), priv->mdio_base); ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; val = readl(priv->mdio_base); if (!(val & MVMDIO_SMI_READ_VALID)) { pr_err("SMI bus read not valid\n"); return -ENODEV; } return val & GENMASK(15, 0); } static int mvmdio_smi_write(struct udevice *dev, int addr, int devad, int reg, u16 value) { struct mvmdio_priv *priv = dev_get_priv(dev); int ret; if (devad != MDIO_DEVAD_NONE) return -EOPNOTSUPP; ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; writel(((addr << MVMDIO_SMI_PHY_ADDR_SHIFT) | (reg << MVMDIO_SMI_PHY_REG_SHIFT) | MVMDIO_SMI_WRITE_OPERATION | (value << MVMDIO_SMI_DATA_SHIFT)), priv->mdio_base); return 0; } static int mvmdio_xsmi_read(struct udevice *dev, int addr, int devad, int reg) { struct mvmdio_priv *priv = dev_get_priv(dev); int ret; if (devad == MDIO_DEVAD_NONE) return -EOPNOTSUPP; ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; writel(reg & GENMASK(15, 0), priv->mdio_base + MVMDIO_XSMI_ADDR_REG); writel(((addr << MVMDIO_XSMI_PHYADDR_SHIFT) | (devad << MVMDIO_XSMI_DEVADDR_SHIFT) | MVMDIO_XSMI_READ_OPERATION), priv->mdio_base + MVMDIO_XSMI_MGNT_REG); ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; if (!(readl(priv->mdio_base + MVMDIO_XSMI_MGNT_REG) & MVMDIO_XSMI_READ_VALID)) { pr_err("XSMI bus read not valid\n"); return -ENODEV; } return readl(priv->mdio_base + MVMDIO_XSMI_MGNT_REG) & GENMASK(15, 0); } static int mvmdio_xsmi_write(struct udevice *dev, int addr, int devad, int reg, u16 value) { struct mvmdio_priv *priv = dev_get_priv(dev); int ret; if (devad == MDIO_DEVAD_NONE) return -EOPNOTSUPP; ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY, false, CONFIG_SYS_HZ, false); if (ret < 0) return ret; writel(reg & GENMASK(15, 0), priv->mdio_base + MVMDIO_XSMI_ADDR_REG); writel(((addr << MVMDIO_XSMI_PHYADDR_SHIFT) | (devad << MVMDIO_XSMI_DEVADDR_SHIFT) | MVMDIO_XSMI_WRITE_OPERATION | value), priv->mdio_base + MVMDIO_XSMI_MGNT_REG); return 0; } static int mvmdio_read(struct udevice *dev, int addr, int devad, int reg) { struct mvmdio_priv *priv = dev_get_priv(dev); int err = -ENOTSUPP; switch (priv->type) { case BUS_TYPE_SMI: err = mvmdio_smi_read(dev, addr, devad, reg); break; case BUS_TYPE_XSMI: err = mvmdio_xsmi_read(dev, addr, devad, reg); break; } return err; } static int mvmdio_write(struct udevice *dev, int addr, int devad, int reg, u16 value) { struct mvmdio_priv *priv = dev_get_priv(dev); int err = -ENOTSUPP; switch (priv->type) { case BUS_TYPE_SMI: err = mvmdio_smi_write(dev, addr, devad, reg, value); break; case BUS_TYPE_XSMI: err = mvmdio_xsmi_write(dev, addr, devad, reg, value); break; } return err; } /* * Name the device, we use the device tree node name. * This can be overwritten by MDIO class code if device-name property is * present. */ static int mvmdio_bind(struct udevice *dev) { if (ofnode_valid(dev_ofnode(dev))) device_set_name(dev, ofnode_get_name(dev_ofnode(dev))); return 0; } /* Get device base address and type, either C22 SMII or C45 XSMI */ static int mvmdio_probe(struct udevice *dev) { struct mvmdio_priv *priv = dev_get_priv(dev); priv->mdio_base = dev_read_addr_ptr(dev); priv->type = (enum mvmdio_bus_type)dev_get_driver_data(dev); return 0; } static const struct mdio_ops mvmdio_ops = { .read = mvmdio_read, .write = mvmdio_write, }; static const struct udevice_id mvmdio_ids[] = { { .compatible = "marvell,orion-mdio", .data = BUS_TYPE_SMI }, { .compatible = "marvell,xmdio", .data = BUS_TYPE_XSMI }, { } }; U_BOOT_DRIVER(mvmdio) = { .name = "mvmdio", .id = UCLASS_MDIO, .of_match = mvmdio_ids, .bind = mvmdio_bind, .probe = mvmdio_probe, .ops = &mvmdio_ops, .priv_auto = sizeof(struct mvmdio_priv), };