MOD-ENC28J60 and Linux (Debian/GNU)

Started by leo, December 03, 2012, 05:43:03 PM

Previous topic - Next topic

leo

Hi,
i have proplems enabling the lan interface (MOD-ENC28J60 UEXT) over spi.
I compiled the kernel with active spi and the the ENC28J60 driver as a module.

'dmesg | grep spi' told me:

[    0.170000] [spi]: sw spi init !!
[    0.170000] [spi]: sw spi init fetch spi0 uning configuration failed
[    0.180000] [spi]: Get spi devices number failed
[    0.180000] [spi]: register spi devices board info failed
[    0.190000] [spi]: drivers/spi/spi_sunxi.c(L1871) get spi 2 para failed, err code = -4
[    0.190000] [spi]: source = sdram_pll_p, src_clk = 408000000, mclk 102000000
[    0.200000] [spi]: allwinners SoC SPI Driver loaded for Bus SPI-2 with 1 Slaves attached
[    0.210000] [spi]: [spi-2]: driver probe succeed, base dc8aa000, irq 12, dma_id 2!


'modprobe enc28j60' works but i cannot activate the interface afterwards. Probably i have to connect/register the ethernet driver with the spi bus but i have no idea how to do that. My skills in kernel hacking are very limited.
Can someone point out the way (or code) to enable the network interface?

Some more informations...

uname -a

Linux debian 3.0.52 #2 PREEMPT Mon Dec 3 13:58:15 CET 2012 armv7l GNU/Linux


Kernel .config

CONFIG_ENC28J60=m
CONFIG_SPI=y
CONFIG_SPI_MASTER=y
CONFIG_SPI_SUN5I=y
CONFIG_SUN5I_SPI_NDMA=y


Kind reguards

Leo

Fadil Berisha

#1
Hi Leo,

Quote from: leo on December 03, 2012, 05:43:03 PM
I compiled the kernel with active spi and the the ENC28J60 driver as a module.
1st step is OK. You need to pass to kernel also spi device board info. This explain following message:
Quote
[    0.180000] [spi]: register spi devices board info failed
This info need to be included in your <board>.c file. Here is example with pi board: http://www.raspberrypi.org/phpBB3/viewtopic.php?f=44&t=18397

Regards
Fadil Berisha


olimex

this is very interesting! as if it works we can make easily A13-MICRO with wired ethernet!

leo

Thanks for your hints Fadil.
I followed the instructions from the Raspberry Pi forum but without much success.
I added the following lines to arch/arm/mach-sun5i/core.c:


#include <linux/spi/spi.h>
...
static struct spi_board_info spi_sun5i_info[] __initdata = {
{
.modalias = "enc28j60",
.max_speed_hz = 20000000,
.bus_num = 2,
.chip_select = 0,
.mode = SPI_MODE_0,
}
};
...
spi_register_board_info(spi_sun5i_info, ARRAY_SIZE(spi_sun5i_info));


With this kernel the enc28j60 module is recognized and loaded but i get the following error:


enc28j60 spi2.0: enc28j60 Ethernet driver 1.01 loaded
nc28j60 spi2.0: enc28j60: request irq 0 failed (ret = -16)
enc28j60: probe of spi2.0 failed with error -16


Something seems to be wrong with the IRQ assignment. Here is the corresponding code from
drivers/net/enc28j60.c:


/* Board setup must set the relevant edge trigger type;
* level triggers won't currently work.
*/
ret = request_irq(spi->irq, enc28j60_irq, 0, DRV_NAME, priv);

if (ret < 0) {
if (netif_msg_probe(priv))
dev_err(&spi->dev, DRV_NAME ": request irq %d failed "
"(ret = %d)\n", spi->irq, ret);
goto error_irq;
}


/proc/interrupts seems to be fine but the NIC is missing:


           CPU0       
  0:          0    sw_vic  axp_mfd
  2:        161    sw_vic  serial
  7:        908    sw_vic  sun5i-i2c.0
  8:         36    sw_vic  sun5i-i2c.1
  9:          0    sw_vic  sun5i-i2c.2
12:         55    sw_vic  sun5i-spi
22:       3376    sw_vic  timer0
27:          0    sw_vic  dma_irq
32:       6616    sw_vic  sunxi-mmc
37:          0    sw_vic  nand
39:         25    sw_vic  ehci_hcd:usb2
44:       3867    sw_vic  sunxi lcd0
47:       1925    sw_vic  sunxi scaler0
53:          0    sw_vic  cedar_dev
64:          0    sw_vic  ohci_hcd:usb3


After digging through the Crane Fex Guide i found another sollution. So i
added the following lines and created a new script.bin with:


[spi_devices]
spi_dev_num = 1

[spi_board0]
modalias = "enc28j60"
max_speed_hz = 20000000
bus_num = 2
chip_select = 0
mode = 0


