OpenWrt Forum Archive

Topic: CAN Bus adapter with MCP2515 - IRQ problem / ADM5120 - BR-6104K

The content of this topic has been archived on 24 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Hi,
i have connected a MCP2515 CAN bus controller with a MCP2551 tranceiver to a BR-6104-K.
Using the bitbang SPI module the controller can be initialized:

BusyBox v1.19.3 (2012-04-05 12:15:53 CEST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 ATTITUDE ADJUSTMENT (bleeding edge, r30857) ----------

root@OpenWrt:/# uname -a
Linux OpenWrt 3.1.10 #6 Thu Apr 5 16:39:22 CEST 2012 mips GNU/Linux

root@OpenWrt:/# insmod mcp251x
root@OpenWrt:/# cat /sys/kernel/debug/gpio 
GPIOs 0-3, adm5120 gpio0:
 gpio-2   (MCP251x CAN INT     ) in  hi

GPIOs 8-22, adm5120 gpio1:
 gpio-17  (spi_gpio.1          ) in  lo
 gpio-18  (spi_gpio.1          ) out lo
 gpio-20  (spi_gpio.1          ) out lo
 gpio-21  (spi1.0              ) out hi
root@OpenWrt:/# insmod mcp251x
[  368.736000] mcp251x spi1.0: probed

root@OpenWrt:/# ip link set can0 type can bitrate 125000 loopback on

root@OpenWrt:/# ifconfig can0 up
[  402.324000] mcp251x spi1.0: CNF: 0x03 0xb5 0x01

root@OpenWrt:/# ip -details link show can0
10: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
    link/can 
    can <LOOPBACK> state ERROR-ACTIVE restart-ms 0 
    bitrate 125000 sample-point 0.875 
    tq 500 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
    mcp251x: tseg1 3..16 tseg2 2..8 sjw 1..4 brp 1..64 brp-inc 1
    clock 8000000

root@OpenWrt:/# cat /sys/kernel/debug/gpio 
GPIOs 0-3, adm5120 gpio0:
 gpio-2   (MCP251x CAN INT     ) in  hi

GPIOs 8-22, adm5120 gpio1:
 gpio-17  (spi_gpio.1          ) in  lo
 gpio-18  (spi_gpio.1          ) out lo
 gpio-20  (spi_gpio.1          ) out lo
 gpio-21  (spi1.0              ) out hi

root@OpenWrt:/# cat /proc/interrupts 
           CPU0       
  2:          0      MIPS  cascade [INTC]
  7:      33933      MIPS  timer
  9:       2477      INTC  uart-pl010
 12:          0      INTC  mcp251x
 17:        614      INTC  eth0, eth1
ERR:          0

root@OpenWrt:/# dmesg | tail -5
[  368.732000] mcp251x spi1.0: CANSTAT 0x80 CANCTRL 0x07
[  368.736000] mcp251x spi1.0: probed
[  402.308000] mcp251x spi1.0:  INTC_REG_IRQ_ENABLE = 0x212
[  402.308000] mcp251x spi1.0:  GPIO2 IRQ initialized
[  402.324000] mcp251x spi1.0: CNF: 0x03 0xb5 0x01

Everything seems to be fine. But the IRQ (/INT from MCP2515) doesn't work. The /INT is pulled down when I
send a message to the controller (expecting a message back thru loopback):

root@OpenWrt:/# cansend can0 123#DEADBEEF
root@OpenWrt:/# cat /proc/interrupts 
           CPU0       
  2:          0      MIPS  cascade [INTC]
  7:      34334      MIPS  timer
  9:       2845      INTC  uart-pl010
 12:          0      INTC  mcp251x
 17:        669      INTC  eth0, eth1
ERR:          0

root@OpenWrt:/# cat /sys/kernel/debug/gpio 
GPIOs 0-3, adm5120 gpio0:
 gpio-2   (MCP251x CAN INT     ) in  lo

GPIOs 8-22, adm5120 gpio1:
 gpio-17  (spi_gpio.1          ) in  lo
 gpio-18  (spi_gpio.1          ) out lo
 gpio-20  (spi_gpio.1          ) out lo
 gpio-21  (spi1.0              ) out hi

I have done several tests (setting up the IRQs in the board setup) - with no luck.
To get further I have put the IRQ setup into the driver setup (I know it's ugly and not the right way):
mcp251x.c

 static irqreturn_t mcp251x_can_ist(int irq, void *dev_id)
{
        struct mcp251x_priv *priv = dev_id;
        struct spi_device *spi = priv->spi;
        struct net_device *net = priv->net;
        unsigned long flags;
        unsigned int intc_reg_int_level;

        mutex_lock(&priv->mcp_lock);
        dev_dbg(&spi->dev, " in mcp251x_can_ist ...\n");

        // ADM5120 IRQs can only be level, not edge so we are going
        //   switch level and only use high to low trigger
        spin_lock_irqsave(&level_register_lock, flags);
        {
                intc_reg_int_level = BIT(4) ^ intc_read_reg(INTC_REG_INT_LEVEL);
                intc_write_reg(INTC_REG_INT_LEVEL, intc_reg_int_level);
        }
        spin_unlock_irqrestore(&level_register_lock, flags);
        // we only need falling edge
        if (BIT(4) & intc_reg_int_level)
                return(IRQ_HANDLED);
        while (!priv->force_quit) {
        ...
        }
}

...
static int mcp251x_open(struct net_device *net)
{
        struct mcp251x_priv *priv = netdev_priv(net);
        struct spi_device *spi = priv->spi;
        struct mcp251x_platform_data *pdata = spi->dev.platform_data;
        int ret;
        unsigned int ire;
        unsigned long flags;

        ret = open_candev(net);
        if (ret) {
                dev_err(&spi->dev, "unable to set initial baudrate!\n");
                return ret;
        }

        mutex_lock(&priv->mcp_lock);
        if (pdata->transceiver_enable)
                pdata->transceiver_enable(1);

        priv->force_quit = 0;
        priv->tx_skb = NULL;
        priv->tx_len = 0;

        ret = request_threaded_irq(spi->irq, NULL, mcp251x_can_ist,
                  pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_FALLING,
                  DEVICE_NAME, priv);
        if (ret) {
                dev_err(&spi->dev, "failed to acquire irq %d\n", spi->irq);
                if (pdata->transceiver_enable)
                        pdata->transceiver_enable(0);
                close_candev(net);
                goto open_unlock;
        } else {
                spin_lock_irqsave(&level_register_lock, flags);
                // enable interrupt on GPIO 2
                // pp37 ADM5120 manual
                ire = intc_read_reg(INTC_REG_IRQ_ENABLE);
                ire |= BIT(4) | BIT(9); // II0E | SWIE
                intc_write_reg(INTC_REG_IRQ_ENABLE, ire);
                dev_dbg(&spi->dev, " INTC_REG_IRQ_ENABLE = 0x%x\n", ire);

                // Enable CSX0/INTX0 processing as described on pp131 of ADM5120 manual
                SW_WRITE_REG(SWITCH_REG_GPIO_CONF2, BIT(4));
                // sets GPIO4 and 2 as active low
                // pp41/42 of ADM5120 manual.
                intc_write_reg(INTC_REG_INT_LEVEL, BIT(4) | intc_read_reg(INTC_REG_INT_LEVEL));
                spin_unlock_irqrestore(&level_register_lock, flags);
                dev_dbg(&spi->dev, " GPIO2 IRQ initialized\n");
        }
...
}

A simple test with this code http://www.omnima.co.uk/forums/lofivers … p?t72.html works:

root@OpenWrt:/# insmod gpio2
[  130.380000] INTC_REG_IRQ_ENABLE = 0x212
[  130.384000] GPIO_RESET: requesting IRQ -> fine
root@OpenWrt:/# [  133.820000] pressed
[  134.040000] released
[  135.196000] pressed
[  135.412000] released
[  136.076000] pressed
[  136.220000] released

root@OpenWrt:/# cat /proc/interrupts 
           CPU0       
  2:          0      MIPS  cascade [INTC]
  7:      28970      MIPS  timer
  9:        180      INTC  uart-pl010
 12:          6      INTC  gpio_reset
 17:        334      INTC  eth0, eth1
ERR:          0

Does anybody has a hint ?

Regards bertc3p0

The board setup

nizza ~/projekte/openwrt/trunk_3.1 (svn)-[trunk:30857] % cat build_dir/linux-adm5120_router_le/linux-3.1.10/arch/mips/adm5120/edimax/br-61xx.c
/*
 *  Edimax BR-61xx support
 *
 *  Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
 *
 *  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 "br-61xx.h"

#include <prom/admboot.h>
#include <linux/spi/spi_gpio.h>
#include <linux/can/platform/mcp251x.h>

#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>

#include <adm5120_defs.h>


#define BR61XX_GPIO_DEV_MASK    0

#define BR61XX_CONFIG_OFFSET    0x8000
#define BR61XX_CONFIG_SIZE    0x1000

static struct mtd_partition br61xx_partitions[] = {
    {
        .name    = "admboot",
        .offset    = 0,
        .size    = 32*1024,
        .mask_flags = MTD_WRITEABLE,
    } , {
        .name    = "config",
        .offset    = MTDPART_OFS_APPEND,
        .size    = 32*1024,
    } , {
        .name    = "firmware",
        .offset    = MTDPART_OFS_APPEND,
        .size    = MTDPART_SIZ_FULL,
    }
};

/* static struct gpio_button br61xx_gpio_buttons[] __initdata = {
    {
        .desc        = "reset_button",
        .type        = EV_KEY,
        .code        = BTN_0,
        .threshold    = 5,
        .gpio        = ADM5120_GPIO_PIN2,
    }
};
*/

static u8 br61xx_vlans[6] __initdata = {
    0x41, 0x42, 0x44, 0x48, 0x50, 0x00
};


/* SPI specific part 
   CS    GPIO_21
   SCLK GPIO_20
   MOSI GPIO_18
   MISO GPIO_17
*/

static struct mcp251x_platform_data mcp251x_info = {
        .oscillator_frequency    = 16000000,
        .board_specific_setup    = NULL,
        .irq_flags        = IRQF_TRIGGER_LOW,
        .power_enable        = NULL,
        .transceiver_enable    = NULL,
};

static struct spi_gpio_platform_data br61xx_gpio_spi = {
        .sck            = 20,
        .mosi           = 18,
        .miso           = 17,
        .num_chipselect = 1, // number of chip selects for spi gpio master 
};

static struct platform_device spi_gpio_device = {
        .name                   = "spi_gpio",
        .id                     = 1, // Bus number 
        .dev.platform_data      = &br61xx_gpio_spi,
};

static struct spi_board_info mcp2515_spi_gpio_board_info [] = {
        {
                .modalias               = "mcp2515",
                .max_speed_hz           = 10000000,
                .bus_num                = 1,
                .chip_select            = 0,
                .platform_data          = &mcp251x_info,
                .irq             = ADM5120_IRQ_GPIO2,
                .mode                   = SPI_MODE_0,
                .controller_data        = (void *) 21,
        },
};

static struct platform_device *br61xx_devices[] __initdata = {
        &spi_gpio_device,
};

static void __init adm5120_mcp251x_init(void) {
    printk(KERN_DEBUG "adm5120_mcp251x_init: Entry\n");
    if ((gpio_request(ADM5120_GPIO_PIN2, "MCP251x CAN INT") == 0) &&
        (gpio_direction_input(ADM5120_GPIO_PIN2) == 0)) {
        gpio_export(ADM5120_GPIO_PIN2, 0);
        
        printk(KERN_DEBUG "  registered GPIO2 for mcp251x INT\n");
    } else {
        printk(KERN_ERR "could not obtain gpio for MCP251x CAN bus interrupt\n");
        return;
    }
}


static void __init br61xx_mac_setup(void)
{
    u8 mac_base[6];
    int err;

    err = admboot_get_mac_base(BR61XX_CONFIG_OFFSET,
                   BR61XX_CONFIG_SIZE, mac_base);

    if ((err) || !is_valid_ether_addr(mac_base))
        random_ether_addr(mac_base);

    adm5120_setup_eth_macs(mac_base);
}

void __init br61xx_generic_setup(void)
{

    adm5120_flash0_data.nr_parts = ARRAY_SIZE(br61xx_partitions);
    adm5120_flash0_data.parts = br61xx_partitions;
    adm5120_add_device_flash(0);

    adm5120_add_device_gpio(BR61XX_GPIO_DEV_MASK);

    adm5120_add_device_uart(0);
    adm5120_add_device_uart(1);

    adm5120_add_device_switch(5, br61xx_vlans);

    adm5120_mcp251x_init();
    spi_register_board_info(mcp2515_spi_gpio_board_info,ARRAY_SIZE(mcp2515_spi_gpio_board_info));

//    adm5120_add_device_gpio_buttons(ARRAY_SIZE(br61xx_gpio_buttons), br61xx_gpio_buttons);

    br61xx_mac_setup();
    platform_add_devices(br61xx_devices,ARRAY_SIZE(br61xx_devices));
}

(Last edited by bertc3p0 on 5 Apr 2012, 16:18)

Sorry for being a bit off-topic, but I saw you obviously using the 3.1-patches from the mailing list/patchwork. Since nobody else has commented on that so far, I would ask you to write a short comment about your experience, there. Maybe this will help a bit to get it into trunk.
Thanks

MBS

I am using the patches because of the CAN drivers which are a little bit outdated in the the ADM5120 Openwrt default trunk
kernel. IMHO the patches from the mailing list are OK. But I don't use the USB part, which should be more interesting IMHO.

I had no issue until now. Worth a try. Tried to port them to 3.2.14, but I'm not an kernel nor openwrt expert. There are some
mismatches because the arch folders and drivers seems to be reorganized in 3.2. Maybe someone else with more kernel in
debt know how could do this in a few hours ... and much faster than me ;-) Isn't the 3.2 a LTS kernel ?

(Last edited by bertc3p0 on 16 Apr 2012, 17:27)

Yeah. Got it working with a wild hack of mcp251x:

root@OpenWrt:~# cansend can0 666#DEADBEEF

root@OpenWrt:~# candump any,0:0,#FFFFFFFF
  can0  666  [4] DE AD BE EF
  can0  666  [4] DE AD BE EF

root@OpenWrt:~# ip -s -details link show can0
9: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN
qlen 10
    link/can
    can <LOOPBACK> state ERROR-ACTIVE restart-ms 0
    bitrate 125000 sample-point 0.875
    tq 500 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
    mcp251x: tseg1 3..16 tseg2 2..8 sjw 1..4 brp 1..64 brp-inc 1
    clock 8000000
    re-started bus-errors arbit-lost error-warn error-pass bus-off
    0          0          0          0          0          0
    RX: bytes  packets  errors  dropped overrun mcast
    48         10       0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    48         10       0       0       0       0

root@OpenWrt:~# cat /sys/kernel/debug/gpio
GPIOs 0-3, adm5120 gpio0:
 gpio-2   (MCP251x /INT        ) in  hi

GPIOs 8-22, adm5120 gpio1:
 gpio-17  (spi_gpio.1          ) in  lo
 gpio-18  (spi_gpio.1          ) out lo
 gpio-20  (spi_gpio.1          ) out lo
 gpio-21  (spi1.0              ) out hi

root@OpenWrt:~# cat /proc/interrupts
           CPU0
  2:          0      MIPS  cascade [INTC]
  7:     553160      MIPS  timer
  9:       1482      INTC  uart-pl010
 12:         20      INTC  mcp251x
 17:     119245      INTC  eth0, eth1
ERR:          0
--- mcp251x.c   2012-01-18 16:33:18.000000000 +0100
+++ mcp251x_funkt2.c    2012-04-18 15:33:49.926203717 +0200
@@ -76,6 +76,15 @@
 #include <linux/spi/spi.h>
 #include <linux/uaccess.h>

+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#include <adm5120_defs.h>
+#include <asm/mipsregs.h>
+#include <asm/mach-adm5120/adm5120_info.h>
+#include <asm/mach-adm5120/adm5120_defs.h>
+#include <asm/mach-adm5120/adm5120_switch.h>
+
 /* SPI interface instruction set */
 #define INSTRUCTION_WRITE      0x02
 #define INSTRUCTION_READ       0x03
@@ -210,6 +219,21 @@

 #define DEVICE_NAME "mcp251x"

+static inline void intc_write_reg(unsigned int reg, u32 val)
+{
+        void __iomem *base = (void __iomem *)KSEG1ADDR(ADM5120_INTC_BASE);
+
+        __raw_writel(val, base + reg);
+}
+
+static inline u32 intc_read_reg(unsigned int reg)
+{
+        void __iomem *base = (void __iomem *)KSEG1ADDR(ADM5120_INTC_BASE);
+
+        return __raw_readl(base + reg);
+}
+
+
 static int mcp251x_enable_dma; /* Enable SPI DMA. Default: 0 (Off) */
 module_param(mcp251x_enable_dma, int, S_IRUGO);
 MODULE_PARM_DESC(mcp251x_enable_dma, "Enable SPI DMA. Default: 0 (Off)");
@@ -779,6 +803,39 @@
        mutex_unlock(&priv->mcp_lock);
 }

