Files
rtems/bsps/arm/beagle/qep/qep.c

446 lines
13 KiB
C

/**
* @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;
}