The spi initalization looks perfect now but the error is the same:


[    0.170000] [spi]: sw spi init !!
[    0.170000] [spi]: sw spi init fetch spi0 uning configuration failed
[    0.180000] [spi]: Found 1 spi devices in config files
[    0.190000] [spi]: boards num modalias         max_spd_hz       bus_num  cs   mode
[    0.190000] [spi]: 0          enc28j60         20000000         2        0    0x0   
[    0.200000] [spi]: bus num = 2, spi used = 0
[    0.210000] [spi]: source = sdram_pll_p, src_clk = 408000000, mclk 102000000
[    0.210000] [spi]: allwinners SoC SPI Driver loaded for Bus SPI-2 with 1 Slaves attached
[    0.220000] [spi]: [spi-2]: driver probe succeed, base dc8aa000, irq 12, dma_id 2!
...
[    3.630000] enc28j60 spi2.0: enc28j60 Ethernet driver 1.01 loaded
[    3.720000] enc28j60 spi2.0: enc28j60: request irq 0 failed (ret = -16)
[    3.780000] enc28j60: probe of spi2.0 failed with error -16


So I'm stuck again. Whats the problem now and how to fix it?

Kind regards

Leo

lorenzo

Hi Leo,

try to add

.irq = SW_INT_IRQNO_SPI02,

to you spi_board_info structure.

Let me know if it works with that.

BR
Lorenzo

lukas

Modification in "script.bin" is enough for me to make module load. No need for changing "core.c". But I'm stuck with the same error:
nc28j60 spi2.0: enc28j60: request irq 0 failed (ret = -16)
My kernel is 3.4.19.
When I make modifications in "core.c" compilers show errors in this line:
spi_register_board_info(spi_sun5i_info, ARRAY_SIZE(spi_sun5i_info));

Best regards,
Lukas

leo

Hi Lorenzo,

thank you. I added your code to the spi_board_info struct but still get an error. But now, the modul complains about IRQ 12 (the same IRQ of the spi controller) and not IRQ 0.


[    3.700000] enc28j60 spi2.0: enc28j60 Ethernet driver 1.01 loaded
[    3.760000] enc28j60 spi2.0: enc28j60: request irq 12 failed (ret = -16)
[    3.780000] enc28j60: probe of spi2.0 failed with error -16


According to the request_irq man page the error -16 means:
QuoteEBUSY There exists a registered handler for the requested interrupt.

Is it a problem related to shared interrupts?

Thanks in advance.

@Lukas

Did you put it in the right location?


void __init sw_core_init(void)
{
sw_pdev_init();
spi_register_board_info(spi_sun5i_info, ARRAY_SIZE(spi_sun5i_info));
}


Kind regards

Leo

lukas

