I.MX6 PHY fixup 调用流程 hacking
/********************************************************************************** * I.MX6 PHY fixup 调用流程 hacking * 说明: * 跟一下i.MX6中对PHY进行fixup的代码是如何被调用的。 * * 2017-4-14 深圳 龙华民治樟坑村 曾剑锋 *********************************************************************************/ static struct platform_driver fec_driver = { <-----+ .driver = { | .name = DRIVER_NAME, | .owner = THIS_MODULE, | .pm = &fec_pm_ops, | .of_match_table = fec_dt_ids, | }, | .id_table = fec_devtype, ---------*-+ .probe = fec_probe, | | .remove = fec_drv_remove, | | }; | | | | module_platform_driver(fec_driver); ---------+ | | MODULE_ALIAS("platform:"DRIVER_NAME); | MODULE_LICENSE("GPL"); | | static int | fec_probe(struct platform_device *pdev) <----------+ { struct fec_enet_private *fep; struct fec_platform_data *pdata; struct net_device *ndev; int i, irq, ret = 0; struct resource *r; const struct of_device_id *of_id; static int dev_id; struct device_node *np = pdev->dev.of_node, *phy_node; int num_tx_qs; int num_rx_qs; fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs); /* Init network device */ ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private), num_tx_qs, num_rx_qs); if (!ndev) return -ENOMEM; SET_NETDEV_DEV(ndev, &pdev->dev); /* setup board info structure */ fep = netdev_priv(ndev); of_id = of_match_device(fec_dt_ids, &pdev->dev); if (of_id) pdev->id_entry = of_id->data; fep->quirks = pdev->id_entry->driver_data; fep->netdev = ndev; fep->num_rx_queues = num_rx_qs; fep->num_tx_queues = num_tx_qs; #if !defined(CONFIG_M5272) /* default enable pause frame auto negotiation */ if (fep->quirks & FEC_QUIRK_HAS_GBIT) fep->pause_flag |= FEC_PAUSE_FLAG_AUTONEG; #endif /* Select default pin state */ pinctrl_pm_select_default_state(&pdev->dev); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); fep->hwp = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(fep->hwp)) { ret = PTR_ERR(fep->hwp); goto failed_ioremap; } fep->pdev = pdev; fep->dev_id = dev_id++; platform_set_drvdata(pdev, ndev); fec_enet_of_parse_stop_mode(pdev); if (of_get_property(np, "fsl,magic-packet", NULL)) fep->wol_flag |= FEC_WOL_HAS_MAGIC_PACKET; phy_node = of_parse_phandle(np, "phy-handle", 0); if (!phy_node && of_phy_is_fixed_link(np)) { ret = of_phy_register_fixed_link(np); if (ret < 0) { dev_err(&pdev->dev, "broken fixed-link specification\n"); goto failed_phy; } phy_node = of_node_get(np); } fep->phy_node = phy_node; ret = of_get_phy_mode(pdev->dev.of_node); if (ret < 0) { pdata = dev_get_platdata(&pdev->dev); if (pdata) fep->phy_interface = pdata->phy; else fep->phy_interface = PHY_INTERFACE_MODE_MII; } else { fep->phy_interface = ret; } fep->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(fep->clk_ipg)) { ret = PTR_ERR(fep->clk_ipg); goto failed_clk; } fep->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); if (IS_ERR(fep->clk_ahb)) { ret = PTR_ERR(fep->clk_ahb); goto failed_clk; } fep->itr_clk_rate = clk_get_rate(fep->clk_ahb); /* enet_out is optional, depends on board */ fep->clk_enet_out = devm_clk_get(&pdev->dev, "enet_out"); if (IS_ERR(fep->clk_enet_out)) fep->clk_enet_out = NULL; fep->ptp_clk_on = false; mutex_init(&fep->ptp_clk_mutex); /* clk_ref is optional, depends on board */ fep->clk_ref = devm_clk_get(&pdev->dev, "enet_clk_ref"); if (IS_ERR(fep->clk_ref)) fep->clk_ref = NULL; fep->bufdesc_ex = fep->quirks & FEC_QUIRK_HAS_BUFDESC_EX; fep->clk_ptp = devm_clk_get(&pdev->dev, "ptp"); if (IS_ERR(fep->clk_ptp)) { fep->clk_ptp = NULL; fep->bufdesc_ex = false; } pm_runtime_enable(&pdev->dev); ret = fec_enet_clk_enable(ndev, true); if (ret) goto failed_clk; fep->reg_phy = devm_regulator_get(&pdev->dev, "phy"); if (!IS_ERR(fep->reg_phy)) { ret = regulator_enable(fep->reg_phy); if (ret) { dev_err(&pdev->dev, "Failed to enable phy regulator: %d\n", ret); goto failed_regulator; } } else { fep->reg_phy = NULL; } fec_reset_phy(pdev); if (fep->bufdesc_ex) fec_ptp_init(pdev); ret = fec_enet_init(ndev); ----------------------------+ if (ret) | goto failed_init; | | for (i = 0; i < FEC_IRQ_NUM; i++) { | irq = platform_get_irq(pdev, i); | if (irq < 0) { | if (i) | break; | ret = irq; | goto failed_irq; | } | ret = devm_request_irq(&pdev->dev, irq, fec_enet_interrupt, | 0, pdev->name, ndev); | if (ret) | goto failed_irq; | | fep->irq[i] = irq; | } | | ret = of_property_read_u32(np, "fsl,wakeup_irq", &irq); | if (!ret && irq < FEC_IRQ_NUM) | fep->wake_irq = fep->irq[irq]; | else | fep->wake_irq = fep->irq[0]; | | init_completion(&fep->mdio_done); | ret = fec_enet_mii_init(pdev); | if (ret) | goto failed_mii_init; | | /* Carrier starts down, phylib will bring it up */ | netif_carrier_off(ndev); | fec_enet_clk_enable(ndev, false); | pinctrl_pm_select_sleep_state(&pdev->dev); | | ret = register_netdev(ndev); | if (ret) | goto failed_register; | | device_init_wakeup(&ndev->dev, fep->wol_flag & | FEC_WOL_HAS_MAGIC_PACKET); | | if (fep->bufdesc_ex && fep->ptp_clock) | netdev_info(ndev, "registered PHC device %d\n", fep->dev_id); | | fep->rx_copybreak = COPYBREAK_DEFAULT; | INIT_WORK(&fep->tx_timeout_work, fec_enet_timeout_work); | return 0; | | failed_register: | fec_enet_mii_remove(fep); | failed_mii_init: | failed_irq: | failed_init: | if (fep->reg_phy) | regulator_disable(fep->reg_phy); | failed_regulator: | fec_enet_clk_enable(ndev, false); | failed_clk: | failed_phy: | of_node_put(phy_node); | failed_ioremap: | free_netdev(ndev); | | return ret; | } | | /* | * XXX: We need to clean up on failure exits here. | * | */ | static int fec_enet_init(struct net_device *ndev) <-----------------+ { struct fec_enet_private *fep = netdev_priv(ndev); struct fec_enet_priv_tx_q *txq; struct fec_enet_priv_rx_q *rxq; struct bufdesc *cbd_base; dma_addr_t bd_dma; int bd_size; unsigned int i; #if defined(CONFIG_ARM) fep->rx_align = 0xf; fep->tx_align = 0xf; #else fep->rx_align = 0x3; fep->tx_align = 0x3; #endif fec_enet_alloc_queue(ndev); if (fep->bufdesc_ex) fep->bufdesc_size = sizeof(struct bufdesc_ex); else fep->bufdesc_size = sizeof(struct bufdesc); bd_size = (fep->total_tx_ring_size + fep->total_rx_ring_size) * fep->bufdesc_size; /* Allocate memory for buffer descriptors. */ cbd_base = dma_alloc_coherent(NULL, bd_size, &bd_dma, GFP_KERNEL); if (!cbd_base) { return -ENOMEM; } memset(cbd_base, 0, bd_size); /* Get the Ethernet address */ fec_get_mac(ndev); /* make sure MAC we just acquired is programmed into the hw */ fec_set_mac_address(ndev, NULL); /* Set receive and transmit descriptor base. */ for (i = 0; i < fep->num_rx_queues; i++) { rxq = fep->rx_queue[i]; rxq->index = i; rxq->rx_bd_base = (struct bufdesc *)cbd_base; rxq->bd_dma = bd_dma; if (fep->bufdesc_ex) { bd_dma += sizeof(struct bufdesc_ex) * rxq->rx_ring_size; cbd_base = (struct bufdesc *) (((struct bufdesc_ex *)cbd_base) + rxq->rx_ring_size); } else { bd_dma += sizeof(struct bufdesc) * rxq->rx_ring_size; cbd_base += rxq->rx_ring_size; } } for (i = 0; i < fep->num_tx_queues; i++) { txq = fep->tx_queue[i]; txq->index = i; txq->tx_bd_base = (struct bufdesc *)cbd_base; txq->bd_dma = bd_dma; if (fep->bufdesc_ex) { bd_dma += sizeof(struct bufdesc_ex) * txq->tx_ring_size; cbd_base = (struct bufdesc *) (((struct bufdesc_ex *)cbd_base) + txq->tx_ring_size); } else { bd_dma += sizeof(struct bufdesc) * txq->tx_ring_size; cbd_base += txq->tx_ring_size; } } /* The FEC Ethernet specific entries in the device structure */ ndev->watchdog_timeo = TX_TIMEOUT; ndev->netdev_ops = &fec_netdev_ops; --------------------+ ndev->ethtool_ops = &fec_enet_ethtool_ops; | | writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK); | netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT); | | if (fep->quirks & FEC_QUIRK_HAS_VLAN) | /* enable hw VLAN support */ | ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; | | if (fep->quirks & FEC_QUIRK_HAS_CSUM) { | ndev->gso_max_segs = FEC_MAX_TSO_SEGS; | | /* enable hw accelerator */ | ndev->features |= (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | | NETIF_F_RXCSUM | NETIF_F_SG | NETIF_F_TSO); | fep->csum_flags |= FLAG_RX_CSUM_ENABLED; | } | | if (fep->quirks & FEC_QUIRK_HAS_AVB) { | fep->tx_align = 0; | fep->rx_align = 0x3f; | } | | ndev->hw_features = ndev->features; | | fec_restart(ndev); | | return 0; | } | | static const struct net_device_ops fec_netdev_ops = { <-----------+ .ndo_open = fec_enet_open, ---------+ .ndo_stop = fec_enet_close, | .ndo_start_xmit = fec_enet_start_xmit, | .ndo_select_queue = fec_enet_select_queue, | .ndo_set_rx_mode = set_multicast_list, | .ndo_change_mtu = eth_change_mtu, | .ndo_validate_addr = eth_validate_addr, | .ndo_tx_timeout = fec_timeout, | .ndo_set_mac_address = fec_set_mac_address, | .ndo_do_ioctl = fec_enet_ioctl, | #ifdef CONFIG_NET_POLL_CONTROLLER | .ndo_poll_controller = fec_poll_controller, | #endif | .ndo_set_features = fec_set_features, | }; | | static int | fec_enet_open(struct net_device *ndev) <--------+ { struct fec_enet_private *fep = netdev_priv(ndev); const struct platform_device_id *id_entry = platform_get_device_id(fep->pdev); int ret; pinctrl_pm_select_default_state(&fep->pdev->dev); ret = fec_enet_clk_enable(ndev, true); if (ret) return ret; /* I should reset the ring buffers here, but I don't yet know * a simple way to do that. */ ret = fec_enet_alloc_buffers(ndev); if (ret) goto err_enet_alloc; /* Init MAC firstly for suspend/resume with megafix off case */ fec_restart(ndev); /* Probe and connect to PHY when open the interface */ ret = fec_enet_mii_probe(ndev); -----+ if (ret) | goto err_enet_mii_probe; | | napi_enable(&fep->napi); | phy_start(fep->phy_dev); | netif_tx_start_all_queues(ndev); | | pm_runtime_get_sync(ndev->dev.parent); | if ((id_entry->driver_data & FEC_QUIRK_BUG_WAITMODE) && | !fec_enet_irq_workaround(fep)) | pm_qos_add_request(&ndev->pm_qos_req, | PM_QOS_CPU_DMA_LATENCY, | 0); | else | pm_qos_add_request(&ndev->pm_qos_req, | PM_QOS_CPU_DMA_LATENCY, | PM_QOS_DEFAULT_VALUE); | | device_set_wakeup_enable(&ndev->dev, fep->wol_flag & | FEC_WOL_FLAG_ENABLE); | fep->miibus_up_failed = false; | | return 0; | | err_enet_mii_probe: | fec_enet_free_buffers(ndev); | err_enet_alloc: | fep->miibus_up_failed = true; | if (!fep->mii_bus_share) | pinctrl_pm_select_sleep_state(&fep->pdev->dev); | return ret; | } | | static int fec_enet_mii_probe(struct net_device *ndev) <----+ { struct fec_enet_private *fep = netdev_priv(ndev); struct phy_device *phy_dev = NULL; char mdio_bus_id[MII_BUS_ID_SIZE]; char phy_name[MII_BUS_ID_SIZE + 3]; int phy_id; int dev_id = fep->dev_id; fep->phy_dev = NULL; if (fep->phy_node) { phy_dev = of_phy_connect(ndev, fep->phy_node, &fec_enet_adjust_link, 0, fep->phy_interface); if (!phy_dev) return -ENODEV; } else { /* check for attached phy */ for (phy_id = 0; (phy_id < PHY_MAX_ADDR); phy_id++) { if ((fep->mii_bus->phy_mask & (1 << phy_id))) continue; if (fep->mii_bus->phy_map[phy_id] == NULL) continue; if (fep->mii_bus->phy_map[phy_id]->phy_id == 0) continue; if (dev_id--) continue; strlcpy(mdio_bus_id, fep->mii_bus->id, MII_BUS_ID_SIZE); break; } if (phy_id >= PHY_MAX_ADDR) { netdev_info(ndev, "no PHY, assuming direct connection to switch\n"); strlcpy(mdio_bus_id, "fixed-0", MII_BUS_ID_SIZE); phy_id = 0; } snprintf(phy_name, sizeof(phy_name), PHY_ID_FMT, mdio_bus_id, phy_id); phy_dev = phy_connect(ndev, phy_name, &fec_enet_adjust_link, ---------------+ fep->phy_interface); | } | | if (IS_ERR(phy_dev)) { | netdev_err(ndev, "could not attach to PHY\n"); | return PTR_ERR(phy_dev); | } | | /* mask with MAC supported features */ | if (fep->quirks & FEC_QUIRK_HAS_GBIT) { | phy_dev->supported &= PHY_GBIT_FEATURES; | // phy_dev->supported &= ~SUPPORTED_1000baseT_Half; | phy_dev->supported |= SUPPORTED_Pause; | // phy_dev->supported |= SUPPORTED_1000baseT_Half; | printk("FEC_QUIRK_HAS_GBIT\n"); | #if !defined(CONFIG_M5272) | phy_dev->supported |= SUPPORTED_Pause; | #endif | phy_dev->advertising = phy_dev->supported; | } | else | { | printk("PHY_BASIC_FEATURES\n"); | // phy_dev->supported &= PHY_BASIC_FEATURES; | phy_dev->advertising = phy_dev->supported & PHY_BASIC_FEATURES; | } | // phy_dev->advertising = phy_dev->supported; | | fep->phy_dev = phy_dev; | fep->link = 0; | fep->full_duplex = 0; | | netdev_info(ndev, "Freescale FEC PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n", | fep->phy_dev->drv->name, dev_name(&fep->phy_dev->dev), | fep->phy_dev->irq); | | return 0; | } | | struct phy_device *phy_connect(struct net_device *dev, const char *bus_id, <--------+ void (*handler)(struct net_device *), phy_interface_t interface) { struct phy_device *phydev; struct device *d; int rc; /* Search the list of PHY devices on the mdio bus for the * PHY with the requested name */ d = bus_find_device_by_name(&mdio_bus_type, NULL, bus_id); if (!d) { pr_err("PHY %s not found\n", bus_id); return ERR_PTR(-ENODEV); } phydev = to_phy_device(d); rc = phy_connect_direct(dev, phydev, handler, interface); --------------+ if (rc) | return ERR_PTR(rc); | | return phydev; | } | EXPORT_SYMBOL(phy_connect); | | int phy_connect_direct(struct net_device *dev, struct phy_device *phydev, <----+ void (*handler)(struct net_device *), phy_interface_t interface) { int rc; rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface); --------+ if (rc) | return rc; | | phy_prepare_link(phydev, handler); | phy_start_machine(phydev); | if (phydev->irq > 0) | phy_start_interrupts(phydev); | | return 0; | } | EXPORT_SYMBOL(phy_connect_direct); | | int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, <-------+ u32 flags, phy_interface_t interface) { struct device *d = &phydev->dev; int err; /* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. */ if (NULL == d->driver) { if (phydev->is_c45) d->driver = &genphy_driver[GENPHY_DRV_10G].driver; else d->driver = &genphy_driver[GENPHY_DRV_1G].driver; err = d->driver->probe(d); if (err >= 0) err = device_bind_driver(d); if (err) return err; } if (phydev->attached_dev) { dev_err(&dev->dev, "PHY already attached\n"); return -EBUSY; } phydev->attached_dev = dev; dev->phydev = phydev; phydev->dev_flags = flags; phydev->interface = interface; phydev->state = PHY_READY; /* Do initial configuration here, now that * we have certain key parameters * (dev_flags and interface) */ err = phy_init_hw(phydev); --------------------+ if (err) | phy_detach(phydev); | else | phy_resume(phydev); | | return err; | } | EXPORT_SYMBOL(phy_attach_direct); | | int phy_init_hw(struct phy_device *phydev) <------------+ { int ret; if (!phydev->drv || !phydev->drv->config_init) return 0; ret = phy_write(phydev, MII_BMCR, BMCR_RESET); if (ret < 0) return ret; ret = phy_poll_reset(phydev); if (ret < 0) return ret; ret = phy_scan_fixups(phydev); -------------+ if (ret < 0) | return ret; | | return phydev->drv->config_init(phydev); | } | EXPORT_SYMBOL(phy_init_hw); | | /* Runs any matching fixups for this phydev */ | int phy_scan_fixups(struct phy_device *phydev) <--------+ { struct phy_fixup *fixup; mutex_lock(&phy_fixup_lock); list_for_each_entry(fixup, &phy_fixup_list, list) { ------------------+ if (phy_needs_fixup(phydev, fixup)) { --------+ | int err = fixup->run(phydev); | | | | if (err < 0) { | | mutex_unlock(&phy_fixup_lock); | | return err; | | } | | } | | } | | mutex_unlock(&phy_fixup_lock); | | | | return 0; | | } | | EXPORT_SYMBOL(phy_scan_fixups); | | v--------------------------------------+ | static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) | { | if (strcmp(fixup->bus_id, dev_name(&phydev->dev)) != 0) | if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) | return 0; | | if ((fixup->phy_uid & fixup->phy_uid_mask) != | (phydev->phy_id & fixup->phy_uid_mask)) | if (fixup->phy_uid != PHY_ANY_UID) | return 0; | | return 1; | } | | static LIST_HEAD(phy_fixup_list); <-----------------+ | int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, ----*-+ int (*run)(struct phy_device *)) | | { | | struct phy_fixup *fixup = kzalloc(sizeof(*fixup), GFP_KERNEL); | | | | if (!fixup) | | return -ENOMEM; | | | | strlcpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); | | fixup->phy_uid = phy_uid; | | fixup->phy_uid_mask = phy_uid_mask; | | fixup->run = run; | | | | mutex_lock(&phy_fixup_lock); | | list_add_tail(&fixup->list, &phy_fixup_list); <-----------------+ | mutex_unlock(&phy_fixup_lock); | | return 0; | } | EXPORT_SYMBOL(phy_register_fixup); | | static void __init imx6q_enet_phy_init(void) -------------------*-+ { | | if (IS_BUILTIN(CONFIG_PHYLIB)) { | | phy_register_fixup_for_uid(PHY_ID_KSZ9021, MICREL_PHY_ID_MASK, | | ksz9021rn_phy_fixup); | | phy_register_fixup_for_uid(PHY_ID_KSZ9031, MICREL_PHY_ID_MASK, | | ksz9031rn_phy_fixup); | | phy_register_fixup_for_uid(PHY_ID_AR8031, 0xffffffff, <-------------+ | ar8031_phy_fixup); --------------+ | phy_register_fixup_for_uid(PHY_ID_AR8035, 0xffffffef, | | ar8035_phy_fixup); | | } | | } | | | | static int ar8031_phy_fixup(struct phy_device *dev) <-------------+ | { | u16 val; | | /* Set RGMII IO voltage to 1.8V */ | phy_write(dev, 0x1d, 0x1f); | phy_write(dev, 0x1e, 0x8); | | /* disable phy AR8031 SmartEEE function. */ | phy_write(dev, 0xd, 0x3); | phy_write(dev, 0xe, 0x805d); | phy_write(dev, 0xd, 0x4003); | val = phy_read(dev, 0xe); | val &= ~(0x1 << 8); | phy_write(dev, 0xe, val); | | /* To enable AR8031 output a 125MHz clk from CLK_25M */ | phy_write(dev, 0xd, 0x7); | phy_write(dev, 0xe, 0x8016); | phy_write(dev, 0xd, 0x4007); | | val = phy_read(dev, 0xe); | val &= 0xffe3; | val |= 0x18; | phy_write(dev, 0xe, val); | | /* introduce tx clock delay */ | phy_write(dev, 0x1d, 0x5); | val = phy_read(dev, 0x1e); | val |= 0x0100; | phy_write(dev, 0x1e, val); | | return 0; | } | | static inline void imx6q_enet_init(void) -----------------------------------*-+ { | | imx6_enet_mac_init("fsl,imx6q-fec"); | | imx6q_enet_phy_init(); <-----------------------------------+ | imx6q_1588_init(); | if (cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0) | imx6q_enet_clk_sel(); | imx6q_enet_plt_init(); | } | | static void __init imx6q_init_machine(void) | { | struct device *parent; | | if (cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0) | imx_print_silicon_rev("i.MX6QP", IMX_CHIP_REVISION_1_0); | else | imx_print_silicon_rev(cpu_is_imx6dl() ? "i.MX6DL" : "i.MX6Q", | imx_get_soc_revision()); | | mxc_arch_reset_init_dt(); | | parent = imx_soc_device_init(); | if (parent == NULL) | pr_warn("failed to initialize soc device\n"); | | of_platform_populate(NULL, of_default_bus_match_table, | imx6q_auxdata_lookup, parent); | | imx6q_enet_init(); <---------------------------+ imx_anatop_init(); imx6q_csi_mux_init(); cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init(); imx6q_mini_pcie_init(); }