| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) | |
| /* | |
| * Copyright (c) 2018 Microsemi Corporation | |
| */ | |
| #include <miiphy.h> | |
| #include <wait_bit.h> | |
| #include <linux/bitops.h> | |
| #include "mscc_miim.h" | |
| #define MIIM_STATUS 0x0 | |
| #define MIIM_STAT_BUSY BIT(3) | |
| #define MIIM_CMD 0x8 | |
| #define MIIM_CMD_SCAN BIT(0) | |
| #define MIIM_CMD_OPR_WRITE BIT(1) | |
| #define MIIM_CMD_OPR_READ BIT(2) | |
| #define MIIM_CMD_SINGLE_SCAN BIT(3) | |
| #define MIIM_CMD_WRDATA(x) ((x) << 4) | |
| #define MIIM_CMD_REGAD(x) ((x) << 20) | |
| #define MIIM_CMD_PHYAD(x) ((x) << 25) | |
| #define MIIM_CMD_VLD BIT(31) | |
| #define MIIM_DATA 0xC | |
| #define MIIM_DATA_ERROR (0x2 << 16) | |
| static int mscc_miim_wait_ready(struct mscc_miim_dev *miim) | |
| { | |
| return wait_for_bit_le32(miim->regs + MIIM_STATUS, MIIM_STAT_BUSY, | |
| false, 250, false); | |
| } | |
| int mscc_miim_read(struct mii_dev *bus, int addr, int devad, int reg) | |
| { | |
| struct mscc_miim_dev *miim = (struct mscc_miim_dev *)bus->priv; | |
| u32 val; | |
| int ret; | |
| ret = mscc_miim_wait_ready(miim); | |
| if (ret) | |
| goto out; | |
| writel(MIIM_CMD_VLD | MIIM_CMD_PHYAD(addr) | | |
| MIIM_CMD_REGAD(reg) | MIIM_CMD_OPR_READ, | |
| miim->regs + MIIM_CMD); | |
| ret = mscc_miim_wait_ready(miim); | |
| if (ret) | |
| goto out; | |
| val = readl(miim->regs + MIIM_DATA); | |
| if (val & MIIM_DATA_ERROR) { | |
| ret = -EIO; | |
| goto out; | |
| } | |
| ret = val & 0xFFFF; | |
| out: | |
| return ret; | |
| } | |
| int mscc_miim_write(struct mii_dev *bus, int addr, int devad, int reg, | |
| u16 val) | |
| { | |
| struct mscc_miim_dev *miim = (struct mscc_miim_dev *)bus->priv; | |
| int ret; | |
| ret = mscc_miim_wait_ready(miim); | |
| if (ret < 0) | |
| goto out; | |
| writel(MIIM_CMD_VLD | MIIM_CMD_PHYAD(addr) | | |
| MIIM_CMD_REGAD(reg) | MIIM_CMD_WRDATA(val) | | |
| MIIM_CMD_OPR_WRITE, miim->regs + MIIM_CMD); | |
| out: | |
| return ret; | |
| } | |
| struct mii_dev *mscc_mdiobus_init(struct mscc_miim_dev *miim, int *miim_count, | |
| phys_addr_t miim_base, | |
| unsigned long miim_size) | |
| { | |
| struct mii_dev *bus; | |
| bus = mdio_alloc(); | |
| if (!bus) | |
| return NULL; | |
| *miim_count += 1; | |
| sprintf(bus->name, "miim-bus%d", *miim_count); | |
| miim[*miim_count].regs = ioremap(miim_base, miim_size); | |
| miim[*miim_count].miim_base = miim_base; | |
| miim[*miim_count].miim_size = miim_size; | |
| bus->priv = &miim[*miim_count]; | |
| bus->read = mscc_miim_read; | |
| bus->write = mscc_miim_write; | |
| if (mdio_register(bus)) | |
| return NULL; | |
| miim[*miim_count].bus = bus; | |
| return bus; | |
| } |