summaryrefslogtreecommitdiffstats
path: root/bsps/arm/beagle/qep/qep.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/arm/beagle/qep/qep.c')
-rw-r--r--bsps/arm/beagle/qep/qep.c445
1 files changed, 445 insertions, 0 deletions
diff --git a/bsps/arm/beagle/qep/qep.c b/bsps/arm/beagle/qep/qep.c
new file mode 100644
index 0000000000..34eb453258
--- /dev/null
+++ b/bsps/arm/beagle/qep/qep.c
@@ -0,0 +1,445 @@
+/**
+ * @file
+ *
+ * @ingroup arm_beagle
+ *
+ * @brief Support for eQEP for the BeagleBone Black.
+ */
+
+/**
+ * Copyright (c) 2020 James Fitzsimons <james.fitzsimons@gmail.com>
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.org/license/LICENSE.
+ */
+
+#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;
+}