summaryrefslogtreecommitdiffstats
path: root/bsps/arm/beagle/qep/qep.c
blob: fbbf3ec4d161eff9675da7346d3ad0fa33a5f1ee (plain) (blame)
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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/**
 * @file
 *
 * @ingroup arm_beagle
 *
 * @brief Support for eQEP for the BeagleBone Black.
 */

/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2020, 2021 James Fitzsimons <james.fitzsimons@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <libcpu/am335x.h>
#include <stdio.h>
#include <stdlib.h>
#include <bsp/gpio.h>
#include <bsp/bbb-gpio.h>
#include <bsp.h>
#include <bsp/pwmss.h>
#include <bsp/qep.h>
#include <bsp/beagleboneblack.h>


/**
 * @brief Represents all the PWMSS QEP modules and their default values.
 */
static bbb_eqep bbb_eqep_table[ BBB_PWMSS_COUNT ] =
{
 {
  .pwmss_id = BBB_PWMSS0,
  .mmio_base = AM335X_EQEP_0_REGS,
  .irq = AM335X_INT_eQEP0INT,
  .timer_callback = NULL,
  .user = NULL,
  .count_mode = QUADRATURE_COUNT,
  .quadrature_mode = ABSOLUTE,
  .invert_qa = 0,
  .invert_qb = 0,
  .invert_qi = 0,
  .invert_qs = 0,
  .swap_inputs = 0
 },
 {
  .pwmss_id = BBB_PWMSS1,
  .mmio_base = AM335X_EQEP_1_REGS,
  .irq = AM335X_INT_eQEP1INT,
  .timer_callback = NULL,
  .user = NULL,
  .count_mode = QUADRATURE_COUNT,
  .quadrature_mode = ABSOLUTE,
  .invert_qa = 0,
  .invert_qb = 0,
  .invert_qi = 0,
  .invert_qs = 0,
  .swap_inputs = 0
 },
 {
  .pwmss_id = BBB_PWMSS2,
  .mmio_base = AM335X_EQEP_2_REGS,
  .irq = AM335X_INT_eQEP2INT,
  .timer_callback = NULL,
  .user = NULL,
  .count_mode = QUADRATURE_COUNT,
  .quadrature_mode = ABSOLUTE,
  .invert_qa = 0,
  .invert_qb = 0,
  .invert_qi = 0,
  .invert_qs = 0,
  .swap_inputs = 0
 }
};

/* eQEP Interrupt handler */
static void beagle_eqep_irq_handler(void *arg)
{
  uint16_t flags;
  int32_t position = 0;
  bbb_eqep* eqep = arg;

  /* Use the interrupt register (QFLG) mask to determine what caused the
   * interrupt. */
  flags = REG16(eqep->mmio_base + AM335x_EQEP_QFLG) & AM335x_EQEP_QFLG_MASK;
  /* Check the interrupt source to see if it was a unit timer overflow  */
  if (flags & AM335x_EQEP_QFLG_UTO && eqep->timer_callback != NULL) {
    /* Handle the unit timer overflow interrupt */
    position = beagle_qep_get_position(eqep->pwmss_id);
    eqep->timer_callback(eqep->pwmss_id, position, eqep->user);
  }

  /* Clear interrupt flags (write back triggered flags to the clear register) */
  REG16(eqep->mmio_base + AM335x_EQEP_QCLR) = flags;
}