+static DEFINE_SPINLOCK(level_register_lock);
+
+
+static irqreturn_t mcp251x_can_hardirq(int irq, void *dev_id)
+{
+       struct mcp251x_priv *priv = dev_id;
+       struct spi_device *spi = priv->spi;
+       unsigned long flags;
+       unsigned int intc_reg_int_level;
+
+        // ADM5120 IRQs can only be level, not edge so we are going
+        //   switch level and only use high to low trigger
+        spin_lock_irqsave(&level_register_lock, flags);
+        intc_reg_int_level = intc_read_reg(INTC_REG_INT_LEVEL) ^ BIT(4);
+        intc_write_reg(INTC_REG_INT_LEVEL, intc_reg_int_level);
+       dev_info(&spi->dev, " mcp251x INT level 0x%X \n",intc_reg_int_level);
+
+        // we ignore raising edge - we have already xored
+        // if (intc_reg_int_level && BIT(4))
+       if (intc_reg_int_level &= 0x10)
+       {
+               dev_info(&spi->dev, "  mcp251x INT high -> IRQ_HANDLED\n");
+               spin_unlock_irqrestore(&level_register_lock, flags);
+               return IRQ_HANDLED;
+       } else {
+               dev_info(&spi->dev, "  mcp251x INT low  -> IRQ_WAKE_THREAD\n");
+               spin_unlock_irqrestore(&level_register_lock, flags);
+               return IRQ_WAKE_THREAD;
+       }
+}
+
+
+
 static irqreturn_t mcp251x_can_ist(int irq, void *dev_id)
 {
        struct mcp251x_priv *priv = dev_id;
@@ -786,6 +843,8 @@
        struct net_device *net = priv->net;

        mutex_lock(&priv->mcp_lock);
+       dev_info(&spi->dev, " in mcp251x_can_ist ...\n");
+
        while (!priv->force_quit) {
                enum can_state new_state;
                u8 intf, eflag;
@@ -909,12 +968,15 @@
        return IRQ_HANDLED;
 }

+
 static int mcp251x_open(struct net_device *net)
 {
        struct mcp251x_priv *priv = netdev_priv(net);
        struct spi_device *spi = priv->spi;
        struct mcp251x_platform_data *pdata = spi->dev.platform_data;
        int ret;
+       unsigned int ire;
+       unsigned long flags;

        ret = open_candev(net);
        if (ret) {
@@ -930,8 +992,9 @@
        priv->tx_skb = NULL;
        priv->tx_len = 0;

-       ret = request_threaded_irq(spi->irq, NULL, mcp251x_can_ist,
-                 pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_FALLING,
+       ret = request_threaded_irq(spi->irq, mcp251x_can_hardirq, mcp251x_can_ist,
+                 // pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_FALLING,
+                 pdata->irq_flags ? pdata->irq_flags : IRQF_TRIGGER_NONE,
                  DEVICE_NAME, priv);
        if (ret) {
                dev_err(&spi->dev, "failed to acquire irq %d\n", spi->irq);
@@ -939,6 +1002,24 @@
                        pdata->transceiver_enable(0);
                close_candev(net);
                goto open_unlock;
+       } else {
+               spin_lock_irqsave(&level_register_lock, flags);
+               // enable interrupt on GPIO 2
+               // pp37 ADM5120 manual
+              ire = intc_read_reg(INTC_REG_IRQ_ENABLE);
+              dev_info(&spi->dev, " INTC_REG_IRQ_ENABLE was = 0x%x\n", ire);
+              ire |= BIT(4) | BIT(9); // II0E | SWIE
+              // ire |= BIT(4); // II0E
+              intc_write_reg(INTC_REG_IRQ_ENABLE, ire);
+               dev_info(&spi->dev, " INTC_REG_IRQ_ENABLE now = 0x%x\n", ire);
+
+               // Enable CSX0/INTX0 processing as described on pp131 of ADM5120 manual
+               SW_WRITE_REG(SWITCH_REG_GPIO_CONF2, BIT(4));
+               // sets GPIO4 and 2 as active low
+                // pp41/42 of ADM5120 manual.
+               intc_write_reg(INTC_REG_INT_LEVEL, BIT(4) | intc_read_reg(INTC_REG_INT_LEVEL));
+               spin_unlock_irqrestore(&level_register_lock, flags);
+               dev_info(&spi->dev, " GPIO2 IRQ initialized: IRQ %d\n",spi->irq);
        }

        priv->wq = create_freezable_workqueue("mcp251x_wq");

and br-61xx.c

static struct mcp251x_platform_data mcp251x_info = {
        .oscillator_frequency    = 16000000,
        .board_specific_setup    = NULL,
        .irq_flags               = IRQF_TRIGGER_NONE,
        .power_enable            = NULL,
        .transceiver_enable      = NULL,
};

(Last edited by bertc3p0 on 18 Apr 2012, 14:30)

The discussion might have continued from here.