diff options
-rw-r--r-- | Documentation/devicetree/bindings/net/hisilicon-hix5hd2-gmac.txt | 27 | ||||
-rw-r--r-- | arch/arm/boot/dts/hisi-x5hd2.dtsi | 6 | ||||
-rw-r--r-- | drivers/net/ethernet/hisilicon/hix5hd2_gmac.c | 352 |
3 files changed, 352 insertions, 33 deletions
diff --git a/Documentation/devicetree/bindings/net/hisilicon-hix5hd2-gmac.txt b/Documentation/devicetree/bindings/net/hisilicon-hix5hd2-gmac.txt index 75d398bb1fbb..063c02da018a 100644 --- a/Documentation/devicetree/bindings/net/hisilicon-hix5hd2-gmac.txt +++ b/Documentation/devicetree/bindings/net/hisilicon-hix5hd2-gmac.txt @@ -1,7 +1,12 @@ Hisilicon hix5hd2 gmac controller Required properties: -- compatible: should be "hisilicon,hix5hd2-gmac". +- compatible: should contain one of the following SoC strings: + * "hisilicon,hix5hd2-gemac" + * "hisilicon,hi3798cv200-gemac" + and one of the following version string: + * "hisilicon,hisi-gemac-v1" + * "hisilicon,hisi-gemac-v2" - reg: specifies base physical address(s) and size of the device registers. The first region is the MAC register base and size. The second region is external interface control register. @@ -12,6 +17,16 @@ Required properties: - phy-handle: see ethernet.txt [1]. - mac-address: see ethernet.txt [1]. - clocks: clock phandle and specifier pair. +- clock-names: contain the clock name "mac_core"(required) and "mac_ifc"(optional). +- resets: should contain the phandle to the MAC core reset signal(optional), + the MAC interface reset signal(optional) + and the PHY reset signal(optional). +- reset-names: contain the reset signal name "mac_core"(optional), + "mac_ifc"(optional) and "phy"(optional). +- hisilicon,phy-reset-delays-us: triplet of delays if PHY reset signal given. + The 1st cell is reset pre-delay in micro seconds. + The 2nd cell is reset pulse in micro seconds. + The 3rd cell is reset post-delay in micro seconds. - PHY subnode: inherits from phy binding [2] @@ -20,15 +35,19 @@ Required properties: Example: gmac0: ethernet@f9840000 { - compatible = "hisilicon,hix5hd2-gmac"; + compatible = "hisilicon,hi3798cv200-gemac", "hisilicon,hisi-gemac-v2"; reg = <0xf9840000 0x1000>,<0xf984300c 0x4>; interrupts = <0 71 4>; #address-cells = <1>; #size-cells = <0>; - phy-mode = "mii"; + phy-mode = "rgmii"; phy-handle = <&phy2>; mac-address = [00 00 00 00 00 00]; - clocks = <&clock HIX5HD2_MAC0_CLK>; + clocks = <&crg HISTB_ETH0_MAC_CLK>, <&crg HISTB_ETH0_MACIF_CLK>; + clock-names = "mac_core", "mac_ifc"; + resets = <&crg 0xcc 8>, <&crg 0xcc 10>, <&crg 0xcc 12>; + reset-names = "mac_core", "mac_ifc", "phy"; + hisilicon,phy-reset-delays-us = <10000 10000 30000>; phy2: ethernet-phy@2 { reg = <2>; diff --git a/arch/arm/boot/dts/hisi-x5hd2.dtsi b/arch/arm/boot/dts/hisi-x5hd2.dtsi index fdcc23d203e5..0da76c5ff6d7 100644 --- a/arch/arm/boot/dts/hisi-x5hd2.dtsi +++ b/arch/arm/boot/dts/hisi-x5hd2.dtsi @@ -436,18 +436,20 @@ }; gmac0: ethernet@1840000 { - compatible = "hisilicon,hix5hd2-gmac"; + compatible = "hisilicon,hix5hd2-gemac", "hisilicon,hisi-gemac-v1"; reg = <0x1840000 0x1000>,<0x184300c 0x4>; interrupts = <0 71 4>; clocks = <&clock HIX5HD2_MAC0_CLK>; + clock-names = "mac_core"; status = "disabled"; }; gmac1: ethernet@1841000 { - compatible = "hisilicon,hix5hd2-gmac"; + compatible = "hisilicon,hix5hd2-gemac", "hisilicon,hisi-gemac-v1"; reg = <0x1841000 0x1000>,<0x1843010 0x4>; interrupts = <0 72 4>; clocks = <&clock HIX5HD2_MAC1_CLK>; + clock-names = "mac_core"; status = "disabled"; }; diff --git a/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c b/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c index e69a6bed31a9..ee7e9ce2f5b3 100644 --- a/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c +++ b/drivers/net/ethernet/hisilicon/hix5hd2_gmac.c @@ -11,8 +11,10 @@ #include <linux/interrupt.h> #include <linux/etherdevice.h> #include <linux/platform_device.h> +#include <linux/of_device.h> #include <linux/of_net.h> #include <linux/of_mdio.h> +#include <linux/reset.h> #include <linux/clk.h> #include <linux/circ_buf.h> @@ -183,12 +185,28 @@ #define DESC_DATA_LEN_OFF 16 #define DESC_BUFF_LEN_OFF 0 #define DESC_DATA_MASK 0x7ff +#define DESC_SG BIT(30) +#define DESC_FRAGS_NUM_OFF 11 /* DMA descriptor ring helpers */ #define dma_ring_incr(n, s) (((n) + 1) & ((s) - 1)) #define dma_cnt(n) ((n) >> 5) #define dma_byte(n) ((n) << 5) +#define HW_CAP_TSO BIT(0) +#define GEMAC_V1 0 +#define GEMAC_V2 (GEMAC_V1 | HW_CAP_TSO) +#define HAS_CAP_TSO(hw_cap) ((hw_cap) & HW_CAP_TSO) + +#define PHY_RESET_DELAYS_PROPERTY "hisilicon,phy-reset-delays-us" + +enum phy_reset_delays { + PRE_DELAY, + PULSE, + POST_DELAY, + DELAYS_NUM, +}; + struct hix5hd2_desc { __le32 buff_addr; __le32 cmd; @@ -201,6 +219,27 @@ struct hix5hd2_desc_sw { unsigned int size; }; +struct hix5hd2_sg_desc_ring { + struct sg_desc *desc; + dma_addr_t phys_addr; +}; + +struct frags_info { + __le32 addr; + __le32 size; +}; + +/* hardware supported max skb frags num */ +#define SG_MAX_SKB_FRAGS 17 +struct sg_desc { + __le32 total_len; + __le32 resvd0; + __le32 linear_addr; + __le32 linear_len; + /* reserve one more frags for memory alignment */ + struct frags_info frags[SG_MAX_SKB_FRAGS + 1]; +}; + #define QUEUE_NUMS 4 struct hix5hd2_priv { struct hix5hd2_desc_sw pool[QUEUE_NUMS]; @@ -208,6 +247,7 @@ struct hix5hd2_priv { #define rx_bq pool[1] #define tx_bq pool[2] #define tx_rq pool[3] + struct hix5hd2_sg_desc_ring tx_ring; void __iomem *base; void __iomem *ctrl_base; @@ -221,15 +261,30 @@ struct hix5hd2_priv { struct device_node *phy_node; phy_interface_t phy_mode; + unsigned long hw_cap; unsigned int speed; unsigned int duplex; - struct clk *clk; + struct clk *mac_core_clk; + struct clk *mac_ifc_clk; + struct reset_control *mac_core_rst; + struct reset_control *mac_ifc_rst; + struct reset_control *phy_rst; + u32 phy_reset_delays[DELAYS_NUM]; struct mii_bus *bus; struct napi_struct napi; struct work_struct tx_timeout_task; }; +static inline void hix5hd2_mac_interface_reset(struct hix5hd2_priv *priv) +{ + if (!priv->mac_ifc_rst) + return; + + reset_control_assert(priv->mac_ifc_rst); + reset_control_deassert(priv->mac_ifc_rst); +} + static void hix5hd2_config_port(struct net_device *dev, u32 speed, u32 duplex) { struct hix5hd2_priv *priv = netdev_priv(dev); @@ -262,6 +317,7 @@ static void hix5hd2_config_port(struct net_device *dev, u32 speed, u32 duplex) if (duplex) val |= GMAC_FULL_DUPLEX; writel_relaxed(val, priv->ctrl_base); + hix5hd2_mac_interface_reset(priv); writel_relaxed(BIT_MODE_CHANGE_EN, priv->base + MODE_CHANGE_EN); if (speed == SPEED_1000) @@ -511,6 +567,27 @@ next: return num; } +static void hix5hd2_clean_sg_desc(struct hix5hd2_priv *priv, + struct sk_buff *skb, u32 pos) +{ + struct sg_desc *desc; + dma_addr_t addr; + u32 len; + int i; + + desc = priv->tx_ring.desc + pos; + + addr = le32_to_cpu(desc->linear_addr); + len = le32_to_cpu(desc->linear_len); + dma_unmap_single(priv->dev, addr, len, DMA_TO_DEVICE); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + addr = le32_to_cpu(desc->frags[i].addr); + len = le32_to_cpu(desc->frags[i].size); + dma_unmap_page(priv->dev, addr, len, DMA_TO_DEVICE); + } +} + static void hix5hd2_xmit_reclaim(struct net_device *dev) { struct sk_buff *skb; @@ -538,8 +615,15 @@ static void hix5hd2_xmit_reclaim(struct net_device *dev) pkts_compl++; bytes_compl += skb->len; desc = priv->tx_rq.desc + pos; - addr = le32_to_cpu(desc->buff_addr); - dma_unmap_single(priv->dev, addr, skb->len, DMA_TO_DEVICE); + + if (skb_shinfo(skb)->nr_frags) { + hix5hd2_clean_sg_desc(priv, skb, pos); + } else { + addr = le32_to_cpu(desc->buff_addr); + dma_unmap_single(priv->dev, addr, skb->len, + DMA_TO_DEVICE); + } + priv->tx_skb[pos] = NULL; dev_consume_skb_any(skb); pos = dma_ring_incr(pos, TX_DESC_NUM); @@ -600,12 +684,66 @@ static irqreturn_t hix5hd2_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static u32 hix5hd2_get_desc_cmd(struct sk_buff *skb, unsigned long hw_cap) +{ + u32 cmd = 0; + + if (HAS_CAP_TSO(hw_cap)) { + if (skb_shinfo(skb)->nr_frags) + cmd |= DESC_SG; + cmd |= skb_shinfo(skb)->nr_frags << DESC_FRAGS_NUM_OFF; + } else { + cmd |= DESC_FL_FULL | + ((skb->len & DESC_DATA_MASK) << DESC_BUFF_LEN_OFF); + } + + cmd |= (skb->len & DESC_DATA_MASK) << DESC_DATA_LEN_OFF; + cmd |= DESC_VLD_BUSY; + + return cmd; +} + +static int hix5hd2_fill_sg_desc(struct hix5hd2_priv *priv, + struct sk_buff *skb, u32 pos) +{ + struct sg_desc *desc; + dma_addr_t addr; + int ret; + int i; + + desc = priv->tx_ring.desc + pos; + + desc->total_len = cpu_to_le32(skb->len); + addr = dma_map_single(priv->dev, skb->data, skb_headlen(skb), + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(priv->dev, addr))) + return -EINVAL; + desc->linear_addr = cpu_to_le32(addr); + desc->linear_len = cpu_to_le32(skb_headlen(skb)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + int len = frag->size; + + addr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE); + ret = dma_mapping_error(priv->dev, addr); + if (unlikely(ret)) + return -EINVAL; + desc->frags[i].addr = cpu_to_le32(addr); + desc->frags[i].size = cpu_to_le32(len); + } + + return 0; +} + static int hix5hd2_net_xmit(struct sk_buff *skb, struct net_device *dev) { struct hix5hd2_priv *priv = netdev_priv(dev); struct hix5hd2_desc *desc; dma_addr_t addr; u32 pos; + u32 cmd; + int ret; /* software write pointer */ pos = dma_cnt(readl_relaxed(priv->base + TX_BQ_WR_ADDR)); @@ -616,18 +754,31 @@ static int hix5hd2_net_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_BUSY; } - addr = dma_map_single(priv->dev, skb->data, skb->len, DMA_TO_DEVICE); - if (dma_mapping_error(priv->dev, addr)) { - dev_kfree_skb_any(skb); - return NETDEV_TX_OK; - } - desc = priv->tx_bq.desc + pos; + + cmd = hix5hd2_get_desc_cmd(skb, priv->hw_cap); + desc->cmd = cpu_to_le32(cmd); + + if (skb_shinfo(skb)->nr_frags) { + ret = hix5hd2_fill_sg_desc(priv, skb, pos); + if (unlikely(ret)) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + addr = priv->tx_ring.phys_addr + pos * sizeof(struct sg_desc); + } else { + addr = dma_map_single(priv->dev, skb->data, skb->len, + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(priv->dev, addr))) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + } desc->buff_addr = cpu_to_le32(addr); + priv->tx_skb[pos] = skb; - desc->cmd = cpu_to_le32(DESC_VLD_BUSY | DESC_FL_FULL | - (skb->len & DESC_DATA_MASK) << DESC_DATA_LEN_OFF | - (skb->len & DESC_DATA_MASK) << DESC_BUFF_LEN_OFF); /* ensure desc updated */ wmb(); @@ -681,16 +832,26 @@ static int hix5hd2_net_open(struct net_device *dev) struct phy_device *phy; int ret; - ret = clk_prepare_enable(priv->clk); + ret = clk_prepare_enable(priv->mac_core_clk); + if (ret < 0) { + netdev_err(dev, "failed to enable mac core clk %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->mac_ifc_clk); if (ret < 0) { - netdev_err(dev, "failed to enable clk %d\n", ret); + clk_disable_unprepare(priv->mac_core_clk); + netdev_err(dev, "failed to enable mac ifc clk %d\n", ret); return ret; } phy = of_phy_connect(dev, priv->phy_node, &hix5hd2_adjust_link, 0, priv->phy_mode); - if (!phy) + if (!phy) { + clk_disable_unprepare(priv->mac_ifc_clk); + clk_disable_unprepare(priv->mac_core_clk); return -ENODEV; + } phy_start(phy); hix5hd2_hw_init(priv); @@ -721,7 +882,8 @@ static int hix5hd2_net_close(struct net_device *dev) phy_disconnect(dev->phydev); } - clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->mac_ifc_clk); + clk_disable_unprepare(priv->mac_core_clk); return 0; } @@ -862,10 +1024,82 @@ error_free_pool: return -ENOMEM; } +static int hix5hd2_init_sg_desc_queue(struct hix5hd2_priv *priv) +{ + struct sg_desc *desc; + dma_addr_t phys_addr; + + desc = (struct sg_desc *)dma_alloc_coherent(priv->dev, + TX_DESC_NUM * sizeof(struct sg_desc), + &phys_addr, GFP_KERNEL); + if (!desc) + return -ENOMEM; + + priv->tx_ring.desc = desc; + priv->tx_ring.phys_addr = phys_addr; + + return 0; +} + +static void hix5hd2_destroy_sg_desc_queue(struct hix5hd2_priv *priv) +{ + if (priv->tx_ring.desc) { + dma_free_coherent(priv->dev, + TX_DESC_NUM * sizeof(struct sg_desc), + priv->tx_ring.desc, priv->tx_ring.phys_addr); + priv->tx_ring.desc = NULL; + } +} + +static inline void hix5hd2_mac_core_reset(struct hix5hd2_priv *priv) +{ + if (!priv->mac_core_rst) + return; + + reset_control_assert(priv->mac_core_rst); + reset_control_deassert(priv->mac_core_rst); +} + +static void hix5hd2_sleep_us(u32 time_us) +{ + u32 time_ms; + + if (!time_us) + return; + + time_ms = DIV_ROUND_UP(time_us, 1000); + if (time_ms < 20) + usleep_range(time_us, time_us + 500); + else + msleep(time_ms); +} + +static void hix5hd2_phy_reset(struct hix5hd2_priv *priv) +{ + /* To make sure PHY hardware reset success, + * we must keep PHY in deassert state first and + * then complete the hardware reset operation + */ + reset_control_deassert(priv->phy_rst); + hix5hd2_sleep_us(priv->phy_reset_delays[PRE_DELAY]); + + reset_control_assert(priv->phy_rst); + /* delay some time to ensure reset ok, + * this depends on PHY hardware feature + */ + hix5hd2_sleep_us(priv->phy_reset_delays[PULSE]); + reset_control_deassert(priv->phy_rst); + /* delay some time to ensure later MDIO access */ + hix5hd2_sleep_us(priv->phy_reset_delays[POST_DELAY]); +} + +static const struct of_device_id hix5hd2_of_match[]; + static int hix5hd2_dev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; + const struct of_device_id *of_id = NULL; struct net_device *ndev; struct hix5hd2_priv *priv; struct resource *res; @@ -883,6 +1117,13 @@ static int hix5hd2_dev_probe(struct platform_device *pdev) priv->dev = dev; priv->netdev = ndev; + of_id = of_match_device(hix5hd2_of_match, dev); + if (!of_id) { + ret = -EINVAL; + goto out_free_netdev; + } + priv->hw_cap = (unsigned long)of_id->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->base)) { @@ -897,23 +1138,55 @@ static int hix5hd2_dev_probe(struct platform_device *pdev) goto out_free_netdev; } - priv->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(priv->clk)) { - netdev_err(ndev, "failed to get clk\n"); + priv->mac_core_clk = devm_clk_get(&pdev->dev, "mac_core"); + if (IS_ERR(priv->mac_core_clk)) { + netdev_err(ndev, "failed to get mac core clk\n"); ret = -ENODEV; goto out_free_netdev; } - ret = clk_prepare_enable(priv->clk); + ret = clk_prepare_enable(priv->mac_core_clk); if (ret < 0) { - netdev_err(ndev, "failed to enable clk %d\n", ret); + netdev_err(ndev, "failed to enable mac core clk %d\n", ret); goto out_free_netdev; } + priv->mac_ifc_clk = devm_clk_get(&pdev->dev, "mac_ifc"); + if (IS_ERR(priv->mac_ifc_clk)) + priv->mac_ifc_clk = NULL; + + ret = clk_prepare_enable(priv->mac_ifc_clk); + if (ret < 0) { + netdev_err(ndev, "failed to enable mac ifc clk %d\n", ret); + goto out_disable_mac_core_clk; + } + + priv->mac_core_rst = devm_reset_control_get(dev, "mac_core"); + if (IS_ERR(priv->mac_core_rst)) + priv->mac_core_rst = NULL; + hix5hd2_mac_core_reset(priv); + + priv->mac_ifc_rst = devm_reset_control_get(dev, "mac_ifc"); + if (IS_ERR(priv->mac_ifc_rst)) + priv->mac_ifc_rst = NULL; + + priv->phy_rst = devm_reset_control_get(dev, "phy"); + if (IS_ERR(priv->phy_rst)) { + priv->phy_rst = NULL; + } else { + ret = of_property_read_u32_array(node, + PHY_RESET_DELAYS_PROPERTY, + priv->phy_reset_delays, + DELAYS_NUM); + if (ret) + goto out_disable_clk; + hix5hd2_phy_reset(priv); + } + bus = mdiobus_alloc(); if (bus == NULL) { ret = -ENOMEM; - goto out_free_netdev; + goto out_disable_clk; } bus->priv = priv; @@ -972,22 +1245,38 @@ static int hix5hd2_dev_probe(struct platform_device *pdev) ndev->ethtool_ops = &hix5hd2_ethtools_ops; SET_NETDEV_DEV(ndev, dev); + if (HAS_CAP_TSO(priv->hw_cap)) + ndev->hw_features |= NETIF_F_SG; + + ndev->features |= ndev->hw_features | NETIF_F_HIGHDMA; + ndev->vlan_features |= ndev->features; + ret = hix5hd2_init_hw_desc_queue(priv); if (ret) goto out_phy_node; netif_napi_add(ndev, &priv->napi, hix5hd2_poll, NAPI_POLL_WEIGHT); + + if (HAS_CAP_TSO(priv->hw_cap)) { + ret = hix5hd2_init_sg_desc_queue(priv); + if (ret) + goto out_destroy_queue; + } + ret = register_netdev(priv->netdev); if (ret) { netdev_err(ndev, "register_netdev failed!"); goto out_destroy_queue; } - clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->mac_ifc_clk); + clk_disable_unprepare(priv->mac_core_clk); return ret; out_destroy_queue: + if (HAS_CAP_TSO(priv->hw_cap)) + hix5hd2_destroy_sg_desc_queue(priv); netif_napi_del(&priv->napi); hix5hd2_destroy_hw_desc_queue(priv); out_phy_node: @@ -996,6 +1285,10 @@ err_mdiobus: mdiobus_unregister(bus); err_free_mdio: mdiobus_free(bus); +out_disable_clk: + clk_disable_unprepare(priv->mac_ifc_clk); +out_disable_mac_core_clk: + clk_disable_unprepare(priv->mac_core_clk); out_free_netdev: free_netdev(ndev); @@ -1012,6 +1305,8 @@ static int hix5hd2_dev_remove(struct platform_device *pdev) mdiobus_unregister(priv->bus); mdiobus_free(priv->bus); + if (HAS_CAP_TSO(priv->hw_cap)) + hix5hd2_destroy_sg_desc_queue(priv); hix5hd2_destroy_hw_desc_queue(priv); of_node_put(priv->phy_node); cancel_work_sync(&priv->tx_timeout_task); @@ -1021,7 +1316,10 @@ static int hix5hd2_dev_remove(struct platform_device *pdev) } static const struct of_device_id hix5hd2_of_match[] = { - {.compatible = "hisilicon,hix5hd2-gmac",}, + { .compatible = "hisilicon,hisi-gemac-v1", .data = (void *)GEMAC_V1 }, + { .compatible = "hisilicon,hisi-gemac-v2", .data = (void *)GEMAC_V2 }, + { .compatible = "hisilicon,hix5hd2-gemac", .data = (void *)GEMAC_V1 }, + { .compatible = "hisilicon,hi3798cv200-gemac", .data = (void *)GEMAC_V2 }, {}, }; @@ -1029,7 +1327,7 @@ MODULE_DEVICE_TABLE(of, hix5hd2_of_match); static struct platform_driver hix5hd2_dev_driver = { .driver = { - .name = "hix5hd2-gmac", + .name = "hisi-gemac", .of_match_table = hix5hd2_of_match, }, .probe = hix5hd2_dev_probe, @@ -1038,6 +1336,6 @@ static struct platform_driver hix5hd2_dev_driver = { module_platform_driver(hix5hd2_dev_driver); -MODULE_DESCRIPTION("HISILICON HIX5HD2 Ethernet driver"); +MODULE_DESCRIPTION("HISILICON Gigabit Ethernet MAC driver"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:hix5hd2-gmac"); +MODULE_ALIAS("platform:hisi-gemac"); |