Hi Leo,
Thank you for your info. You were right, I was putting it in wrong place. Now everything compiles fine.
I changed IRQ in spi info struct to 3 (free according to /proc/interrupts) and module has been registered successfully.
"ifconfig -a" shows "eth0" but that's everything. Ping doesn't work, activity LED just blinks funny :(

Best regards,
Lukas

lorenzo

Hi Lukas,

I think that the problem is that ENC28J60 requires an interrupt to work correctly. The interrupt must be connected to an external interrupt of A13, but the problem is that there is no information about how to manage them. So, unless we use the interrupt 0 (NMI) we're stucked.

I confirm that to configure the enc28j60 you can only change the script.txt file, because the function 'spi_register_board_info' is called inside the spi_sunxi.c file.

If I found something useful, I'll let you know.

BR
Lorenzo

lorenzo

Hi,

good news here. I found how to enable enc28j60 interrupt and make the module (almost) working... Now I need your help to understand why rx always give me errors...

To make enc28j60 work undo mach-sun5i/core.c changes. You only need script.bin. Configure spi section of script.txt as follow:


[spi_devices]
spi_dev_num = 1

[spi1_para]
spi_used = 0
spi_cs_bitmap = 1
spi_cs0 = port:PG09<2><default><default><default>
spi_cs1 = port:PG13<2><default><default><default>
spi_sclk = port:PG10<2><default><default><default>
spi_mosi = port:PG11<2><default><default><default>
spi_miso = port:PG12<2><default><default><default>

[spi2_para]
spi_used = 1
spi_cs_bitmap = 3
spi_cs0 = port:PE00<4><default><default><default>
spi_cs1 = port:PB10<2><default><default><default>
spi_sclk = port:PE01<4><default><default><default>
spi_mosi = port:PE02<4><default><default><default>
spi_miso = port:PE03<4><default><default><default>

[spi_board0]
modalias = "enc28j60"
max_speed_hz = 20000000
chip_select = 0
bus_num = 2
mode = 0
spi_irq = port:PB04<6><default><default><default>


You will enable EINT18 on PB4 of your OlinuXino (GPIO alternate function 6). Now you need to make a hardware hack and connect the enc28j60 IRQ to this pin. You can easily do that by connecting the IRQ to PIN8 of GPIO connector.

Now you need to patch the enc28j60 module. I don't know how to attach a file, so here is the patch:


diff --git a/drivers/net/enc28j60.c b/drivers/net/enc28j60.c
index 2837ce2..83ce803 100644
--- a/drivers/net/enc28j60.c
+++ b/drivers/net/enc28j60.c
@@ -31,8 +31,35 @@

#include "enc28j60_hw.h"

-#define DRV_NAME "enc28j60"
-#define DRV_VERSION "1.01"
+#include <mach/irqs.h>
+#include <mach/system.h>
+#include <mach/hardware.h>
+#include <mach/sys_config.h>
+
+#define DRV_NAME "enc28j60"
+#define DRV_VERSION "1.01"
+#define ENC28J60_IRQ_NO      (18)
+#define PIO_BASE_ADDRESS (PIO_BASE)
+#define PIO_RANGE_SIZE          (0x400)
+#define PIO_INT_STAT_OFFSET     (0x214)
+#define PIO_INT_CTRL_OFFSET     (0x210)
+
+typedef enum {
+     PIO_INT_CFG0_OFFSET = 0x200,
+     PIO_INT_CFG1_OFFSET = 0x204,
+     PIO_INT_CFG2_OFFSET = 0x208,
+     PIO_INT_CFG3_OFFSET = 0x20c,
+} int_cfg_offset;
+
+typedef enum{
+        POSITIVE_EDGE = 0x0,
+        NEGATIVE_EDGE = 0x1,
+        HIGH_LEVEL = 0x2,
+        LOW_LEVEL = 0x3,
+        DOUBLE_EDGE = 0x4
+} ext_int_mode;
+
+static int int_cfg_addr[]={PIO_INT_CFG0_OFFSET,PIO_INT_CFG1_OFFSET,PIO_INT_CFG2_OFFSET, PIO_INT_CFG3_OFFSET};

#define SPI_OPLEN 1

@@ -80,6 +107,9 @@ static struct {
u32 msg_enable;
} debug = { -1 };

+static user_gpio_set_t gpio_int_info[1];
+static void* __iomem gpio_addr = NULL;
+
/*
  * SPI read buffer
  * wait for the SPI transfer and copy received data to destination
@@ -1309,10 +1339,21 @@ static void enc28j60_tx_work_handler(struct work_struct *work)
enc28j60_hw_tx(priv);
}

-static irqreturn_t enc28j60_irq(int irq, void *dev_id)
+static void enc28j60_clear_penirq(struct spi_device *spi)
{
- struct enc28j60_net *priv = dev_id;
+ int reg_val;
+ reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);
+       
+ if((reg_val = (reg_val&(1<<(ENC28J60_IRQ_NO))))){
+ dev_info(&spi->dev, DRV_NAME "==ENC28J60_CLEAR_IRQ_NO=\n");
+ writel(reg_val,gpio_addr + PIO_INT_STAT_OFFSET);
+ }
+ return;
+}
+

+static irqreturn_t enc28j60_irq(int irq, void *dev_id)
+{
/*
* Can't do anything in interrupt context because we need to
* block (spi_sync() is blocking) so fire of the interrupt
@@ -1320,8 +1361,27 @@ static irqreturn_t enc28j60_irq(int irq, void *dev_id)
* Remember that we access enc28j60 registers through SPI bus
* via spi_sync() call.
*/
- schedule_work(&priv->irq_work);
+
+ struct enc28j60_net *priv = dev_id;
+ int reg_val;
+ struct spi_device *spi = priv->spi;

+ dev_info(&spi->dev, DRV_NAME " ==========------ENC28J60 Interrupt-----============\n");
+
+ reg_val = readl(gpio_addr + PIO_INT_STAT_OFFSET);
+
+ /* Debug info */
+ //dev_info(&spi->dev, DRV_NAME " reg: %08X\n", reg_val);
+
+ if (reg_val&(1<<(ENC28J60_IRQ_NO))) {
+ dev_info(&spi->dev, DRV_NAME " ==ENC28J60 IRQ OK=\n");
+ writel(reg_val&(1<<(ENC28J60_IRQ_NO)), gpio_addr + PIO_INT_STAT_OFFSET);
+ schedule_work(&priv->irq_work);
+ } else {
+ dev_info(&spi->dev, DRV_NAME " Interrupt not for enc28j60 \n");
+ return IRQ_NONE;
+ }
+
return IRQ_HANDLED;
}

