| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018 Marvell International Ltd. |
| * Author: Ken Ma<make@marvell.com> |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <dm/device-internal.h> |
| #include <dm/lists.h> |
| #include <miiphy.h> |
| #include <phy.h> |
| #include <asm/io.h> |
| #include <wait_bit.h> |
| #include <linux/bitops.h> |
| |
| #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->node)) |
| device_set_name(dev, ofnode_get_name(dev->node)); |
| |
| 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 = (void *)dev_read_addr(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), |
| }; |
| |