Skip to content

Fuse Configuration for extended OTP on TI AM62L — in U-Boot#

Irreversible Operation

Writing to the OTP fuse registers is irreversible and can brick your device. Please always double-check and verify what you are doing.

Fuse Register Documentation

See this table for the fuse locations reserved by KED:

Please also consult the AM62L "Technical Reference Manual" for further information on the interpretation of the register values.

Extended OTP Memory#

The extended OTP memory of the AM62L holds 1024 bit at start offset 0. Writing and reading use two different granularities: it is programmed in rows of 25 bit (41 rows) and read back through memory mapped registers of 32 bit (32 MMRs). Row n covers the physical bits 25n to 25n+24, MMR n the physical bits 32n to 32n+31.

<word> Means Different Things for Reading and Writing

The fuse command takes a <word> argument for both operations, but it is interpreted in a different index space:

  • fuse read 0 <word> — <word> is an MMR index (32 bit granularity)
  • fuse prog 0 <word> — <word> is a row index (25 bit granularity)

Row n and MMR n therefore cover different physical bits. Mixing up the two indices while programming blows the wrong fuses irreversibly.

Two OTP Banks

The AM62L provides two banks for programming:

  • Bank 0 — general purpose, user defined OTP bits, the extended OTP
  • Bank 0xff — reduced pin boot-mode related OTP bits

This section describes only the processes for reading from and writing to bank 0 in U-Boot.

Extended OTP Write#

The AM62L is programmed with a row granularity of 25 bit: the extended OTP consists of 41 rows of 25 bit each, starting at offset 0 (row 0 to row 40). A row is always written as a whole, so the width of the value decides how much has to be done:

  • A value of up to 25 bit fits into one row and is written as it is.
  • A wider value has to be split into portions of 25 bit first, one portion per row.

The procedure to program the extended OTP on a Kontron AM62L board in U-Boot is:

  1. Choose the row range. Row 0 to 15 are reserved for Kontron use and must not be written, see Kontron reserved area on extended OTP Memory. Freely usable rows start at row 16.

  2. Split the value, if it is wider than 25 bit.

  3. Burn the OTP with fuse, lowest row first. The Vpp regulator has to be active while the fuses are burned, otherwise programming fails:

    # enable the vpp_1v8 regulator before programming
    => gpio set 2
    => regulator dev vpp_1v8
    => regulator enable
    
    # one command per row, starting with portion 0 in the lowest row
    => fuse prog 0 <row> <0xvalue>
    
    # disable the vpp_1v8 regulator afterwards
    => regulator disable
    => gpio clear 2
    

Example: Writing a MAC Address for Ethernet 2 on the Kontron AM62L#

e.g. MAC 2 = 00:11:22:33:44:56

  1. Determine the rows. According to the Kontron reserved area on extended OTP Memory, MAC 2 starts at physical bit 50 and is 48 bit wide. From these two numbers the rows follow:

    start = 50                                   # physical start bit
    len   = 48                                   # width of a MAC address
    
    row   = start / 25     = 50 / 25     = 2     # first row
    bit   = start % 25     = 50 % 25     = 0     # offset inside that row
    

    bit = 0 means MAC 2 starts at a row boundary. MAC 2 therefore occupies row 2 and row 3.

  2. Split the value into portions of 25 bit:

    value     = 0x001122334456
    
    portion 0 = value & 0x1ffffff         = 0x0334456   # bits 0–24  → row 2
    portion 1 = (value >> 25) & 0x1ffffff = 0x0000891   # bits 25–47 → row 3
    
  3. Program both rows, with the Vpp regulator enabled as shown in step 3 of the procedure above:

    => fuse prog 0 2 0x0334456
    => fuse prog 0 3 0x0000891
    

Extended OTP Read#

Reading uses a different granularity than writing: the extended OTP is read back through 32 memory mapped registers of 32 bit each (MMR 0 to 31). fuse read 0 <mmr> <count> returns <count> registers starting at MMR <mmr>, so here the index is an MMR index and has nothing to do with the row index used for programming.

Because rows are 25 bit and MMRs 32 bit wide, the two grids do not line up. A value therefore rarely starts at bit 0 of an MMR and usually spreads over two or three of them, which is why it has to be cut out of the registers after reading.

