imx rt 1024 read and write mac address

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

imx rt 1024 read and write mac address

Jump to solution
2,162 Views
bp1979
Senior Contributor I

I am trying to read and write the mac address on a imx rt 1024. I thought this would be simply reading and writing some registers, but maybe I think I need to do more for fuses.

This writes the mac address, but after a reset it's reset again

    // unlock mac address
    volatile auto lock = (uint8_t*)0x400; // address for mac address lock
    lock[0] &= ~((1 << 0) | (1<<1));

    // set mac address
    volatile auto addr = (uint8_t*)0x620; // address for mac address
    addr[0] = 0xaa;
    addr[1] = 0xaa;
    addr[2] = 0xaa;
    addr[3] = 0xbb;
    addr[4] = 0xbb;
    addr[5] = 0xcc;

    // lock mac address
    lock[0] |= ((1<<0) | (1<<1));

 

Who can help me out?

0 Kudos
1 Solution
2,135 Views
mjbcswitzerland
Specialist V

Hi

The shadow register indexes can be mapped to eFUSE addresses

Eg.
#define SHADOW_REG_INDEX_MAC_0 0x22 // 0x620
#define SHADOW_REG_INDEX_MAC_1 0x23 // 0x630

but these are not usually needed by application code since eFUSEs can be read directly from the OCOTP.

A shadow register update is only really needed after changing a setting so that it its read value matches the new one (which the modifying code will know anyway).

 

I expect the SDK routines are limited in some circumstances but you can simply remove this limitation by making a cover routine that does something like

MyReadFunction(unsigned long ulStartAddress, unsigned long *ptr_ulBuffer, int iNumber)
{
    while (iNumber-- != 0) {
        OCOTP_ReadFuseShadowRegisterExt(, , , , ptr_ulBuffer++, ulStartAddress++, , , );
    }

}
(whereby I haven't tried to match the passed parameters exactly)

As reference, the uTasker project includes a method that does allow reading multiple values as follows:

extern void fnReadFuseShadowRegister(unsigned long ulAddress, unsigned long *ptr_ulData, int iNumber)
{
    do {
        POWER_UP_ATOMIC(2, OCOTP_CTRL_CLOCK);                            // ensure that the module is clocked
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);          // wait until previous operations have completed
        _CLEAR_REGISTER(HW_OCOTP_CTRL, HW_OCOTP_CTRL_ERROR);             // ensure any error condition flag is cleared
        HW_OCOTP_TIMING = (OCOTP_WAIT_TIMING | OCOTP_RELAX_TIMING | OCOTP_STROBE_PROGRAM_TIMING | OCOTP_STROBE_READ_TIMING); // set appropriate STROBE_READ and RELAX timings
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);              // wait until operation has completed
        _CLEAR_REGISTER(HW_OCOTP_CTRL, HW_OCOTP_CTRL_ADDR_MASK);         // ensure address field is empty
        _SET_REGISTER(HW_OCOTP_CTRL, (ulAddress++ & HW_OCOTP_CTRL_ADDR_MASK)); // set register address
        HW_OCOTP_READ_CTRL = HW_OCOTP_READ_CTRL_READ_FUSE;               // initiate OTP read
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);          // wait until operation has completed
        *ptr_ulData++ = HW_OCOTP_READ_FUSE_DATA;                         // read the value
    } while (iNumber-- > 1);                                             // allow reading multiple contiguous registers
    POWER_DOWN_ATOMIC(2, OCOTP_CTRL_CLOCK);
}



Regards

Mark


View solution in original post

8 Replies
2,144 Views
bp1979
Senior Contributor I

Hi @jingpan @mjbcswitzerland 

Thanks for both replies! 

I found the correct keywords to find and open the SDK example for the 1024. Together with reference manual and both your answers I am beginning to understand how this works.

Few dumb questions maybe, but some guidance would be greatly appreciated.

A MAC address is 6 bytes. The MAC1 seems the only "non reserved" fuse which lives on address offset 0x620. 

bp1979_0-1646254858461.png

Question 1: What does the 15:0 refer to? That would suggest 2 bytes which doesn't make sense. So what does it mean?

Question 2: Does the 48 fusebits imply that I can store 6 bytes in a row starting from that address?

When I look at the memory map, I get a bit lost. It states that 0x620 is described in 23.6.1.21, but there it's referred to as MAC0.

Question 3: Shouldn't that be MAC1 as suggested in the fusemap overview?

In 23.6.1.21 the shadow address is given: 0x22. 

Question 4: Does that mean that I can use 0x22 with the OCOTP read shadow register function as shown in the EVK example?

 

    uint8_t mac[6] ={};
    status_t status = OCOTP_ReadFuseShadowRegisterExt(OCOTP, EXAMPLE_OCOTP_FUSE_MAP_ADDRESS, mac, 1);

 

