[PATCH v4 1/1] can: add pruss CAN driver.
Subhasish Ghosh
subhasish at mistralsolutions.com
Fri Apr 22 08:11:07 EDT 2011
This patch adds support for the CAN device emulated on PRUSS.
Signed-off-by: Subhasish Ghosh <subhasish at mistralsolutions.com>
---
drivers/net/can/Kconfig | 7 +
drivers/net/can/Makefile | 1 +
drivers/net/can/pruss_can.c | 1074 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1082 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/can/pruss_can.c
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index 5dec456..4682a4f 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -111,6 +111,13 @@ config PCH_CAN
embedded processor.
This driver can access CAN bus.
+config CAN_TI_DA8XX_PRU
+ depends on CAN_DEV && ARCH_DAVINCI && ARCH_DAVINCI_DA850
+ tristate "PRU based CAN emulation for DA8XX"
+ ---help---
+ Enable this to emulate a CAN controller on the PRU of DA8XX.
+ If not sure, mark N
+
source "drivers/net/can/mscan/Kconfig"
source "drivers/net/can/sja1000/Kconfig"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index 53c82a7..d0b7cbd 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_CAN_SJA1000) += sja1000/
obj-$(CONFIG_CAN_MSCAN) += mscan/
obj-$(CONFIG_CAN_AT91) += at91_can.o
obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
+obj-$(CONFIG_CAN_TI_DA8XX_PRU) += pruss_can.o
obj-$(CONFIG_CAN_MCP251X) += mcp251x.o
obj-$(CONFIG_CAN_BFIN) += bfin_can.o
obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
diff --git a/drivers/net/can/pruss_can.c b/drivers/net/can/pruss_can.c
new file mode 100644
index 0000000..7702509
--- /dev/null
+++ b/drivers/net/can/pruss_can.c
@@ -0,0 +1,1074 @@
+/*
+ * TI DA8XX PRU CAN Emulation device driver
+ * Author: subhasish at mistralsolutions.com
+ *
+ * This driver supports TI's PRU CAN Emulation and the
+ * specs for the same is available at <http://www.ti.com>
+ *
+ * Copyright (C) 2010, 2011 Texas Instruments Incorporated
+ *
+ * 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 version 2.
+ *
+ * This program is distributed as is WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/clk.h>
+#include <linux/types.h>
+#include <linux/sysfs.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/mfd/pruss.h>
+
+#define PRUSS_CAN_RX_PRU_0 PRUSS_NUM0
+#define PRUSS_CAN_TX_PRU_1 PRUSS_NUM1
+#define PRUSS_CAN_TX_SYS_EVT 34
+#define PRUSS_CAN_RX_SYS_EVT 33
+#define PRUSS_CAN_ARM2DSP_SYS_EVT 32
+#define PRUSS_CAN_DELAY_LOOP_LENGTH 2
+#define PRUSS_CAN_TIMER_SETUP_DELAY 14
+#define PRUSS_CAN_GPIO_SETUP_DELAY 150
+#define PRUSS_CAN_TX_GBL_STAT_REG (PRUSS_PRU1_BASE_ADDRESS + 0x04)
+#define PRUSS_CAN_TX_INTR_MASK_REG (PRUSS_PRU1_BASE_ADDRESS + 0x08)
+#define PRUSS_CAN_TX_INTR_STAT_REG (PRUSS_PRU1_BASE_ADDRESS + 0x0C)
+#define PRUSS_CAN_TX_MBOX0_STAT_REG (PRUSS_PRU1_BASE_ADDRESS + 0x10)
+#define PRUSS_CAN_TX_ERR_CNTR_REG (PRUSS_PRU1_BASE_ADDRESS + 0x30)
+#define PRUSS_CAN_TX_INT_STAT 0x2
+#define PRUSS_CAN_MBOX_OFFSET 0x40
+#define PRUSS_CAN_MBOX_SIZE 0x10
+#define PRUSS_CAN_MBOX_STAT_OFFSET 0x10
+#define PRUSS_CAN_MBOX_STAT_SIZE 0x04
+#define PRUSS_CAN_GBL_STAT_REG_VAL 0x00000040
+#define PRUSS_CAN_INTR_MASK_REG_VAL 0x00004000
+#define PRUSS_CAN_TIMING_VAL_TX (PRUSS_PRU1_BASE_ADDRESS + 0xC0)
+#define PRUSS_CAN_TIMING_SETUP (PRUSS_PRU1_BASE_ADDRESS + 0xC4)
+#define PRUSS_CAN_RX_GBL_STAT_REG (PRUSS_PRU0_BASE_ADDRESS + 0x04)
+#define PRUSS_CAN_RX_INTR_MASK_REG (PRUSS_PRU0_BASE_ADDRESS + 0x08)
+#define PRUSS_CAN_RX_INTR_STAT_REG (PRUSS_PRU0_BASE_ADDRESS + 0x0C)
+#define PRUSS_CAN_RX_ERR_CNTR_REG (PRUSS_PRU0_BASE_ADDRESS + 0x34)
+#define PRUSS_CAN_TIMING_VAL_RX (PRUSS_PRU0_BASE_ADDRESS + 0xD0)
+#define PRUSS_CAN_BIT_TIMING_VAL_RX (PRUSS_PRU0_BASE_ADDRESS + 0xD4)
+#define PRUSS_CAN_ID_MAP (PRUSS_PRU0_BASE_ADDRESS + 0xF0)
+#define PRUSS_CAN_ERROR_ACTIVE 128
+#define PRUSS_CAN_GBL_STAT_REG_MASK 0x1F
+#define PRUSS_CAN_GBL_STAT_REG_SET_TX 0x80
+#define PRUSS_CAN_GBL_STAT_REG_SET_RX 0x40
+#define PRUSS_CAN_GBL_STAT_REG_STOP_TX 0x7F
+#define PRUSS_CAN_GBL_STAT_REG_STOP_RX 0xBF
+#define PRUSS_CAN_SET_TX_REQ 0x00000080
+#define PRUSS_CAN_STD_FRAME_MASK 18
+#define PRUSS_CAN_START 1
+#define PRUSS_CAN_MB_MIN 0
+#define PRUSS_CAN_MB_MAX 7
+#define PRUSS_CAN_MID_IDE BIT(29)
+#define PRUSS_CAN_ISR_BIT_CCI BIT(15)
+#define PRUSS_CAN_ISR_BIT_ESI BIT(14)
+#define PRUSS_CAN_ISR_BIT_SRDI BIT(13)
+#define PRUSS_CAN_ISR_BIT_RRI BIT(8)
+#define PRUSS_CAN_GSR_BIT_EPM BIT(4)
+#define PRUSS_CAN_GSR_BIT_BFM BIT(3)
+#define PRUSS_CAN_RTR_BUFF_NUM 8
+#define PRUSS_DEF_NAPI_WEIGHT 8
+#define PRUSS_CAN_DRV_NAME "da8xx_pruss_can"
+#define PRUSS_CAN_DRV_DESC "TI PRU CAN Controller Driver v1.0"
+
+enum can_tx_dir {
+ TRANSMIT = 1,
+ RECEIVE
+};
+
+struct can_mbox {
+ canid_t can_id;
+ u8 data[8];
+ u16 datalength;
+ u16 crc;
+};
+
+struct can_emu_cntx {
+ enum can_tx_dir txdir;
+ struct can_mbox mbox;
+ u32 mboxno;
+ u8 pruno;
+ u32 gbl_stat;
+ u32 intr_stat;
+ u32 mbox_stat;
+};
+
+struct can_emu_priv {
+ struct can_priv can;
+ struct napi_struct napi;
+ struct net_device *ndev;
+ struct device *dev;
+ struct clk *clk_timer;
+ struct can_emu_cntx can_tx_cntx;
+ struct can_emu_cntx can_rx_cntx;
+ unsigned int clk_freq_pru;
+ unsigned int trx_irq;
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_next;
+ unsigned int mbx_id[8];
+};
+
+static struct can_bittiming_const pru_can_bittiming_const = {
+ .name = PRUSS_CAN_DRV_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 256,
+ .brp_inc = 1,
+};
+
+static int pru_can_mbox_write(struct device *dev,
+ struct can_emu_cntx *emu_cntx)
+{
+ u32 offset = 0;
+
+ offset = PRUSS_PRU1_BASE_ADDRESS + PRUSS_CAN_MBOX_OFFSET
+ + (PRUSS_CAN_MBOX_SIZE * emu_cntx->mboxno);
+
+ pruss_writel_multi(dev, offset, (u32 *) &(emu_cntx->mbox), 4);
+
+ return 0;
+}
+
+static int pru_can_mbox_read(struct device *dev,
+ struct can_emu_cntx *emu_cntx)
+{
+ u32 offset = 0;
+
+ offset = PRUSS_PRU0_BASE_ADDRESS + PRUSS_CAN_MBOX_OFFSET
+ + (PRUSS_CAN_MBOX_SIZE * emu_cntx->mboxno);
+
+ pruss_readl_multi(dev, offset, (u32 *) &emu_cntx->mbox, 4);
+
+ return 0;
+}
+
+static int pru_can_rx_id_set(struct device *dev, u32 nodeid, u32 mboxno)
+{
+ pruss_writel(dev, (PRUSS_CAN_ID_MAP + (mboxno * 4)), nodeid);
+
+ return 0;
+}
+
+static int pru_can_intr_stat_get(struct device *dev,
+ struct can_emu_cntx *emu_cntx)
+{
+ if (emu_cntx->pruno == PRUCORE_1)
+ pruss_readl(dev, PRUSS_CAN_TX_INTR_STAT_REG,
+ &emu_cntx->intr_stat);
+ else if (emu_cntx->pruno == PRUCORE_0)
+ pruss_readl(dev, PRUSS_CAN_RX_INTR_STAT_REG,
+ &emu_cntx->intr_stat);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pru_can_gbl_stat_get(struct device *dev,
+ struct can_emu_cntx *emu_cntx)
+{
+ if (emu_cntx->pruno == PRUCORE_1)
+ pruss_readl(dev, PRUSS_CAN_TX_GBL_STAT_REG,
+ &emu_cntx->gbl_stat);
+ else if (emu_cntx->pruno == PRUCORE_0)
+ pruss_readl(dev, PRUSS_CAN_RX_GBL_STAT_REG,
+ &emu_cntx->gbl_stat);
+ else
+ return -EINVAL;
+ return 0;
+}
+
+static int pru_can_tx_mode_set(struct device *dev, bool transfer_flag,
+ enum can_tx_dir ecan_trx)
+{
+ u32 value;
+
+ if (ecan_trx == TRANSMIT) {
+ pruss_readl(dev, PRUSS_CAN_RX_GBL_STAT_REG, &value);
+ if (transfer_flag) {
+ value &= PRUSS_CAN_GBL_STAT_REG_MASK;
+ value |= PRUSS_CAN_GBL_STAT_REG_SET_TX;
+ } else
+ value &= PRUSS_CAN_GBL_STAT_REG_STOP_TX;
+
+ pruss_writel(dev, PRUSS_CAN_RX_GBL_STAT_REG, value);
+ pruss_writel(dev, PRUSS_CAN_TX_GBL_STAT_REG, value);
+ } else if (ecan_trx == RECEIVE) {
+ pruss_readl(dev, PRUSS_CAN_RX_GBL_STAT_REG, &value);
+ if (transfer_flag) {
+ value &= PRUSS_CAN_GBL_STAT_REG_MASK;
+ value |= PRUSS_CAN_GBL_STAT_REG_SET_RX;
+ } else
+ value &= PRUSS_CAN_GBL_STAT_REG_STOP_RX;
+
+ pruss_writel(dev, PRUSS_CAN_RX_GBL_STAT_REG, value);
+ pruss_writel(dev, PRUSS_CAN_TX_GBL_STAT_REG, value);
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+static u32 pruss_intc_init[19][3] = {
+ {PRUSS_INTC_POLARITY0, PRU_INTC_REGMAP_MASK, 0xFFFFFFFF},
+ {PRUSS_INTC_POLARITY1, PRU_INTC_REGMAP_MASK, 0xFFFFFFFF},
+ {PRUSS_INTC_TYPE0, PRU_INTC_REGMAP_MASK, 0x1C000000},
+ {PRUSS_INTC_TYPE1, PRU_INTC_REGMAP_MASK, 0},
+ {PRUSS_INTC_GLBLEN, 0, 1},
+ {PRUSS_INTC_HOSTMAP0, PRU_INTC_REGMAP_MASK, 0x03020100},
+ {PRUSS_INTC_HOSTMAP1, PRU_INTC_REGMAP_MASK, 0x07060504},
+ {PRUSS_INTC_HOSTMAP2, PRU_INTC_REGMAP_MASK, 0x0000908},
+ {PRUSS_INTC_CHANMAP0, PRU_INTC_REGMAP_MASK, 0},
+ {PRUSS_INTC_CHANMAP8, PRU_INTC_REGMAP_MASK, 0x00020200},
+ {PRUSS_INTC_STATIDXCLR, 0, 32},
+ {PRUSS_INTC_STATIDXCLR, 0, 19},
+ {PRUSS_INTC_ENIDXSET, 0, 19},
+ {PRUSS_INTC_STATIDXCLR, 0, 18},
+ {PRUSS_INTC_ENIDXSET, 0, 18},
+ {PRUSS_INTC_STATIDXCLR, 0, 34},
+ {PRUSS_INTC_ENIDXSET, 0, 34},
+ {PRUSS_INTC_ENIDXSET, 0, 32},
+ {PRUSS_INTC_HOSTINTEN, 0, 5}
+};
+
+static int pru_can_emu_init(struct device *dev, u32 pruclock)
+{
+ u32 value[8] = {[0 ... 7] = 1};
+ u32 i;
+
+ /* PRU Internal Registers Initializations */
+ pruss_writel_multi(dev, PRUSS_CAN_TX_MBOX0_STAT_REG, value, 8);
+
+ *value = PRUSS_CAN_GBL_STAT_REG_VAL;
+ pruss_writel(dev, PRUSS_CAN_TX_GBL_STAT_REG, value[0]);
+ pruss_writel(dev, PRUSS_CAN_RX_GBL_STAT_REG, value[0]);
+
+ *value = PRUSS_CAN_INTR_MASK_REG_VAL;
+ pruss_writel(dev, PRUSS_CAN_TX_INTR_MASK_REG, value[0]);
+ pruss_writel(dev, PRUSS_CAN_RX_INTR_MASK_REG, value[0]);
+
+ for (i = 0; i < 19; i++)
+ (i < 12) ? pruss_rmwl(dev, pruss_intc_init[i][0],
+ pruss_intc_init[i][1],
+ pruss_intc_init[i][2]) :
+ pruss_idx_writel(dev, pruss_intc_init[i][0],
+ pruss_intc_init[i][2]);
+ return 0;
+}
+
+static void pru_can_emu_exit(struct device *dev)
+{
+ pruss_disable(dev, PRUSS_CAN_RX_PRU_0);
+ pruss_disable(dev, PRUSS_CAN_TX_PRU_1);
+}
+
+static int pru_can_tx(struct device *dev, u8 mboxnumber, u8 pruno)
+{
+ u32 value = 0;
+
+ if (PRUCORE_1 == pruno) {
+ value = PRUSS_CAN_SET_TX_REQ;
+ pruss_writel(dev, ((PRUSS_PRU1_BASE_ADDRESS +
+ (PRUSS_CAN_MBOX_STAT_OFFSET +
+ (PRUSS_CAN_MBOX_STAT_SIZE * mboxnumber)))
+ & 0xFFFF), value);
+ } else if (PRUCORE_0 == pruno) {
+ pruss_readl(dev, PRUSS_CAN_RX_INTR_STAT_REG, &value);
+ value = value & ~(1 << mboxnumber);
+ pruss_writel(dev, PRUSS_CAN_RX_INTR_STAT_REG, value);
+ value = 0;
+ pruss_writel(dev, ((PRUSS_PRU0_BASE_ADDRESS +
+ (PRUSS_CAN_MBOX_STAT_OFFSET +
+ (PRUSS_CAN_MBOX_STAT_SIZE * mboxnumber)))
+ & 0xFFFF), value);
+ } else
+ return -EINVAL;
+ return 0;
+}
+
+static int pru_can_reset_tx(struct device *dev)
+{
+ pruss_idx_writel(dev, PRUSS_INTC_STATIDXCLR, PRUSS_CAN_ARM2DSP_SYS_EVT);
+ pruss_idx_writel(dev, PRUSS_INTC_ENIDXSET, PRUSS_CAN_ARM2DSP_SYS_EVT);
+ pruss_idx_writel(dev, PRUSS_INTC_STATIDXSET, PRUSS_CAN_ARM2DSP_SYS_EVT);
+ return 0;
+}
+
+static void pru_can_mask_ints(struct device *dev, u32 trx, bool enable)
+{
+ u32 value = 0;
+
+ value = (trx == PRUSS_CAN_TX_PRU_1) ?
+ PRUSS_CAN_TX_SYS_EVT : PRUSS_CAN_RX_SYS_EVT;
+ enable ? pruss_idx_writel(dev, PRUSS_INTC_ENIDXSET, value) :
+ pruss_idx_writel(dev, PRUSS_INTC_ENIDXCLR, value);
+}
+
+static unsigned int pru_can_get_error_cnt(struct device *dev, u8 pruno)
+{
+ u32 value = 0;
+
+ if (pruno == PRUSS_CAN_RX_PRU_0)
+ pruss_readl(dev, PRUSS_CAN_RX_ERR_CNTR_REG, &value);
+ else if (pruno == PRUSS_CAN_TX_PRU_1)
+ pruss_readl(dev, PRUSS_CAN_TX_ERR_CNTR_REG, &value);
+ else
+ return -EINVAL;
+
+ return value;
+}
+
+static unsigned int pru_can_get_intc_status(struct device *dev)
+{
+ u32 value = 0;
+
+ pruss_readl(dev, PRUSS_INTC_STATCLRINT1, &value);
+ return value;
+}
+
+static void pru_can_clr_intc_status(struct device *dev, u32 trx)
+{
+ u32 value = 0;
+
+ value = (trx == PRUSS_CAN_TX_PRU_1) ?
+ PRUSS_CAN_TX_SYS_EVT : PRUSS_CAN_RX_SYS_EVT;
+ pruss_idx_writel(dev, PRUSS_INTC_STATIDXCLR, value);
+}
+
+static int pru_can_get_state(const struct net_device *ndev,
+ enum can_state *state)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ *state = priv->can.state;
+
+ return 0;
+}
+
+static int pru_can_set_bittiming(struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ struct can_bittiming *bt = &priv->can.bittiming;
+ u32 value;
+
+ value = priv->can.clock.freq / bt->bitrate;
+ pruss_writel(priv->dev, PRUSS_CAN_TIMING_VAL_TX, value);
+ pruss_writel(priv->dev, PRUSS_CAN_BIT_TIMING_VAL_RX, value);
+
+ value = (bt->phase_seg2 + bt->phase_seg1 +
+ bt->prop_seg + 1) * bt->brp;
+ value = (value >> 1) - PRUSS_CAN_TIMER_SETUP_DELAY;
+ value = (value << 16) | value;
+ pruss_writel(priv->dev, PRUSS_CAN_TIMING_VAL_RX, value);
+
+ value = (PRUSS_CAN_GPIO_SETUP_DELAY *
+ (priv->clk_freq_pru / 1000000) / 1000) /
+ PRUSS_CAN_DELAY_LOOP_LENGTH;
+
+ pruss_writel(priv->dev, PRUSS_CAN_TIMING_SETUP, value);
+ return 0;
+}
+
+static void pru_can_stop(struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_TX_PRU_1, false);
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_RX_PRU_0, false);
+ pru_can_reset_tx(priv->dev);
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+/*
+ * This is to just set the can state to ERROR_ACTIVE
+ * ip link set canX up type can bitrate 125000
+ */
+static void pru_can_start(struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+
+ if (priv->can.state != CAN_STATE_STOPPED)
+ pru_can_stop(ndev);
+
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_TX_PRU_1, true);
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_RX_PRU_0, true);
+
+ pru_can_gbl_stat_get(priv->dev, &priv->can_tx_cntx);
+ pru_can_gbl_stat_get(priv->dev, &priv->can_rx_cntx);
+
+ if (PRUSS_CAN_GSR_BIT_EPM & priv->can_tx_cntx.gbl_stat)
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ else if (PRUSS_CAN_GSR_BIT_BFM & priv->can_tx_cntx.gbl_stat)
+ priv->can.state = CAN_STATE_BUS_OFF;
+ else
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+}
+
+static int pru_can_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret = 0;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ pru_can_start(ndev);
+ netif_wake_queue(ndev);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ return ret;
+}
+
+static netdev_tx_t pru_can_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ struct can_frame *cf = (struct can_frame *)skb->data;
+ int count;
+ u8 *data = cf->data;
+ u8 dlc = cf->can_dlc;
+ u8 *pdata = NULL;
+
+ if (can_dropped_invalid_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ netif_stop_queue(ndev);
+ if (cf->can_id & CAN_EFF_FLAG) /* Extended frame format */
+ priv->can_tx_cntx.mbox.can_id =
+ (cf->can_id & CAN_EFF_MASK) | PRUSS_CAN_MID_IDE;
+ else /* Standard frame format */
+ priv->can_tx_cntx.mbox.can_id =
+ (cf->can_id & CAN_SFF_MASK) << PRUSS_CAN_STD_FRAME_MASK;
+
+ if (cf->can_id & CAN_RTR_FLAG) /* Remote transmission request */
+ priv->can_tx_cntx.mbox.can_id |= CAN_RTR_FLAG;
+
+ pdata = &priv->can_tx_cntx.mbox.data[0] + (dlc - 1);
+ for (count = 0; count < (u8) dlc; count++)
+ *pdata-- = *data++;
+
+ priv->can_tx_cntx.mbox.datalength = (u16) dlc;
+ priv->can_tx_cntx.mbox.crc = 0;
+/*
+ * search for the next available mbx
+ * if the next mbx is busy, then try the next + 1
+ * do this until the head is reached.
+ * if still unable to tx, stop accepting any packets
+ * if able to tx and the head is reached, then reset next to tail, i.e mbx0
+ * if head is not reached, then just point to the next mbx
+ */
+ for (; priv->tx_next <= priv->tx_head; priv->tx_next++) {
+ priv->can_tx_cntx.mboxno = priv->tx_next;
+ if (-1 == pru_can_mbox_write(priv->dev,
+ &priv->can_tx_cntx)) {
+ if (priv->tx_next == priv->tx_head) {
+ priv->tx_next = priv->tx_tail;
+ netif_stop_queue(ndev); /* IF stalled */
+ dev_err(priv->dev,
+ "%s: no tx mbx available", __func__);
+ return NETDEV_TX_BUSY;
+ } else
+ continue;
+ } else {
+ /* set transmit request */
+ pru_can_tx(priv->dev, priv->tx_next,
+ PRUSS_CAN_TX_PRU_1);
+ pru_can_tx_mode_set(priv->dev, false, RECEIVE);
+ pru_can_tx_mode_set(priv->dev, true, TRANSMIT);
+ pru_can_reset_tx(priv->dev);
+ priv->tx_next++;
+ can_put_echo_skb(skb, ndev, 0);
+ break;
+ }
+ }
+ if (priv->tx_next > priv->tx_head)
+ priv->tx_next = priv->tx_tail;
+
+ return NETDEV_TX_OK;
+}
+
+static int pru_can_rx(struct net_device *ndev, u32 mbxno)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ u8 *data = NULL;
+ u8 *pdata = NULL;
+ int count = 0;
+
+ skb = alloc_can_skb(ndev, &cf);
+ if (!skb) {
+ if (printk_ratelimit())
+ dev_err(priv->dev,
+ "alloc_can_skb() failed\n");
+ return -ENOMEM;
+ }
+ data = cf->data;
+ /* get payload */
+ priv->can_rx_cntx.mboxno = mbxno;
+ if (pru_can_mbox_read(priv->dev, &priv->can_rx_cntx)) {
+ dev_err(priv->dev, "failed to get data from mailbox\n");
+ return -EAGAIN;
+ }
+ /* give ownweship to pru */
+ pru_can_tx(priv->dev, mbxno, PRUSS_CAN_RX_PRU_0);
+
+ /* get data length code */
+ cf->can_dlc = get_can_dlc(priv->can_rx_cntx.mbox.datalength & 0xF);
+ if (cf->can_dlc <= 4) {
+ pdata = &priv->can_rx_cntx.mbox.data[4] + (4 - cf->can_dlc);
+ for (count = 0; count < cf->can_dlc; count++)
+ *data++ = *pdata++;
+ } else {
+ pdata = &priv->can_rx_cntx.mbox.data[4];
+ for (count = 0; count < 4; count++)
+ *data++ = *pdata++;
+ pdata = &priv->can_rx_cntx.mbox.data[3] - (cf->can_dlc - 5);
+ for (count = 0; count < cf->can_dlc - 4; count++)
+ *data++ = *pdata++;
+ }
+
+ /* get id extended or std */
+ if (priv->can_rx_cntx.mbox.can_id & PRUSS_CAN_MID_IDE)
+ cf->can_id = (priv->can_rx_cntx.mbox.can_id & CAN_EFF_MASK)
+ | CAN_EFF_FLAG;
+ else
+ cf->can_id = (priv->can_rx_cntx.mbox.can_id >> 18)
+ & CAN_SFF_MASK;
+
+ if (priv->can_rx_cntx.mbox.can_id & CAN_RTR_FLAG)
+ cf->can_id |= CAN_RTR_FLAG;
+
+ stats->rx_bytes += cf->can_dlc;
+ netif_receive_skb(skb);
+ stats->rx_packets++;
+ return 0;
+}
+
+static int pru_can_err(struct net_device *ndev, int int_status,
+ int err_status)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ u32 tx_err_cnt, rx_err_cnt;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (!skb) {
+ if (printk_ratelimit())
+ dev_err(priv->dev,
+ "alloc_can_err_skb() failed\n");
+ return -ENOMEM;
+ }
+
+ if (err_status & PRUSS_CAN_GSR_BIT_EPM) { /* error passive int */
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ ++priv->can.can_stats.error_passive;
+ cf->can_id |= CAN_ERR_CRTL;
+ tx_err_cnt = pru_can_get_error_cnt(priv->dev,
+ PRUSS_CAN_TX_PRU_1);
+ rx_err_cnt = pru_can_get_error_cnt(priv->dev,
+ PRUSS_CAN_RX_PRU_0);
+ if (tx_err_cnt > PRUSS_CAN_ERROR_ACTIVE - 1)
+ cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+ if (rx_err_cnt > PRUSS_CAN_ERROR_ACTIVE - 1)
+ cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+
+ dev_dbg(priv->ndev->dev.parent, "Error passive interrupt\n");
+ }
+
+ if (err_status & PRUSS_CAN_GSR_BIT_BFM) {
+ priv->can.state = CAN_STATE_BUS_OFF;
+ cf->can_id |= CAN_ERR_BUSOFF;
+ /*
+ * Disable all interrupts in bus-off to avoid int hog
+ * this should be handled by the pru
+ */
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_TX_PRU_1, false);
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_RX_PRU_0, false);
+ can_bus_off(ndev);
+ dev_dbg(priv->ndev->dev.parent, "Bus off mode\n");
+ }
+
+ netif_rx(skb);
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ return 0;
+}
+
+static int pru_can_rx_poll(struct napi_struct *napi, int quota)
+{
+ struct net_device *ndev = napi->dev;
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ u32 bit_set, mbxno = 0;
+ u32 num_pkts = 0;
+
+ if (!netif_running(ndev))
+ return 0;
+
+ do {
+ /* rx int sys_evt -> 33 */
+ pru_can_clr_intc_status(priv->dev, PRUSS_CAN_RX_PRU_0);
+ if (pru_can_intr_stat_get(priv->dev, &priv->can_rx_cntx))
+ return num_pkts;
+
+ if (PRUSS_CAN_ISR_BIT_RRI & priv->can_rx_cntx.intr_stat) {
+ mbxno = PRUSS_CAN_RTR_BUFF_NUM;
+ pru_can_rx(ndev, mbxno);
+ num_pkts++;
+ } else {
+ /* Extract the mboxno from the status */
+ bit_set = fls(priv->can_rx_cntx.intr_stat & 0xFF);
+ if (bit_set) {
+ num_pkts++;
+ mbxno = bit_set - 1;
+ if (PRUSS_CAN_ISR_BIT_ESI & priv->can_rx_cntx.
+ intr_stat) {
+ pru_can_gbl_stat_get(priv->dev,
+ &priv->can_rx_cntx);
+ pru_can_err(ndev,
+ priv->can_rx_cntx.intr_stat,
+ priv->can_rx_cntx.gbl_stat);
+ } else
+ pru_can_rx(ndev, mbxno);
+ } else
+ break;
+ }
+ } while (((PRUSS_CAN_TX_INT_STAT & pru_can_get_intc_status(priv->dev))
+ && (num_pkts < quota)));
+
+ /* Enable packet interrupt if all pkts are handled */
+ if (!(PRUSS_CAN_TX_INT_STAT & pru_can_get_intc_status(priv->dev))) {
+ napi_complete(napi);
+ /* Re-enable RX mailbox interrupts */
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_RX_PRU_0, true);
+ }
+ return num_pkts;
+}
+
+static irqreturn_t pru_tx_can_intr(int irq, void *dev_id)
+{
+ struct net_device *ndev = dev_id;
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ u32 bit_set, mbxno;
+
+ pru_can_intr_stat_get(priv->dev, &priv->can_tx_cntx);
+ if ((PRUSS_CAN_ISR_BIT_CCI & priv->can_tx_cntx.intr_stat)
+ || (PRUSS_CAN_ISR_BIT_SRDI & priv->can_tx_cntx.intr_stat)) {
+ dev_dbg(priv->ndev->dev.parent, "tx_int_status = 0x%X\n",
+ priv->can_tx_cntx.intr_stat);
+ can_free_echo_skb(ndev, 0);
+ } else {
+ bit_set = fls(priv->can_tx_cntx.intr_stat & 0xFF);
+ if (!bit_set) {
+ dev_err(priv->dev, "%s: invalid mailbox number\n",
+ __func__);
+ can_free_echo_skb(ndev, 0);
+ } else {
+ mbxno = bit_set - 1;
+ if (PRUSS_CAN_ISR_BIT_ESI & priv->can_tx_cntx.
+ intr_stat) {
+ /* read gsr and ack pru */
+ pru_can_gbl_stat_get(priv->dev,
+ &priv->can_tx_cntx);
+ pru_can_err(ndev, priv->can_tx_cntx.intr_stat,
+ priv->can_tx_cntx.gbl_stat);
+ } else {
+ stats->tx_packets++;
+ /* stats->tx_bytes += dlc; */
+ /*can_get_echo_skb(ndev, 0);*/
+ }
+ }
+ }
+ netif_wake_queue(ndev);
+ can_get_echo_skb(ndev, 0);
+ pru_can_tx_mode_set(priv->dev, true, RECEIVE);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pru_rx_can_intr(int irq, void *dev_id)
+{
+ struct net_device *ndev = dev_id;
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ u32 intc_status = 0;
+
+ intc_status = pru_can_get_intc_status(priv->dev);
+
+ /* tx int sys_evt -> 34 */
+ if (intc_status & 4) {
+ pru_can_clr_intc_status(priv->dev, PRUSS_CAN_TX_PRU_1);
+ return pru_tx_can_intr(irq, dev_id);
+ }
+ /* Disable RX mailbox interrupts and let NAPI reenable them */
+ if (intc_status & 2) {
+ pru_can_mask_ints(priv->dev, PRUSS_CAN_RX_PRU_0, false);
+ napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pru_can_open(struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ s32 err;
+
+ /* register interrupt handler */
+ err = request_irq(priv->trx_irq, &pru_rx_can_intr, IRQF_SHARED,
+ "pru_can_irq", ndev);
+ if (err) {
+ dev_err(priv->dev, "error requesting rx interrupt\n");
+ goto exit_trx_irq;
+ }
+ err = open_candev(ndev);
+ if (err) {
+ dev_err(priv->dev, "open_candev() failed %d\n", err);
+ goto exit_open;
+ }
+
+ pru_can_emu_init(priv->dev, priv->can.clock.freq);
+ priv->tx_tail = PRUSS_CAN_MB_MIN;
+ priv->tx_head = PRUSS_CAN_MB_MAX;
+ pru_can_start(ndev);
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+ return 0;
+
+exit_open:
+ free_irq(priv->trx_irq, ndev);
+exit_trx_irq:
+ return err;
+}
+
+static int pru_can_close(struct net_device *ndev)
+{
+ struct can_emu_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+ close_candev(ndev);
+ free_irq(priv->trx_irq, ndev);
+ return 0;
+}
+
+static const struct net_device_ops pru_can_netdev_ops = {
+ .ndo_open = pru_can_open,
+ .ndo_stop = pru_can_close,
+ .ndo_start_xmit = pru_can_start_xmit,
+};
+
+/* Shows all the mailbox IDs */
+static ssize_t pru_sysfs_mbx_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct can_emu_priv *priv = netdev_priv(to_net_dev(dev));
+
+ return snprintf(buf, PAGE_SIZE, "<mbx_no:mbx_id>\n"
+ "0:0x%X 1:0x%X 2:0x%X 3:0x%X "
+ "4:0x%X 5:0x%X 6:0x%X 7:0x%X\n",
+ priv->mbx_id[0], priv->mbx_id[1],
+ priv->mbx_id[2], priv->mbx_id[3],
+ priv->mbx_id[4], priv->mbx_id[5],
+ priv->mbx_id[6], priv->mbx_id[7]);
+}
+
+/*
+ * Sets Mailbox IDs
+ * This should be programmed as mbx_num:mbx_id (in hex)
+ * eg: $ echo 0:0x123 > /sys/class/net/can0/mbx_id
+ */
+static ssize_t pru_sysfs_mbx_id_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev = to_net_dev(dev);
+ struct can_emu_priv *priv = netdev_priv(ndev);
+ unsigned long can_id;
+ unsigned long mbx_num;
+ char mbx[2] = {*buf, '\0'}; /* mbx num */
+ ssize_t ret = count;
+ s32 err;
+
+ if (ndev->flags & IFF_UP) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (*(buf + 1) != ':') {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ err = strict_strtoul(mbx, 0, &mbx_num);
+ if (err) {
+ ret = err;
+ goto out;
+ }
+
+ if (mbx_num > 7) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ err = strict_strtoul((buf + 2), 0, &can_id);
+ if (err) {
+ ret = err;
+ goto out;
+ }
+
+ priv->mbx_id[mbx_num] = can_id;
+ pru_can_rx_id_set(priv->dev, priv->mbx_id[mbx_num], mbx_num);
+
+ return ret;
+out:
+ dev_err(priv->dev, "invalid buffer format\n");
+ return ret;
+}
+
+static DEVICE_ATTR(mbx_id, S_IWUSR | S_IRUGO,
+ pru_sysfs_mbx_id_show, pru_sysfs_mbx_id_set);
+
+static struct attribute *pru_sysfs_attrs[] = {
+ &dev_attr_mbx_id.attr,
+ NULL,
+};
+
+static struct attribute_group pru_sysfs_attr_group = {
+ .attrs = pru_sysfs_attrs,
+};
+
+static int __devinit pru_can_probe(struct platform_device *pdev)
+{
+ struct net_device *ndev = NULL;
+ const struct da850_evm_pruss_can_data *pdata;
+ struct can_emu_priv *priv = NULL;
+ struct device *dev = &pdev->dev;
+ struct clk *clk_pruss;
+ const struct firmware *fw_rx;
+ const struct firmware *fw_tx;
+ u32 err;
+
+ pdata = dev->platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform data not found\n");
+ return -EINVAL;
+ }
+ (pdata->setup)();
+
+ ndev = alloc_candev(sizeof(struct can_emu_priv), PRUSS_CAN_MB_MAX + 1);
+ if (!ndev) {
+ dev_err(&pdev->dev, "alloc_candev failed\n");
+ err = -ENOMEM;
+ goto probe_exit;
+ }
+
+ ndev->sysfs_groups[0] = &pru_sysfs_attr_group;
+
+ priv = netdev_priv(ndev);
+
+ priv->trx_irq = platform_get_irq(to_platform_device(dev->parent), 0);
+ if (!priv->trx_irq) {
+ dev_err(&pdev->dev, "unable to get pru "
+ "interrupt resources!\n");
+ err = -ENODEV;
+ goto probe_exit;
+ }
+
+ priv->ndev = ndev;
+ priv->dev = dev;
+
+ priv->can.bittiming_const = &pru_can_bittiming_const;
+ priv->can.do_set_bittiming = pru_can_set_bittiming;
+ priv->can.do_set_mode = pru_can_set_mode;
+ priv->can.do_get_state = pru_can_get_state;
+ priv->can_tx_cntx.pruno = PRUSS_CAN_TX_PRU_1;
+ priv->can_rx_cntx.pruno = PRUSS_CAN_RX_PRU_0;
+
+ /* we support local echo, no arp */
+ ndev->flags |= (IFF_ECHO | IFF_NOARP);
+
+ /* pdev->dev->device_private->driver_data = ndev */
+ platform_set_drvdata(pdev, ndev);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+ ndev->netdev_ops = &pru_can_netdev_ops;
+
+ priv->clk_timer = clk_get(&pdev->dev, "pll1_sysclk2");
+ if (IS_ERR(priv->clk_timer)) {
+ dev_err(&pdev->dev, "no timer clock available\n");
+ err = PTR_ERR(priv->clk_timer);
+ priv->clk_timer = NULL;
+ goto probe_exit_candev;
+ }
+
+ priv->can.clock.freq = clk_get_rate(priv->clk_timer);
+
+ clk_pruss = clk_get(NULL, "pruss");
+ if (IS_ERR(clk_pruss)) {
+ dev_err(&pdev->dev, "no clock available: pruss\n");
+ err = -ENODEV;
+ goto probe_exit_clk;
+ }
+ priv->clk_freq_pru = clk_get_rate(clk_pruss);
+ clk_put(clk_pruss);
+
+ err = register_candev(ndev);
+ if (err) {
+ dev_err(&pdev->dev, "register_candev() failed\n");
+ err = -ENODEV;
+ goto probe_exit_clk;
+ }
+
+ err = request_firmware(&fw_tx, "PRU_CAN_Emulation_Tx.bin",
+ &pdev->dev);
+ if (err) {
+ dev_err(&pdev->dev, "can't load firmware\n");
+ err = -ENODEV;
+ goto probe_exit_clk;
+ }
+
+ dev_info(&pdev->dev, "fw_tx size %d. downloading...\n", fw_tx->size);
+
+ err = request_firmware(&fw_rx, "PRU_CAN_Emulation_Rx.bin",
+ &pdev->dev);
+ if (err) {
+ dev_err(&pdev->dev, "can't load firmware\n");
+ err = -ENODEV;
+ goto probe_release_fw;
+ }
+ dev_info(&pdev->dev, "fw_rx size %d. downloading...\n", fw_rx->size);
+
+ /* init the pru */
+ pru_can_emu_init(priv->dev, priv->can.clock.freq);
+ udelay(200);
+
+ netif_napi_add(ndev, &priv->napi, pru_can_rx_poll,
+ PRUSS_DEF_NAPI_WEIGHT);
+
+ pruss_enable(priv->dev, PRUSS_CAN_RX_PRU_0);
+ pruss_enable(priv->dev, PRUSS_CAN_TX_PRU_1);
+
+ /* download firmware into pru */
+ err = pruss_load(priv->dev, PRUSS_CAN_RX_PRU_0,
+ (u32 *)fw_rx->data, (fw_rx->size / 4));
+ if (err) {
+ dev_err(&pdev->dev, "firmware download error\n");
+ err = -ENODEV;
+ goto probe_release_fw_1;
+ }
+ release_firmware(fw_rx);
+ err = pruss_load(priv->dev, PRUSS_CAN_TX_PRU_1,
+ (u32 *)fw_tx->data, (fw_tx->size / 4));
+ if (err) {
+ dev_err(&pdev->dev, "firmware download error\n");
+ err = -ENODEV;
+ goto probe_release_fw_1;
+ }
+ release_firmware(fw_tx);
+
+ pruss_run(priv->dev, PRUSS_CAN_RX_PRU_0);
+ pruss_run(priv->dev, PRUSS_CAN_TX_PRU_1);
+
+ dev_info(&pdev->dev,
+ "%s device registered (trx_irq = %d, clk = %d)\n",
+ PRUSS_CAN_DRV_NAME, priv->trx_irq, priv->can.clock.freq);
+
+ return 0;
+
+probe_release_fw_1:
+ release_firmware(fw_rx);
+probe_release_fw:
+ release_firmware(fw_tx);
+probe_exit_clk:
+ clk_put(priv->clk_timer);
+probe_exit_candev:
+ if (NULL != ndev)
+ free_candev(ndev);
+probe_exit:
+ return err;
+}
+
+static int __devexit pru_can_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct can_emu_priv *priv = netdev_priv(ndev);
+
+ pru_can_stop(ndev);
+ pru_can_emu_exit(priv->dev);
+ clk_put(priv->clk_timer);
+ unregister_candev(ndev);
+ free_candev(ndev);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pru_can_suspend(struct platform_device *pdev,
+ pm_message_t mesg)
+{
+ dev_info(&pdev->dev, "%s not yet implemented\n", __func__);
+ return 0;
+}
+
+static int pru_can_resume(struct platform_device *pdev)
+{
+ dev_info(&pdev->dev, "%s not yet implemented\n", __func__);
+ return 0;
+}
+#else
+#define pru_can_suspend NULL
+#define pru_can_resume NULL
+#endif
+
+static struct platform_driver omapl_pru_can_driver = {
+ .probe = pru_can_probe,
+ .remove = __devexit_p(pru_can_remove),
+ .suspend = pru_can_suspend,
+ .resume = pru_can_resume,
+ .driver = {
+ .name = PRUSS_CAN_DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pru_can_init(void)
+{
+ pr_debug(KERN_INFO PRUSS_CAN_DRV_DESC "\n");
+ return platform_driver_register(&omapl_pru_can_driver);
+}
+
+module_init(pru_can_init);
+
+static void __exit pru_can_exit(void)
+{
+ pr_debug(KERN_INFO PRUSS_CAN_DRV_DESC " unloaded\n");
+ platform_driver_unregister(&omapl_pru_can_driver);
+}
+
+module_exit(pru_can_exit);
+
+MODULE_AUTHOR("Subhasish Ghosh <subhasish at mistralsolutions.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("omapl pru CAN netdevice driver");
--
1.7.2.3
More information about the linux-arm-kernel
mailing list