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, ®s->miimadd); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* Write out the value we want */ 10862306a36Sopenharmony_ci iowrite32be(value, ®s->miimcon); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Wait for the transaction to finish */ 11162306a36Sopenharmony_ci timeout = MII_TIMEOUT; 11262306a36Sopenharmony_ci while ((ioread32be(®s->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, ®s->miimadd); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* Clear miimcom, and then initiate a read */ 14162306a36Sopenharmony_ci iowrite32be(0, ®s->miimcom); 14262306a36Sopenharmony_ci iowrite32be(MII_READ_COMMAND, ®s->miimcom); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* Wait for the transaction to finish, normally less than 100us */ 14562306a36Sopenharmony_ci timeout = MII_TIMEOUT; 14662306a36Sopenharmony_ci while ((ioread32be(®s->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(®s->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, ®s->miimcfg); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* Setup the MII Mgmt clock speed */ 17562306a36Sopenharmony_ci iowrite32be(MIIMCFG_INIT_VALUE, ®s->miimcfg); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Wait until the bus is free */ 17862306a36Sopenharmony_ci timeout = MII_TIMEOUT; 17962306a36Sopenharmony_ci while ((ioread32be(®s->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