Then my final question, when I call above function, the last argument specified the number of fuse words I want to read. In my case, I think I want to specify 2. But, the fsl library asserts on the argument being between larger than 0 and smaller or equal than 1. 

 

#ifndef OCOTP_READ_FUSE_DATA_COUNT
#define OCOTP_READ_FUSE_DATA_COUNT (1U)
#endif

status_t OCOTP_ReadFuseShadowRegisterExt(OCOTP_Type *base, uint32_t address, uint32_t *data, uint8_t fuseWords)
{
    assert((fuseWords > 0U) && (fuseWords <= OCOTP_READ_FUSE_DATA_COUNT));
    assert(NULL != data);

    status_t status = kStatus_Success;
...
...
}

 

 

Question 5: what is the reason for this limitation? Can I simply define OCOTP_READ_FUSE_DATA_COUNT myself as '2' ? Or do I need to change more? 

UPDATE: Changing that define in fsl_ocotp.h from 1 to 2 does not compile. The code in the c file is wrong in the SDK example.

for (i = 0; i < fuseWords; i++)
{
data[i] = base->READ_FUSE_DATAS[i].READ_FUSE_DATA;
}

There is no field in the struct with a name READ_FUSE_DATAS. The field READ_FUSE_DATA does exist, but is declared as a uint32_t thus can't be indexed. 

 

I wouldn't mind trying out stuff to learn, but since the fuses are a one time thing to write I am a bit worried I am going to regret things when I don't know what I am doing.

Thanks a million for helping out in advance!

best regards, bas

 

0 Kudos
2,136 Views
mjbcswitzerland
Specialist V

Hi

The shadow register indexes can be mapped to eFUSE addresses

Eg.
#define SHADOW_REG_INDEX_MAC_0 0x22 // 0x620
#define SHADOW_REG_INDEX_MAC_1 0x23 // 0x630

but these are not usually needed by application code since eFUSEs can be read directly from the OCOTP.

A shadow register update is only really needed after changing a setting so that it its read value matches the new one (which the modifying code will know anyway).

 

I expect the SDK routines are limited in some circumstances but you can simply remove this limitation by making a cover routine that does something like

MyReadFunction(unsigned long ulStartAddress, unsigned long *ptr_ulBuffer, int iNumber)
{
    while (iNumber-- != 0) {
        OCOTP_ReadFuseShadowRegisterExt(, , , , ptr_ulBuffer++, ulStartAddress++, , , );
    }

}
(whereby I haven't tried to match the passed parameters exactly)

As reference, the uTasker project includes a method that does allow reading multiple values as follows:

extern void fnReadFuseShadowRegister(unsigned long ulAddress, unsigned long *ptr_ulData, int iNumber)
{
    do {
        POWER_UP_ATOMIC(2, OCOTP_CTRL_CLOCK);                            // ensure that the module is clocked
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);          // wait until previous operations have completed
        _CLEAR_REGISTER(HW_OCOTP_CTRL, HW_OCOTP_CTRL_ERROR);             // ensure any error condition flag is cleared
        HW_OCOTP_TIMING = (OCOTP_WAIT_TIMING | OCOTP_RELAX_TIMING | OCOTP_STROBE_PROGRAM_TIMING | OCOTP_STROBE_READ_TIMING); // set appropriate STROBE_READ and RELAX timings
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);              // wait until operation has completed
        _CLEAR_REGISTER(HW_OCOTP_CTRL, HW_OCOTP_CTRL_ADDR_MASK);         // ensure address field is empty
        _SET_REGISTER(HW_OCOTP_CTRL, (ulAddress++ & HW_OCOTP_CTRL_ADDR_MASK)); // set register address
        HW_OCOTP_READ_CTRL = HW_OCOTP_READ_CTRL_READ_FUSE;               // initiate OTP read
        _WAIT_REGISTER_TRUE(HW_OCOTP_CTRL, HW_OCOTP_CTRL_BUSY);          // wait until operation has completed
        *ptr_ulData++ = HW_OCOTP_READ_FUSE_DATA;                         // read the value
    } while (iNumber-- > 1);                                             // allow reading multiple contiguous registers
    POWER_DOWN_ATOMIC(2, OCOTP_CTRL_CLOCK);
}



Regards

Mark


2,134 Views
mjbcswitzerland
Specialist V

Hi

Note also that the uTasker project includes an emulation of the i.MX RT 1024 and its OCOTP and so allows testing the eFUSE operations and all application code using them (and understanding their internal operation) without risking HW damage.

Regards

Mark

 

2,138 Views
mjbcswitzerland
Specialist V

Hi

The table in the 1024 document is not always accurate - search the document for HW_OCOTP_
MAC1) and you will see that it actually exists at 0x630.

Since the 1024 is one of the newest parts its documentation may not yet be mature so also use the 1020 manual as cross reference since they are more or less the same part with the addition of an on-board QSPI flash.

