Back to home page

LXR

 
 

    


File indexing completed on 2025-05-11 08:22:48

0001 /**
0002  * @file
0003  *
0004  * @ingroup arm_beagle
0005  *
0006  * @brief Support for eQEP for the BeagleBone Black.
0007  */
0008 
0009 /*
0010  * SPDX-License-Identifier: BSD-2-Clause
0011  *
0012  * Copyright (c) 2020, 2021 James Fitzsimons <james.fitzsimons@gmail.com>
0013  *
0014  * Redistribution and use in source and binary forms, with or without
0015  * modification, are permitted provided that the following conditions
0016  * are met:
0017  * 1. Redistributions of source code must retain the above copyright
0018  *    notice, this list of conditions and the following disclaimer.
0019  * 2. Redistributions in binary form must reproduce the above copyright
0020  *    notice, this list of conditions and the following disclaimer in the
0021  *    documentation and/or other materials provided with the distribution.
0022  *
0023  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0024  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0025  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0026  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0027  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0028  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0029  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0030  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0031  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0032  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0033  * POSSIBILITY OF SUCH DAMAGE.
0034  */
0035 
0036 #include <libcpu/am335x.h>
0037 #include <stdio.h>
0038 #include <stdlib.h>
0039 #include <bsp/gpio.h>
0040 #include <bsp/bbb-gpio.h>
0041 #include <bsp.h>
0042 #include <bsp/pwmss.h>
0043 #include <bsp/qep.h>
0044 #include <bsp/beagleboneblack.h>
0045 
0046 
0047 /**
0048  * @brief Represents all the PWMSS QEP modules and their default values.
0049  */
0050 static bbb_eqep bbb_eqep_table[ BBB_PWMSS_COUNT ] =
0051 {
0052  {
0053   .pwmss_id = BBB_PWMSS0,
0054   .mmio_base = AM335X_EQEP_0_REGS,
0055   .irq = AM335X_INT_eQEP0INT,
0056   .timer_callback = NULL,
0057   .user = NULL,
0058   .count_mode = QUADRATURE_COUNT,
0059   .quadrature_mode = ABSOLUTE,
0060   .invert_qa = 0,
0061   .invert_qb = 0,
0062   .invert_qi = 0,
0063   .invert_qs = 0,
0064   .swap_inputs = 0
0065  },
0066  {
0067   .pwmss_id = BBB_PWMSS1,
0068   .mmio_base = AM335X_EQEP_1_REGS,
0069   .irq = AM335X_INT_eQEP1INT,
0070   .timer_callback = NULL,
0071   .user = NULL,
0072   .count_mode = QUADRATURE_COUNT,
0073   .quadrature_mode = ABSOLUTE,
0074   .invert_qa = 0,
0075   .invert_qb = 0,
0076   .invert_qi = 0,
0077   .invert_qs = 0,
0078   .swap_inputs = 0
0079  },
0080  {
0081   .pwmss_id = BBB_PWMSS2,
0082   .mmio_base = AM335X_EQEP_2_REGS,
0083   .irq = AM335X_INT_eQEP2INT,
0084   .timer_callback = NULL,
0085   .user = NULL,
0086   .count_mode = QUADRATURE_COUNT,
0087   .quadrature_mode = ABSOLUTE,
0088   .invert_qa = 0,
0089   .invert_qb = 0,
0090   .invert_qi = 0,
0091   .invert_qs = 0,
0092   .swap_inputs = 0
0093  }
0094 };
0095 
0096 /* eQEP Interrupt handler */
0097 static void beagle_eqep_irq_handler(void *arg)
0098 {
0099   uint16_t flags;
0100   int32_t position = 0;
0101   bbb_eqep* eqep = arg;
0102 
0103   /* Use the interrupt register (QFLG) mask to determine what caused the
0104    * interrupt. */
0105   flags = REG16(eqep->mmio_base + AM335x_EQEP_QFLG) & AM335x_EQEP_QFLG_MASK;
0106   /* Check the interrupt source to see if it was a unit timer overflow  */
0107   if (flags & AM335x_EQEP_QFLG_UTO && eqep->timer_callback != NULL) {
0108     /* Handle the unit timer overflow interrupt */
0109     position = beagle_qep_get_position(eqep->pwmss_id);
0110     eqep->timer_callback(eqep->pwmss_id, position, eqep->user);
0111   }
0112 
0113   /* Clear interrupt flags (write back triggered flags to the clear register) */
0114   REG16(eqep->mmio_base + AM335x_EQEP_QCLR) = flags;
0115 }
0116 
0117 rtems_status_code beagle_qep_init(BBB_PWMSS pwmss_id)
0118 {
0119   rtems_status_code sc;
0120   uint16_t qdecctl;
0121 
0122   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0123     return RTEMS_INVALID_ID;
0124   }
0125   bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0126 
0127   sc = pwmss_module_clk_config(eqep->pwmss_id);
0128   if (sc != RTEMS_SUCCESSFUL) {
0129     /* failed to successfully configure the PWMSS module clocks */
0130     return sc;
0131   }
0132 
0133   /* This enables clock for EQEP module in PWMSS subsystem. */
0134   REG(eqep->mmio_base + AM335X_PWMSS_CLKCONFIG) |= AM335x_EQEP_CLK_EN;
0135 
0136   /* Setup interrupt handler */
0137   sc = rtems_interrupt_handler_install(
0138       eqep->irq,
0139       NULL,
0140       RTEMS_INTERRUPT_UNIQUE,
0141       (rtems_interrupt_handler)beagle_eqep_irq_handler,
0142       (void*)eqep
0143   );
0144 
0145   /* The QDECCTL register configures the QEP Decoder module. We use it to set */
0146   /* the count mode, input inversion, channel swaps, unit timer interrupt etc. */
0147   qdecctl = 0;
0148   if (eqep->count_mode <= 3) {
0149     qdecctl |= eqep->count_mode << 14;
0150 
0151     /* If the count mode is UP_COUNT or DOWN_COUNT then only count on
0152      * the rising edge. QUADRATURE_COUNT and DIRECTION_COUNT count on
0153      * both edges.  */
0154     if (eqep->count_mode >= 2) {
0155       qdecctl |= AM335x_EQEP_QDECCTL_XCR;
0156     }
0157   }
0158 
0159   /* Should we swap the cha and chb inputs */
0160   if (eqep->swap_inputs == 1) {
0161     qdecctl |= AM335x_EQEP_QDECCTL_SWAP;
0162   }
0163   /* Should we invert the qa input */
0164   if (eqep->invert_qa == 1) {
0165     qdecctl |= AM335x_EQEP_QDECCTL_QAP;
0166   }
0167   /* Should we invert the qb input */
0168   if (eqep->invert_qb == 1) {
0169     qdecctl |= AM335x_EQEP_QDECCTL_QBP;
0170   }
0171   /* Should we invert the index input */
0172   if (eqep->invert_qi == 1) {
0173     qdecctl |= AM335x_EQEP_QDECCTL_QIP;
0174 
0175   }
0176   /* Should we invert the strobe input */
0177   if (eqep->invert_qs == 1) {
0178     qdecctl |= AM335x_EQEP_QDECCTL_QSP;
0179   }
0180 
0181   /* Write the configured decoder control settings to the QDECCTL register */
0182   REG16(eqep->mmio_base + AM335x_EQEP_QDECCTL) = qdecctl;
0183   /* Set the position counter initialisation register */
0184   REG(eqep->mmio_base + AM335x_EQEP_QPOSINIT) = 0;
0185   /* initialise the maximum position counter value */
0186   REG(eqep->mmio_base + AM335x_EQEP_QPOSMAX) = ~0;
0187   /* initialise the position counter register */
0188   REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT) = 0;
0189   /* Enable Unit Time Period interrupt. */
0190   REG16(eqep->mmio_base + AM335x_EQEP_QEINT) |= AM335x_EQEP_QEINT_UTO;
0191 
0192   /* The following bitmasks enable the eQEP module with:
0193    * - the unit timer disabled
0194    * - will latch the value in QPOSLAT to QPOSCNT upon unit timer overflow
0195    * - will latch QPOSILAT on index signal.
0196    * - Software initialisation of position counter (will be set to 0 because
0197    *   QPOSINIT = 0).
0198    */
0199   uint32_t value = AM335x_EQEP_QEPCTL_QCLM | AM335x_EQEP_QEPCTL_IEL |
0200       AM335x_EQEP_QEPCTL_PHEN | AM335x_EQEP_QEPCTL_SWI;
0201 
0202   /* set the enable bit of the control register */
0203   REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = value;
0204 
0205   return RTEMS_SUCCESSFUL;
0206 }
0207 
0208 rtems_status_code beagle_qep_enable(BBB_PWMSS pwmss_id)
0209 {
0210   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0211     return RTEMS_INVALID_ID;
0212   }
0213   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0214   /* set the enable bit of the control register */
0215   REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) |= AM335x_EQEP_QEPCTL_PHEN;
0216 
0217   return RTEMS_SUCCESSFUL;
0218 }
0219 
0220 rtems_status_code beagle_qep_disable(BBB_PWMSS pwmss_id)
0221 {
0222   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0223     return RTEMS_INVALID_ID;
0224   }
0225   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0226   /* clear the enable bit of the control register */
0227   REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) &= ~AM335x_EQEP_QEPCTL_PHEN;
0228 
0229   return RTEMS_SUCCESSFUL;
0230 }
0231 
0232 rtems_status_code beagle_qep_pinmux_setup(
0233     bbb_qep_pin pin_no,
0234     BBB_PWMSS pwmss_id,
0235     bool pullup_enable
0236 )
0237 {
0238   rtems_status_code result = RTEMS_SUCCESSFUL;
0239   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0240     return RTEMS_INVALID_ID;
0241   }
0242   /* enable internal pull up / pull down resistor in pull up mode, and set the
0243    * pin as an input. */
0244   uint32_t pin_mode =  BBB_RXACTIVE;
0245   if ( pullup_enable ) {
0246     pin_mode |= BBB_PU_EN;
0247   }
0248   // The offsets from AM335X_PADCONF_BASE (44e10000) are named after the mode0 mux for that pin.
0249   if(pwmss_id == BBB_PWMSS0) {
0250     if (pin_no == BBB_P9_25_0_STROBE) {
0251       REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_AHCLKX) = pin_mode | BBB_MUXMODE(BBB_P9_25_MUX_QEP);
0252     } else if (pin_no == BBB_P9_27_0B_IN) {
0253       REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_FSR) = pin_mode | BBB_MUXMODE(BBB_P9_27_MUX_QEP);
0254     } else if (pin_no == BBB_P9_41_0_IDX) {
0255       REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_AXR1) = pin_mode | BBB_MUXMODE(BBB_P9_41_MUX_QEP);
0256     } else if (pin_no == BBB_P9_42_0A_IN) {
0257       REG(AM335X_PADCONF_BASE + AM335X_CONF_MCASP0_ACLKR) = pin_mode | BBB_MUXMODE(BBB_P9_42_MUX_QEP);
0258     } else {
0259       result = RTEMS_INTERNAL_ERROR;
0260     }
0261   } else if (pwmss_id == BBB_PWMSS1) {
0262     if (pin_no == BBB_P8_31_1_IDX) {
0263       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA14) = pin_mode | BBB_MUXMODE(BBB_P8_31_MUX_QEP);
0264     } else if (pin_no == BBB_P8_32_1_STROBE) {
0265       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA15) = pin_mode | BBB_MUXMODE(BBB_P8_32_MUX_QEP);
0266     } else if (pin_no == BBB_P8_33_1B_IN) {
0267       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA13) = pin_mode | BBB_MUXMODE(BBB_P8_33_MUX_QEP);
0268     } else if (pin_no == BBB_P8_35_1A_IN) {
0269       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA12) = pin_mode | BBB_MUXMODE(BBB_P8_35_MUX_QEP);
0270     } else {
0271       result = RTEMS_INTERNAL_ERROR;
0272     }
0273   } else if (pwmss_id == BBB_PWMSS2) {
0274     if (pin_no == BBB_P8_11_2B_IN) {
0275       REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD13) = pin_mode | BBB_MUXMODE(BBB_P8_11_MUX_QEP);
0276     } else if (pin_no == BBB_P8_12_2A_IN) {
0277       REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD12) = pin_mode | BBB_MUXMODE(BBB_P8_12_MUX_QEP);
0278     } else if (pin_no == BBB_P8_15_2_STROBE) {
0279       REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD15) = pin_mode | BBB_MUXMODE(BBB_P8_15_MUX_QEP);
0280     } else if (pin_no == BBB_P8_16_2_IDX) {
0281       REG(AM335X_PADCONF_BASE + AM335X_CONF_GPMC_AD14) = pin_mode | BBB_MUXMODE(BBB_P8_16_MUX_QEP);
0282     } else if (pin_no == BBB_P8_39_2_IDX) {
0283       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA6) = pin_mode | BBB_MUXMODE(BBB_P8_39_MUX_QEP);
0284     } else if (pin_no == BBB_P8_40_2_STROBE) {
0285       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA7) = pin_mode | BBB_MUXMODE(BBB_P8_40_MUX_QEP);
0286     } else if (pin_no == BBB_P8_41_2A_IN) {
0287       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA4) = pin_mode | BBB_MUXMODE(BBB_P8_41_MUX_QEP);
0288     } else if (pin_no == BBB_P8_42_2B_IN) {
0289       REG(AM335X_PADCONF_BASE + AM335X_CONF_LCD_DATA5) = pin_mode | BBB_MUXMODE(BBB_P8_42_MUX_QEP);
0290     } else {
0291       result = RTEMS_INTERNAL_ERROR;
0292     }
0293   } else {
0294     result = RTEMS_INTERNAL_ERROR;
0295   }
0296   return result;
0297 }
0298 
0299 int32_t beagle_qep_get_position(BBB_PWMSS pwmss_id)
0300 {
0301   int32_t position = 0;
0302   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0303     return -1;
0304   }
0305   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0306 
0307   if (eqep->quadrature_mode == ABSOLUTE) {
0308     /* return the current value of the QPOSCNT register */
0309     position = REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT);
0310   } else if (eqep->quadrature_mode == RELATIVE) {
0311     /* return the latched value from the last unit timer interrupt */
0312     position = REG(eqep->mmio_base + AM335x_EQEP_QPOSLAT);
0313   }
0314 
0315   return position;
0316 }
0317 
0318 rtems_status_code beagle_qep_set_position(BBB_PWMSS pwmss_id, uint32_t position)
0319 {
0320   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0321     return RTEMS_INVALID_ID;
0322   }
0323   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0324   /* setting the position only really makes sense in ABSOLUTE mode. */
0325   if (eqep->quadrature_mode == ABSOLUTE) {
0326     REG(eqep->mmio_base + AM335x_EQEP_QPOSCNT) = position;
0327   }
0328 
0329   return RTEMS_SUCCESSFUL;
0330 }
0331 
0332 rtems_status_code beagle_qep_set_count_mode(
0333     BBB_PWMSS pwmss_id,
0334     BBB_QEP_COUNT_MODE mode
0335 )
0336 {
0337   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0338     return RTEMS_INVALID_ID;
0339   }
0340   bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0341   eqep->count_mode = mode;
0342 
0343   return RTEMS_SUCCESSFUL;
0344 }
0345 
0346 BBB_QEP_COUNT_MODE beagle_qep_get_count_mode(BBB_PWMSS pwmss_id)
0347 {
0348   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0349     return RTEMS_INVALID_ID;
0350   }
0351   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0352 
0353   return eqep->count_mode;
0354 }
0355 
0356 rtems_status_code beagle_qep_set_quadrature_mode(
0357     BBB_PWMSS pwmss_id,
0358     BBB_QEP_QUADRATURE_MODE mode
0359 )
0360 {
0361   uint16_t qepctl;
0362   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0363     return RTEMS_INVALID_ID;
0364   }
0365   bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0366 
0367   qepctl = REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL);
0368 
0369   if (mode == ABSOLUTE) {
0370     /*
0371      * Disable the unit timer position reset
0372      */
0373     qepctl &= ~AM335x_EQEP_QEPCTL_PCRM;
0374 
0375     eqep->quadrature_mode = ABSOLUTE;
0376   } else if (mode == RELATIVE) {
0377     /*
0378      *  enable the unit timer position reset
0379      */
0380     qepctl |= AM335x_EQEP_QEPCTL_PCRM;
0381 
0382     eqep->quadrature_mode = RELATIVE;
0383   }
0384 
0385   REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;
0386 
0387   return RTEMS_SUCCESSFUL;
0388 }
0389 
0390 BBB_QEP_QUADRATURE_MODE beagle_qep_get_quadrature_mode(BBB_PWMSS pwmss_id)
0391 {
0392   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0393     return -1;
0394   }
0395   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0396 
0397   return eqep->quadrature_mode;
0398 }
0399 
0400 
0401 /* Function to read the period of the unit time event timer */
0402 uint32_t beagle_eqep_get_timer_period(BBB_PWMSS pwmss_id)
0403 {
0404   uint64_t period;
0405   uint32_t timer_period;
0406   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0407     return -1;
0408   }
0409   const bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0410 
0411   /* Convert from counts per interrupt back into period_ns */
0412   period = REG(eqep->mmio_base + AM335x_EQEP_QUPRD);
0413   period = period * NANO_SEC_PER_SEC;
0414   timer_period = (uint32_t)(period / SYSCLKOUT);
0415 
0416   return timer_period;
0417 }
0418 
0419 rtems_status_code beagle_eqep_set_timer_period(
0420     BBB_PWMSS pwmss_id,
0421     uint64_t period,
0422     bbb_eqep_timer_callback timer_callback,
0423     void* user
0424 )
0425 {
0426   uint16_t qepctl;
0427   uint64_t tmp_period;
0428   uint32_t timer_period;
0429   if ( pwmss_id >= BBB_PWMSS_COUNT ) {
0430     return RTEMS_INVALID_ID;
0431   }
0432   bbb_eqep* eqep = &bbb_eqep_table[pwmss_id];
0433 
0434   /* Disable the unit timer before modifying its period register */
0435   qepctl = readw(eqep->mmio_base + AM335x_EQEP_QEPCTL);
0436   qepctl &= ~(AM335x_EQEP_QEPCTL_UTE | AM335x_EQEP_QEPCTL_QCLM);
0437   REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;
0438 
0439   /* Zero the unit timer counter register */
0440   REG(eqep->mmio_base + AM335x_EQEP_QUTMR) = 0;
0441 
0442   /* If the timer is enabled (a non-zero period has been passed) */
0443   if (period) {
0444     /* update the period */
0445     tmp_period = period * SYSCLKOUT;
0446     timer_period = (uint32_t)(tmp_period / NANO_SEC_PER_SEC);
0447     REG(eqep->mmio_base + AM335x_EQEP_QUPRD) = timer_period;
0448 
0449     /* Enable unit timer, and latch QPOSLAT to QPOSCNT on timer expiration */
0450     qepctl |= AM335x_EQEP_QEPCTL_UTE | AM335x_EQEP_QEPCTL_QCLM;
0451     REG16(eqep->mmio_base + AM335x_EQEP_QEPCTL) = qepctl;
0452 
0453     /* attach the unit timer interrupt handler if one has been supplied */
0454     if (timer_callback != NULL) {
0455       eqep->timer_callback = timer_callback;
0456     }
0457     /* attach the user data if it has been provided */
0458     if (user != NULL) {
0459       eqep->user = user;
0460     }
0461   }
0462 
0463   return RTEMS_SUCCESSFUL;
0464 }