rtems_status_code beagle_qep_init(BBB_PWMSS pwmss_id)
{
  rtems_status_code sc;
  uint16_t qdecctl;

  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  sc = pwmss_module_clk_config(eqep->pwmss_id);
  if (sc != RTEMS_SUCCESSFUL) {
    /* failed to successfully configure the PWMSS module clocks */
    return sc;
  }

  /* This enables clock for EQEP module in PWMSS subsystem. */
  REG(eqep->mmio_base + AM335X_PWMSS_CLKCONFIG) |= AM335x_EQEP_CLK_EN;

  /* Setup interrupt handler */
  sc = rtems_interrupt_handler_install(
      eqep->irq,
      NULL,
      RTEMS_INTERRUPT_UNIQUE,
      (rtems_interrupt_handler)beagle_eqep_irq_handler,
      (void*)eqep
  );

  /* The QDECCTL register configures the QEP Decoder module. We use it to set */
  /* the count mode, input inversion, channel swaps, unit timer interrupt etc. */
  qdecctl = 0;
  if (eqep->count_mode <= 3) {
    qdecctl |= eqep->count_mode << 14;

    /* If the count mode is UP_COUNT or DOWN_COUNT then only count on
     * the rising edge. QUADRATURE_COUNT and DIRECTION_COUNT count on
     * both edges.  */
    if (eqep->count_mode >= 2) {
      qdecctl |= AM335x_EQEP_QDECCTL_XCR;
    }
  }

  /* Should we swap the cha and chb inputs */
  if (eqep->swap_inputs == 1) {
    qdecctl |= AM335x_EQEP_QDECCTL_SWAP;
  }
  /* Should we invert the qa input */
  if (eqep->invert_qa == 1) {
    qdecctl |= AM335x_EQEP_QDECCTL_QAP;
  }
  /* Should we invert the qb input */
  if (eqep->invert_qb == 1) {
    qdecctl |= AM335x_EQEP_QDECCTL_QBP;
  }
  /* Should we invert the index input */
  if (eqep->invert_qi == 1) {
    qdecctl |= AM335x_EQEP_QDECCTL_QIP;

  }
  /* Should we invert the strobe input */
  if (eqep->invert_qs == 1) {
    qdecctl |= AM335x_EQEP_QDECCTL_QSP;
  }

  /* Write the configured decoder control settings to the QDECCTL register */
  REG16(eqep->mmio_base + AM335x_EQEP_QDECCTL) = qdecctl;
  /* Set the position counter initialisation register */
  REG(eqep->mmio_base + AM335x_EQEP_QPOSINIT) = 0;
  /* initialise the maximum position counter value */
  REG(eqep->mmio_base + AM335x_EQEP_QPOSMAX) = ~0;
  /* initialise the position counter register */
  REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT) = 0;
  /* Enable Unit Time Period interrupt. */
  REG16(eqep->mmio_base + AM335x_EQEP_QEINT) |= AM335x_EQEP_QEINT_UTO;

  /* The following bitmasks enable the eQEP module with:
   * - the unit timer disabled
   * - will latch the value in QPOSLAT to QPOSCNT upon unit timer overflow
   * - will latch QPOSILAT on index signal.
   * - Software initialisation of position counter (will be set to 0 because
   *   QPOSINIT = 0).
   */
  uint32_t value = AM335x_EQEP_QEPCTL_QCLM | AM335x_EQEP_QEPCTL_IEL |
      AM335x_EQEP_QEPCTL_PHEN | AM335x_EQEP_QEPCTL_SWI;

  /* set the enable bit of the control register */
  REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = value;

  return RTEMS_SUCCESSFUL;
}

rtems_status_code beagle_qep_enable(BBB_PWMSS pwmss_id)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
  /* set the enable bit of the control register */
  REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) |= AM335x_EQEP_QEPCTL_PHEN;

  return RTEMS_SUCCESSFUL;
}

rtems_status_code beagle_qep_disable(BBB_PWMSS pwmss_id)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
  /* clear the enable bit of the control register */
  REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) &= ~AM335x_EQEP_QEPCTL_PHEN;

  return RTEMS_SUCCESSFUL;
}

