diff options
Diffstat (limited to 'drivers/net/ethernet/samsung/sxgbe/sxgbe_dma.c')
-rw-r--r-- | drivers/net/ethernet/samsung/sxgbe/sxgbe_dma.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/drivers/net/ethernet/samsung/sxgbe/sxgbe_dma.c b/drivers/net/ethernet/samsung/sxgbe/sxgbe_dma.c new file mode 100644 index 000000000000..4d989ff6c978 --- /dev/null +++ b/drivers/net/ethernet/samsung/sxgbe/sxgbe_dma.c @@ -0,0 +1,381 @@ +/* 10G controller driver for Samsung SoCs + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Siva Reddy Kallam <siva.kallam@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/netdevice.h> +#include <linux/phy.h> + +#include "sxgbe_common.h" +#include "sxgbe_dma.h" +#include "sxgbe_reg.h" +#include "sxgbe_desc.h" + +/* DMA core initialization */ +static int sxgbe_dma_init(void __iomem *ioaddr, int fix_burst, int burst_map) +{ + int retry_count = 10; + u32 reg_val; + + /* reset the DMA */ + writel(SXGBE_DMA_SOFT_RESET, ioaddr + SXGBE_DMA_MODE_REG); + while (retry_count--) { + if (!(readl(ioaddr + SXGBE_DMA_MODE_REG) & + SXGBE_DMA_SOFT_RESET)) + break; + mdelay(10); + } + + if (retry_count < 0) + return -EBUSY; + + reg_val = readl(ioaddr + SXGBE_DMA_SYSBUS_MODE_REG); + + /* if fix_burst = 0, Set UNDEF = 1 of DMA_Sys_Mode Register. + * if fix_burst = 1, Set UNDEF = 0 of DMA_Sys_Mode Register. + * burst_map is bitmap for BLEN[4, 8, 16, 32, 64, 128 and 256]. + * Set burst_map irrespective of fix_burst value. + */ + if (!fix_burst) + reg_val |= SXGBE_DMA_AXI_UNDEF_BURST; + + /* write burst len map */ + reg_val |= (burst_map << SXGBE_DMA_BLENMAP_LSHIFT); + + writel(reg_val, ioaddr + SXGBE_DMA_SYSBUS_MODE_REG); + + return 0; +} + +static void sxgbe_dma_channel_init(void __iomem *ioaddr, int cha_num, + int fix_burst, int pbl, dma_addr_t dma_tx, + dma_addr_t dma_rx, int t_rsize, int r_rsize) +{ + u32 reg_val; + dma_addr_t dma_addr; + + reg_val = readl(ioaddr + SXGBE_DMA_CHA_CTL_REG(cha_num)); + /* set the pbl */ + if (fix_burst) { + reg_val |= SXGBE_DMA_PBL_X8MODE; + writel(reg_val, ioaddr + SXGBE_DMA_CHA_CTL_REG(cha_num)); + /* program the TX pbl */ + reg_val = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cha_num)); + reg_val |= (pbl << SXGBE_DMA_TXPBL_LSHIFT); + writel(reg_val, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cha_num)); + /* program the RX pbl */ + reg_val = readl(ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cha_num)); + reg_val |= (pbl << SXGBE_DMA_RXPBL_LSHIFT); + writel(reg_val, ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cha_num)); + } + + /* program desc registers */ + writel(upper_32_bits(dma_tx), + ioaddr + SXGBE_DMA_CHA_TXDESC_HADD_REG(cha_num)); + writel(lower_32_bits(dma_tx), + ioaddr + SXGBE_DMA_CHA_TXDESC_LADD_REG(cha_num)); + + writel(upper_32_bits(dma_rx), + ioaddr + SXGBE_DMA_CHA_RXDESC_HADD_REG(cha_num)); + writel(lower_32_bits(dma_rx), + ioaddr + SXGBE_DMA_CHA_RXDESC_LADD_REG(cha_num)); + + /* program tail pointers */ + /* assumption: upper 32 bits are constant and + * same as TX/RX desc list + */ + dma_addr = dma_tx + ((t_rsize - 1) * SXGBE_DESC_SIZE_BYTES); + writel(lower_32_bits(dma_addr), + ioaddr + SXGBE_DMA_CHA_TXDESC_TAILPTR_REG(cha_num)); + + dma_addr = dma_rx + ((r_rsize - 1) * SXGBE_DESC_SIZE_BYTES); + writel(lower_32_bits(dma_addr), + ioaddr + SXGBE_DMA_CHA_RXDESC_LADD_REG(cha_num)); + /* program the ring sizes */ + writel(t_rsize - 1, ioaddr + SXGBE_DMA_CHA_TXDESC_RINGLEN_REG(cha_num)); + writel(r_rsize - 1, ioaddr + SXGBE_DMA_CHA_RXDESC_RINGLEN_REG(cha_num)); + + /* Enable TX/RX interrupts */ + writel(SXGBE_DMA_ENA_INT, + ioaddr + SXGBE_DMA_CHA_INT_ENABLE_REG(cha_num)); +} + +static void sxgbe_enable_dma_transmission(void __iomem *ioaddr, int cha_num) +{ + u32 tx_config; + + tx_config = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cha_num)); + tx_config |= SXGBE_TX_START_DMA; + writel(tx_config, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cha_num)); +} + +static void sxgbe_enable_dma_irq(void __iomem *ioaddr, int dma_cnum) +{ + /* Enable TX/RX interrupts */ + writel(SXGBE_DMA_ENA_INT, + ioaddr + SXGBE_DMA_CHA_INT_ENABLE_REG(dma_cnum)); +} + +static void sxgbe_disable_dma_irq(void __iomem *ioaddr, int dma_cnum) +{ + /* Disable TX/RX interrupts */ + writel(0, ioaddr + SXGBE_DMA_CHA_INT_ENABLE_REG(dma_cnum)); +} + +static void sxgbe_dma_start_tx(void __iomem *ioaddr, int tchannels) +{ + int cnum; + u32 tx_ctl_reg; + + for (cnum = 0; cnum < tchannels; cnum++) { + tx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cnum)); + tx_ctl_reg |= SXGBE_TX_ENABLE; + writel(tx_ctl_reg, + ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cnum)); + } +} + +static void sxgbe_dma_start_tx_queue(void __iomem *ioaddr, int dma_cnum) +{ + u32 tx_ctl_reg; + + tx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(dma_cnum)); + tx_ctl_reg |= SXGBE_TX_ENABLE; + writel(tx_ctl_reg, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(dma_cnum)); +} + +static void sxgbe_dma_stop_tx_queue(void __iomem *ioaddr, int dma_cnum) +{ + u32 tx_ctl_reg; + + tx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(dma_cnum)); + tx_ctl_reg &= ~(SXGBE_TX_ENABLE); + writel(tx_ctl_reg, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(dma_cnum)); +} + +static void sxgbe_dma_stop_tx(void __iomem *ioaddr, int tchannels) +{ + int cnum; + u32 tx_ctl_reg; + + for (cnum = 0; cnum < tchannels; cnum++) { + tx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cnum)); + tx_ctl_reg &= ~(SXGBE_TX_ENABLE); + writel(tx_ctl_reg, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(cnum)); + } +} + +static void sxgbe_dma_start_rx(void __iomem *ioaddr, int rchannels) +{ + int cnum; + u32 rx_ctl_reg; + + for (cnum = 0; cnum < rchannels; cnum++) { + rx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cnum)); + rx_ctl_reg |= SXGBE_RX_ENABLE; + writel(rx_ctl_reg, + ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cnum)); + } +} + +static void sxgbe_dma_stop_rx(void __iomem *ioaddr, int rchannels) +{ + int cnum; + u32 rx_ctl_reg; + + for (cnum = 0; cnum < rchannels; cnum++) { + rx_ctl_reg = readl(ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cnum)); + rx_ctl_reg &= ~(SXGBE_RX_ENABLE); + writel(rx_ctl_reg, ioaddr + SXGBE_DMA_CHA_RXCTL_REG(cnum)); + } +} + +static int sxgbe_tx_dma_int_status(void __iomem *ioaddr, int channel_no, + struct sxgbe_extra_stats *x) +{ + u32 int_status = readl(ioaddr + SXGBE_DMA_CHA_STATUS_REG(channel_no)); + u32 clear_val = 0; + u32 ret_val = 0; + + /* TX Normal Interrupt Summary */ + if (likely(int_status & SXGBE_DMA_INT_STATUS_NIS)) { + x->normal_irq_n++; + if (int_status & SXGBE_DMA_INT_STATUS_TI) { + ret_val |= handle_tx; + x->tx_normal_irq_n++; + clear_val |= SXGBE_DMA_INT_STATUS_TI; + } + + if (int_status & SXGBE_DMA_INT_STATUS_TBU) { + x->tx_underflow_irq++; + ret_val |= tx_bump_tc; + clear_val |= SXGBE_DMA_INT_STATUS_TBU; + } + } else if (unlikely(int_status & SXGBE_DMA_INT_STATUS_AIS)) { + /* TX Abnormal Interrupt Summary */ + if (int_status & SXGBE_DMA_INT_STATUS_TPS) { + ret_val |= tx_hard_error; + clear_val |= SXGBE_DMA_INT_STATUS_TPS; + x->tx_process_stopped_irq++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_FBE) { + ret_val |= tx_hard_error; + x->fatal_bus_error_irq++; + + /* Assumption: FBE bit is the combination of + * all the bus access erros and cleared when + * the respective error bits cleared + */ + + /* check for actual cause */ + if (int_status & SXGBE_DMA_INT_STATUS_TEB0) { + x->tx_read_transfer_err++; + clear_val |= SXGBE_DMA_INT_STATUS_TEB0; + } else { + x->tx_write_transfer_err++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_TEB1) { + x->tx_desc_access_err++; + clear_val |= SXGBE_DMA_INT_STATUS_TEB1; + } else { + x->tx_buffer_access_err++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_TEB2) { + x->tx_data_transfer_err++; + clear_val |= SXGBE_DMA_INT_STATUS_TEB2; + } + } + + /* context descriptor error */ + if (int_status & SXGBE_DMA_INT_STATUS_CTXTERR) { + x->tx_ctxt_desc_err++; + clear_val |= SXGBE_DMA_INT_STATUS_CTXTERR; + } + } + + /* clear the served bits */ + writel(clear_val, ioaddr + SXGBE_DMA_CHA_STATUS_REG(channel_no)); + + return ret_val; +} + +static int sxgbe_rx_dma_int_status(void __iomem *ioaddr, int channel_no, + struct sxgbe_extra_stats *x) +{ + u32 int_status = readl(ioaddr + SXGBE_DMA_CHA_STATUS_REG(channel_no)); + u32 clear_val = 0; + u32 ret_val = 0; + + /* RX Normal Interrupt Summary */ + if (likely(int_status & SXGBE_DMA_INT_STATUS_NIS)) { + x->normal_irq_n++; + if (int_status & SXGBE_DMA_INT_STATUS_RI) { + ret_val |= handle_rx; + x->rx_normal_irq_n++; + clear_val |= SXGBE_DMA_INT_STATUS_RI; + } + } else if (unlikely(int_status & SXGBE_DMA_INT_STATUS_AIS)) { + /* RX Abnormal Interrupt Summary */ + if (int_status & SXGBE_DMA_INT_STATUS_RBU) { + ret_val |= rx_bump_tc; + clear_val |= SXGBE_DMA_INT_STATUS_RBU; + x->rx_underflow_irq++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_RPS) { + ret_val |= rx_hard_error; + clear_val |= SXGBE_DMA_INT_STATUS_RPS; + x->rx_process_stopped_irq++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_FBE) { + ret_val |= rx_hard_error; + x->fatal_bus_error_irq++; + + /* Assumption: FBE bit is the combination of + * all the bus access erros and cleared when + * the respective error bits cleared + */ + + /* check for actual cause */ + if (int_status & SXGBE_DMA_INT_STATUS_REB0) { + x->rx_read_transfer_err++; + clear_val |= SXGBE_DMA_INT_STATUS_REB0; + } else { + x->rx_write_transfer_err++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_REB1) { + x->rx_desc_access_err++; + clear_val |= SXGBE_DMA_INT_STATUS_REB1; + } else { + x->rx_buffer_access_err++; + } + + if (int_status & SXGBE_DMA_INT_STATUS_REB2) { + x->rx_data_transfer_err++; + clear_val |= SXGBE_DMA_INT_STATUS_REB2; + } + } + } + + /* clear the served bits */ + writel(clear_val, ioaddr + SXGBE_DMA_CHA_STATUS_REG(channel_no)); + + return ret_val; +} + +/* Program the HW RX Watchdog */ +static void sxgbe_dma_rx_watchdog(void __iomem *ioaddr, u32 riwt) +{ + u32 que_num; + + SXGBE_FOR_EACH_QUEUE(SXGBE_RX_QUEUES, que_num) { + writel(riwt, + ioaddr + SXGBE_DMA_CHA_INT_RXWATCHTMR_REG(que_num)); + } +} + +static void sxgbe_enable_tso(void __iomem *ioaddr, u8 chan_num) +{ + u32 ctrl; + + ctrl = readl(ioaddr + SXGBE_DMA_CHA_TXCTL_REG(chan_num)); + ctrl |= SXGBE_DMA_CHA_TXCTL_TSE_ENABLE; + writel(ctrl, ioaddr + SXGBE_DMA_CHA_TXCTL_REG(chan_num)); +} + +static const struct sxgbe_dma_ops sxgbe_dma_ops = { + .init = sxgbe_dma_init, + .cha_init = sxgbe_dma_channel_init, + .enable_dma_transmission = sxgbe_enable_dma_transmission, + .enable_dma_irq = sxgbe_enable_dma_irq, + .disable_dma_irq = sxgbe_disable_dma_irq, + .start_tx = sxgbe_dma_start_tx, + .start_tx_queue = sxgbe_dma_start_tx_queue, + .stop_tx = sxgbe_dma_stop_tx, + .stop_tx_queue = sxgbe_dma_stop_tx_queue, + .start_rx = sxgbe_dma_start_rx, + .stop_rx = sxgbe_dma_stop_rx, + .tx_dma_int_status = sxgbe_tx_dma_int_status, + .rx_dma_int_status = sxgbe_rx_dma_int_status, + .rx_watchdog = sxgbe_dma_rx_watchdog, + .enable_tso = sxgbe_enable_tso, +}; + +const struct sxgbe_dma_ops *sxgbe_get_dma_ops(void) +{ + return &sxgbe_dma_ops; +} |