diff options
Diffstat (limited to 'bsps/arm/beagle/qep/qep.c')
-rw-r--r-- | bsps/arm/beagle/qep/qep.c | 445 |
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; +} |