iMX233 + ENC28J60 - Successful (kernel 3.9.3)!

Started by Chax, June 24, 2013, 07:38:48 pm

Previous topic - Next topic

Chax

June 24, 2013, 07:38:48 pm Last Edit: June 25, 2013, 03:04:56 am by Chax
Edit: it works after modifying enc28j60.c spi_read_buf function. See last post.

---
Hi All,

The iMX23 series does not have ethernet on chip. The Olinuxino Maxi includes USB ethernet but this uses a QFN chip which is not so easy to solder by hand. I am trying to get the ENC28J60 UEXT module (also from Olimex) running on the iMX23. This took me quite some time already because most options to get this thing up and running are not or only partially documented.

At this moment the hardware seems to be recognized correctly. The ENC28J60 is initialized, I get a MAC address, the kernel does not complain about interrupts anymore but for now I can only send data but not receive any data. ifconfig tells me something like:

RX packets:0 errors:34 dropped:0 overruns:0 frame:0
TX packets:38 errors:0 dropped:0 overruns:0 carrier:0


I see that there is a topic about the ENC28J60 on the A13 board. They experienced similar problems and they made quite some changes to the ENC28J60 driver sources. I think this is also neccesary for the iMX23 (probably because it does not support a full duplex SPI connection) but their sources for the A13 are specific for the Allwinner boards.

I guess more people would like to get a simple ethernet solution for this board so I will post some of the relevant configuration files later today. Maybe one of you out there can point me in the right direction about the failing RX packets of the module?

Regards,
Chax

Fadil Berisha

Quote from: Chax on June 24, 2013, 07:38:48 pm
...
I guess more people would like to get a simple ethernet solution for this board so I will post some of the relevant configuration files later today. Maybe one of you out there can point me in the right direction about the failing RX packets of the module?


Hi Chax,

The Freescale i.MX23 Evaluation Kit has ethernet solution based on ENC28J60. Since that solution  work for that board (kernel 2.6), there is no reason to fail on imx23-olinuxino boards.

Linux driver for ENC28J80 is not depend on board and SoC (imx23, A13 etc) so I don't see need to modify driver. Focus should be on configuration files for particular board and kernel version.



Chax

June 25, 2013, 01:26:35 am #2 Last Edit: June 25, 2013, 02:15:26 am by Chax
One of the reasons I am trying this is because the EVK has the Microchip ethernet device on the board but this is all kernel 2.6 based. Since SPI is working for kernel 3.7.something+ and the ENC28J60 driver is part of that kernel I try to get it all up and running on (let me check...) kernel 3.9.3 but no luck so far.

I guess it might have something to do with the DTS file for the Olinuxino in combination with SPI. I added the following to the imx23-olinuxino.dts:

ssp1: ssp@80034000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx23-spi";
pinctrl-names = "default";
pinctrl-0 = <&spi2_pins_a>;
status = "okay";

eth: enc28j60@0 {
// #address-cells = <1>;
// #size-cells = <1>;
compatible = "microchip,enc28j60";
spi-max-frequency = <1000000>;
reg = <0>;
interrupt-parent = <&gpio0>;
interrupts = <24 0x01>;
};

// spidev: spidev@0 {
//         compatible = "spidev";
//     spi-max-frequency = <1000000>;
//     reg = <0>;
//     };

};


The part that is commented out can be enabled (when disabling the eth: enc28j60@0 { ... } part. This way you can do some spi testing when you modify spidev_test.c.

Especially the 'interrupt-parent = <&gpio0>' and 'interrupts = <24 0x01>;' took me quite a while to figure out and I'm not sure IF the interrupts are actually working or handled correctly. I can see the number of interrupts increasing in /proc/interrupts but it also does this while I unplug the interrupt line from the development board. It is connected on the mx23 to physical pin 34 (GPMI_WPN) which Olimex has labeled Pin29/SoftSCL. Although this is GPIO23, you give it number 24 here because the number is one-based (aargh).

           CPU0
16:     222695         -  MXS Timer Tick
17:          6         -  mxs-mmc
18:      17875         -  mxs-dma
44:   29972285  gpio-mxs  enc28j60
118:          0         -  mxs-spi
119:        905         -  mxs-dma
120:          0         -  RTC alarm
124:          0         -  8006c000.serial
127:      12811         -  uart-pl011
128:       4533         -  ci13xxx_imx
Err:          0


I am aware that the linux driver is (or should be) independant of the board or soc that is used but in the other topic they made quite some modifications and they seemed to have the same problem: failing RX https://www.olimex.com/forum/index.php?topic=462.0. This had something to do with half duplex spi? The same probably applies for the mx23?

At this moment I don't know why the ENC28J60 is not working. When I use a scope everything about the hardware seems to be in order. Chip Select is doing ok, MISO/MOSI is working (I can query eth0 and it has a valid MAC address), cable unplug and plug events are registered by the kernel. The interrupts seem to do something because INT# on the ENC29J60 goes low and back high again. When I choose the wrong GPIO channel for the mx23 in the DTS file, it only goes low once and never goes high anymore.

The mx23 is very busy however doing... something with SPI:


  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND   
   27 root      20   0     0    0    0 R  51.5  0.0   2:09.29 spi32766         
   15 root      20   0     0    0    0 D  36.5  0.0   1:16.91 kworker/0:1   


Maybe you have a suggestion where to look next?

Chax

Yeah! Result! After messing with 'ethtool -s eth0 msglvl  0x7ff' I got messages about spi_read_buf(...) failing. This is due to the fact that SPI is half duplex in the mx23.

The guys from the A13 also changed their spi_read_buf(...) function to support half duplex. I simply removed the old spi_read_buf function in the enc28j60.c file and replaced it with their code:


/*
* SPI read buffer
* wait for the SPI transfer and copy received data to destination
*/
static int
spi_read_buf(struct enc28j60_net *priv, int len, u8 *data)
{
u8 *rx_buf = priv->spi_transfer_buf + 4;
u8 *tx_buf = priv->spi_transfer_buf;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = NULL,
.len = SPI_OPLEN,
.cs_change = 0,
};

struct spi_transfer r = {
.tx_buf = NULL,
.rx_buf = rx_buf,
.len = len,
.cs_change = 1,
};

struct spi_message msg;
int ret;

tx_buf[0] = ENC28J60_READ_BUF_MEM;
tx_buf[1] = tx_buf[2] = tx_buf[3] = 0;  /* don't care */

spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
spi_message_add_tail(&r, &msg);
ret = spi_sync(priv->spi, &msg);
if (ret == 0) {
memcpy(data, rx_buf, len);
ret = msg.status;
}
if (ret && netif_msg_drv(priv))
printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n",
__func__, ret);

