mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-19 00:18:51 +09:00
![Suneel Garapati](/assets/img/avatar_default.png)
Adds support for Network Interface controllers found on OcteonTX SoC platforms. Signed-off-by: Suneel Garapati <sgarapati@marvell.com> Signed-off-by: Stefan Roese <sr@denx.de> Cc: Joe Hershberger <joe.hershberger@ni.com>
381 lines
7.9 KiB
C
381 lines
7.9 KiB
C
// 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/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)
|
|
{
|
|
int ret, subnode, cnt = 0, node = dev->node.of_offset;
|
|
struct mii_dev *bus;
|
|
struct octeontx_smi_priv *priv;
|
|
pci_dev_t bdf = dm_pci_get_bdf(dev);
|
|
|
|
debug("SMI PCI device: %x\n", bdf);
|
|
dev->req_seq = PCI_FUNC(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;
|
|
}
|
|
|
|
fdt_for_each_subnode(subnode, gd->fdt_blob, node) {
|
|
ret = fdt_node_check_compatible(gd->fdt_blob, subnode,
|
|
"cavium,thunder-8890-mdio");
|
|
if (ret)
|
|
continue;
|
|
|
|
bus = mdio_alloc();
|
|
priv = malloc(sizeof(*priv));
|
|
if (!bus || !priv) {
|
|
printf("Failed to allocate OcteonTX MDIO bus # %u\n",
|
|
dev->seq);
|
|
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 *)fdtdec_get_addr(gd->fdt_blob,
|
|
subnode,
|
|
"reg");
|
|
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);
|