I.MX6 千兆网卡设置跟踪
/************************************************************************************ * I.MX6 千兆网卡设置跟踪 * 说明: * 设备只能识别到百兆网卡,跟一下源代码,找一下原因,结果是默认被注释了。 * 2017-4-5 深圳 南山平山村 曾剑锋 ***********************************************************************************/ // 1. Linux内核网络部分控制流 // http://longerzone.blog.51cto.com/6081195/1154807 // 2. linux 内核网络,数据发送流程图 // http://blog.csdn.net/echoisland/article/details/6993756 DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)") /* * i.MX6Q/DL maps system memory at 0x10000000 (offset 256MiB), and * GPU has a limit on physical address that it accesses, which must * be below 2GiB. */ .dma_zone_size = (SZ_2G - SZ_256M), .smp = smp_ops(imx_smp_ops), .map_io = imx6q_map_io, .init_irq = imx6q_init_irq, .init_machine = imx6q_init_machine, ----------------+ .init_late = imx6q_init_late, | .dt_compat = imx6q_dt_compat, | .reserve = imx6q_reserve, | .restart = mxc_restart, | MACHINE_END | | 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(); | } | | 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(); | | | } | | | | | | void __init imx6_enet_mac_init(const char *compatible) <-------------+ | | { | | struct device_node *ocotp_np, *enet_np, *from = NULL; | | void __iomem *base; | | struct property *newmac; | | u32 macaddr_low; | | u32 macaddr_high = 0; | | u32 macaddr1_high = 0; | | u8 *macaddr; | | int i; | | | | for (i = 0; i < 2; i++) { | | enet_np = of_find_compatible_node(from, NULL, compatible); | | if (!enet_np) | | return; | | | | from = enet_np; | | | | if (of_get_mac_address(enet_np)) | | goto put_enet_node; | | | | ocotp_np = of_find_compatible_node(NULL, --------+ | | NULL, "fsl,imx6q-ocotp"); | | | if (!ocotp_np) { | | | pr_warn("failed to find ocotp node\n"); | | | goto put_enet_node; | | | } | | | | | | base = of_iomap(ocotp_np, 0); | | | if (!base) { | | | pr_warn("failed to map ocotp\n"); | | | goto put_ocotp_node; | | | } | | | | | | macaddr_low = readl_relaxed(base + OCOTP_MACn(1)); | | | if (i) | | | macaddr1_high = readl_relaxed(base + OCOTP_MACn(2)); | | | else | | | macaddr_high = readl_relaxed(base + OCOTP_MACn(0)); | | | | | | newmac = kzalloc(sizeof(*newmac) + 6, GFP_KERNEL); | | | if (!newmac) | | | goto put_ocotp_node; | | | | | | newmac->value = newmac + 1; | | | newmac->length = 6; | | | newmac->name = kstrdup("local-mac-address", GFP_KERNEL); | | | if (!newmac->name) { | | | kfree(newmac); | | | goto put_ocotp_node; | | | } | | | | | | macaddr = newmac->value; | | | if (i) { | | | macaddr[5] = (macaddr_low >> 16) & 0xff; | | | macaddr[4] = (macaddr_low >> 24) & 0xff; | | | macaddr[3] = macaddr1_high & 0xff; | | | macaddr[2] = (macaddr1_high >> 8) & 0xff; | | | macaddr[1] = (macaddr1_high >> 16) & 0xff; | | | macaddr[0] = (macaddr1_high >> 24) & 0xff; | | | } else { | | | macaddr[5] = macaddr_high & 0xff; | | | macaddr[4] = (macaddr_high >> 8) & 0xff; | | | macaddr[3] = (macaddr_high >> 16) & 0xff; | | | macaddr[2] = (macaddr_high >> 24) & 0xff; | | | macaddr[1] = macaddr_low & 0xff; | | | macaddr[0] = (macaddr_low >> 8) & 0xff; | | | } | | | | | | of_update_property(enet_np, newmac); | | | | | | put_ocotp_node: | | | of_node_put(ocotp_np); | | | put_enet_node: | | | of_node_put(enet_np); | | | } | | | } +---------------------------------------+ | | V | | struct device_node *of_find_compatible_node(struct device_node *from, | | const char *type, const char *compatible) | | { | | struct device_node *np; | | unsigned long flags; | | | | raw_spin_lock_irqsave(&devtree_lock, flags); | | np = from ? from->allnext : of_allnodes; -------------------+ | | for (; np; np = np->allnext) { | | | if (__of_device_is_compatible(np, compatible, type, NULL) && | | | of_node_get(np)) | | | break; | | | } | | | of_node_put(from); | | | raw_spin_unlock_irqrestore(&devtree_lock, flags); | | | return np; | | | } | | | EXPORT_SYMBOL(of_find_compatible_node); | | | | | | struct device_node *of_allnodes; <---------------------------+ | | EXPORT_SYMBOL(of_allnodes); ------------+ | | | | | void __init unflatten_device_tree(void) | | | { V | | __unflatten_device_tree(initial_boot_params, &of_allnodes, ---------+ | | early_init_dt_alloc_memory_arch); ---------*-+ | | | | | | /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ | | | | of_alias_scan(early_init_dt_alloc_memory_arch); ---------*-*-+ | | } | | | | | | | | | | static void __unflatten_device_tree(struct boot_param_header *blob, <-----+ | | | | struct device_node **mynodes, | | | | void * (*dt_alloc)(u64 size, u64 align)) | | | | { | | | | unsigned long size; | | | | int start; | | | | void *mem; | | | | struct device_node **allnextp = mynodes; | | | | | | | | pr_debug(" -> unflatten_device_tree()\n"); | | | | | | | | if (!blob) { | | | | pr_debug("No device tree pointer\n"); | | | | return; | | | | } | | | | | | | | pr_debug("Unflattening device tree:\n"); | | | | pr_debug("magic: %08x\n", be32_to_cpu(blob->magic)); | | | | pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize)); | | | | pr_debug("version: %08x\n", be32_to_cpu(blob->version)); | | | | | | | | if (be32_to_cpu(blob->magic) != OF_DT_HEADER) { | | | | pr_err("Invalid device tree blob header\n"); | | | | return; | | | | } | | | | | | | | /* First pass, scan for size */ | | | | start = 0; | | | | size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0); | | | | size = ALIGN(size, 4); | | | | | | | | pr_debug(" size is %lx, allocating...\n", size); | | | | | | | | /* Allocate memory for the expanded device tree */ | | | | mem = dt_alloc(size + 4, __alignof__(struct device_node)); | | | | memset(mem, 0, size); | | | | | | | | *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); | | | | | | | | pr_debug(" unflattening %p...\n", mem); | | | | | | | | /* Second pass, do actual unflattening */ | | | | start = 0; | | | | unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0); | | | | if (be32_to_cpup(mem + size) != 0xdeadbeef) | | | | pr_warning("End of tree marker overwritten: %08x\n", | | | | be32_to_cpup(mem + size)); | | | | *allnextp = NULL; | | | | | | | | pr_debug(" <- unflatten_device_tree()\n"); | | | | } | | | | | | | | void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align) <--------+ | | | { | | | return memblock_virt_alloc(size, align); | | | } | | | | | | static inline void * __init memblock_virt_alloc( <---------------+ | | phys_addr_t size, phys_addr_t align) | | { | | return memblock_virt_alloc_try_nid(size, align, BOOTMEM_LOW_LIMIT, -----+ | | BOOTMEM_ALLOC_ACCESSIBLE, | | | NUMA_NO_NODE); | | | } | | | | | | void * __init memblock_virt_alloc_try_nid( <-----------+ | | phys_addr_t size, phys_addr_t align, | | phys_addr_t min_addr, phys_addr_t max_addr, | | int nid) | | { | | void *ptr; | | | | memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=0x%llx max_addr=0x%llx %pF\n", | | __func__, (u64)size, (u64)align, nid, (u64)min_addr, | | (u64)max_addr, (void *)_RET_IP_); | | ptr = memblock_virt_alloc_internal(size, align, | | min_addr, max_addr, nid); | | if (ptr) | | return ptr; | | | | panic("%s: Failed to allocate %llu bytes align=0x%llx nid=%d from=0x%llx max_addr=0x%llx\n",| | __func__, (u64)size, (u64)align, nid, (u64)min_addr, | | (u64)max_addr); | | return NULL; | | } | | | | 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 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); | | | | | | static struct platform_device_id fec_devtype[] = { <------+ | | { | | /* keep it for coldfire */ | | .name = DRIVER_NAME, | | .driver_data = 0, | | }, { | | .name = "imx25-fec", | | .driver_data = FEC_QUIRK_USE_GASKET, | | }, { | | .name = "imx27-fec", | | .driver_data = 0, | | }, { | | .name = "imx28-fec", | | .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME, | | }, { | | .name = "imx6q-fec", <----------*-------------------+ .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | -----------*------+ FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM | | | FEC_QUIRK_HAS_VLAN | FEC_QUIRK_ERR006358 | | | FEC_QUIRK_BUG_WAITMODE, | | }, { | | .name = "mvf600-fec", | | .driver_data = FEC_QUIRK_ENET_MAC, | | }, { | | .name = "imx6sx-fec", | | .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | | | FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM | | | FEC_QUIRK_HAS_VLAN | FEC_QUIRK_HAS_AVB | | | FEC_QUIRK_ERR007885 | FEC_QUIRK_TKT210590, | | }, { | | .name = "imx6ul-fec", | | .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | | | FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM | | | FEC_QUIRK_HAS_VLAN, | | }, { | | /* sentinel */ | | } | | }; | | MODULE_DEVICE_TABLE(platform, fec_devtype); | | | | 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; | | | } | | | | | | 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; | //printk("FEC_QUIRK_HAS_GBIT\n"); | #if !defined(CONFIG_M5272) | //phy_dev->supported |= SUPPORTED_Pause; | #endif | //} | //else | { | printk("PHY_BASIC_FEATURES\n"); | 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; | } | | static int fec_enet_mii_init(struct platform_device *pdev) <------------------+ { static struct mii_bus *fec0_mii_bus; static int *fec_mii_bus_share; struct net_device *ndev = platform_get_drvdata(pdev); struct fec_enet_private *fep = netdev_priv(ndev); struct device_node *node; int err = -ENXIO, i; u32 mii_speed, holdtime; /* * The dual fec interfaces are not equivalent with enet-mac. * Here are the differences: * * - fec0 supports MII & RMII modes while fec1 only supports RMII * - fec0 acts as the 1588 time master while fec1 is slave * - external phys can only be configured by fec0 * * That is to say fec1 can not work independently. It only works * when fec0 is working. The reason behind this design is that the * second interface is added primarily for Switch mode. * * Because of the last point above, both phys are attached on fec0 * mdio interface in board design, and need to be configured by * fec0 mii_bus. */ if ((fep->quirks & FEC_QUIRK_ENET_MAC) && fep->dev_id > 0) { /* fec1 uses fec0 mii_bus */ if (mii_cnt && fec0_mii_bus) { fep->mii_bus = fec0_mii_bus; *fec_mii_bus_share = FEC0_MII_BUS_SHARE_TRUE; mii_cnt++; return 0; } return -ENOENT; } fep->mii_timeout = 0; /* * Set MII speed to 2.5 MHz (= clk_get_rate() / 2 * phy_speed) * * The formula for FEC MDC is 'ref_freq / (MII_SPEED x 2)' while * for ENET-MAC is 'ref_freq / ((MII_SPEED + 1) x 2)'. The i.MX28 * Reference Manual has an error on this, and gets fixed on i.MX6Q * document. */ mii_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000); if (fep->quirks & FEC_QUIRK_ENET_MAC) mii_speed--; if (mii_speed > 63) { dev_err(&pdev->dev, "fec clock (%lu) to fast to get right mii speed\n", clk_get_rate(fep->clk_ipg)); err = -EINVAL; goto err_out; } /* * The i.MX28 and i.MX6 types have another filed in the MSCR (aka * MII_SPEED) register that defines the MDIO output hold time. Earlier * versions are RAZ there, so just ignore the difference and write the * register always. * The minimal hold time according to IEE802.3 (clause 22) is 10 ns. * HOLDTIME + 1 is the number of clk cycles the fec is holding the * output. * The HOLDTIME bitfield takes values between 0 and 7 (inclusive). * Given that ceil(clkrate / 5000000) <= 64, the calculation for * holdtime cannot result in a value greater than 3. */ holdtime = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 100000000) - 1; fep->phy_speed = mii_speed << 1 | holdtime << 8; writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED); fep->mii_bus = mdiobus_alloc(); if (fep->mii_bus == NULL) { err = -ENOMEM; goto err_out; } fep->mii_bus->name = "fec_enet_mii_bus"; fep->mii_bus->read = fec_enet_mdio_read; fep->mii_bus->write = fec_enet_mdio_write; snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x", pdev->name, fep->dev_id + 1); fep->mii_bus->priv = fep; fep->mii_bus->parent = &pdev->dev; fep->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); if (!fep->mii_bus->irq) { err = -ENOMEM; goto err_out_free_mdiobus; } for (i = 0; i < PHY_MAX_ADDR; i++) fep->mii_bus->irq[i] = PHY_POLL; node = of_get_child_by_name(pdev->dev.of_node, "mdio"); if (node) { err = of_mdiobus_register(fep->mii_bus, node); of_node_put(node); } else { err = mdiobus_register(fep->mii_bus); } if (err) goto err_out_free_mdio_irq; mii_cnt++; /* save fec0 mii_bus */ if (fep->quirks & FEC_QUIRK_ENET_MAC) { fec0_mii_bus = fep->mii_bus; fec_mii_bus_share = &fep->mii_bus_share; } return 0; err_out_free_mdio_irq: kfree(fep->mii_bus->irq); err_out_free_mdiobus: mdiobus_free(fep->mii_bus); err_out: return err; }