This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Accessing AM335x Registers

Other Parts Discussed in Thread: AM3358

Hello!

Currently it seems to be the tradition (at least in the TI example code that I have seen thus far) to access registers like this:

HW_WR_REG32(domainBaseAddr + clkRegOffset, PRCM_MODULEMODE_ENABLE);

Or perhaps utilize a driver function like this:

PRCMModuleEnable(CHIPDB_MOD_ID_DMTIMER, pDmtimerObj->instNum, FALSE);

that makes the assignment for me.

Has it ever been considered (as a design choice and a programmer interface choice) to establish these register addresses (with unique names for each register) in a supplemental linker script (one for each microcontroller and SoC) to established named register addresses, so instead of the above, I could say this:

DMT3CLKMUX = 2;
DMT3CLKCTRL = 2;

while ((DMT3CLKCTRL & DMT3CLKCTRL_IDLEST_MASK) == DMT3CLKCTRL_IDLEST_DISABLE) {
    continue;
}

// ...and register "struct" typedefs for SoC-specific include files to enable saying things like this:

DMT3OCPCFG.SOFTRESET = 1;

// Wait for reset to complete:

while (DMT3OCPCFG.SOFTRESET) {
    continue;
}

I'm fairly certain that the GNU compiler (at least) supports it -- a two-pronged approach including register address definitions in the linker script, and struct typedefs in a .H file.

Has this ever been considered?  If it was and was declined as a design choice for programmer interface, does anyone know the rationale behind it?

Kind regards,
Vic

  • Hi,

    What software are you talking about?
  • The example software that I have seen that has the (base_addr + offset) cast into a pointer, and then assigned to, I have seen in StarterWare, and in many places in the PDK 1.0.1 recently released (January 2016).

    Is that the software you were referring to?

    Kind regards,
    Vic

  • Hi, Biser!

    Note that this is a PROPOSAL, not a problem.

    Here is a working example that does this "register naming" with only one register:  CM_PER_TIMER3_CLKCTRL.

    This register is in the clock module, peripheral clock domain, and serves to set the mode (namely ENABLED or DISABLED) for the DMTimer3 clock.

    We start with a file called (say) soc_am3358_register_addresses.S:

    DMT3CLKCTRL = 0x44E00084
      .global DMT3CLKCTRL
    

    Next we add a supplemental linker script (or indeed, it could be included in the generated linker script  linker.cmd):

    INPUT("soc_am3358_register_addresses.o")
    

    Then every .C file that needs to use registers does this:

    #include <hw/soc_am3358.h>
    

    The file soc_am3358.h contains something like this for each register:

    typedef union {
      struct {
        unsigned MODULEMODE:2;
        unsigned :14;
        unsigned IDLEST:2;
        unsigned :14;
      };
      struct {
        unsigned w:32;
      };
    } __DMT3CLKCTRLbits_t;
    
    extern volatile __DMT3CLKCTRLbits_t DMT3CLKCTRLbits __asm__ ("DMT3CLKCTRL");

    #define DMT3CLKCTRL_MODULEMODE_SHIFT (0U) #define DMT3CLKCTRL_MODULEMODE_MASK (0x00000003U) #define DMT3CLKCTRL_MODULEMODE_ENABLE (2U) #define DMT3CLKCTRL_MODULEMODE_DISABLE (0U)
    #define DMT3CLKCTRL_IDLEST_SHIFT (16U) #define DMT3CLKCTRL_IDLEST_MASK (0x00030000U) #define DMT3CLKCTRL_IDLEST_FUNC (0U) #define DMT3CLKCTRL_IDLEST_TRANS (1U) #define DMT3CLKCTRL_IDLEST_IDLE (2U) #define DMT3CLKCTRL_IDLEST_DISABLED (3U)

    And then finally, in user code (as well as drivers, utilities, etc.), you can now do this:

    void           dmTimerClkInitialization(void) {
    	/* Set DMTimer 3 clock selection = 32 KHz */
    	*((volatile uint32_t *) 0x44E0050C) = (uint32_t) 2;
    
    	/* Enable DMTimer 3 clock. */
    	DMT3CLKCTRLbits.MODULEMODE = DMT3CLKCTRL_MODULEMODE_ENABLE;
    	// replaces:  *((volatile uint32_t *) 0x44E00084) = (uint32_t) 2;
    	// or      :  HW_WR_REG32(SOC_CM_PER_REGS + CM_PER_TIMER3_CLKCTRL, PRCM_MODULEMODE_ENABLE);
    
    	/* Wait until timer is no longer disabled. */
    	while (DMT3CLKCTRLbits.IDLEST == DMT3CLKCTRL_IDLEST_DISABLED) {
    		continue;
    	}
    	// replaces:
    	// while ((*((volatile uint32_t *) 0x44E00084) & 0x30000) == 0x30000) {
    	//	continue;
    	// }
    	// etc.
    }
    

    Advantages:

    1.  5X easier to read, understand, and therefore, 5X less bug prone.  (The magnitude of 5X is my opinion.)

    2.  Permits compiler to take advantage of processor's native bit and bit-field operations (if this is not already being done).

    3.  Continues to internally use the safety of load/store operations for registers.

    4.  Is more efficient at run time (than adding a base + offset address every time you need to read from or write to a register).  I am aware that

    HW_WR_REG32(SOC_CM_PER_REGS + CM_PER_TIMER3_CLKCTRL, PRCM_MODULEMODE_ENABLE);

    if all the constants are available in the same statement like that, that the compiler computes the address at compile time, and not run time.  However, a lot of the peripheral driver source code I have seen passes at least one of the base or offset or both as arguments, in which case the addition has to be done at run time.

    Note carefully:  The above code is now compiled and running in one of my applications -- I set it up to prove it could be done.  It works.

    I'm just wondering if this approach to accessing registers has been considered by the powers that be, and if so, and if it was declined, I'm sure it was for good reason, but I would like to be enlightened if I may as to the rationale, so I can tell my boss (and other programmers I apprentice):  "This is why the TI examples and drivers do it the way they do it.  ..."  and thereby fill in the gap of understanding in people around me (including myself) who are wondering the same question.

    Kind regards,
    Vic

  • I have brought this to the attention of the software team.
  • Thank you, Biser!  :-)

    Kind regards,
    Vic