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.z...@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.z...@nxp.com>
> +L:   net...@vger.kernel.org
> +L:   linuxppc-...@lists.ozlabs.org
> +S:   Maintained
> +F:   drivers/net/wan/fsl_ucc_hdlc*
> +
>  FREESCALE QUICC ENGINE UCC UART DRIVER
>  M:   Timur Tabi <ti...@tabi.org>
>  L:   linuxppc-...@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

Reply via email to