[Patch v2 5/5] drivers/net: support hdlc function for QE-UCC

Joakim Tjernlund Joakim.Tjernlund at infinera.com
Thu Jun 2 17:37:32 AEST 2016


On Thu, 2016-06-02 at 09:45 +0800, Zhao Qiang wrote:
> The driver add hdlc support for Freescale QUICC Engine.
> It support NMSI and TSA mode.
> 
> Signed-off-by: Zhao Qiang <qiang.zhao at nxp.com>
> ---
> Changes for v2:
> 	- remove useless code.
> 	- remove Unnecessary casts
> 	- return IRQ_NONE when there are no interrupt
> 	- remove Useless comments
> 
>  MAINTAINERS                    |    7 +
>  drivers/net/wan/Kconfig        |   11 +
>  drivers/net/wan/Makefile       |    1 +
>  drivers/net/wan/fsl_ucc_hdlc.c | 1189 ++++++++++++++++++++++++++++++++++++++++
>  drivers/net/wan/fsl_ucc_hdlc.h |  147 +++++
>  include/soc/fsl/qe/qe.h        |    1 +
>  include/soc/fsl/qe/ucc_fast.h  |   21 +-
>  7 files changed, 1375 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/net/wan/fsl_ucc_hdlc.c
>  create mode 100644 drivers/net/wan/fsl_ucc_hdlc.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 74bbff3..bdada16 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4572,6 +4572,13 @@ F:	drivers/net/ethernet/freescale/gianfar*
>  X:	drivers/net/ethernet/freescale/gianfar_ptp.c
>  F:	Documentation/devicetree/bindings/net/fsl-tsec-phy.txt
>  
> +FREESCALE QUICC ENGINE UCC HDLC DRIVER
> +M:	Zhao Qiang <qiang.zhao at nxp.com>
> +L:	netdev at vger.kernel.org
> +L:	linuxppc-dev at lists.ozlabs.org
> +S:	Maintained
> +F:	drivers/net/wan/fsl_ucc_hdlc*
> +
>  FREESCALE QUICC ENGINE UCC UART DRIVER
>  M:	Timur Tabi <timur at tabi.org>
>  L:	linuxppc-dev at lists.ozlabs.org
> diff --git a/drivers/net/wan/Kconfig b/drivers/net/wan/Kconfig
> index a2fdd15..9e314b7 100644
> --- a/drivers/net/wan/Kconfig
> +++ b/drivers/net/wan/Kconfig
> @@ -280,6 +280,17 @@ config DSCC4
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called dscc4.
>  
> +config FSL_UCC_HDLC
> +	tristate "Freescale QUICC Engine HDLC support"
> +	depends on HDLC
> +	depends on QUICC_ENGINE
> +	help
> +	  Driver for Freescale QUICC Engine HDLC controller. The driver
> +	  supports HDLC in NMSI and TDM mode.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called fsl_ucc_hdlc.
> +
>  config DSCC4_PCISYNC
>  	bool "Etinc PCISYNC features"
>  	depends on DSCC4
> diff --git a/drivers/net/wan/Makefile b/drivers/net/wan/Makefile
> index c135ef4..25fec40 100644
> --- a/drivers/net/wan/Makefile
> +++ b/drivers/net/wan/Makefile
> @@ -32,6 +32,7 @@ obj-$(CONFIG_WANXL)		+= wanxl.o
>  obj-$(CONFIG_PCI200SYN)		+= pci200syn.o
>  obj-$(CONFIG_PC300TOO)		+= pc300too.o
>  obj-$(CONFIG_IXP4XX_HSS)	+= ixp4xx_hss.o
> +obj-$(CONFIG_FSL_UCC_HDLC)	+= fsl_ucc_hdlc.o
>  
>  clean-files := wanxlfw.inc
>  $(obj)/wanxl.o:	$(obj)/wanxlfw.inc
> diff --git a/drivers/net/wan/fsl_ucc_hdlc.c b/drivers/net/wan/fsl_ucc_hdlc.c
> new file mode 100644
> index 0000000..f72634d
> --- /dev/null
> +++ b/drivers/net/wan/fsl_ucc_hdlc.c
> @@ -0,0 +1,1189 @@
> +/* Freescale QUICC Engine HDLC Device Driver
> + *
> + * Copyright 2016 Freescale Semiconductor Inc.
> + *
> + * This program is free software; you can redistribute  it and/or modify it
> + * under  the terms of  the GNU General  Public License as published by the
> + * Free Software Foundation;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/hdlc.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/sched.h>
> +#include <linux/skbuff.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/stddef.h>
> +#include <soc/fsl/qe/qe_tdm.h>
> +#include <uapi/linux/if_arp.h>
> +
> +#include "fsl_ucc_hdlc.h"
> +
> +#define DRV_DESC "Freescale QE UCC HDLC Driver"
> +#define DRV_NAME "ucc_hdlc"
> +
> +#define TDM_PPPOHT_SLIC_MAXIN
> +#define BROKEN_FRAME_INFO
> +
> +static struct ucc_tdm_info utdm_primary_info = {
> +	.uf_info = {
> +		.tsa = 0,
> +		.cdp = 0,
> +		.cds = 1,
> +		.ctsp = 1,
> +		.ctss = 1,
> +		.revd = 0,
> +		.urfs = 256,
> +		.utfs = 256,
> +		.urfet = 128,
> +		.urfset = 192,
> +		.utfet = 128,
> +		.utftt = 0x40,
> +		.ufpt = 256,
> +		.mode = UCC_FAST_PROTOCOL_MODE_HDLC,
> +		.ttx_trx = UCC_FAST_GUMR_TRANSPARENT_TTX_TRX_NORMAL,
> +		.tenc = UCC_FAST_TX_ENCODING_NRZ,
> +		.renc = UCC_FAST_RX_ENCODING_NRZ,
> +		.tcrc = UCC_FAST_16_BIT_CRC,
> +		.synl = UCC_FAST_SYNC_LEN_NOT_USED,
> +	},
> +
> +	.si_info = {
> +#ifdef TDM_PPPOHT_SLIC_MAXIN
> +		.simr_rfsd = 1,
> +		.simr_tfsd = 2,
> +#else
> +		.simr_rfsd = 0,
> +		.simr_tfsd = 0,
> +#endif
> +		.simr_crt = 0,
> +		.simr_sl = 0,
> +		.simr_ce = 1,
> +		.simr_fe = 1,
> +		.simr_gm = 0,
> +	},
> +};
> +
> +static struct ucc_tdm_info utdm_info[MAX_HDLC_NUM];
> +
> +static int uhdlc_init(struct ucc_hdlc_private *priv)
> +{
> +	struct ucc_tdm_info *ut_info;
> +	struct ucc_fast_info *uf_info;
> +	u32 cecr_subblock;
> +	u16 bd_status;
> +	int ret, i;
> +	void *bd_buffer;
> +	dma_addr_t bd_dma_addr;
> +	u32 riptr;
> +	u32 tiptr;
> +	u32 gumr;
> +
> +	ut_info = priv->ut_info;
> +	uf_info = &ut_info->uf_info;
> +
> +	if (priv->tsa) {
> +		uf_info->tsa = 1;
> +		uf_info->ctsp = 1;
> +	}
> +	uf_info->uccm_mask = ((UCC_HDLC_UCCE_RXB | UCC_HDLC_UCCE_RXF |
> +				UCC_HDLC_UCCE_TXB) << 16);
> +
> +	ret = ucc_fast_init(uf_info, &priv->uccf);
> +	if (ret) {
> +		dev_err(priv->dev, "Failed to init uccf.");
> +		return ret;
> +	}
> +
> +	priv->uf_regs = priv->uccf->uf_regs;
> +	ucc_fast_disable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX);
> +
> +	/* Loopback mode */
> +	if (priv->loopback) {
> +		dev_info(priv->dev, "Loopback Mode\n");
> +		gumr = ioread32be(&priv->uf_regs->gumr);
> +		gumr |= (UCC_FAST_GUMR_LOOPBACK | UCC_FAST_GUMR_CDS |
> +			 UCC_FAST_GUMR_TCI);
> +		gumr &= ~(UCC_FAST_GUMR_CTSP | UCC_FAST_GUMR_RSYN);
> +		iowrite32be(gumr, &priv->uf_regs->gumr);
> +	}
> +
> +	/* Initialize SI */
> +	if (priv->tsa)
> +		ucc_tdm_init(priv->utdm, priv->ut_info);
> +
> +	/* Write to QE CECR, UCCx channel to Stop Transmission */
> +	cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num);
> +	ret = qe_issue_cmd(QE_STOP_TX, cecr_subblock,
> +			   QE_CR_PROTOCOL_UNSPECIFIED, 0);
> +
> +	/* Set UPSMR normal mode (need fixed)*/
> +	iowrite32be(0, &priv->uf_regs->upsmr);
> +
> +	priv->rx_ring_size = RX_BD_RING_LEN;
> +	priv->tx_ring_size = TX_BD_RING_LEN;
> +	/* Alloc Rx BD */
> +	priv->rx_bd_base = dma_alloc_coherent(priv->dev,
> +			RX_BD_RING_LEN * sizeof(struct qe_bd *),
> +			&priv->dma_rx_bd, GFP_KERNEL);
> +
> +	if (!priv->rx_bd_base) {
> +		dev_err(priv->dev, "Cannot allocate MURAM memory for RxBDs\n");
> +		ret = -ENOMEM;
> +		goto rxbd_alloc_error;
> +	}
> +
> +	/* Alloc Tx BD */
> +	priv->tx_bd_base = dma_alloc_coherent(priv->dev,
> +			TX_BD_RING_LEN * sizeof(struct qe_bd *),
> +			&priv->dma_tx_bd, GFP_KERNEL);
> +
> +	if (!priv->tx_bd_base) {
> +		dev_err(priv->dev, "Cannot allocate MURAM memory for TxBDs\n");
> +		ret = -ENOMEM;
> +		goto txbd_alloc_error;
> +	}
> +
> +	/* Alloc parameter ram for ucc hdlc */
> +	priv->ucc_pram_offset = qe_muram_alloc(sizeof(priv->ucc_pram),
> +				ALIGNMENT_OF_UCC_HDLC_PRAM);
> +
> +	if (priv->ucc_pram_offset < 0) {
> +		dev_err(priv->dev, "Can not allocate MURAM for hdlc prameter.\n");
> +		ret = -ENOMEM;
> +		goto pram_alloc_error;
> +	}
> +
> +	priv->rx_skbuff = kzalloc(priv->rx_ring_size * sizeof(*priv->rx_skbuff),
> +				  GFP_KERNEL);
> +	if (!priv->rx_skbuff)
> +		goto rx_skb_alloc_error;
> +
> +	priv->tx_skbuff = kzalloc(priv->tx_ring_size * sizeof(*priv->tx_skbuff),
> +				  GFP_KERNEL);
> +	if (!priv->tx_skbuff)
> +		goto tx_skb_alloc_error;
> +
> +	priv->skb_curtx = 0;
> +	priv->skb_dirtytx = 0;
> +	priv->curtx_bd = priv->tx_bd_base;
> +	priv->dirty_tx = priv->tx_bd_base;
> +	priv->currx_bd = priv->rx_bd_base;
> +	priv->currx_bdnum = 0;
> +
> +	/* init parameter base */
> +	cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num);
> +	ret = qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, cecr_subblock,
> +			   QE_CR_PROTOCOL_UNSPECIFIED, priv->ucc_pram_offset);
> +
> +	priv->ucc_pram = (struct ucc_hdlc_param __iomem *)
> +					qe_muram_addr(priv->ucc_pram_offset);
> +
> +	/* Zero out parameter ram */
> +	memset_io(priv->ucc_pram, 0, sizeof(struct ucc_hdlc_param));
> +
> +	/* Alloc riptr, tiptr */
> +	riptr = qe_muram_alloc(32, 32);
> +	if (riptr < 0) {
> +		dev_err(priv->dev, "Cannot allocate MURAM mem for Receive internal temp data pointer\n");
> +		ret = -ENOMEM;
> +		goto riptr_alloc_error;
> +	}
> +
> +	tiptr = qe_muram_alloc(32, 32);
> +	if (tiptr < 0) {
> +		dev_err(priv->dev, "Cannot allocate MURAM mem for Transmit internal temp data pointer\n");
> +		ret = -ENOMEM;
> +		goto tiptr_alloc_error;
> +	}
> +
> +	/* Set RIPTR, TIPTR */
> +	iowrite16be(riptr, &priv->ucc_pram->riptr);
> +	iowrite16be(tiptr, &priv->ucc_pram->tiptr);
> +
> +	/* Set MRBLR */
> +	iowrite16be(MAX_RX_BUF_LENGTH, &priv->ucc_pram->mrblr);
> +
> +	/* Set RBASE, TBASE */
> +	iowrite32be(priv->dma_rx_bd, &priv->ucc_pram->rbase);
> +	iowrite32be(priv->dma_tx_bd, &priv->ucc_pram->tbase);
> +
> +	/* Set RSTATE, TSTATE */
> +	iowrite32be(BMR_GBL | BMR_BIG_ENDIAN, &priv->ucc_pram->rstate);
> +	iowrite32be(BMR_GBL | BMR_BIG_ENDIAN, &priv->ucc_pram->tstate);
> +
> +	/* Set C_MASK, C_PRES for 16bit CRC */
> +	iowrite32be(CRC_16BIT_MASK, &priv->ucc_pram->c_mask);
> +	iowrite32be(CRC_16BIT_PRES, &priv->ucc_pram->c_pres);
> +
> +	iowrite16be(MAX_FRAME_LENGTH, &priv->ucc_pram->mflr);
> +	iowrite16be(DEFAULT_RFTHR, &priv->ucc_pram->rfthr);
> +	iowrite16be(DEFAULT_RFTHR, &priv->ucc_pram->rfcnt);
> +	iowrite16be(DEFAULT_ADDR_MASK, &priv->ucc_pram->hmask);
> +	iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr1);
> +	iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr2);
> +	iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr3);
> +	iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr4);
> +
> +	/* Get BD buffer */
> +	bd_buffer = dma_alloc_coherent(priv->dev,
> +				       (RX_BD_RING_LEN + TX_BD_RING_LEN) *
> +				       MAX_RX_BUF_LENGTH,
> +				       &bd_dma_addr, GFP_KERNEL);
> +
> +	if (!bd_buffer) {
> +		dev_err(priv->dev, "Could not allocate buffer descriptors\n");
> +		ret = -ENOMEM;
> +		goto bd_alloc_error;
> +	}
> +
> +	memset(bd_buffer, 0, (RX_BD_RING_LEN + TX_BD_RING_LEN)
> +			* MAX_RX_BUF_LENGTH);
> +
> +	priv->rx_buffer = bd_buffer;
> +	priv->tx_buffer = bd_buffer + RX_BD_RING_LEN * MAX_RX_BUF_LENGTH;
> +
> +	priv->dma_rx_addr = bd_dma_addr;
> +	priv->dma_tx_addr = bd_dma_addr + RX_BD_RING_LEN * MAX_RX_BUF_LENGTH;
> +
> +	for (i = 0; i < RX_BD_RING_LEN; i++) {
> +		if (i < (RX_BD_RING_LEN - 1))
> +			bd_status = R_E_S | R_I_S;
> +		else
> +			bd_status = R_E_S | R_I_S | R_W_S;
> +
> +		iowrite16be(bd_status, &priv->rx_bd_base[i].status);
> +		iowrite32be(priv->dma_rx_addr + i * MAX_RX_BUF_LENGTH,
> +			    &priv->rx_bd_base[i].buf);
> +	}
> +
> +	for (i = 0; i < TX_BD_RING_LEN; i++) {
> +		if (i < (TX_BD_RING_LEN - 1))
> +			bd_status =  T_I_S | T_TC_S;
> +		else
> +			bd_status =  T_I_S | T_TC_S | T_W_S;
> +
> +		iowrite16be(bd_status, &priv->tx_bd_base[i].status);
> +		iowrite32be(priv->dma_tx_addr + i * MAX_RX_BUF_LENGTH,
> +			    &priv->tx_bd_base[i].buf);
> +	}
> +
> +	return 0;
> +
> +bd_alloc_error:
> +	qe_muram_free(tiptr);
> +tiptr_alloc_error:
> +	qe_muram_free(riptr);
> +riptr_alloc_error:
> +	kfree(priv->tx_skbuff);
> +tx_skb_alloc_error:
> +	kfree(priv->rx_skbuff);
> +rx_skb_alloc_error:
> +	qe_muram_free(priv->ucc_pram_offset);
> +pram_alloc_error:
> +	dma_free_coherent(priv->dev,
> +			  TX_BD_RING_LEN * sizeof(struct qe_bd),
> +			  priv->tx_bd_base, priv->dma_tx_bd);
> +txbd_alloc_error:
> +	dma_free_coherent(priv->dev,
> +			  RX_BD_RING_LEN * sizeof(struct qe_bd),
> +			  priv->rx_bd_base, priv->dma_rx_bd);
> +rxbd_alloc_error:
> +	ucc_fast_free(priv->uccf);
> +
> +	return ret;
> +}
> +
> +static netdev_tx_t ucc_hdlc_tx(struct sk_buff *skb, struct net_device *dev)
> +{
> +	hdlc_device *hdlc = dev_to_hdlc(dev);
> +	struct ucc_hdlc_private *priv = (struct ucc_hdlc_private *)hdlc->priv;
> +	struct qe_bd __iomem *bd;
> +	u16 bd_status;
> +	unsigned long flags;
> +	u8 *send_buf;
> +	int i;
> +	u16 *proto_head;
> +
> +	switch (dev->type) {
> +	case ARPHRD_RAWHDLC:
> +		if (skb_headroom(skb) < HDLC_HEAD_LEN) {
> +			dev->stats.tx_dropped++;
> +			dev_kfree_skb(skb);
> +			netdev_err(dev, "No enough space for hdlc head\n");
> +			return -ENOMEM;
> +		}
> +
> +		skb_push(skb, HDLC_HEAD_LEN);
> +
> +		proto_head = (u16 *)skb->data;
> +		*proto_head = htons(DEFAULT_HDLC_HEAD);
> +
> +		dev->stats.tx_bytes += skb->len;
> +		break;
> +
> +	case ARPHRD_PPP:
> +		proto_head = (u16 *)skb->data;
> +		if (*proto_head != htons(DEFAULT_PPP_HEAD)) {
> +			dev->stats.tx_dropped++;
> +			dev_kfree_skb(skb);
> +			netdev_err(dev, "Wrong ppp header\n");
> +			return -ENOMEM;
> +		}
> +
> +		dev->stats.tx_bytes += skb->len;
> +		break;
> +
> +	default:
> +		dev->stats.tx_dropped++;
> +		dev_kfree_skb(skb);
> +		return -ENOMEM;
> +	}
> +
> +	pr_info("Tx data skb->len:%d ", skb->len);
> +	send_buf = (u8 *)skb->data;
> +	pr_info("\nTransmitted data:\n");
> +	for (i = 0; i < 16; i++) {
> +		if (i == skb->len)
> +			pr_info("++++");
> +		else
> +		pr_info("%02x\n", send_buf[i]);
> +	}
> +	spin_lock_irqsave(&priv->lock, flags);
> +
> +	/* Start from the next BD that should be filled */
> +	bd = priv->curtx_bd;
> +	bd_status = ioread16be(&bd->status);
> +	/* Save the skb pointer so we can free it later */
> +	priv->tx_skbuff[priv->skb_curtx] = skb;
> +
> +	/* Update the current skb pointer (wrapping if this was the last) */
> +	priv->skb_curtx =
> +	    (priv->skb_curtx + 1) & TX_RING_MOD_MASK(TX_BD_RING_LEN);
> +
> +	/* copy skb data to tx buffer for sdma processing */
> +	memcpy(priv->tx_buffer + (be32_to_cpu(bd->buf) - priv->dma_tx_addr),
> +	       skb->data, skb->len);
> +
> +	/* set bd status and length */
> +	bd_status = (bd_status & T_W_S) | T_R_S | T_I_S | T_L_S | T_TC_S;
> +
> +	iowrite16be(bd_status, &bd->status);
> +	iowrite16be(skb->len, &bd->length);

Should not status write be after length as status write will trigger the transmit?


Also, RX/TX error handling is very basic. There is only RX/TX errors but no
detail such as overrun/frame, carrier, crc etc. 
These are very useful when trying to find the cause of an RX/TX error, especially
during devlopment.

 Jocke


More information about the Linuxppc-dev mailing list