Blame view

RIOT/drivers/w5100/w5100.c 9.18 KB
a752c7ab   elopes   add first test an...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
  /*
   * Copyright (C) 2016-2017 Freie Universitรคt Berlin
   *
   * This file is subject to the terms and conditions of the GNU Lesser
   * General Public License v2.1. See the file LICENSE in the top level
   * directory for more details.
   */
  
  /**
   * @ingroup     drivers_w5100
   * @{
   *
   * @file
   * @brief       Device driver implementation for w5100 Ethernet devices
   *
   * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
   *
   * @}
   */
  
  #include <stdio.h>
  #include <string.h>
  
  #include "log.h"
  #include "luid.h"
  #include "assert.h"
  
  #include "net/ethernet.h"
  #include "net/netdev/eth.h"
  
  #include "w5100.h"
  #include "w5100_regs.h"
  
  #define ENABLE_DEBUG        (0)
  #include "debug.h"
  
  
  #define SPI_CONF            SPI_MODE_0
  #define RMSR_DEFAULT_VALUE  (0x55)
  
  #define S0_MEMSIZE          (0x2000)
  #define S0_MASK             (S0_MEMSIZE - 1)
  #define S0_TX_BASE          (0x4000)
  #define S0_RX_BASE          (0x6000)
  
  static const netdev_driver_t netdev_driver_w5100;
  
  static inline void send_addr(w5100_t *dev, uint16_t addr)
  {
  #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, (addr >> 8));
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, (addr & 0xff));
  #else
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, (addr & 0xff));
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, (addr >> 8));
  #endif
  }
  
  static uint8_t rreg(w5100_t *dev, uint16_t reg)
  {
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, CMD_READ);
      send_addr(dev, reg++);
      return spi_transfer_byte(dev->p.spi, dev->p.cs, false, 0);
  }
  
  static void wreg(w5100_t *dev, uint16_t reg, uint8_t data)
  {
      spi_transfer_byte(dev->p.spi, dev->p.cs, true, CMD_WRITE);
      send_addr(dev, reg);
      spi_transfer_byte(dev->p.spi, dev->p.cs, false, data);
  }
  
  static uint16_t raddr(w5100_t *dev, uint16_t addr_high, uint16_t addr_low)
  {
      uint16_t res = (rreg(dev, addr_high) << 8);
      res |= rreg(dev, addr_low);
      return res;
  }
  
  static void waddr(w5100_t *dev,
                    uint16_t addr_high, uint16_t addr_low, uint16_t val)
  {
      wreg(dev, addr_high, (uint8_t)(val >> 8));
      wreg(dev, addr_low, (uint8_t)(val & 0xff));
  }
  
  static void rchunk(w5100_t *dev, uint16_t addr, uint8_t *data, size_t len)
  {
      /* reading a chunk must be split in multiple single byte reads, as the
       * device does not support auto address increment via SPI */
      for (int i = 0; i < (int)len; i++) {
          data[i] = rreg(dev, addr++);
      }
  }
  
  static void wchunk(w5100_t *dev, uint16_t addr, uint8_t *data, size_t len)
  {
      /* writing a chunk must be split in multiple single byte writes, as the
       * device does not support auto address increment via SPI */
      for (int i = 0; i < (int)len; i++) {
          wreg(dev, addr++, data[i]);
      }
  }
  
  static void extint(void *arg)
  {
      w5100_t *dev = (w5100_t *)arg;
  
      if (dev->nd.event_callback) {
          dev->nd.event_callback(&dev->nd, NETDEV_EVENT_ISR);
      }
  }
  
  void w5100_setup(w5100_t *dev, const w5100_params_t *params)
  {
      /* initialize netdev structure */
      dev->nd.driver = &netdev_driver_w5100;
      dev->nd.event_callback = NULL;
      dev->nd.context = dev;
  
      /* initialize the device descriptor */
      memcpy(&dev->p, params, sizeof(w5100_params_t));
  
      /* initialize the chip select pin and the external interrupt pin */
      spi_init_cs(dev->p.spi, dev->p.cs);
      gpio_init_int(dev->p.evt, GPIO_IN, GPIO_FALLING, extint, dev);
  }
  
  static int init(netdev_t *netdev)
  {
      w5100_t *dev = (w5100_t *)netdev;
      uint8_t tmp;
      uint8_t hwaddr[ETHERNET_ADDR_LEN];
  
      /* get access to the SPI bus for the duration of this function */
      spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk);
  
      /* test the SPI connection by reading the value of the RMSR register */
      tmp = rreg(dev, REG_TMSR);
      if (tmp != RMSR_DEFAULT_VALUE) {
          spi_release(dev->p.spi);
          LOG_ERROR("[w5100] error: no SPI connection\n");
          return W5100_ERR_BUS;
      }
  
      /* reset the device */
      wreg(dev, REG_MODE, MODE_RESET);
      while (rreg(dev, REG_MODE) & MODE_RESET) {};
  
      /* initialize the device, start with writing the MAC address */
      luid_get(hwaddr, ETHERNET_ADDR_LEN);
      hwaddr[0] &= ~0x03;         /* no group address and not globally unique */
      wchunk(dev, REG_SHAR0, hwaddr, ETHERNET_ADDR_LEN);
  
      /* configure all memory to be used by socket 0 */
      wreg(dev, REG_RMSR, RMSR_8KB_TO_S0);
      wreg(dev, REG_TMSR, TMSR_8KB_TO_S0);
  
      /* configure interrupt pin to trigger on socket 0 events */
      wreg(dev, REG_IMR, IMR_S0_INT);
  
      /* next we configure socket 0 to work in MACRAW mode */
      wreg(dev, S0_MR, MR_MACRAW);
      wreg(dev, S0_CR, CR_OPEN);
  
      /* set the source IP address to something random to prevent the device to do
       * stupid thing (e.g. answering ICMP echo requests on its own) */
      wreg(dev, REG_SIPR0, 0x01);
      wreg(dev, REG_SIPR1, 0x01);
      wreg(dev, REG_SIPR2, 0x01);
      wreg(dev, REG_SIPR3, 0x01);
  
      /* start receiving packets */
      wreg(dev, S0_CR, CR_RECV);
  
      /* release the SPI bus again */
      spi_release(dev->p.spi);
  
      return 0;
  }
  
  static uint16_t tx_upload(w5100_t *dev, uint16_t start, void *data, size_t len)
  {
      if ((start + len) >= (S0_TX_BASE + S0_MEMSIZE)) {
          size_t limit = ((S0_TX_BASE + S0_MEMSIZE) - start);
          wchunk(dev, start, data, limit);
          wchunk(dev, S0_TX_BASE, &((uint8_t *)data)[limit], len - limit);
          return (S0_TX_BASE + limit);
      }
      else {
          wchunk(dev, start, data, len);
          waddr(dev, S0_TX_WR0, S0_TX_WR1, start + len);
          return (start + len);
      }
  }
  
  static int send(netdev_t *netdev, const struct iovec *vector, unsigned count)
  {
      w5100_t *dev = (w5100_t *)netdev;
      int sum = 0;
  
      /* get access to the SPI bus for the duration of this function */
      spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk);
  
      uint16_t pos = raddr(dev, S0_TX_WR0, S0_TX_WR1);
  
      /* the register is only set correctly after the first send pkt, so we need
       * this fix here */
      if (pos == 0) {
          pos = S0_TX_BASE;
      }
  
      for (unsigned i = 0; i < count; i++) {
          pos = tx_upload(dev, pos, vector[i].iov_base, vector[i].iov_len);
          sum += vector[i].iov_len;
      }
  
      waddr(dev, S0_TX_WR0, S0_TX_WR1, pos);
  
      /* trigger the sending process */
      wreg(dev, S0_CR, CR_SEND_MAC);
      while (!(rreg(dev, S0_IR) & IR_SEND_OK)) {};
      wreg(dev, S0_IR, IR_SEND_OK);
  
      DEBUG("[w5100] send: transferred %i byte (at 0x%04x)\n", sum, (int)pos);
  
      /* release the SPI bus again */
      spi_release(dev->p.spi);
  
      return sum;
  }
  
  static int recv(netdev_t *netdev, void *buf, size_t len, void *info)
  {
      (void)info;
      w5100_t *dev = (w5100_t *)netdev;
      uint8_t *in_buf = (uint8_t *)buf;
      int n = 0;
  
      /* get access to the SPI bus for the duration of this function */
      spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk);
  
      uint16_t num = raddr(dev, S0_RX_RSR0, S0_RX_RSR1);
  
      if (num > 0) {
          /* find the size of the next packet in the RX buffer */
          uint16_t rp = raddr(dev, S0_RX_RD0, S0_RX_RD1);
          uint16_t psize = raddr(dev, (S0_RX_BASE + (rp & S0_MASK)),
                                    (S0_RX_BASE + ((rp + 1) & S0_MASK)));
          n = psize - 2;
  
          DEBUG("[w5100] recv: got packet of %i byte (at 0x%04x)\n", n, (int)rp);
  
          /* read the actual data into the given buffer if wanted */
          if (in_buf != NULL) {
              uint16_t pos = rp + 2;
              len = (n <= len) ? n : len;
              for (int i = 0; i < (int)len; i++) {
                  in_buf[i] = rreg(dev, (S0_RX_BASE + ((pos++) & S0_MASK)));
              }
  
              DEBUG("[w5100] recv: read %i byte from device (at 0x%04x)\n",
                    n, (int)rp);
  
              /* set the new read pointer address */
              waddr(dev, S0_RX_RD0, S0_RX_RD1, rp += psize);
              wreg(dev, S0_CR, CR_RECV);
  
              /* if RX buffer now empty, clear RECV interrupt flag */
              if ((num - psize) == 0) {
                  wreg(dev, S0_IR, IR_RECV);
              }
          }
      }
  
      /* release the SPI bus again */
      spi_release(dev->p.spi);
  
      return n;
  }
  
  static void isr(netdev_t *netdev)
  {
      uint8_t ir;
      w5100_t *dev = (w5100_t *)netdev;
  
      /* we only react on RX events, and if we see one, we read from the RX buffer
       * until it is empty */
      spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk);
      ir = rreg(dev, S0_IR);
      spi_release(dev->p.spi);
      while (ir & IR_RECV) {
          DEBUG("[w5100] netdev RX complete\n");
          netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
      }
  }
  
  static int get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len)
  {
      w5100_t *dev = (w5100_t *)netdev;
      int res = 0;
  
      switch (opt) {
          case NETOPT_ADDRESS:
              assert(max_len >= ETHERNET_ADDR_LEN);
              spi_acquire(dev->p.spi, dev->p.cs, SPI_CONF, dev->p.clk);
              rchunk(dev, REG_SHAR0, value, ETHERNET_ADDR_LEN);
              spi_release(dev->p.spi);
              res = ETHERNET_ADDR_LEN;
              break;
          default:
              res = netdev_eth_get(netdev, opt, value, max_len);
              break;
      }
  
      return res;
  }
  
  static const netdev_driver_t netdev_driver_w5100 = {
      .send = send,
      .recv = recv,
      .init = init,
      .isr = isr,
      .get = get,
      .set = netdev_eth_set,
  };