return ret;
}


Compiled it and now it works! 8)

Iperf results (half duplex @1MHz SPI as client):

Client connecting to 192.168.200.150, TCP port 5001
TCP window size: 21.0 KByte (default)
------------------------------------------------------------
[  3] local 192.168.200.216 port 48029 connected with 192.168.200.150 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-14.4 sec   768 KBytes   436 Kbits/sec


Iperf results (half duplex @1MHz SPI as server):

Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------
[  4] local 192.168.200.216 port 5001 connected with 192.168.200.150 port 56999
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0-14.2 sec   768 KBytes   443 Kbits/sec


No more errors:

eth0      Link encap:Ethernet  HWaddr 86:e6:3f:0a:b4:45 
          inet addr:192.168.200.216  Bcast:192.168.200.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1836 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2330 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:978034 (955.1 KiB)  TX bytes:2520956 (2.4 MiB)
          Interrupt:44


wget throughput of 10 MB file (SPI @1 MHz.):

100%[======================================>] 10,485,760  46.2K/s   in 3m 39s 
2013-06-24 23:36:39 (46.7 KB/s) - `10mb.bin' saved [10485760/10485760]


wget throughput of 10 MB file (SPI @4 MHz.):

100%[======================================>] 10,485,760  78.3K/s   in 2m 12s 
2013-06-24 23:58:36 (77.7 KB/s) - `10mb.bin.1' saved [10485760/10485760]


The imx23 is still very busy though with handling SPI (even if there are no interrupts). 'top' output:

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND           
   27 root      20   0     0    0    0 S  46.5  0.0   4:59.18 spi32766         
2652 root      20   0     0    0    0 D  39.6  0.0   0:55.20 kworker/0:0       
   15 root      20   0     0    0    0 S  10.2  0.0   2:05.58 kworker/0:1 


That is for later but at least it works!

pparent

Hi,

Just a precision form my experience:

GPIO are not counted form 1 but from 0. The thing is you specified 0x1 as parameter wich means the interruption is done form low to high. In this case you  want to put 0x2 wich means high to low.

So this is interrupts = <23 0x02>;

By choosing 24 (corresponding to spi_clk) you get an interruption at every clock tick, so processor constantly handles interruptions which explains high CPU usage that you experience.

see:
https://community.freescale.com/message/341723#341723

Pierre

pparent

Also for me the function you said above did not work, I had to use this function:


/*
* SPI read buffer
* wait for the SPI transfer and copy received data to destination
* New version correcting bug for half duplex mode
*/
static int
spi_read_buf(struct enc28j60_net *priv, int len, u8 *data)
{
  static int nbmsg=0;
  nbmsg++;
//     printk("Receive message %d\n",nbmsg);
u8 *rx_buf = priv->spi_transfer_buf + 4;
u8 *tx2_buf=priv->spi_transfer_buf+2;
u8 *tx_buf = priv->spi_transfer_buf;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = NULL,
.len = SPI_OPLEN,
.cs_change = 0,
};

struct spi_transfer r = {
.tx_buf = NULL,
.rx_buf = rx_buf,
.len = len,
.cs_change = 1,
};

struct spi_transfer t2 = {
.tx_buf = tx2_buf,
.rx_buf = NULL,
.len = SPI_OPLEN,
.cs_change = 0,
};

struct spi_message msg;
int ret;

tx_buf[0] = ENC28J60_READ_BUF_MEM;
tx_buf[1] = tx_buf[2] = tx_buf[3] = 0;  /* don't care */
tx2_buf[0]= ENC28J60_MSG_DEFAULT;
spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
spi_message_add_tail(&r, &msg);
spi_message_add_tail(&t2, &msg);
ret = spi_sync(priv->spi, &msg);
if (ret == 0) {
memcpy(data, rx_buf, len);
ret = msg.status;
}
if (ret && netif_msg_drv(priv))
{
printk(KERN_DEBUG DRV_NAME ": %s() failed: ret = %d\n",
__func__, ret);
}

return ret;
}