rtems_status_code beagle_qep_pinmux_setup(
    bbb_qep_pin pin_no,
    BBB_PWMSS pwmss_id,
    bool pullup_enable
)
{
  rtems_status_code result = RTEMS_SUCCESSFUL;
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  /* enable internal pull up / pull down resistor in pull up mode, and set the
   * pin as an input. */
  uint32_t pin_mode =  BBB_RXACTIVE;
  if ( pullup_enable ) {
    pin_mode |= BBB_PU_EN;
  }
  // The offsets from AM335X_PADCONF_BASE (44e10000) are named after the mode0 mux for that pin.
  if(pwmss_id == BBB_PWMSS0) {
    if (pin_no == BBB_P9_25_0_STROBE) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_AHCLKX) = pin_mode | BBB_MUXMODE(BBB_P9_25_MUX_QEP);
    } else if (pin_no == BBB_P9_27_0B_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_FSR) = pin_mode | BBB_MUXMODE(BBB_P9_27_MUX_QEP);
    } else if (pin_no == BBB_P9_41_0_IDX) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_AXR1) = pin_mode | BBB_MUXMODE(BBB_P9_41_MUX_QEP);
    } else if (pin_no == BBB_P9_42_0A_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_ACLKR) = pin_mode | BBB_MUXMODE(BBB_P9_42_MUX_QEP);
    } else {
      result = RTEMS_INTERNAL_ERROR;
    }
  } else if (pwmss_id == BBB_PWMSS1) {
    if (pin_no == BBB_P8_31_1_IDX) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA14) = pin_mode | BBB_MUXMODE(BBB_P8_31_MUX_QEP);
    } else if (pin_no == BBB_P8_32_1_STROBE) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA15) = pin_mode | BBB_MUXMODE(BBB_P8_32_MUX_QEP);
    } else if (pin_no == BBB_P8_33_1B_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA13) = pin_mode | BBB_MUXMODE(BBB_P8_33_MUX_QEP);
    } else if (pin_no == BBB_P8_35_1A_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA12) = pin_mode | BBB_MUXMODE(BBB_P8_35_MUX_QEP);
    } else {
      result = RTEMS_INTERNAL_ERROR;
    }
  } else if (pwmss_id == BBB_PWMSS2) {
    if (pin_no == BBB_P8_11_2B_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD13) = pin_mode | BBB_MUXMODE(BBB_P8_11_MUX_QEP);
    } else if (pin_no == BBB_P8_12_2A_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD12) = pin_mode | BBB_MUXMODE(BBB_P8_12_MUX_QEP);
    } else if (pin_no == BBB_P8_15_2_STROBE) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD15) = pin_mode | BBB_MUXMODE(BBB_P8_15_MUX_QEP);
    } else if (pin_no == BBB_P8_16_2_IDX) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD14) = pin_mode | BBB_MUXMODE(BBB_P8_16_MUX_QEP);
    } else if (pin_no == BBB_P8_39_2_IDX) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA6) = pin_mode | BBB_MUXMODE(BBB_P8_39_MUX_QEP);
    } else if (pin_no == BBB_P8_40_2_STROBE) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA7) = pin_mode | BBB_MUXMODE(BBB_P8_40_MUX_QEP);
    } else if (pin_no == BBB_P8_41_2A_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA4) = pin_mode | BBB_MUXMODE(BBB_P8_41_MUX_QEP);
    } else if (pin_no == BBB_P8_42_2B_IN) {
      REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA5) = pin_mode | BBB_MUXMODE(BBB_P8_42_MUX_QEP);
    } else {
      result = RTEMS_INTERNAL_ERROR;
    }
  } else {
    result = RTEMS_INTERNAL_ERROR;
  }
  return result;
}

int32_t beagle_qep_get_position(BBB_PWMSS pwmss_id)
{
  int32_t position = 0;
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return -1;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  if (eqep->quadrature_mode == ABSOLUTE) {
    /* return the current value of the QPOSCNT register */
    position = REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT);
  } else if (eqep->quadrature_mode == RELATIVE) {
    /* return the latched value from the last unit timer interrupt */
    position = REG(eqep->mmio_base + AM335x_EQEP_QPOSLAT);
  }

  return position;
}

