Blame view

RIOT/cpu/kinetis_common/periph/gpio.c 7.83 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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
  /*
   * Copyright (C) 2014 Freie Universität Berlin
   * Copyright (C) 2014 PHYTEC Messtechnik GmbH
   * Copyright (C) 2014 Eistec AB
   *
   * 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     cpu_kinetis_common
   * @ingroup     drivers_periph_gpio
   *
   * @{
   *
   * @file
   * @brief       Low-level GPIO driver implementation
   *
   * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
   * @author      Johann Fischer <j.fischer@phytec.de>
   * @author      Jonas Remmert <j.remmert@phytec.de>
   * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
   *
   * @}
   */
  
  #include <stddef.h>
  #include <stdint.h>
  #include "cpu.h"
  #include "bit.h"
  #include "periph/gpio.h"
  
  /**
   * @brief   Get the OCR reg value from the gpio_mode_t value
   */
  #define MODE_PCR_MASK       (PORT_PCR_ODE_MASK | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK)
  
  /**
   * @brief   This bit in the mode is set to 1 for output configuration
   */
  #define MODE_OUT            (0x80)
  
  /**
   * @brief   Shifting a gpio_t value by this number of bit we can extract the
   *          port number from the GPIO base address
   */
  #define GPIO_SHIFT          (6)
  
  /**
   * @brief   Mask used to extract the PORT base address from the gpio_t value
   */
  #define PORT_ADDR_MASK      (0x00007000)
  
  /**
   * @brief   Mask used to extract the GPIO base address from the gpio_t value
   */
  #define GPIO_ADDR_MASK      (0x000001c0)
  
  /**
   * @brief   Cleaned up PORT base address
   */
  #define PORT_ADDR_BASE      (PORTA_BASE & ~(PORT_ADDR_MASK))
  
  /**
   * @brief   Cleaned up GPIO base address
   */
  #define GPIO_ADDR_BASE      (GPIOA_BASE & ~(GPIO_ADDR_MASK))
  
  /**
   * @brief   Kinetis CPUs have 32 pins per port
   */
  #define PINS_PER_PORT       (32)
  
  /**
   * @brief   Calculate the needed memory (in byte) needed to save 4 bits per MCU
   *          pin
   */
  #define ISR_MAP_SIZE        (GPIO_PORTS_NUMOF * PINS_PER_PORT * 4 / 8)
  
  /**
   * @brief   Define the number of simultaneously configurable interrupt channels
   *
   * We have configured 4-bits per pin, so we can go up to 16 simultaneous active
   * extern interrupt sources.
   */
  #define CTX_NUMOF           (8U)
  
  /**
   * @brief   Interrupt context data
   */
  typedef struct {
      gpio_cb_t cb;
      void *arg;
      uint32_t state;
  } isr_ctx_t;
  
  /**
   * @brief   Allocation of memory for each independent interrupt slot
   *
   * We trust the start-up code here to initialize all bytes of this array to
   * zero.
   */
  static isr_ctx_t isr_ctx[CTX_NUMOF];
  
  /**
   * @brief   Allocation of 4 bit per pin to map a pin to an interrupt context
   */
  static uint32_t isr_map[ISR_MAP_SIZE];
  
  
  static inline PORT_Type *port(gpio_t pin)
  {
      return (PORT_Type *)(PORT_ADDR_BASE | (pin & PORT_ADDR_MASK));
  }
  
  static inline GPIO_Type *gpio(gpio_t pin)
  {
      return (GPIO_Type *)(GPIO_ADDR_BASE | (pin & GPIO_ADDR_MASK));
  }
  
  static inline int port_num(gpio_t pin)
  {
      return (int)((pin >> GPIO_SHIFT) & 0x7);
  }
  
  static inline int pin_num(gpio_t pin)
  {
      return (int)(pin & 0x3f);
  }
  
  static inline void clk_en(gpio_t pin)
  {
      bit_set32(&SIM->SCGC5, SIM_SCGC5_PORTA_SHIFT + port_num(pin));
  }
  
  /**
   * @brief   Get context for a specific pin
   */
  static inline int get_ctx(int port, int pin)
  {
      return (isr_map[(port * 4) + (pin >> 3)] >> ((pin & 0x7) * 4)) & 0xf;
  }
  
  /**
   * @brief   Find a free spot in the array containing the interrupt contexts
   */
  static int get_free_ctx(void)
  {
      for (unsigned int i = 0; i < CTX_NUMOF; i++) {
          if (isr_ctx[i].cb == NULL) {
              return i;
          }
      }
      return -1;
  }
  
  /**
   * @brief   Write an entry to the context map array
   */
  static void write_map(int port, int pin, int ctx)
  {
      isr_map[(port * 4) + (pin >> 3)] &= ~(0xf << ((pin & 0x7) * 4));
      isr_map[(port * 4) + (pin >> 3)] |=  (ctx << ((pin & 0x7) * 4));
  }
  
  /**
   * @brief   Clear the context for the given pin
   */
  static void ctx_clear(int port, int pin)
  {
      int ctx = get_ctx(port, pin);
      write_map(port, pin, ctx);
  }
  
  int gpio_init(gpio_t pin, gpio_mode_t mode)
  {
      /* set pin to analog mode while configuring it */
      gpio_init_port(pin, GPIO_AF_ANALOG);
  
      /* set pin direction */
      if (mode & MODE_OUT) {
          gpio(pin)->PDDR |=  (1 << pin_num(pin));
      }
      else {
          gpio(pin)->PDDR &= ~(1 << pin_num(pin));
      }
  
      /* enable GPIO function */
      port(pin)->PCR[pin_num(pin)] = (GPIO_AF_GPIO | (mode & MODE_PCR_MASK));
      return 0;
  }
  
  int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
                    gpio_cb_t cb, void *arg)
  {
      if (gpio_init(pin, mode) < 0) {
          return -1;
      }
  
      /* try go grab a free spot in the context array */
      int ctx_num = get_free_ctx();
      if (ctx_num < 0) {
          return -1;
      }
  
      /* save interrupt context */
      isr_ctx[ctx_num].cb = cb;
      isr_ctx[ctx_num].arg = arg;
      isr_ctx[ctx_num].state = flank;
      write_map(port_num(pin), pin_num(pin), ctx_num);
  
      /* clear interrupt flags */
      port(pin)->ISFR &= ~(1 << pin_num(pin));
      /* enable global port interrupts in the NVIC */
      NVIC_EnableIRQ(PORTA_IRQn + port_num(pin));
      /* finally, enable the interrupt for the select pin */
      port(pin)->PCR[pin_num(pin)] |= flank;
      return 0;
  }
  
  void gpio_init_port(gpio_t pin, uint32_t pcr)
  {
      /* enable PORT clock in case it was not active before */
      clk_en(pin);
  
      /* if the given interrupt was previously configured as interrupt source, we
       * need to free its interrupt context. We to this only after we
       * re-configured the pin in case an event is happening just in between... */
      uint32_t isr_state = port(pin)->PCR[pin_num(pin)];
      /* set new PCR value */
      port(pin)->PCR[pin_num(pin)] = pcr;
      /* and clear the interrupt context if needed */
      if (isr_state & PORT_PCR_IRQC_MASK) {
          ctx_clear(port_num(pin), pin_num(pin));
      }
  }
  
  void gpio_irq_enable(gpio_t pin)
  {
      int ctx = get_ctx(port_num(pin), pin_num(pin));
      port(pin)->PCR[pin_num(pin)] |= isr_ctx[ctx].state;
  }
  
  void gpio_irq_disable(gpio_t pin)
  {
      int ctx = get_ctx(port_num(pin), pin_num(pin));
      isr_ctx[ctx].state = port(pin)->PCR[pin_num(pin)] & PORT_PCR_IRQC_MASK;
      port(pin)->PCR[pin_num(pin)] &= ~(PORT_PCR_IRQC_MASK);
  }
  
  int gpio_read(gpio_t pin)
  {
      if (gpio(pin)->PDDR & (1 << pin_num(pin))) {
          return (gpio(pin)->PDOR & (1 << pin_num(pin))) ? 1 : 0;
      }
      else {
          return (gpio(pin)->PDIR & (1 << pin_num(pin))) ? 1 : 0;
      }
  }
  
  void gpio_set(gpio_t pin)
  {
      gpio(pin)->PSOR = (1 << pin_num(pin));
  }
  
  void gpio_clear(gpio_t pin)
  {
      gpio(pin)->PCOR = (1 << pin_num(pin));
  }
  
  void gpio_toggle(gpio_t pin)
  {
      gpio(pin)->PTOR = (1 << pin_num(pin));
  }
  
  void gpio_write(gpio_t pin, int value)
  {
      if (value) {
          gpio(pin)->PSOR = (1 << pin_num(pin));
      }
      else {
          gpio(pin)->PCOR = (1 << pin_num(pin));
      }
  }
  
  static inline void irq_handler(PORT_Type *port, int port_num)
  {
      /* take interrupt flags only from pins which interrupt is enabled */
      uint32_t status = port->ISFR;
  
      for (int i = 0; i < 32; i++) {
          if ((status & (1 << i)) && (port->PCR[i] & PORT_PCR_IRQC_MASK)) {
              port->ISFR = (1 << i);
              int ctx = get_ctx(port_num, i);
              isr_ctx[ctx].cb(isr_ctx[ctx].arg);
          }
      }
      cortexm_isr_end();
  }
  
  #ifdef PORTA_BASE
  void isr_porta(void)
  {
      irq_handler(PORTA, 0);
  }
  #endif /* PORTA_BASE */
  
  #ifdef PORTB_BASE
  void isr_portb(void)
  {
      irq_handler(PORTB, 1);
  }
  #endif /* ISR_PORT_B */
  
  #ifdef PORTC_BASE
  void isr_portc(void)
  {
      irq_handler(PORTC, 2);
  }
  #endif /* ISR_PORT_C */
  
  #ifdef PORTD_BASE
  void isr_portd(void)
  {
      irq_handler(PORTD, 3);
  }
  #endif /* ISR_PORT_D */
  
  #ifdef PORTE_BASE
  void isr_porte(void)
  {
      irq_handler(PORTE, 4);
  }
  #endif /* ISR_PORT_E */
  
  #ifdef PORTF_BASE
  void isr_portf(void)
  {
      irq_handler(PORTF, 5);
  }
  #endif /* ISR_PORT_F */
  
  #ifdef PORTG_BASE
  void isr_portg(void)
  {
      irq_handler(PORTG, 6);
  }
  #endif /* ISR_PORT_G */