From df98acc6a1252456608958fc17f7da3ae4cfb46c Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 21 May 2016 22:16:56 +0200 Subject: mvebu: backport upstream ethernet driver improvements and enable buffer manager support Signed-off-by: Felix Fietkau --- ...bm-add-support-for-hardware-buffer-manage.patch | 1684 ++++++++++++++++++++ 1 file changed, 1684 insertions(+) create mode 100644 target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch (limited to 'target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch') diff --git a/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch b/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch new file mode 100644 index 0000000000..d343b3983f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch @@ -0,0 +1,1684 @@ +From: Marcin Wojtas +Date: Mon, 14 Mar 2016 09:39:03 +0100 +Subject: [PATCH] net: mvneta: bm: add support for hardware buffer management + +Buffer manager (BM) is a dedicated hardware unit that can be used by all +ethernet ports of Armada XP and 38x SoC's. It allows to offload CPU on RX +path by sparing DRAM access on refilling buffer pool, hardware-based +filling of descriptor ring data and better memory utilization due to HW +arbitration for using 'short' pools for small packets. + +Tests performed with A388 SoC working as a network bridge between two +packet generators showed increase of maximum processed 64B packets by +~20k (~555k packets with BM enabled vs ~535 packets without BM). Also +when pushing 1500B-packets with a line rate achieved, CPU load decreased +from around 25% without BM to 20% with BM. + +BM comprise up to 4 buffer pointers' (BP) rings kept in DRAM, which +are called external BP pools - BPPE. Allocating and releasing buffer +pointers (BP) to/from BPPE is performed indirectly by write/read access +to a dedicated internal SRAM, where internal BP pools (BPPI) are placed. +BM hardware controls status of BPPE automatically, as well as assigning +proper buffers to RX descriptors. For more details please refer to +Functional Specification of Armada XP or 38x SoC. + +In order to enable support for a separate hardware block, common for all +ports, a new driver has to be implemented ('mvneta_bm'). It provides +initialization sequence of address space, clocks, registers, SRAM, +empty pools' structures and also obtaining optional configuration +from DT (please refer to device tree binding documentation). mvneta_bm +exposes also a necessary API to mvneta driver, as well as a dedicated +structure with BM information (bm_priv), whose presence is used as a +flag notifying of BM usage by port. It has to be ensured that mvneta_bm +probe is executed prior to the ones in ports' driver. In case BM is not +used or its probe fails, mvneta falls back to use software buffer +management. + +A sequence executed in mvneta_probe function is modified in order to have +an access to needed resources before possible port's BM initialization is +done. According to port-pools mapping provided by DT appropriate registers +are configured and the buffer pools are filled. RX path is modified +accordingly. Becaues the hardware allows a wide variety of configuration +options, following assumptions are made: +* using BM mechanisms can be selectively disabled/enabled basing + on DT configuration among the ports +* 'long' pool's single buffer size is tied to port's MTU +* using 'long' pool by port is obligatory and it cannot be shared +* using 'short' pool for smaller packets is optional +* one 'short' pool can be shared among all ports + +This commit enables hardware buffer management operation cooperating with +existing mvneta driver. New device tree binding documentation is added and +the one of mvneta is updated accordingly. + +[gregory.clement@free-electrons.com: removed the suspend/resume part] + +Signed-off-by: Marcin Wojtas +Signed-off-by: Gregory CLEMENT +Signed-off-by: David S. Miller +--- + create mode 100644 Documentation/devicetree/bindings/net/marvell-neta-bm.txt + create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.c + create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.h + +--- a/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt ++++ b/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt +@@ -13,15 +13,30 @@ Optional properties: + Value is presented in bytes. If not used, by default 1600B is set for + "marvell,armada-370-neta" and 9800B for others. + ++Optional properties (valid only for Armada XP/38x): ++ ++- buffer-manager: a phandle to a buffer manager node. Please refer to ++ Documentation/devicetree/bindings/net/marvell-neta-bm.txt ++- bm,pool-long: ID of a pool, that will accept all packets of a size ++ higher than 'short' pool's threshold (if set) and up to MTU value. ++ Obligatory, when the port is supposed to use hardware ++ buffer management. ++- bm,pool-short: ID of a pool, that will be used for accepting ++ packets of a size lower than given threshold. If not set, the port ++ will use a single 'long' pool for all packets, as defined above. ++ + Example: + +-ethernet@d0070000 { ++ethernet@70000 { + compatible = "marvell,armada-370-neta"; +- reg = <0xd0070000 0x2500>; ++ reg = <0x70000 0x2500>; + interrupts = <8>; + clocks = <&gate_clk 4>; + tx-csum-limit = <9800> + status = "okay"; + phy = <&phy0>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <1>; + }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/marvell-neta-bm.txt +@@ -0,0 +1,49 @@ ++* Marvell Armada 380/XP Buffer Manager driver (BM) ++ ++Required properties: ++ ++- compatible: should be "marvell,armada-380-neta-bm". ++- reg: address and length of the register set for the device. ++- clocks: a pointer to the reference clock for this device. ++- internal-mem: a phandle to BM internal SRAM definition. ++ ++Optional properties (port): ++ ++- pool<0 : 3>,capacity: size of external buffer pointers' ring maintained ++ in DRAM. Can be set for each pool (id 0 : 3) separately. The value has ++ to be chosen between 128 and 16352 and it also has to be aligned to 32. ++ Otherwise the driver would adjust a given number or choose default if ++ not set. ++- pool<0 : 3>,pkt-size: maximum size of a packet accepted by a given buffer ++ pointers' pool (id 0 : 3). It will be taken into consideration only when pool ++ type is 'short'. For 'long' ones it would be overridden by port's MTU. ++ If not set a driver will choose a default value. ++ ++In order to see how to hook the BM to a given ethernet port, please ++refer to Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt. ++ ++Example: ++ ++- main node: ++ ++bm: bm@c8000 { ++ compatible = "marvell,armada-380-neta-bm"; ++ reg = <0xc8000 0xac>; ++ clocks = <&gateclk 13>; ++ internal-mem = <&bm_bppi>; ++ status = "okay"; ++ pool2,capacity = <4096>; ++ pool1,pkt-size = <512>; ++}; ++ ++- internal SRAM node: ++ ++bm_bppi: bm-bppi { ++ compatible = "mmio-sram"; ++ reg = ; ++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gateclk 13>; ++ status = "okay"; ++}; +--- a/drivers/net/ethernet/marvell/Kconfig ++++ b/drivers/net/ethernet/marvell/Kconfig +@@ -40,6 +40,19 @@ config MVMDIO + + This driver is used by the MV643XX_ETH and MVNETA drivers. + ++config MVNETA_BM ++ tristate "Marvell Armada 38x/XP network interface BM support" ++ depends on MVNETA ++ ---help--- ++ This driver supports auxiliary block of the network ++ interface units in the Marvell ARMADA XP and ARMADA 38x SoC ++ family, which is called buffer manager. ++ ++ This driver, when enabled, strictly cooperates with mvneta ++ driver and is common for all network ports of the devices, ++ even for Armada 370 SoC, which doesn't support hardware ++ buffer management. ++ + config MVNETA + tristate "Marvell Armada 370/38x/XP network interface support" + depends on PLAT_ORION +--- a/drivers/net/ethernet/marvell/Makefile ++++ b/drivers/net/ethernet/marvell/Makefile +@@ -4,6 +4,7 @@ + + obj-$(CONFIG_MVMDIO) += mvmdio.o + obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o ++obj-$(CONFIG_MVNETA_BM) += mvneta_bm.o + obj-$(CONFIG_MVNETA) += mvneta.o + obj-$(CONFIG_MVPP2) += mvpp2.o + obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -30,6 +30,7 @@ + #include + #include + #include ++#include "mvneta_bm.h" + #include + #include + #include +@@ -37,6 +38,10 @@ + /* Registers */ + #define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2)) + #define MVNETA_RXQ_HW_BUF_ALLOC BIT(0) ++#define MVNETA_RXQ_SHORT_POOL_ID_SHIFT 4 ++#define MVNETA_RXQ_SHORT_POOL_ID_MASK 0x30 ++#define MVNETA_RXQ_LONG_POOL_ID_SHIFT 6 ++#define MVNETA_RXQ_LONG_POOL_ID_MASK 0xc0 + #define MVNETA_RXQ_PKT_OFFSET_ALL_MASK (0xf << 8) + #define MVNETA_RXQ_PKT_OFFSET_MASK(offs) ((offs) << 8) + #define MVNETA_RXQ_THRESHOLD_REG(q) (0x14c0 + ((q) << 2)) +@@ -50,6 +55,9 @@ + #define MVNETA_RXQ_STATUS_UPDATE_REG(q) (0x1500 + ((q) << 2)) + #define MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT 16 + #define MVNETA_RXQ_ADD_NON_OCCUPIED_MAX 255 ++#define MVNETA_PORT_POOL_BUFFER_SZ_REG(pool) (0x1700 + ((pool) << 2)) ++#define MVNETA_PORT_POOL_BUFFER_SZ_SHIFT 3 ++#define MVNETA_PORT_POOL_BUFFER_SZ_MASK 0xfff8 + #define MVNETA_PORT_RX_RESET 0x1cc0 + #define MVNETA_PORT_RX_DMA_RESET BIT(0) + #define MVNETA_PHY_ADDR 0x2000 +@@ -107,6 +115,7 @@ + #define MVNETA_GMAC_CLOCK_DIVIDER 0x24f4 + #define MVNETA_GMAC_1MS_CLOCK_ENABLE BIT(31) + #define MVNETA_ACC_MODE 0x2500 ++#define MVNETA_BM_ADDRESS 0x2504 + #define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2)) + #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff + #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00 +@@ -253,7 +262,10 @@ + #define MVNETA_CPU_D_CACHE_LINE_SIZE 32 + #define MVNETA_TX_CSUM_DEF_SIZE 1600 + #define MVNETA_TX_CSUM_MAX_SIZE 9800 +-#define MVNETA_ACC_MODE_EXT 1 ++#define MVNETA_ACC_MODE_EXT1 1 ++#define MVNETA_ACC_MODE_EXT2 2 ++ ++#define MVNETA_MAX_DECODE_WIN 6 + + /* Timeout constants */ + #define MVNETA_TX_DISABLE_TIMEOUT_MSEC 1000 +@@ -293,7 +305,8 @@ + ((addr >= txq->tso_hdrs_phys) && \ + (addr < txq->tso_hdrs_phys + txq->size * TSO_HEADER_SIZE)) + +-#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD) ++#define MVNETA_RX_GET_BM_POOL_ID(rxd) \ ++ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) + + struct mvneta_statistic { + unsigned short offset; +@@ -359,6 +372,7 @@ struct mvneta_pcpu_port { + }; + + struct mvneta_port { ++ u8 id; + struct mvneta_pcpu_port __percpu *ports; + struct mvneta_pcpu_stats __percpu *stats; + +@@ -392,6 +406,11 @@ struct mvneta_port { + unsigned int tx_csum_limit; + unsigned int use_inband_status:1; + ++ struct mvneta_bm *bm_priv; ++ struct mvneta_bm_pool *pool_long; ++ struct mvneta_bm_pool *pool_short; ++ int bm_win_id; ++ + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; + + u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; +@@ -417,6 +436,8 @@ struct mvneta_port { + #define MVNETA_TX_L4_CSUM_NOT BIT(31) + + #define MVNETA_RXD_ERR_CRC 0x0 ++#define MVNETA_RXD_BM_POOL_SHIFT 13 ++#define MVNETA_RXD_BM_POOL_MASK (BIT(13) | BIT(14)) + #define MVNETA_RXD_ERR_SUMMARY BIT(16) + #define MVNETA_RXD_ERR_OVERRUN BIT(17) + #define MVNETA_RXD_ERR_LEN BIT(18) +@@ -561,6 +582,9 @@ static int rxq_def; + + static int rx_copybreak __read_mostly = 256; + ++/* HW BM need that each port be identify by a unique ID */ ++static int global_port_id; ++ + #define MVNETA_DRIVER_NAME "mvneta" + #define MVNETA_DRIVER_VERSION "1.0" + +@@ -827,6 +851,214 @@ static void mvneta_rxq_bm_disable(struct + mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); + } + ++/* Enable buffer management (BM) */ ++static void mvneta_rxq_bm_enable(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val |= MVNETA_RXQ_HW_BUF_ALLOC; ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Notify HW about port's assignment of pool for bigger packets */ ++static void mvneta_rxq_long_pool_set(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val &= ~MVNETA_RXQ_LONG_POOL_ID_MASK; ++ val |= (pp->pool_long->id << MVNETA_RXQ_LONG_POOL_ID_SHIFT); ++ ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Notify HW about port's assignment of pool for smaller packets */ ++static void mvneta_rxq_short_pool_set(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val &= ~MVNETA_RXQ_SHORT_POOL_ID_MASK; ++ val |= (pp->pool_short->id << MVNETA_RXQ_SHORT_POOL_ID_SHIFT); ++ ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Set port's receive buffer size for assigned BM pool */ ++static inline void mvneta_bm_pool_bufsize_set(struct mvneta_port *pp, ++ int buf_size, ++ u8 pool_id) ++{ ++ u32 val; ++ ++ if (!IS_ALIGNED(buf_size, 8)) { ++ dev_warn(pp->dev->dev.parent, ++ "illegal buf_size value %d, round to %d\n", ++ buf_size, ALIGN(buf_size, 8)); ++ buf_size = ALIGN(buf_size, 8); ++ } ++ ++ val = mvreg_read(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id)); ++ val |= buf_size & MVNETA_PORT_POOL_BUFFER_SZ_MASK; ++ mvreg_write(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id), val); ++} ++ ++/* Configure MBUS window in order to enable access BM internal SRAM */ ++static int mvneta_mbus_io_win_set(struct mvneta_port *pp, u32 base, u32 wsize, ++ u8 target, u8 attr) ++{ ++ u32 win_enable, win_protect; ++ int i; ++ ++ win_enable = mvreg_read(pp, MVNETA_BASE_ADDR_ENABLE); ++ ++ if (pp->bm_win_id < 0) { ++ /* Find first not occupied window */ ++ for (i = 0; i < MVNETA_MAX_DECODE_WIN; i++) { ++ if (win_enable & (1 << i)) { ++ pp->bm_win_id = i; ++ break; ++ } ++ } ++ if (i == MVNETA_MAX_DECODE_WIN) ++ return -ENOMEM; ++ } else { ++ i = pp->bm_win_id; ++ } ++ ++ mvreg_write(pp, MVNETA_WIN_BASE(i), 0); ++ mvreg_write(pp, MVNETA_WIN_SIZE(i), 0); ++ ++ if (i < 4) ++ mvreg_write(pp, MVNETA_WIN_REMAP(i), 0); ++ ++ mvreg_write(pp, MVNETA_WIN_BASE(i), (base & 0xffff0000) | ++ (attr << 8) | target); ++ ++ mvreg_write(pp, MVNETA_WIN_SIZE(i), (wsize - 1) & 0xffff0000); ++ ++ win_protect = mvreg_read(pp, MVNETA_ACCESS_PROTECT_ENABLE); ++ win_protect |= 3 << (2 * i); ++ mvreg_write(pp, MVNETA_ACCESS_PROTECT_ENABLE, win_protect); ++ ++ win_enable &= ~(1 << i); ++ mvreg_write(pp, MVNETA_BASE_ADDR_ENABLE, win_enable); ++ ++ return 0; ++} ++ ++/* Assign and initialize pools for port. In case of fail ++ * buffer manager will remain disabled for current port. ++ */ ++static int mvneta_bm_port_init(struct platform_device *pdev, ++ struct mvneta_port *pp) ++{ ++ struct device_node *dn = pdev->dev.of_node; ++ u32 long_pool_id, short_pool_id, wsize; ++ u8 target, attr; ++ int err; ++ ++ /* Get BM window information */ ++ err = mvebu_mbus_get_io_win_info(pp->bm_priv->bppi_phys_addr, &wsize, ++ &target, &attr); ++ if (err < 0) ++ return err; ++ ++ pp->bm_win_id = -1; ++ ++ /* Open NETA -> BM window */ ++ err = mvneta_mbus_io_win_set(pp, pp->bm_priv->bppi_phys_addr, wsize, ++ target, attr); ++ if (err < 0) { ++ netdev_info(pp->dev, "fail to configure mbus window to BM\n"); ++ return err; ++ } ++ ++ if (of_property_read_u32(dn, "bm,pool-long", &long_pool_id)) { ++ netdev_info(pp->dev, "missing long pool id\n"); ++ return -EINVAL; ++ } ++ ++ /* Create port's long pool depending on mtu */ ++ pp->pool_long = mvneta_bm_pool_use(pp->bm_priv, long_pool_id, ++ MVNETA_BM_LONG, pp->id, ++ MVNETA_RX_PKT_SIZE(pp->dev->mtu)); ++ if (!pp->pool_long) { ++ netdev_info(pp->dev, "fail to obtain long pool for port\n"); ++ return -ENOMEM; ++ } ++ ++ pp->pool_long->port_map |= 1 << pp->id; ++ ++ mvneta_bm_pool_bufsize_set(pp, pp->pool_long->buf_size, ++ pp->pool_long->id); ++ ++ /* If short pool id is not defined, assume using single pool */ ++ if (of_property_read_u32(dn, "bm,pool-short", &short_pool_id)) ++ short_pool_id = long_pool_id; ++ ++ /* Create port's short pool */ ++ pp->pool_short = mvneta_bm_pool_use(pp->bm_priv, short_pool_id, ++ MVNETA_BM_SHORT, pp->id, ++ MVNETA_BM_SHORT_PKT_SIZE); ++ if (!pp->pool_short) { ++ netdev_info(pp->dev, "fail to obtain short pool for port\n"); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ return -ENOMEM; ++ } ++ ++ if (short_pool_id != long_pool_id) { ++ pp->pool_short->port_map |= 1 << pp->id; ++ mvneta_bm_pool_bufsize_set(pp, pp->pool_short->buf_size, ++ pp->pool_short->id); ++ } ++ ++ return 0; ++} ++ ++/* Update settings of a pool for bigger packets */ ++static void mvneta_bm_update_mtu(struct mvneta_port *pp, int mtu) ++{ ++ struct mvneta_bm_pool *bm_pool = pp->pool_long; ++ int num; ++ ++ /* Release all buffers from long pool */ ++ mvneta_bm_bufs_free(pp->bm_priv, bm_pool, 1 << pp->id); ++ if (bm_pool->buf_num) { ++ WARN(1, "cannot free all buffers in pool %d\n", ++ bm_pool->id); ++ goto bm_mtu_err; ++ } ++ ++ bm_pool->pkt_size = MVNETA_RX_PKT_SIZE(mtu); ++ bm_pool->buf_size = MVNETA_RX_BUF_SIZE(bm_pool->pkt_size); ++ bm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + ++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size)); ++ ++ /* Fill entire long pool */ ++ num = mvneta_bm_bufs_add(pp->bm_priv, bm_pool, bm_pool->size); ++ if (num != bm_pool->size) { ++ WARN(1, "pool %d: %d of %d allocated\n", ++ bm_pool->id, num, bm_pool->size); ++ goto bm_mtu_err; ++ } ++ mvneta_bm_pool_bufsize_set(pp, bm_pool->buf_size, bm_pool->id); ++ ++ return; ++ ++bm_mtu_err: ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, 1 << pp->id); ++ ++ pp->bm_priv = NULL; ++ mvreg_write(pp, MVNETA_ACC_MODE, MVNETA_ACC_MODE_EXT1); ++ netdev_info(pp->dev, "fail to update MTU, fall back to software BM\n"); ++} ++ + /* Start the Ethernet port RX and TX activity */ + static void mvneta_port_up(struct mvneta_port *pp) + { +@@ -1147,9 +1379,17 @@ static void mvneta_defaults_set(struct m + mvreg_write(pp, MVNETA_PORT_RX_RESET, 0); + + /* Set Port Acceleration Mode */ +- val = MVNETA_ACC_MODE_EXT; ++ if (pp->bm_priv) ++ /* HW buffer management + legacy parser */ ++ val = MVNETA_ACC_MODE_EXT2; ++ else ++ /* SW buffer management + legacy parser */ ++ val = MVNETA_ACC_MODE_EXT1; + mvreg_write(pp, MVNETA_ACC_MODE, val); + ++ if (pp->bm_priv) ++ mvreg_write(pp, MVNETA_BM_ADDRESS, pp->bm_priv->bppi_phys_addr); ++ + /* Update val of portCfg register accordingly with all RxQueue types */ + val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def); + mvreg_write(pp, MVNETA_PORT_CONFIG, val); +@@ -1516,23 +1756,25 @@ static void mvneta_txq_done(struct mvnet + } + } + +-static void *mvneta_frag_alloc(const struct mvneta_port *pp) ++void *mvneta_frag_alloc(unsigned int frag_size) + { +- if (likely(pp->frag_size <= PAGE_SIZE)) +- return netdev_alloc_frag(pp->frag_size); ++ if (likely(frag_size <= PAGE_SIZE)) ++ return netdev_alloc_frag(frag_size); + else +- return kmalloc(pp->frag_size, GFP_ATOMIC); ++ return kmalloc(frag_size, GFP_ATOMIC); + } ++EXPORT_SYMBOL_GPL(mvneta_frag_alloc); + +-static void mvneta_frag_free(const struct mvneta_port *pp, void *data) ++void mvneta_frag_free(unsigned int frag_size, void *data) + { +- if (likely(pp->frag_size <= PAGE_SIZE)) ++ if (likely(frag_size <= PAGE_SIZE)) + skb_free_frag(data); + else + kfree(data); + } ++EXPORT_SYMBOL_GPL(mvneta_frag_free); + +-/* Refill processing */ ++/* Refill processing for SW buffer management */ + static int mvneta_rx_refill(struct mvneta_port *pp, + struct mvneta_rx_desc *rx_desc) + +@@ -1540,7 +1782,7 @@ static int mvneta_rx_refill(struct mvnet + dma_addr_t phys_addr; + void *data; + +- data = mvneta_frag_alloc(pp); ++ data = mvneta_frag_alloc(pp->frag_size); + if (!data) + return -ENOMEM; + +@@ -1548,7 +1790,7 @@ static int mvneta_rx_refill(struct mvnet + MVNETA_RX_BUF_SIZE(pp->pkt_size), + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(pp->dev->dev.parent, phys_addr))) { +- mvneta_frag_free(pp, data); ++ mvneta_frag_free(pp->frag_size, data); + return -ENOMEM; + } + +@@ -1594,22 +1836,156 @@ static void mvneta_rxq_drop_pkts(struct + int rx_done, i; + + rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq); ++ if (rx_done) ++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++ ++ if (pp->bm_priv) { ++ for (i = 0; i < rx_done; i++) { ++ struct mvneta_rx_desc *rx_desc = ++ mvneta_rxq_next_desc_get(rxq); ++ u8 pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc); ++ struct mvneta_bm_pool *bm_pool; ++ ++ bm_pool = &pp->bm_priv->bm_pools[pool_id]; ++ /* Return dropped buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++ } ++ return; ++ } ++ + for (i = 0; i < rxq->size; i++) { + struct mvneta_rx_desc *rx_desc = rxq->descs + i; + void *data = (void *)rx_desc->buf_cookie; + + dma_unmap_single(pp->dev->dev.parent, rx_desc->buf_phys_addr, + MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE); +- mvneta_frag_free(pp, data); ++ mvneta_frag_free(pp->frag_size, data); + } ++} + +- if (rx_done) +- mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++/* Main rx processing when using software buffer management */ ++static int mvneta_rx_swbm(struct mvneta_port *pp, int rx_todo, ++ struct mvneta_rx_queue *rxq) ++{ ++ struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); ++ struct net_device *dev = pp->dev; ++ int rx_done; ++ u32 rcvd_pkts = 0; ++ u32 rcvd_bytes = 0; ++ ++ /* Get number of received packets */ ++ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq); ++ ++ if (rx_todo > rx_done) ++ rx_todo = rx_done; ++ ++ rx_done = 0; ++ ++ /* Fairness NAPI loop */ ++ while (rx_done < rx_todo) { ++ struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq); ++ struct sk_buff *skb; ++ unsigned char *data; ++ dma_addr_t phys_addr; ++ u32 rx_status, frag_size; ++ int rx_bytes, err; ++ ++ rx_done++; ++ rx_status = rx_desc->status; ++ rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE); ++ data = (unsigned char *)rx_desc->buf_cookie; ++ phys_addr = rx_desc->buf_phys_addr; ++ ++ if (!mvneta_rxq_desc_is_first_last(rx_status) || ++ (rx_status & MVNETA_RXD_ERR_SUMMARY)) { ++err_drop_frame: ++ dev->stats.rx_errors++; ++ mvneta_rx_error(pp, rx_desc); ++ /* leave the descriptor untouched */ ++ continue; ++ } ++ ++ if (rx_bytes <= rx_copybreak) { ++ /* better copy a small frame and not unmap the DMA region */ ++ skb = netdev_alloc_skb_ip_align(dev, rx_bytes); ++ if (unlikely(!skb)) ++ goto err_drop_frame; ++ ++ dma_sync_single_range_for_cpu(dev->dev.parent, ++ rx_desc->buf_phys_addr, ++ MVNETA_MH_SIZE + NET_SKB_PAD, ++ rx_bytes, ++ DMA_FROM_DEVICE); ++ memcpy(skb_put(skb, rx_bytes), ++ data + MVNETA_MH_SIZE + NET_SKB_PAD, ++ rx_bytes); ++ ++ skb->protocol = eth_type_trans(skb, dev); ++ mvneta_rx_csum(pp, rx_status, skb); ++ napi_gro_receive(&port->napi, skb); ++ ++ rcvd_pkts++; ++ rcvd_bytes += rx_bytes; ++ ++ /* leave the descriptor and buffer untouched */ ++ continue; ++ } ++ ++ /* Refill processing */ ++ err = mvneta_rx_refill(pp, rx_desc); ++ if (err) { ++ netdev_err(dev, "Linux processing - Can't refill\n"); ++ rxq->missed++; ++ goto err_drop_frame; ++ } ++ ++ frag_size = pp->frag_size; ++ ++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size); ++ ++ /* After refill old buffer has to be unmapped regardless ++ * the skb is successfully built or not. ++ */ ++ dma_unmap_single(dev->dev.parent, phys_addr, ++ MVNETA_RX_BUF_SIZE(pp->pkt_size), ++ DMA_FROM_DEVICE); ++ ++ if (!skb) ++ goto err_drop_frame; ++ ++ rcvd_pkts++; ++ rcvd_bytes += rx_bytes; ++ ++ /* Linux processing */ ++ skb_reserve(skb, MVNETA_MH_SIZE + NET_SKB_PAD); ++ skb_put(skb, rx_bytes); ++ ++ skb->protocol = eth_type_trans(skb, dev); ++ ++ mvneta_rx_csum(pp, rx_status, skb); ++ ++ napi_gro_receive(&port->napi, skb); ++ } ++ ++ if (rcvd_pkts) { ++ struct mvneta_pcpu_stats *stats = this_cpu_ptr(pp->stats); ++ ++ u64_stats_update_begin(&stats->syncp); ++ stats->rx_packets += rcvd_pkts; ++ stats->rx_bytes += rcvd_bytes; ++ u64_stats_update_end(&stats->syncp); ++ } ++ ++ /* Update rxq management counters */ ++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++ ++ return rx_done; + } + +-/* Main rx processing */ +-static int mvneta_rx(struct mvneta_port *pp, int rx_todo, +- struct mvneta_rx_queue *rxq) ++/* Main rx processing when using hardware buffer management */ ++static int mvneta_rx_hwbm(struct mvneta_port *pp, int rx_todo, ++ struct mvneta_rx_queue *rxq) + { + struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); + struct net_device *dev = pp->dev; +@@ -1628,21 +2004,29 @@ static int mvneta_rx(struct mvneta_port + /* Fairness NAPI loop */ + while (rx_done < rx_todo) { + struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq); ++ struct mvneta_bm_pool *bm_pool = NULL; + struct sk_buff *skb; + unsigned char *data; + dma_addr_t phys_addr; +- u32 rx_status; ++ u32 rx_status, frag_size; + int rx_bytes, err; ++ u8 pool_id; + + rx_done++; + rx_status = rx_desc->status; + rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE); + data = (unsigned char *)rx_desc->buf_cookie; + phys_addr = rx_desc->buf_phys_addr; ++ pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc); ++ bm_pool = &pp->bm_priv->bm_pools[pool_id]; + + if (!mvneta_rxq_desc_is_first_last(rx_status) || + (rx_status & MVNETA_RXD_ERR_SUMMARY)) { +- err_drop_frame: ++err_drop_frame_ret_pool: ++ /* Return the buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++err_drop_frame: + dev->stats.rx_errors++; + mvneta_rx_error(pp, rx_desc); + /* leave the descriptor untouched */ +@@ -1653,7 +2037,7 @@ static int mvneta_rx(struct mvneta_port + /* better copy a small frame and not unmap the DMA region */ + skb = netdev_alloc_skb_ip_align(dev, rx_bytes); + if (unlikely(!skb)) +- goto err_drop_frame; ++ goto err_drop_frame_ret_pool; + + dma_sync_single_range_for_cpu(dev->dev.parent, + rx_desc->buf_phys_addr, +@@ -1671,26 +2055,31 @@ static int mvneta_rx(struct mvneta_port + rcvd_pkts++; + rcvd_bytes += rx_bytes; + ++ /* Return the buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++ + /* leave the descriptor and buffer untouched */ + continue; + } + + /* Refill processing */ +- err = mvneta_rx_refill(pp, rx_desc); ++ err = mvneta_bm_pool_refill(pp->bm_priv, bm_pool); + if (err) { + netdev_err(dev, "Linux processing - Can't refill\n"); + rxq->missed++; +- goto err_drop_frame; ++ goto err_drop_frame_ret_pool; + } + +- skb = build_skb(data, pp->frag_size > PAGE_SIZE ? 0 : pp->frag_size); ++ frag_size = bm_pool->frag_size; ++ ++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size); + + /* After refill old buffer has to be unmapped regardless + * the skb is successfully built or not. + */ +- dma_unmap_single(dev->dev.parent, phys_addr, +- MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE); +- ++ dma_unmap_single(&pp->bm_priv->pdev->dev, phys_addr, ++ bm_pool->buf_size, DMA_FROM_DEVICE); + if (!skb) + goto err_drop_frame; + +@@ -2295,7 +2684,10 @@ static int mvneta_poll(struct napi_struc + + if (rx_queue) { + rx_queue = rx_queue - 1; +- rx_done = mvneta_rx(pp, budget, &pp->rxqs[rx_queue]); ++ if (pp->bm_priv) ++ rx_done = mvneta_rx_hwbm(pp, budget, &pp->rxqs[rx_queue]); ++ else ++ rx_done = mvneta_rx_swbm(pp, budget, &pp->rxqs[rx_queue]); + } + + budget -= rx_done; +@@ -2384,9 +2776,17 @@ static int mvneta_rxq_init(struct mvneta + mvneta_rx_pkts_coal_set(pp, rxq, rxq->pkts_coal); + mvneta_rx_time_coal_set(pp, rxq, rxq->time_coal); + +- /* Fill RXQ with buffers from RX pool */ +- mvneta_rxq_buf_size_set(pp, rxq, MVNETA_RX_BUF_SIZE(pp->pkt_size)); +- mvneta_rxq_bm_disable(pp, rxq); ++ if (!pp->bm_priv) { ++ /* Fill RXQ with buffers from RX pool */ ++ mvneta_rxq_buf_size_set(pp, rxq, ++ MVNETA_RX_BUF_SIZE(pp->pkt_size)); ++ mvneta_rxq_bm_disable(pp, rxq); ++ } else { ++ mvneta_rxq_bm_enable(pp, rxq); ++ mvneta_rxq_long_pool_set(pp, rxq); ++ mvneta_rxq_short_pool_set(pp, rxq); ++ } ++ + mvneta_rxq_fill(pp, rxq, rxq->size); + + return 0; +@@ -2659,6 +3059,9 @@ static int mvneta_change_mtu(struct net_ + dev->mtu = mtu; + + if (!netif_running(dev)) { ++ if (pp->bm_priv) ++ mvneta_bm_update_mtu(pp, mtu); ++ + netdev_update_features(dev); + return 0; + } +@@ -2671,6 +3074,9 @@ static int mvneta_change_mtu(struct net_ + mvneta_cleanup_txqs(pp); + mvneta_cleanup_rxqs(pp); + ++ if (pp->bm_priv) ++ mvneta_bm_update_mtu(pp, mtu); ++ + pp->pkt_size = MVNETA_RX_PKT_SIZE(dev->mtu); + pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); +@@ -3563,6 +3969,7 @@ static int mvneta_probe(struct platform_ + struct resource *res; + struct device_node *dn = pdev->dev.of_node; + struct device_node *phy_node; ++ struct device_node *bm_node; + struct mvneta_port *pp; + struct net_device *dev; + const char *dt_mac_addr; +@@ -3690,26 +4097,39 @@ static int mvneta_probe(struct platform_ + + pp->tx_csum_limit = tx_csum_limit; + ++ dram_target_info = mv_mbus_dram_info(); ++ if (dram_target_info) ++ mvneta_conf_mbus_windows(pp, dram_target_info); ++ + pp->tx_ring_size = MVNETA_MAX_TXD; + pp->rx_ring_size = MVNETA_MAX_RXD; + + pp->dev = dev; + SET_NETDEV_DEV(dev, &pdev->dev); + ++ pp->id = global_port_id++; ++ ++ /* Obtain access to BM resources if enabled and already initialized */ ++ bm_node = of_parse_phandle(dn, "buffer-manager", 0); ++ if (bm_node && bm_node->data) { ++ pp->bm_priv = bm_node->data; ++ err = mvneta_bm_port_init(pdev, pp); ++ if (err < 0) { ++ dev_info(&pdev->dev, "use SW buffer management\n"); ++ pp->bm_priv = NULL; ++ } ++ } ++ + err = mvneta_init(&pdev->dev, pp); + if (err < 0) +- goto err_free_stats; ++ goto err_netdev; + + err = mvneta_port_power_up(pp, phy_mode); + if (err < 0) { + dev_err(&pdev->dev, "can't power up port\n"); +- goto err_free_stats; ++ goto err_netdev; + } + +- dram_target_info = mv_mbus_dram_info(); +- if (dram_target_info) +- mvneta_conf_mbus_windows(pp, dram_target_info); +- + for_each_present_cpu(cpu) { + struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); + +@@ -3744,6 +4164,13 @@ static int mvneta_probe(struct platform_ + + return 0; + ++err_netdev: ++ unregister_netdev(dev); ++ if (pp->bm_priv) { ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, ++ 1 << pp->id); ++ } + err_free_stats: + free_percpu(pp->stats); + err_free_ports: +@@ -3773,6 +4200,12 @@ static int mvneta_remove(struct platform + of_node_put(pp->phy_node); + free_netdev(dev); + ++ if (pp->bm_priv) { ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, ++ 1 << pp->id); ++ } ++ + return 0; + } + +--- /dev/null ++++ b/drivers/net/ethernet/marvell/mvneta_bm.c +@@ -0,0 +1,546 @@ ++/* ++ * Driver for Marvell NETA network controller Buffer Manager. ++ * ++ * Copyright (C) 2015 Marvell ++ * ++ * Marcin Wojtas ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "mvneta_bm.h" ++ ++#define MVNETA_BM_DRIVER_NAME "mvneta_bm" ++#define MVNETA_BM_DRIVER_VERSION "1.0" ++ ++static void mvneta_bm_write(struct mvneta_bm *priv, u32 offset, u32 data) ++{ ++ writel(data, priv->reg_base + offset); ++} ++ ++static u32 mvneta_bm_read(struct mvneta_bm *priv, u32 offset) ++{ ++ return readl(priv->reg_base + offset); ++} ++ ++static void mvneta_bm_pool_enable(struct mvneta_bm *priv, int pool_id) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id)); ++ val |= MVNETA_BM_POOL_ENABLE_MASK; ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val); ++ ++ /* Clear BM cause register */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0); ++} ++ ++static void mvneta_bm_pool_disable(struct mvneta_bm *priv, int pool_id) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id)); ++ val &= ~MVNETA_BM_POOL_ENABLE_MASK; ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val); ++} ++ ++static inline void mvneta_bm_config_set(struct mvneta_bm *priv, u32 mask) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ val |= mask; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static inline void mvneta_bm_config_clear(struct mvneta_bm *priv, u32 mask) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ val &= ~mask; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static void mvneta_bm_pool_target_set(struct mvneta_bm *priv, int pool_id, ++ u8 target_id, u8 attr) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_XBAR_POOL_REG(pool_id)); ++ val &= ~MVNETA_BM_TARGET_ID_MASK(pool_id); ++ val &= ~MVNETA_BM_XBAR_ATTR_MASK(pool_id); ++ val |= MVNETA_BM_TARGET_ID_VAL(pool_id, target_id); ++ val |= MVNETA_BM_XBAR_ATTR_VAL(pool_id, attr); ++ ++ mvneta_bm_write(priv, MVNETA_BM_XBAR_POOL_REG(pool_id), val); ++} ++ ++/* Allocate skb for BM pool */ ++void *mvneta_buf_alloc(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ dma_addr_t *buf_phys_addr) ++{ ++ void *buf; ++ dma_addr_t phys_addr; ++ ++ buf = mvneta_frag_alloc(bm_pool->frag_size); ++ if (!buf) ++ return NULL; ++ ++ /* In order to update buf_cookie field of RX descriptor properly, ++ * BM hardware expects buf virtual address to be placed in the ++ * first four bytes of mapped buffer. ++ */ ++ *(u32 *)buf = (u32)buf; ++ phys_addr = dma_map_single(&priv->pdev->dev, buf, bm_pool->buf_size, ++ DMA_FROM_DEVICE); ++ if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) { ++ mvneta_frag_free(bm_pool->frag_size, buf); ++ return NULL; ++ } ++ *buf_phys_addr = phys_addr; ++ ++ return buf; ++} ++ ++/* Refill processing for HW buffer management */ ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ dma_addr_t buf_phys_addr; ++ void *buf; ++ ++ buf = mvneta_buf_alloc(priv, bm_pool, &buf_phys_addr); ++ if (!buf) ++ return -ENOMEM; ++ ++ mvneta_bm_pool_put_bp(priv, bm_pool, buf_phys_addr); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_refill); ++ ++/* Allocate buffers for the pool */ ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num) ++{ ++ int err, i; ++ ++ if (bm_pool->buf_num == bm_pool->size) { ++ dev_dbg(&priv->pdev->dev, "pool %d already filled\n", ++ bm_pool->id); ++ return bm_pool->buf_num; ++ } ++ ++ if (buf_num < 0 || ++ (buf_num + bm_pool->buf_num > bm_pool->size)) { ++ dev_err(&priv->pdev->dev, ++ "cannot allocate %d buffers for pool %d\n", ++ buf_num, bm_pool->id); ++ return 0; ++ } ++ ++ for (i = 0; i < buf_num; i++) { ++ err = mvneta_bm_pool_refill(priv, bm_pool); ++ if (err < 0) ++ break; ++ } ++ ++ /* Update BM driver with number of buffers added to pool */ ++ bm_pool->buf_num += i; ++ ++ dev_dbg(&priv->pdev->dev, ++ "%s pool %d: pkt_size=%4d, buf_size=%4d, frag_size=%4d\n", ++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", ++ bm_pool->id, bm_pool->pkt_size, bm_pool->buf_size, ++ bm_pool->frag_size); ++ ++ dev_dbg(&priv->pdev->dev, ++ "%s pool %d: %d of %d buffers added\n", ++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", ++ bm_pool->id, i, buf_num); ++ ++ return i; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_add); ++ ++/* Create pool */ ++static int mvneta_bm_pool_create(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ struct platform_device *pdev = priv->pdev; ++ u8 target_id, attr; ++ int size_bytes, err; ++ ++ size_bytes = sizeof(u32) * bm_pool->size; ++ bm_pool->virt_addr = dma_alloc_coherent(&pdev->dev, size_bytes, ++ &bm_pool->phys_addr, ++ GFP_KERNEL); ++ if (!bm_pool->virt_addr) ++ return -ENOMEM; ++ ++ if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { ++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, ++ bm_pool->phys_addr); ++ dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", ++ bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); ++ return -ENOMEM; ++ } ++ ++ err = mvebu_mbus_get_dram_win_info(bm_pool->phys_addr, &target_id, ++ &attr); ++ if (err < 0) { ++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, ++ bm_pool->phys_addr); ++ return err; ++ } ++ ++ /* Set pool address */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(bm_pool->id), ++ bm_pool->phys_addr); ++ ++ mvneta_bm_pool_target_set(priv, bm_pool->id, target_id, attr); ++ mvneta_bm_pool_enable(priv, bm_pool->id); ++ ++ return 0; ++} ++ ++/* Notify the driver that BM pool is being used as specific type and return the ++ * pool pointer on success ++ */ ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size) ++{ ++ struct mvneta_bm_pool *new_pool = &priv->bm_pools[pool_id]; ++ int num, err; ++ ++ if (new_pool->type == MVNETA_BM_LONG && ++ new_pool->port_map != 1 << port_id) { ++ dev_err(&priv->pdev->dev, ++ "long pool cannot be shared by the ports\n"); ++ return NULL; ++ } ++ ++ if (new_pool->type == MVNETA_BM_SHORT && new_pool->type != type) { ++ dev_err(&priv->pdev->dev, ++ "mixing pools' types between the ports is forbidden\n"); ++ return NULL; ++ } ++ ++ if (new_pool->pkt_size == 0 || type != MVNETA_BM_SHORT) ++ new_pool->pkt_size = pkt_size; ++ ++ /* Allocate buffers in case BM pool hasn't been used yet */ ++ if (new_pool->type == MVNETA_BM_FREE) { ++ new_pool->type = type; ++ new_pool->buf_size = MVNETA_RX_BUF_SIZE(new_pool->pkt_size); ++ new_pool->frag_size = ++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(new_pool->pkt_size)) + ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); ++ ++ /* Create new pool */ ++ err = mvneta_bm_pool_create(priv, new_pool); ++ if (err) { ++ dev_err(&priv->pdev->dev, "fail to create pool %d\n", ++ new_pool->id); ++ return NULL; ++ } ++ ++ /* Allocate buffers for this pool */ ++ num = mvneta_bm_bufs_add(priv, new_pool, new_pool->size); ++ if (num != new_pool->size) { ++ WARN(1, "pool %d: %d of %d allocated\n", ++ new_pool->id, num, new_pool->size); ++ return NULL; ++ } ++ } ++ ++ return new_pool; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_use); ++ ++/* Free all buffers from the pool */ ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map) ++{ ++ int i; ++ ++ bm_pool->port_map &= ~port_map; ++ if (bm_pool->port_map) ++ return; ++ ++ mvneta_bm_config_set(priv, MVNETA_BM_EMPTY_LIMIT_MASK); ++ ++ for (i = 0; i < bm_pool->buf_num; i++) { ++ dma_addr_t buf_phys_addr; ++ u32 *vaddr; ++ ++ /* Get buffer physical address (indirect access) */ ++ buf_phys_addr = mvneta_bm_pool_get_bp(priv, bm_pool); ++ ++ /* Work-around to the problems when destroying the pool, ++ * when it occurs that a read access to BPPI returns 0. ++ */ ++ if (buf_phys_addr == 0) ++ continue; ++ ++ vaddr = phys_to_virt(buf_phys_addr); ++ if (!vaddr) ++ break; ++ ++ dma_unmap_single(&priv->pdev->dev, buf_phys_addr, ++ bm_pool->buf_size, DMA_FROM_DEVICE); ++ mvneta_frag_free(bm_pool->frag_size, vaddr); ++ } ++ ++ mvneta_bm_config_clear(priv, MVNETA_BM_EMPTY_LIMIT_MASK); ++ ++ /* Update BM driver with number of buffers removed from pool */ ++ bm_pool->buf_num -= i; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free); ++ ++/* Cleanup pool */ ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map) ++{ ++ bm_pool->port_map &= ~port_map; ++ if (bm_pool->port_map) ++ return; ++ ++ bm_pool->type = MVNETA_BM_FREE; ++ ++ mvneta_bm_bufs_free(priv, bm_pool, port_map); ++ if (bm_pool->buf_num) ++ WARN(1, "cannot free all buffers in pool %d\n", bm_pool->id); ++ ++ if (bm_pool->virt_addr) { ++ dma_free_coherent(&priv->pdev->dev, sizeof(u32) * bm_pool->size, ++ bm_pool->virt_addr, bm_pool->phys_addr); ++ bm_pool->virt_addr = NULL; ++ } ++ ++ mvneta_bm_pool_disable(priv, bm_pool->id); ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_destroy); ++ ++static void mvneta_bm_pools_init(struct mvneta_bm *priv) ++{ ++ struct device_node *dn = priv->pdev->dev.of_node; ++ struct mvneta_bm_pool *bm_pool; ++ char prop[15]; ++ u32 size; ++ int i; ++ ++ /* Activate BM unit */ ++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); ++ ++ /* Create all pools with maximum size */ ++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { ++ bm_pool = &priv->bm_pools[i]; ++ bm_pool->id = i; ++ bm_pool->type = MVNETA_BM_FREE; ++ ++ /* Reset read pointer */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); ++ ++ /* Reset write pointer */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); ++ ++ /* Configure pool size according to DT or use default value */ ++ sprintf(prop, "pool%d,capacity", i); ++ if (of_property_read_u32(dn, prop, &size)) { ++ size = MVNETA_BM_POOL_CAP_DEF; ++ } else if (size > MVNETA_BM_POOL_CAP_MAX) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, set to %d\n", ++ i, size, MVNETA_BM_POOL_CAP_MAX); ++ size = MVNETA_BM_POOL_CAP_MAX; ++ } else if (size < MVNETA_BM_POOL_CAP_MIN) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, set to %d\n", ++ i, size, MVNETA_BM_POOL_CAP_MIN); ++ size = MVNETA_BM_POOL_CAP_MIN; ++ } else if (!IS_ALIGNED(size, MVNETA_BM_POOL_CAP_ALIGN)) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, round to %d\n", ++ i, size, ALIGN(size, ++ MVNETA_BM_POOL_CAP_ALIGN)); ++ size = ALIGN(size, MVNETA_BM_POOL_CAP_ALIGN); ++ } ++ bm_pool->size = size; ++ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), ++ bm_pool->size); ++ ++ /* Obtain custom pkt_size from DT */ ++ sprintf(prop, "pool%d,pkt-size", i); ++ if (of_property_read_u32(dn, prop, &bm_pool->pkt_size)) ++ bm_pool->pkt_size = 0; ++ } ++} ++ ++static void mvneta_bm_default_set(struct mvneta_bm *priv) ++{ ++ u32 val; ++ ++ /* Mask BM all interrupts */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_MASK_REG, 0); ++ ++ /* Clear BM cause register */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0); ++ ++ /* Set BM configuration register */ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ ++ /* Reduce MaxInBurstSize from 32 BPs to 16 BPs */ ++ val &= ~MVNETA_BM_MAX_IN_BURST_SIZE_MASK; ++ val |= MVNETA_BM_MAX_IN_BURST_SIZE_16BP; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static int mvneta_bm_init(struct mvneta_bm *priv) ++{ ++ mvneta_bm_default_set(priv); ++ ++ /* Allocate and initialize BM pools structures */ ++ priv->bm_pools = devm_kcalloc(&priv->pdev->dev, MVNETA_BM_POOLS_NUM, ++ sizeof(struct mvneta_bm_pool), ++ GFP_KERNEL); ++ if (!priv->bm_pools) ++ return -ENOMEM; ++ ++ mvneta_bm_pools_init(priv); ++ ++ return 0; ++} ++ ++static int mvneta_bm_get_sram(struct device_node *dn, ++ struct mvneta_bm *priv) ++{ ++ priv->bppi_pool = of_gen_pool_get(dn, "internal-mem", 0); ++ if (!priv->bppi_pool) ++ return -ENOMEM; ++ ++ priv->bppi_virt_addr = gen_pool_dma_alloc(priv->bppi_pool, ++ MVNETA_BM_BPPI_SIZE, ++ &priv->bppi_phys_addr); ++ if (!priv->bppi_virt_addr) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void mvneta_bm_put_sram(struct mvneta_bm *priv) ++{ ++ gen_pool_free(priv->bppi_pool, priv->bppi_phys_addr, ++ MVNETA_BM_BPPI_SIZE); ++} ++ ++static int mvneta_bm_probe(struct platform_device *pdev) ++{ ++ struct device_node *dn = pdev->dev.of_node; ++ struct mvneta_bm *priv; ++ struct resource *res; ++ int err; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(struct mvneta_bm), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ priv->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->reg_base)) ++ return PTR_ERR(priv->reg_base); ++ ++ priv->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->clk)) ++ return PTR_ERR(priv->clk); ++ err = clk_prepare_enable(priv->clk); ++ if (err < 0) ++ return err; ++ ++ err = mvneta_bm_get_sram(dn, priv); ++ if (err < 0) { ++ dev_err(&pdev->dev, "failed to allocate internal memory\n"); ++ goto err_clk; ++ } ++ ++ priv->pdev = pdev; ++ ++ /* Initialize buffer manager internals */ ++ err = mvneta_bm_init(priv); ++ if (err < 0) { ++ dev_err(&pdev->dev, "failed to initialize controller\n"); ++ goto err_sram; ++ } ++ ++ dn->data = priv; ++ platform_set_drvdata(pdev, priv); ++ ++ dev_info(&pdev->dev, "Buffer Manager for network controller enabled\n"); ++ ++ return 0; ++ ++err_sram: ++ mvneta_bm_put_sram(priv); ++err_clk: ++ clk_disable_unprepare(priv->clk); ++ return err; ++} ++ ++static int mvneta_bm_remove(struct platform_device *pdev) ++{ ++ struct mvneta_bm *priv = platform_get_drvdata(pdev); ++ u8 all_ports_map = 0xff; ++ int i = 0; ++ ++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { ++ struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; ++ ++ mvneta_bm_pool_destroy(priv, bm_pool, all_ports_map); ++ } ++ ++ mvneta_bm_put_sram(priv); ++ ++ /* Dectivate BM unit */ ++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); ++ ++ clk_disable_unprepare(priv->clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id mvneta_bm_match[] = { ++ { .compatible = "marvell,armada-380-neta-bm" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, mvneta_bm_match); ++ ++static struct platform_driver mvneta_bm_driver = { ++ .probe = mvneta_bm_probe, ++ .remove = mvneta_bm_remove, ++ .driver = { ++ .name = MVNETA_BM_DRIVER_NAME, ++ .of_match_table = mvneta_bm_match, ++ }, ++}; ++ ++module_platform_driver(mvneta_bm_driver); ++ ++MODULE_DESCRIPTION("Marvell NETA Buffer Manager Driver - www.marvell.com"); ++MODULE_AUTHOR("Marcin Wojtas "); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/net/ethernet/marvell/mvneta_bm.h +@@ -0,0 +1,189 @@ ++/* ++ * Driver for Marvell NETA network controller Buffer Manager. ++ * ++ * Copyright (C) 2015 Marvell ++ * ++ * Marcin Wojtas ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#ifndef _MVNETA_BM_H_ ++#define _MVNETA_BM_H_ ++ ++/* BM Configuration Register */ ++#define MVNETA_BM_CONFIG_REG 0x0 ++#define MVNETA_BM_STATUS_MASK 0x30 ++#define MVNETA_BM_ACTIVE_MASK BIT(4) ++#define MVNETA_BM_MAX_IN_BURST_SIZE_MASK 0x60000 ++#define MVNETA_BM_MAX_IN_BURST_SIZE_16BP BIT(18) ++#define MVNETA_BM_EMPTY_LIMIT_MASK BIT(19) ++ ++/* BM Activation Register */ ++#define MVNETA_BM_COMMAND_REG 0x4 ++#define MVNETA_BM_START_MASK BIT(0) ++#define MVNETA_BM_STOP_MASK BIT(1) ++#define MVNETA_BM_PAUSE_MASK BIT(2) ++ ++/* BM Xbar interface Register */ ++#define MVNETA_BM_XBAR_01_REG 0x8 ++#define MVNETA_BM_XBAR_23_REG 0xc ++#define MVNETA_BM_XBAR_POOL_REG(pool) \ ++ (((pool) < 2) ? MVNETA_BM_XBAR_01_REG : MVNETA_BM_XBAR_23_REG) ++#define MVNETA_BM_TARGET_ID_OFFS(pool) (((pool) & 1) ? 16 : 0) ++#define MVNETA_BM_TARGET_ID_MASK(pool) \ ++ (0xf << MVNETA_BM_TARGET_ID_OFFS(pool)) ++#define MVNETA_BM_TARGET_ID_VAL(pool, id) \ ++ ((id) << MVNETA_BM_TARGET_ID_OFFS(pool)) ++#define MVNETA_BM_XBAR_ATTR_OFFS(pool) (((pool) & 1) ? 20 : 4) ++#define MVNETA_BM_XBAR_ATTR_MASK(pool) \ ++ (0xff << MVNETA_BM_XBAR_ATTR_OFFS(pool)) ++#define MVNETA_BM_XBAR_ATTR_VAL(pool, attr) \ ++ ((attr) << MVNETA_BM_XBAR_ATTR_OFFS(pool)) ++ ++/* Address of External Buffer Pointers Pool Register */ ++#define MVNETA_BM_POOL_BASE_REG(pool) (0x10 + ((pool) << 4)) ++#define MVNETA_BM_POOL_ENABLE_MASK BIT(0) ++ ++/* External Buffer Pointers Pool RD pointer Register */ ++#define MVNETA_BM_POOL_READ_PTR_REG(pool) (0x14 + ((pool) << 4)) ++#define MVNETA_BM_POOL_SET_READ_PTR_MASK 0xfffc ++#define MVNETA_BM_POOL_GET_READ_PTR_OFFS 16 ++#define MVNETA_BM_POOL_GET_READ_PTR_MASK 0xfffc0000 ++ ++/* External Buffer Pointers Pool WR pointer */ ++#define MVNETA_BM_POOL_WRITE_PTR_REG(pool) (0x18 + ((pool) << 4)) ++#define MVNETA_BM_POOL_SET_WRITE_PTR_OFFS 0 ++#define MVNETA_BM_POOL_SET_WRITE_PTR_MASK 0xfffc ++#define MVNETA_BM_POOL_GET_WRITE_PTR_OFFS 16 ++#define MVNETA_BM_POOL_GET_WRITE_PTR_MASK 0xfffc0000 ++ ++/* External Buffer Pointers Pool Size Register */ ++#define MVNETA_BM_POOL_SIZE_REG(pool) (0x1c + ((pool) << 4)) ++#define MVNETA_BM_POOL_SIZE_MASK 0x3fff ++ ++/* BM Interrupt Cause Register */ ++#define MVNETA_BM_INTR_CAUSE_REG (0x50) ++ ++/* BM interrupt Mask Register */ ++#define MVNETA_BM_INTR_MASK_REG (0x54) ++ ++/* Other definitions */ ++#define MVNETA_BM_SHORT_PKT_SIZE 256 ++#define MVNETA_BM_POOLS_NUM 4 ++#define MVNETA_BM_POOL_CAP_MIN 128 ++#define MVNETA_BM_POOL_CAP_DEF 2048 ++#define MVNETA_BM_POOL_CAP_MAX \ ++ (16 * 1024 - MVNETA_BM_POOL_CAP_ALIGN) ++#define MVNETA_BM_POOL_CAP_ALIGN 32 ++#define MVNETA_BM_POOL_PTR_ALIGN 32 ++ ++#define MVNETA_BM_POOL_ACCESS_OFFS 8 ++ ++#define MVNETA_BM_BPPI_SIZE 0x100000 ++ ++#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD) ++ ++enum mvneta_bm_type { ++ MVNETA_BM_FREE, ++ MVNETA_BM_LONG, ++ MVNETA_BM_SHORT ++}; ++ ++struct mvneta_bm { ++ void __iomem *reg_base; ++ struct clk *clk; ++ struct platform_device *pdev; ++ ++ struct gen_pool *bppi_pool; ++ /* BPPI virtual base address */ ++ void __iomem *bppi_virt_addr; ++ /* BPPI physical base address */ ++ dma_addr_t bppi_phys_addr; ++ ++ /* BM pools */ ++ struct mvneta_bm_pool *bm_pools; ++}; ++ ++struct mvneta_bm_pool { ++ /* Pool number in the range 0-3 */ ++ u8 id; ++ enum mvneta_bm_type type; ++ ++ /* Buffer Pointers Pool External (BPPE) size in number of bytes */ ++ int size; ++ /* Number of buffers used by this pool */ ++ int buf_num; ++ /* Pool buffer size */ ++ int buf_size; ++ /* Packet size */ ++ int pkt_size; ++ /* Single frag size */ ++ u32 frag_size; ++ ++ /* BPPE virtual base address */ ++ u32 *virt_addr; ++ /* BPPE physical base address */ ++ dma_addr_t phys_addr; ++ ++ /* Ports using BM pool */ ++ u8 port_map; ++ ++ struct mvneta_bm *priv; ++}; ++ ++/* Declarations and definitions */ ++void *mvneta_frag_alloc(unsigned int frag_size); ++void mvneta_frag_free(unsigned int frag_size, void *data); ++ ++#if defined(CONFIG_MVNETA_BM) || defined(CONFIG_MVNETA_BM_MODULE) ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map); ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map); ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num); ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool); ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size); ++ ++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, ++ dma_addr_t buf_phys_addr) ++{ ++ writel_relaxed(buf_phys_addr, priv->bppi_virt_addr + ++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS)); ++} ++ ++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ return readl_relaxed(priv->bppi_virt_addr + ++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS)); ++} ++#else ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map) {} ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map) {} ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num) { return 0; } ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) {return 0; } ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size) { return NULL; } ++ ++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, ++ dma_addr_t buf_phys_addr) {} ++ ++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ return 0; } ++#endif /* CONFIG_MVNETA_BM */ ++#endif -- cgit v1.2.3