blob: d70fa820c7b0fdb36632d7bfabfcba8ae20e40a0 [file] [log] [blame]
// 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, 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);