diff options
Diffstat (limited to 'drivers/net/octeontx/smi.c')
| -rw-r--r-- | drivers/net/octeontx/smi.c | 380 | 
1 files changed, 380 insertions, 0 deletions
| diff --git a/drivers/net/octeontx/smi.c b/drivers/net/octeontx/smi.c new file mode 100644 index 00000000000..8e2c3ca5a30 --- /dev/null +++ b/drivers/net/octeontx/smi.c @@ -0,0 +1,380 @@ +// 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); | 