@@ -1546,6 +1606,12 @@ static int __devinit enc28j60_probe(struct spi_device *spi)
struct net_device *dev;
struct enc28j60_net *priv;
int ret = 0;
+ int gpio_int_hdle = 0;
+ __u32 reg_num = 0;
+ __u32 reg_addr = 0;
+ __u32 reg_val = 0;
+
+ spi->irq = SW_INT_IRQNO_PIO;

if (netif_msg_drv(&debug))
dev_info(&spi->dev, DRV_NAME " Ethernet driver %s loaded\n",
@@ -1585,7 +1651,44 @@ static int __devinit enc28j60_probe(struct spi_device *spi)
/* Board setup must set the relevant edge trigger type;
* level triggers won't currently work.
*/
- ret = request_irq(spi->irq, enc28j60_irq, 0, DRV_NAME, priv);
+ gpio_int_hdle = gpio_request_ex("spi_board0", "spi_irq");
+ if(!gpio_int_hdle) {
+ dev_info(&spi->dev, DRV_NAME " enc28j60 spi_irq param load failed. \n");
+ }
+ else {
+ dev_info(&spi->dev, DRV_NAME " enc28j60 spi_irq param load successfully. \n");
+ }
+
+ gpio_addr = ioremap(PIO_BASE_ADDRESS, PIO_RANGE_SIZE);
+ dev_info(&spi->dev, DRV_NAME " %s, gpio_addr = 0x%x. \n", __func__, (unsigned int)gpio_addr);
+ if(!gpio_addr) {
+ dev_info(&spi->dev, DRV_NAME " no available resources (0x%x).\n", (unsigned int)gpio_addr);
+ }
+
+ gpio_get_one_pin_status(gpio_int_hdle, gpio_int_info, "spi_irq", 1);
+ dev_info(&spi->dev, DRV_NAME " %s, %d: gpio_int_info, port = %d, port_num = %d. \n", __func__, __LINE__, \
+                gpio_int_info[0].port, gpio_int_info[0].port_num);
+
+ /* TODO -- this must be done with script param */
+ dev_info(&spi->dev, DRV_NAME " ENC28J60 interrupt configuration\n");
+ reg_num = (ENC28J60_IRQ_NO)%8;
+ reg_addr = (ENC28J60_IRQ_NO)/8;
+ reg_val = readl(gpio_addr + int_cfg_addr[reg_addr]);
+ reg_val &= (~(7 << (reg_num * 4)));
+ reg_val |= (NEGATIVE_EDGE << (reg_num * 4));
+ writel(reg_val,gpio_addr+int_cfg_addr[reg_addr]);
+
+ enc28j60_clear_penirq(spi);
+
+ reg_val = readl(gpio_addr+PIO_INT_CTRL_OFFSET);
+ reg_val |= (1 << (ENC28J60_IRQ_NO));
+ writel(reg_val,gpio_addr+PIO_INT_CTRL_OFFSET);
+
+ udelay(1);
+
+ /* Request for PIO shared interrupt */
+ ret = request_irq(SW_INT_IRQNO_PIO, enc28j60_irq, IRQF_TRIGGER_FALLING | IRQF_SHARED, DRV_NAME, priv);
+ //ret = request_irq(spi->irq, enc28j60_irq, 0, DRV_NAME, priv);
if (ret < 0) {
if (netif_msg_probe(priv))
dev_err(&spi->dev, DRV_NAME ": request irq %d failed "



Compile enc28j60 module and load it. You will see some debug on dmesg... This is an alfa version of the patch, so don't worry. The driver and the interrupt seems to work fine, but I have always errors on RX. Transmissions are fine, I can see ARP packets on my network.

In this code you can find all the instructions you need to set an external interrupt on the olinuxino, so this will be very useful for people who wants to use GPIO as external interrupt.

Please Olimex, if there is a better way to publish this pactch, let me know.

Best regards.
Lorenzo

lukas

Hi Lorenzo,
This is great news! Thank you!
With working TouchScreen too, A13 platform will be fully usable for many of us.
Could you pls. explain how to make this "hardware hack"? Where can I find IRQ pin on enc28j60 board?

BR,
Lukas

lorenzo

Hi Lukas,

just take a look at the datasheet or the hardware schematics. IRQ pin is indicated by the #INT symbol. You have to connect that signal to PB04 on GPIO connector.

Best regards.
Lorenzo

mouha

Hi,

Is there a way to retrieve the compiled version and the .bin of this please ?

lorenzo

Hi,

do you mean enc28j60.ko and script.bin? What kernel version are you running?

Maybe I can try to give you the binaries...

Lorenzo

mouha

Quote from: lorenzo on January 04, 2013, 01:04:42 PM
Hi,

do you mean enc28j60.ko and script.bin? What kernel version are you running?

Maybe I can try to give you the binaries...

Lorenzo
Hi Lorenzo,

To get quick access I'm using the Debian_No_X olimex image.
Do I understood well : to retrieve the IRQ I'll also have to connect to the PB04 from GPIO connector ?