The procedure to read the extended OTP on a Kontron AM62L board in U-Boot is:

  1. Determine the MMR range. start is the physical start bit of the value in the 1024-bit extended OTP, and len its width:

    mmr   = start >> 5                # first MMR of the value
    index = start & 31                # offset of the value inside that MMR
    last  = (start + len - 1) >> 5    # last MMR of the value
    count = last - mmr + 1            # number of MMRs to read
    

    Because an MMR is 32 bit wide, the division is a shift by 5 and the remainder a mask with 31. 2. Read the MMRs with fuse read 0 <mmr> <count>. The output starts at MMR <mmr> and continues upwards, so the first register holds the lowest bits of the value.

  2. Cut the value out of the MMRs.

    mmr   = start >> 5              # first MMR of the value
    index = start & 31              # offset of the value inside that MMR
    
    val   = MMR[mmr] >> index       # first register, shifted down to bit 0
    bits  = 32 - index              # data bits collected so far
    
    while bits < len:               # as long as bits are still missing
        mmr  = mmr + 1
        val  = val | (MMR[mmr] << bits)
        bits = bits + 32
    
    value = val & ((1 << len) - 1)  # cut to the width of the value
    

Example: Reading a MAC Address for Ethernet Port 2 on the AM62L#

  1. Determine the MMRs. According to the Kontron reserved area on extended OTP Memory, MAC 2 starts at physical bit 50 and is 48 bit wide. From these two numbers the MMRs follow:

    start = 50                                      # physical start bit
    len   = 48                                      # width of a MAC address
    
    mmr   = start >> 5             = 50 >> 5   = 1  # first MMR
    index = start & 31             = 50 & 31   = 18 # offset inside that MMR
    last  = (start + len - 1) >> 5 = 97 >> 5   = 3  # last MMR
    

    MAC 2 therefore starts at bit 18 of MMR 1 and ends at bit 1 of MMR 3 (97 & 31 = 1), so reading it means collecting three MMRs:

    Read Bits of the MMR Bits of MAC 2
    MMR 1 18–31 0–13 (the 14 lowest bits)
    MMR 2 0–31 14–45
    MMR 3 0–1 46–47 (the 2 highest bits)
  2. Read these MMRs from the OTP:

    # MMR 1 is the first MMR and 3 the number of MMRs to read
    => fuse read 0 1 3
    
  3. Reassemble the value from the MMRs. Every register involved — including the first one — fits a single formula, using its own absolute MMR index j (1, 2, 3, …, not the count of registers):

    shift(MMR_j) = 32·j − start
    

    A negative result means the register's low, out-of-range bits are dropped with a right-shift; a positive result means the register is moved up to its position in the result with a left-shift:

    shift(MMR1) = 32·1 − 50 = −18   →  negative ⇒ right-shift:  MMR1 >> 18
    shift(MMR2) = 32·2 − 50 =  14   →  positive ⇒ left-shift:   MMR2 << 14
    shift(MMR3) = 32·3 − 50 =  46   →  positive ⇒ left-shift:   MMR3 << 46
    
    mac2 = ((MMR1 >> 18) | (MMR2 << 14) | (MMR3 << 46)) & mask
    mask = (1 << len) - 1        # here: (1 << 48) - 1 = 0xffffffffffff
    

    The mask cuts off everything the OR'd-together registers collected beyond bit 47, since the last register read (MMR3) usually contributes more bits than are actually needed for the value.

Example: Reading the KED SoM Unique ID#

  1. Determine the MMRs. According to the Kontron reserved area on extended OTP Memory, the SoM Unique ID starts at physical bit 100 and is 32 bit wide. From these two numbers the MMRs follow:

    start = 100                                     # physical start bit
    len   = 32                                      # width of a unique ID
    
    mmr   = start >> 5             = 100 >> 5  = 3  # first MMR
    index = start & 31             = 100 & 31  = 4  # offset inside that MMR
    last  = (start + len - 1) >> 5 = 131 >> 5  = 4  # last MMR
    count = last - mmr + 1         = 4 - 3 + 1 = 2  # number of MMRs to read
    

    The ID therefore starts at bit 4 of MMR 3 and ends at bit 3 of MMR 4 (131 & 31 = 3), so reading it means collecting two MMRs:

    Read Bits of the MMR Bits of the SoM Unique ID
    MMR 3 4–31 0–27 (the 28 lowest bits)
    MMR 4 0–3 28–31 (the 4 highest bits)
  2. Read these MMRs from the OTP:

    # MMR 3 is the first MMR and 2 the number of MMRs to read
    => fuse read 0 3 2
    
  3. Reassemble the value from the MMRs, using the same shift(MMR_j) = 32·j − start formula as above:

    shift(MMR3) = 32·3 − 100 =  −4   →  negative ⇒ right-shift: MMR3 >> 4
    shift(MMR4) = 32·4 − 100 =  28   →  positive ⇒ left-shift:  MMR4 << 28
    
    uid = ((MMR3 >> 4) | (MMR4 << 28)) & mask
    mask = (1 << len) - 1        # here: (1 << 32) - 1 = 0xffffffff
    

    As a rule, the mask always has exactly len set bits ((1 << len) - 1), regardless of how many MMRs were combined to build the value.