162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Freescale PowerQUICC Ethernet Driver -- MIIM bus implementation
462306a36Sopenharmony_ci * Provides Bus interface for MIIM regs
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Author: Andy Fleming <afleming@freescale.com>
762306a36Sopenharmony_ci * Modifier: Sandeep Gopalpet <sandeep.kumar@freescale.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright 2002-2004, 2008-2009 Freescale Semiconductor, Inc.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Based on gianfar_mii.c and ucc_geth_mii.c (Li Yang, Kim Phillips)
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/string.h>
1762306a36Sopenharmony_ci#include <linux/errno.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/delay.h>
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <linux/mii.h>
2262306a36Sopenharmony_ci#include <linux/of_address.h>
2362306a36Sopenharmony_ci#include <linux/of_mdio.h>
2462306a36Sopenharmony_ci#include <linux/of_device.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include <asm/io.h>
2762306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_UCC_GETH)
2862306a36Sopenharmony_ci#include <soc/fsl/qe/ucc.h>
2962306a36Sopenharmony_ci#endif
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include "gianfar.h"
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define MIIMIND_BUSY		0x00000001
3462306a36Sopenharmony_ci#define MIIMIND_NOTVALID	0x00000004
3562306a36Sopenharmony_ci#define MIIMCFG_INIT_VALUE	0x00000007
3662306a36Sopenharmony_ci#define MIIMCFG_RESET		0x80000000
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define MII_READ_COMMAND	0x00000001
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistruct fsl_pq_mii {
4162306a36Sopenharmony_ci	u32 miimcfg;	/* MII management configuration reg */
4262306a36Sopenharmony_ci	u32 miimcom;	/* MII management command reg */
4362306a36Sopenharmony_ci	u32 miimadd;	/* MII management address reg */
4462306a36Sopenharmony_ci	u32 miimcon;	/* MII management control reg */
4562306a36Sopenharmony_ci	u32 miimstat;	/* MII management status reg */
4662306a36Sopenharmony_ci	u32 miimind;	/* MII management indication reg */
4762306a36Sopenharmony_ci};
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistruct fsl_pq_mdio {
5062306a36Sopenharmony_ci	u8 res1[16];
5162306a36Sopenharmony_ci	u32 ieventm;	/* MDIO Interrupt event register (for etsec2)*/
5262306a36Sopenharmony_ci	u32 imaskm;	/* MDIO Interrupt mask register (for etsec2)*/
5362306a36Sopenharmony_ci	u8 res2[4];
5462306a36Sopenharmony_ci	u32 emapm;	/* MDIO Event mapping register (for etsec2)*/
5562306a36Sopenharmony_ci	u8 res3[1280];
5662306a36Sopenharmony_ci	struct fsl_pq_mii mii;
5762306a36Sopenharmony_ci	u8 res4[28];
5862306a36Sopenharmony_ci	u32 utbipar;	/* TBI phy address reg (only on UCC) */
5962306a36Sopenharmony_ci	u8 res5[2728];
6062306a36Sopenharmony_ci} __packed;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* Number of microseconds to wait for an MII register to respond */
6362306a36Sopenharmony_ci#define MII_TIMEOUT	1000
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistruct fsl_pq_mdio_priv {
6662306a36Sopenharmony_ci	void __iomem *map;
6762306a36Sopenharmony_ci	struct fsl_pq_mii __iomem *regs;
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/*
7162306a36Sopenharmony_ci * Per-device-type data.  Each type of device tree node that we support gets
7262306a36Sopenharmony_ci * one of these.
7362306a36Sopenharmony_ci *
7462306a36Sopenharmony_ci * @mii_offset: the offset of the MII registers within the memory map of the
7562306a36Sopenharmony_ci * node.  Some nodes define only the MII registers, and some define the whole
7662306a36Sopenharmony_ci * MAC (which includes the MII registers).
7762306a36Sopenharmony_ci *
7862306a36Sopenharmony_ci * @get_tbipa: determines the address of the TBIPA register
7962306a36Sopenharmony_ci *
8062306a36Sopenharmony_ci * @ucc_configure: a special function for extra QE configuration
8162306a36Sopenharmony_ci */
8262306a36Sopenharmony_cistruct fsl_pq_mdio_data {
8362306a36Sopenharmony_ci	unsigned int mii_offset;	/* offset of the MII registers */
8462306a36Sopenharmony_ci	uint32_t __iomem * (*get_tbipa)(void __iomem *p);
8562306a36Sopenharmony_ci	void (*ucc_configure)(phys_addr_t start, phys_addr_t end);
8662306a36Sopenharmony_ci};
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * Write value to the PHY at mii_id at register regnum, on the bus attached
9062306a36Sopenharmony_ci * to the local interface, which may be different from the generic mdio bus
9162306a36Sopenharmony_ci * (tied to a single interface), waiting until the write is done before
9262306a36Sopenharmony_ci * returning. This is helpful in programming interfaces like the TBI which
9362306a36Sopenharmony_ci * control interfaces like onchip SERDES and are always tied to the local
9462306a36Sopenharmony_ci * mdio pins, which may not be the same as system mdio bus, used for
9562306a36Sopenharmony_ci * controlling the external PHYs, for example.
9662306a36Sopenharmony_ci */
9762306a36Sopenharmony_cistatic int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
9862306a36Sopenharmony_ci		u16 value)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct fsl_pq_mdio_priv *priv = bus->priv;
10162306a36Sopenharmony_ci	struct fsl_pq_mii __iomem *regs = priv->regs;
10262306a36Sopenharmony_ci	unsigned int timeout;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Set the PHY address and the register address we want to write */
10562306a36Sopenharmony_ci	iowrite32be((mii_id << 8) | regnum, &regs->miimadd);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* Write out the value we want */
10862306a36Sopenharmony_ci	iowrite32be(value, &regs->miimcon);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* Wait for the transaction to finish */
11162306a36Sopenharmony_ci	timeout = MII_TIMEOUT;
11262306a36Sopenharmony_ci	while ((ioread32be(&regs->miimind) & MIIMIND_BUSY) && timeout) {
11362306a36Sopenharmony_ci		cpu_relax();
11462306a36Sopenharmony_ci		timeout--;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return timeout ? 0 : -ETIMEDOUT;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/*
12162306a36Sopenharmony_ci * Read the bus for PHY at addr mii_id, register regnum, and return the value.
12262306a36Sopenharmony_ci * Clears miimcom first.
12362306a36Sopenharmony_ci *
12462306a36Sopenharmony_ci * All PHY operation done on the bus attached to the local interface, which
12562306a36Sopenharmony_ci * may be different from the generic mdio bus.  This is helpful in programming
12662306a36Sopenharmony_ci * interfaces like the TBI which, in turn, control interfaces like on-chip
12762306a36Sopenharmony_ci * SERDES and are always tied to the local mdio pins, which may not be the
12862306a36Sopenharmony_ci * same as system mdio bus, used for controlling the external PHYs, for eg.
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_cistatic int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct fsl_pq_mdio_priv *priv = bus->priv;
13362306a36Sopenharmony_ci	struct fsl_pq_mii __iomem *regs = priv->regs;
13462306a36Sopenharmony_ci	unsigned int timeout;
13562306a36Sopenharmony_ci	u16 value;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Set the PHY address and the register address we want to read */
13862306a36Sopenharmony_ci	iowrite32be((mii_id << 8) | regnum, &regs->miimadd);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Clear miimcom, and then initiate a read */
14162306a36Sopenharmony_ci	iowrite32be(0, &regs->miimcom);
14262306a36Sopenharmony_ci	iowrite32be(MII_READ_COMMAND, &regs->miimcom);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* Wait for the transaction to finish, normally less than 100us */
14562306a36Sopenharmony_ci	timeout = MII_TIMEOUT;
14662306a36Sopenharmony_ci	while ((ioread32be(&regs->miimind) &
14762306a36Sopenharmony_ci	       (MIIMIND_NOTVALID | MIIMIND_BUSY)) && timeout) {
14862306a36Sopenharmony_ci		cpu_relax();
14962306a36Sopenharmony_ci		timeout--;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!timeout)
15362306a36Sopenharmony_ci		return -ETIMEDOUT;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* Grab the value of the register from miimstat */
15662306a36Sopenharmony_ci	value = ioread32be(&regs->miimstat);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	dev_dbg(&bus->dev, "read %04x from address %x/%x\n", value, mii_id, regnum);
15962306a36Sopenharmony_ci	return value;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/* Reset the MIIM registers, and wait for the bus to free */
16362306a36Sopenharmony_cistatic int fsl_pq_mdio_reset(struct mii_bus *bus)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct fsl_pq_mdio_priv *priv = bus->priv;
16662306a36Sopenharmony_ci	struct fsl_pq_mii __iomem *regs = priv->regs;
16762306a36Sopenharmony_ci	unsigned int timeout;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	mutex_lock(&bus->mdio_lock);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* Reset the management interface */
17262306a36Sopenharmony_ci	iowrite32be(MIIMCFG_RESET, &regs->miimcfg);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* Setup the MII Mgmt clock speed */
17562306a36Sopenharmony_ci	iowrite32be(MIIMCFG_INIT_VALUE, &regs->miimcfg);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	/* Wait until the bus is free */
17862306a36Sopenharmony_ci	timeout = MII_TIMEOUT;
17962306a36Sopenharmony_ci	while ((ioread32be(&regs->miimind) & MIIMIND_BUSY) && timeout) {
18062306a36Sopenharmony_ci		cpu_relax();
18162306a36Sopenharmony_ci		timeout--;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	mutex_unlock(&bus->mdio_lock);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (!timeout) {
18762306a36Sopenharmony_ci		dev_err(&bus->dev, "timeout waiting for MII bus\n");
18862306a36Sopenharmony_ci		return -EBUSY;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	return 0;
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_GIANFAR)
19562306a36Sopenharmony_ci/*
19662306a36Sopenharmony_ci * Return the TBIPA address, starting from the address
19762306a36Sopenharmony_ci * of the mapped GFAR MDIO registers (struct gfar)
19862306a36Sopenharmony_ci * This is mildly evil, but so is our hardware for doing this.
19962306a36Sopenharmony_ci * Also, we have to cast back to struct gfar because of
20062306a36Sopenharmony_ci * definition weirdness done in gianfar.h.
20162306a36Sopenharmony_ci */
20262306a36Sopenharmony_cistatic uint32_t __iomem *get_gfar_tbipa_from_mdio(void __iomem *p)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct gfar __iomem *enet_regs = p;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return &enet_regs->tbipa;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci/*
21062306a36Sopenharmony_ci * Return the TBIPA address, starting from the address
21162306a36Sopenharmony_ci * of the mapped GFAR MII registers (gfar_mii_regs[] within struct gfar)
21262306a36Sopenharmony_ci */
21362306a36Sopenharmony_cistatic uint32_t __iomem *get_gfar_tbipa_from_mii(void __iomem *p)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	return get_gfar_tbipa_from_mdio(container_of(p, struct gfar, gfar_mii_regs));
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci/*
21962306a36Sopenharmony_ci * Return the TBIPAR address for an eTSEC2 node
22062306a36Sopenharmony_ci */
22162306a36Sopenharmony_cistatic uint32_t __iomem *get_etsec_tbipa(void __iomem *p)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	return p;
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci#endif
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_UCC_GETH)
22862306a36Sopenharmony_ci/*
22962306a36Sopenharmony_ci * Return the TBIPAR address for a QE MDIO node, starting from the address
23062306a36Sopenharmony_ci * of the mapped MII registers (struct fsl_pq_mii)
23162306a36Sopenharmony_ci */
23262306a36Sopenharmony_cistatic uint32_t __iomem *get_ucc_tbipa(void __iomem *p)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct fsl_pq_mdio __iomem *mdio = container_of(p, struct fsl_pq_mdio, mii);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return &mdio->utbipar;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci/*
24062306a36Sopenharmony_ci * Find the UCC node that controls the given MDIO node
24162306a36Sopenharmony_ci *
24262306a36Sopenharmony_ci * For some reason, the QE MDIO nodes are not children of the UCC devices
24362306a36Sopenharmony_ci * that control them.  Therefore, we need to scan all UCC nodes looking for
24462306a36Sopenharmony_ci * the one that encompases the given MDIO node.  We do this by comparing
24562306a36Sopenharmony_ci * physical addresses.  The 'start' and 'end' addresses of the MDIO node are
24662306a36Sopenharmony_ci * passed, and the correct UCC node will cover the entire address range.
24762306a36Sopenharmony_ci *
24862306a36Sopenharmony_ci * This assumes that there is only one QE MDIO node in the entire device tree.
24962306a36Sopenharmony_ci */
25062306a36Sopenharmony_cistatic void ucc_configure(phys_addr_t start, phys_addr_t end)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	static bool found_mii_master;
25362306a36Sopenharmony_ci	struct device_node *np = NULL;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	if (found_mii_master)
25662306a36Sopenharmony_ci		return;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	for_each_compatible_node(np, NULL, "ucc_geth") {
25962306a36Sopenharmony_ci		struct resource res;
26062306a36Sopenharmony_ci		const uint32_t *iprop;
26162306a36Sopenharmony_ci		uint32_t id;
26262306a36Sopenharmony_ci		int ret;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		ret = of_address_to_resource(np, 0, &res);
26562306a36Sopenharmony_ci		if (ret < 0) {
26662306a36Sopenharmony_ci			pr_debug("fsl-pq-mdio: no address range in node %pOF\n",
26762306a36Sopenharmony_ci				 np);
26862306a36Sopenharmony_ci			continue;
26962306a36Sopenharmony_ci		}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		/* if our mdio regs fall within this UCC regs range */
27262306a36Sopenharmony_ci		if ((start < res.start) || (end > res.end))
27362306a36Sopenharmony_ci			continue;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		iprop = of_get_property(np, "cell-index", NULL);
27662306a36Sopenharmony_ci		if (!iprop) {
27762306a36Sopenharmony_ci			iprop = of_get_property(np, "device-id", NULL);
27862306a36Sopenharmony_ci			if (!iprop) {
27962306a36Sopenharmony_ci				pr_debug("fsl-pq-mdio: no UCC ID in node %pOF\n",
28062306a36Sopenharmony_ci					 np);
28162306a36Sopenharmony_ci				continue;
28262306a36Sopenharmony_ci			}
28362306a36Sopenharmony_ci		}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci		id = be32_to_cpup(iprop);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		/*
28862306a36Sopenharmony_ci		 * cell-index and device-id for QE nodes are
28962306a36Sopenharmony_ci		 * numbered from 1, not 0.
29062306a36Sopenharmony_ci		 */
29162306a36Sopenharmony_ci		if (ucc_set_qe_mux_mii_mng(id - 1) < 0) {
29262306a36Sopenharmony_ci			pr_debug("fsl-pq-mdio: invalid UCC ID in node %pOF\n",
29362306a36Sopenharmony_ci				 np);
29462306a36Sopenharmony_ci			continue;
29562306a36Sopenharmony_ci		}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci		pr_debug("fsl-pq-mdio: setting node UCC%u to MII master\n", id);
29862306a36Sopenharmony_ci		found_mii_master = true;
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci#endif
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic const struct of_device_id fsl_pq_mdio_match[] = {
30562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_GIANFAR)
30662306a36Sopenharmony_ci	{
30762306a36Sopenharmony_ci		.compatible = "fsl,gianfar-tbi",
30862306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
30962306a36Sopenharmony_ci			.mii_offset = 0,
31062306a36Sopenharmony_ci			.get_tbipa = get_gfar_tbipa_from_mii,
31162306a36Sopenharmony_ci		},
31262306a36Sopenharmony_ci	},
31362306a36Sopenharmony_ci	{
31462306a36Sopenharmony_ci		.compatible = "fsl,gianfar-mdio",
31562306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
31662306a36Sopenharmony_ci			.mii_offset = 0,
31762306a36Sopenharmony_ci			.get_tbipa = get_gfar_tbipa_from_mii,
31862306a36Sopenharmony_ci		},
31962306a36Sopenharmony_ci	},
32062306a36Sopenharmony_ci	{
32162306a36Sopenharmony_ci		.type = "mdio",
32262306a36Sopenharmony_ci		.compatible = "gianfar",
32362306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
32462306a36Sopenharmony_ci			.mii_offset = offsetof(struct fsl_pq_mdio, mii),
32562306a36Sopenharmony_ci			.get_tbipa = get_gfar_tbipa_from_mdio,
32662306a36Sopenharmony_ci		},
32762306a36Sopenharmony_ci	},
32862306a36Sopenharmony_ci	{
32962306a36Sopenharmony_ci		.compatible = "fsl,etsec2-tbi",
33062306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
33162306a36Sopenharmony_ci			.mii_offset = offsetof(struct fsl_pq_mdio, mii),
33262306a36Sopenharmony_ci			.get_tbipa = get_etsec_tbipa,
33362306a36Sopenharmony_ci		},
33462306a36Sopenharmony_ci	},
33562306a36Sopenharmony_ci	{
33662306a36Sopenharmony_ci		.compatible = "fsl,etsec2-mdio",
33762306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
33862306a36Sopenharmony_ci			.mii_offset = offsetof(struct fsl_pq_mdio, mii),
33962306a36Sopenharmony_ci			.get_tbipa = get_etsec_tbipa,
34062306a36Sopenharmony_ci		},
34162306a36Sopenharmony_ci	},
34262306a36Sopenharmony_ci#endif
34362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_UCC_GETH)
34462306a36Sopenharmony_ci	{
34562306a36Sopenharmony_ci		.compatible = "fsl,ucc-mdio",
34662306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
34762306a36Sopenharmony_ci			.mii_offset = 0,
34862306a36Sopenharmony_ci			.get_tbipa = get_ucc_tbipa,
34962306a36Sopenharmony_ci			.ucc_configure = ucc_configure,
35062306a36Sopenharmony_ci		},
35162306a36Sopenharmony_ci	},
35262306a36Sopenharmony_ci	{
35362306a36Sopenharmony_ci		/* Legacy UCC MDIO node */
35462306a36Sopenharmony_ci		.type = "mdio",
35562306a36Sopenharmony_ci		.compatible = "ucc_geth_phy",
35662306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
35762306a36Sopenharmony_ci			.mii_offset = 0,
35862306a36Sopenharmony_ci			.get_tbipa = get_ucc_tbipa,
35962306a36Sopenharmony_ci			.ucc_configure = ucc_configure,
36062306a36Sopenharmony_ci		},
36162306a36Sopenharmony_ci	},
36262306a36Sopenharmony_ci#endif
36362306a36Sopenharmony_ci	/* No Kconfig option for Fman support yet */
36462306a36Sopenharmony_ci	{
36562306a36Sopenharmony_ci		.compatible = "fsl,fman-mdio",
36662306a36Sopenharmony_ci		.data = &(struct fsl_pq_mdio_data) {
36762306a36Sopenharmony_ci			.mii_offset = 0,
36862306a36Sopenharmony_ci			/* Fman TBI operations are handled elsewhere */
36962306a36Sopenharmony_ci		},
37062306a36Sopenharmony_ci	},
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	{},
37362306a36Sopenharmony_ci};
37462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, fsl_pq_mdio_match);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic void set_tbipa(const u32 tbipa_val, struct platform_device *pdev,
37762306a36Sopenharmony_ci		      uint32_t __iomem * (*get_tbipa)(void __iomem *),
37862306a36Sopenharmony_ci		      void __iomem *reg_map, struct resource *reg_res)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
38162306a36Sopenharmony_ci	uint32_t __iomem *tbipa;
38262306a36Sopenharmony_ci	bool tbipa_mapped;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	tbipa = of_iomap(np, 1);
38562306a36Sopenharmony_ci	if (tbipa) {
38662306a36Sopenharmony_ci		tbipa_mapped = true;
38762306a36Sopenharmony_ci	} else {
38862306a36Sopenharmony_ci		tbipa_mapped = false;
38962306a36Sopenharmony_ci		tbipa = (*get_tbipa)(reg_map);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci		/*
39262306a36Sopenharmony_ci		 * Add consistency check to make sure TBI is contained within
39362306a36Sopenharmony_ci		 * the mapped range (not because we would get a segfault,
39462306a36Sopenharmony_ci		 * rather to catch bugs in computing TBI address). Print error
39562306a36Sopenharmony_ci		 * message but continue anyway.
39662306a36Sopenharmony_ci		 */
39762306a36Sopenharmony_ci		if ((void *)tbipa > reg_map + resource_size(reg_res) - 4)
39862306a36Sopenharmony_ci			dev_err(&pdev->dev, "invalid register map (should be at least 0x%04zx to contain TBI address)\n",
39962306a36Sopenharmony_ci				((void *)tbipa - reg_map) + 4);
40062306a36Sopenharmony_ci	}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	iowrite32be(be32_to_cpu(tbipa_val), tbipa);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	if (tbipa_mapped)
40562306a36Sopenharmony_ci		iounmap(tbipa);
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic int fsl_pq_mdio_probe(struct platform_device *pdev)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	const struct of_device_id *id =
41162306a36Sopenharmony_ci		of_match_device(fsl_pq_mdio_match, &pdev->dev);
41262306a36Sopenharmony_ci	const struct fsl_pq_mdio_data *data;
41362306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
41462306a36Sopenharmony_ci	struct resource res;
41562306a36Sopenharmony_ci	struct device_node *tbi;
41662306a36Sopenharmony_ci	struct fsl_pq_mdio_priv *priv;
41762306a36Sopenharmony_ci	struct mii_bus *new_bus;
41862306a36Sopenharmony_ci	int err;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	if (!id) {
42162306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to match device\n");
42262306a36Sopenharmony_ci		return -ENODEV;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	data = id->data;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "found %s compatible node\n", id->compatible);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	new_bus = mdiobus_alloc_size(sizeof(*priv));
43062306a36Sopenharmony_ci	if (!new_bus)
43162306a36Sopenharmony_ci		return -ENOMEM;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	priv = new_bus->priv;
43462306a36Sopenharmony_ci	new_bus->name = "Freescale PowerQUICC MII Bus";
43562306a36Sopenharmony_ci	new_bus->read = &fsl_pq_mdio_read;
43662306a36Sopenharmony_ci	new_bus->write = &fsl_pq_mdio_write;
43762306a36Sopenharmony_ci	new_bus->reset = &fsl_pq_mdio_reset;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	err = of_address_to_resource(np, 0, &res);
44062306a36Sopenharmony_ci	if (err < 0) {
44162306a36Sopenharmony_ci		dev_err(&pdev->dev, "could not obtain address information\n");
44262306a36Sopenharmony_ci		goto error;
44362306a36Sopenharmony_ci	}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%pOFn@%llx", np,
44662306a36Sopenharmony_ci		 (unsigned long long)res.start);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	priv->map = of_iomap(np, 0);
44962306a36Sopenharmony_ci	if (!priv->map) {
45062306a36Sopenharmony_ci		err = -ENOMEM;
45162306a36Sopenharmony_ci		goto error;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	/*
45562306a36Sopenharmony_ci	 * Some device tree nodes represent only the MII registers, and
45662306a36Sopenharmony_ci	 * others represent the MAC and MII registers.  The 'mii_offset' field
45762306a36Sopenharmony_ci	 * contains the offset of the MII registers inside the mapped register
45862306a36Sopenharmony_ci	 * space.
45962306a36Sopenharmony_ci	 */
46062306a36Sopenharmony_ci	if (data->mii_offset > resource_size(&res)) {
46162306a36Sopenharmony_ci		dev_err(&pdev->dev, "invalid register map\n");
46262306a36Sopenharmony_ci		err = -EINVAL;
46362306a36Sopenharmony_ci		goto error;
46462306a36Sopenharmony_ci	}
46562306a36Sopenharmony_ci	priv->regs = priv->map + data->mii_offset;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	new_bus->parent = &pdev->dev;
46862306a36Sopenharmony_ci	platform_set_drvdata(pdev, new_bus);
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (data->get_tbipa) {
47162306a36Sopenharmony_ci		for_each_child_of_node(np, tbi) {
47262306a36Sopenharmony_ci			if (of_node_is_type(tbi, "tbi-phy")) {
47362306a36Sopenharmony_ci				dev_dbg(&pdev->dev, "found TBI PHY node %pOFP\n",
47462306a36Sopenharmony_ci					tbi);
47562306a36Sopenharmony_ci				break;
47662306a36Sopenharmony_ci			}
47762306a36Sopenharmony_ci		}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci		if (tbi) {
48062306a36Sopenharmony_ci			const u32 *prop = of_get_property(tbi, "reg", NULL);
48162306a36Sopenharmony_ci			if (!prop) {
48262306a36Sopenharmony_ci				dev_err(&pdev->dev,
48362306a36Sopenharmony_ci					"missing 'reg' property in node %pOF\n",
48462306a36Sopenharmony_ci					tbi);
48562306a36Sopenharmony_ci				err = -EBUSY;
48662306a36Sopenharmony_ci				goto error;
48762306a36Sopenharmony_ci			}
48862306a36Sopenharmony_ci			set_tbipa(*prop, pdev,
48962306a36Sopenharmony_ci				  data->get_tbipa, priv->map, &res);
49062306a36Sopenharmony_ci		}
49162306a36Sopenharmony_ci	}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	if (data->ucc_configure)
49462306a36Sopenharmony_ci		data->ucc_configure(res.start, res.end);
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	err = of_mdiobus_register(new_bus, np);
49762306a36Sopenharmony_ci	if (err) {
49862306a36Sopenharmony_ci		dev_err(&pdev->dev, "cannot register %s as MDIO bus\n",
49962306a36Sopenharmony_ci			new_bus->name);
50062306a36Sopenharmony_ci		goto error;
50162306a36Sopenharmony_ci	}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	return 0;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cierror:
50662306a36Sopenharmony_ci	if (priv->map)
50762306a36Sopenharmony_ci		iounmap(priv->map);
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	kfree(new_bus);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	return err;
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_cistatic void fsl_pq_mdio_remove(struct platform_device *pdev)
51662306a36Sopenharmony_ci{
51762306a36Sopenharmony_ci	struct device *device = &pdev->dev;
51862306a36Sopenharmony_ci	struct mii_bus *bus = dev_get_drvdata(device);
51962306a36Sopenharmony_ci	struct fsl_pq_mdio_priv *priv = bus->priv;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	mdiobus_unregister(bus);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	iounmap(priv->map);
52462306a36Sopenharmony_ci	mdiobus_free(bus);
52562306a36Sopenharmony_ci}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_cistatic struct platform_driver fsl_pq_mdio_driver = {
52862306a36Sopenharmony_ci	.driver = {
52962306a36Sopenharmony_ci		.name = "fsl-pq_mdio",
53062306a36Sopenharmony_ci		.of_match_table = fsl_pq_mdio_match,
53162306a36Sopenharmony_ci	},
53262306a36Sopenharmony_ci	.probe = fsl_pq_mdio_probe,
53362306a36Sopenharmony_ci	.remove_new = fsl_pq_mdio_remove,
53462306a36Sopenharmony_ci};
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cimodule_platform_driver(fsl_pq_mdio_driver);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
539