rtems_status_code beagle_qep_set_position(BBB_PWMSS pwmss_id, uint32_t position)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
  /* setting the position only really makes sense in ABSOLUTE mode. */
  if (eqep->quadrature_mode == ABSOLUTE) {
    REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT) = position;
  }

  return RTEMS_SUCCESSFUL;
}

rtems_status_code beagle_qep_set_count_mode(
    BBB_PWMSS pwmss_id,
    BBB_QEP_COUNT_MODE mode
)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
  eqep->count_mode = mode;

  return RTEMS_SUCCESSFUL;
}

BBB_QEP_COUNT_MODE beagle_qep_get_count_mode(BBB_PWMSS pwmss_id)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  return eqep->count_mode;
}

rtems_status_code beagle_qep_set_quadrature_mode(
    BBB_PWMSS pwmss_id,
    BBB_QEP_QUADRATURE_MODE mode
)
{
  uint16_t qepctl;
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  qepctl = REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL);

  if (mode == ABSOLUTE) {
    /*
     * Disable the unit timer position reset
     */
    qepctl &= ~AM335x_EQEP_QEPCTL_PCRM;

    eqep->quadrature_mode = ABSOLUTE;
  } else if (mode == RELATIVE) {
    /*
     *  enable the unit timer position reset
     */
    qepctl |= AM335x_EQEP_QEPCTL_PCRM;

    eqep->quadrature_mode = RELATIVE;
  }

  REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;

  return RTEMS_SUCCESSFUL;
}

BBB_QEP_QUADRATURE_MODE beagle_qep_get_quadrature_mode(BBB_PWMSS pwmss_id)
{
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return -1;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  return eqep->quadrature_mode;
}


/* Function to read the period of the unit time event timer */
uint32_t beagle_eqep_get_timer_period(BBB_PWMSS pwmss_id)
{
  uint64_t period;
  uint32_t timer_period;
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return -1;
  }
  const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  /* Convert from counts per interrupt back into period_ns */
  period = REG(eqep->mmio_base + AM335x_EQEP_QUPRD);
  period = period * NANO_SEC_PER_SEC;
  timer_period = (uint32_t)(period / SYSCLKOUT);

  return timer_period;
}

rtems_status_code beagle_eqep_set_timer_period(
    BBB_PWMSS pwmss_id,
    uint64_t period,
    bbb_eqep_timer_callback timer_callback,
    void* user
)
{
  uint16_t qepctl;
  uint64_t tmp_period;
  uint32_t timer_period;
  if ( pwmss_id >= BBB_PWMSS_COUNT ) {
    return RTEMS_INVALID_ID;
  }
  bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];

  /* Disable the unit timer before modifying its period register */
  qepctl = readw(eqep->mmio_base + AM335x_EQEP_QEPCTL);
  qepctl &= ~(AM335x_EQEP_QEPCTL_UTE | AM335x_EQEP_QEPCTL_QCLM);
  REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;

  /* Zero the unit timer counter register */
  REG(eqep->mmio_base + AM335x_EQEP_QUTMR) = 0;

  /* If the timer is enabled (a non-zero period has been passed) */
  if (period) {
    /* update the period */
    tmp_period = period * SYSCLKOUT;
    timer_period = (uint32_t)(tmp_period / NANO_SEC_PER_SEC);
    REG(eqep->mmio_base + AM335x_EQEP_QUPRD) = timer_period;

    /* Enable unit timer, and latch QPOSLAT to QPOSCNT on timer expiration */
    qepctl |= AM335x_EQEP_QEPCTL_UTE | AM335x_EQEP_QEPCTL_QCLM;
    REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;

    /* attach the unit timer interrupt handler if one has been supplied */
    if (timer_callback != NULL) {
      eqep->timer_callback = timer_callback;
    }
    /* attach the user data if it has been provided */
    if (user != NULL) {
      eqep->user = user;
    }
  }

  return RTEMS_SUCCESSFUL;
}