The MAC0 [0:15] means that the first 16 bits of the MAC address are stored in it and the MAC1 [0:31] holds the rest of the 48 bits.
This is however only a "convention" and you can use any bits as you want. You can also use a general purpose register to store it instead if you prefer.

Regards

Mark

 

2,105 Views
bp1979
Senior Contributor I

Hi @mjbcswitzerland 

Thanks a lot for all your time, after reading the reference manual with a lot more care and jumping from the nxp example and your explanation a couple of times I finally get it now.

One last question if I may (and maybe a really stupid question),

You mentioned that the first 16bits of the mac address are stored in 0x620 (mac0) and the remaining 48bits are stored in the 0x630 (mac1).

If for example my mac address is :AA:BB:CC:DD:EE:FF

Should i store it big or little endian then?

big:

0x620 AA BB

0x630 CC DD EE FF

or little:

0x620 FF EE

0x630 DD CC BB AA

 

0 Kudos
2,097 Views
mjbcswitzerland
Specialist V

Hi

As I noted before the document just gives a "convention" for storage.
If you write and read in the same order it can be any one you choose (little/big-endian, inverted, bit-reversed) - whatever you like.

In the read reference I showed you can see that I also convert between conventions since the Teensy 4.1 HW saves it in a different way that I do.

Regards

Mark

 

2,154 Views
jingpan
NXP TechSupport
NXP TechSupport

Hi @bp1979 ,

You know that from address 0 ~ 0x3ffff is ITCM. This is SRAM. Fuse is not in the M7 core memory space.

Fuse is on-chip OTP. Please refer to 23.4.1.2 and 23.4.1.3 in reference manual to get how to read/write fuse from register. Or, you can use NXP-MCUBootUtility to write eFuse.

https://github.com/JayHeng/NXP-MCUBootUtility

 

Regards,

Jing

2,151 Views
mjbcswitzerland
Specialist V

Hi

Below are the methods used in the uTasker project to write the MAC address to the corresponding eFuse register and to retrieve the value after each reset.

If you use an appropriate fuse write routine the same can be used on any i.MX RT 10xx part.

Alternatively the MAC address can be saved in QSPI flash (as a parameter) and retrieved from there as required. QSPI is generally more flexible since it allows multiple MAC addresses to be stored, along with other parameters. The eFUSE method is however good for one-time programming of an IEEE MAC address from a range assigned to your company - notice that the Teensy 4.1 HW is delivered with pre-programmed official MAC addresses (see retrieval code below) and that how the individual bytes are stored is user specific and not defined by the HW in any way.

Write (one-time):

    unsigned long ulBuffer[2];
    ulBuffer[1] = 0;
    uMemcpy(ulBuffer, temp_pars->temp_network[DEFAULT_NETWORK].ucOurMAC, MAC_LENGTH); // retrieve first MAC address from storage
    if (OCOTP_WriteFuseShadowRegister(eFUSE_MAC1_ADDR_0, ulBuffer, 2) != 0) { // commit the new value to OTP
        fnDebugMsg("eFUSE write failed!");
        return;
    }
    ulBuffer[0] = HW_OCOTP_LOCK;                                 // original lock bits
    ulBuffer[0] |= eFUSE_LOCK_MAC_ADDR_WRITE_LOCK;               // set OTP lock
    OCOTP_WriteFuseShadowRegister(eFUSE_LOCK, ulTestBuffer, 1);  // lock further writes to the OTP MAC registers

 
Retrieval:

    unsigned long ulBuffer[2];                                   // the MAC address is saved in two long words
    POWER_UP_ATOMIC(2, OCOTP_CTRL_CLOCK);
    ulTestBuffer[0] = HW_OCOTP_MAC0;
    ulTestBuffer[1] = HW_OCOTP_MAC1;
    #if defined TEENSY_4_1                                       // Teensy 4.1 is delivered with a programmed MAC address but it is stored in reverse order to the method that uTasker uses - we reverse the bytes here
    {
        unsigned long ulTemp = ((ulTestBuffer[1] & 0x0000ffff) | (ulTestBuffer[0] & 0xffff0000));
        ulTestBuffer[1] = ((ulTestBuffer[0] <<  & 0x0000ff00);
        ulTestBuffer[1] |= ((ulTestBuffer[0] >>  & 0x000000ff);
        ulTestBuffer[0] = ((ulTemp >>  & 0x000000ff);
        ulTestBuffer[0] |= ((ulTemp <<  & 0x0000ff00);
        ulTestBuffer[0] |= ((ulTemp <<  & 0xff000000);
        ulTestBuffer[0] |= ((ulTemp >>  & 0x00ff0000);
    }
    #endif
    uMemcpy(network[DEFAULT_NETWORK].ucOurMAC, ulTestBuffer, MAC_LENGTH); // use the stored value as MAC address
    POWER_DOWN_ATOMIC(2, OCOTP_CTRL_CLOCK);

 

Regards

Mark