Use Microchip SAM9X60 OTP to store the MAC address and serial number of the board

1. Introduction

The SAM9X60 processor has part of the OTP (One Time Programming) Aera that can be used to store user data. In this way, we can write the board MAC Address and SN serial number to the fixed OTP User Area.

? ?Why use the OTP area to store the MAC address and serial number? The answer is to save money. If you do not use OTP, you may want to consider using eeprom and other power-off non-volatile storage to save this fixed information. This will cost you more money and occupy the I2C bus, PCB wiring, etc.

? ?One thing to mention here is that the NXP i.MX6 series processors have a more detailed and easier-to-access OTP area than Microchip processors. There are not so many twists and turns. Interested friends can check it out. Check out the i.MX6 Reference Manual.

2. MAC address and SN format

If the format is as follows:

3. SAM9X60 OTP Address

Introduction to SAM9X60 processor OTP.

Memory Mapping

Check out the relevant content in the 23. OTP Memory Controller (OTPC)’ chapter of the SAM9X60 chip manual.



Each Packet contains a 32-bit Header and several 32-bit Payload (data).

Payload’s SIZE itself can be defined in the Header’s register.


If we want to store 1 MAC address and 1 SN serial number here, in order to facilitate operation and entry, four 32-bit payloads are used here. The Header address starts from 0x00, and then the addresses 0x01 ~ 0x04 represent payload0 ~ payload4 respectively.

3.1. Write operation process

OTPC_MR (0x4) write 0xFF0000 - devmem 0xEFF00004 32 0xFF0000

OTPC_CR (0x0) writes 0x40

Read OTPC_ISR (0x1C) to see if this bit EOR (bit8) is 1

Read OTPC_HR (0x20) to see if the value in the register has 1,

Read the OTPC_DR (0x24) to see if the value in the register is 1 (or read the OTPC_SR (0x0C) register to see if the ONEF bit9 is 1)

OTPC_MR (0x4) writes 0x00000010 and sets OTPC_MR.ADDR to 0, indicating that new packets will be generated starting from address 0x0. This operation may trigger automatic flush

OTPC_HR (0x20) writes 0x301, indicating that we want to use 4 payloads, and the packet type is REGULAR ordinary user data.

OTPC_AR (0x08) writes 0x10000, which means that the address starts from 0x0, and every time data is written, the address will automatically increase, so we do not need to manually increase the written data address.

OTPC_DR (0x24) writes the first data 0x41300001

OTPC_DR (0x24) writes the second data 0x00000220

OTPC_DR (0x24) writes the third data 0x55508000

OTPC_DR (0x24) writes the fourth data 0x0000001F

OTPC_DR (0x24) writes the fifth data 0x55508001

OTPC_DR (0x24) writes the sixth data 0x0000001F

After the data is written, OTPC_CR (0x0) writes 0x71670001 to enable programming.

Check whether the write operation is completed, read OTPC_ISR (0x1C) EOP bit0 to see if it is 1, or read OTPC_SR (0x0C) PGM bit0 to see if it is 0

3.2. OTP Lock operation process

After the data is written, Lock is performed as follows:

3.3. Read operation process

The OTPC_MR (0x4) register first writes the address of the header, and then writes 0x0

OTPC_CR.READ (0x0) must be set to 1. The OTPC_CR register is write-only. Just write 0x40, so that the user area can be read.

Wait for the EOR (bit8) bit in OTPC_ISR (0x1C) to change to 1 or wait for the bit OTPC_SR (0x0C) READ (bit6) to change to 0, which means that the entire packet has been transferred to the temporary register.

Read the OTPC_HR (0x20) header register and read each payload word. The address of the payload word must have been written to OTPC_AR (0x08).DADDR. The address of OTPC_AR.DADDR will automatically increment after reading OTPC_DR, so when continuously reading payload word data, you only need to read OTPC_DR.

4. MAC Address/SN Programming and Read

4.1. Programming

The shell script written for board MAC &SN entry is as follows, for reference only.

#!/bin/sh
 
################################################ #######
### Programming MAC Address and SN into SAM9X60 OTP
###
### Author: Heat.Huang
### Date: 2022-5-7
################################################ #######
 
function usage(){<!-- -->
    echo "Usage:"
    echo ""
    echo "Programming OTP:"
    echo "$0 22041100001 00:60:2D:0C:84:27"
 
}
 
function prog_mac(){<!-- -->
    # get serial number
    SN=$1
    if [ ${<!-- -->#SN} -ne 11 ]; then
        echo "ERROR: The type of serial number is error."
        usage
        exit 1
    else
        SN_1=0x${SN:3:11}
        SN_2=0x00000${SN:0:3}
        echo "SN_1: $SN_1"
        echo "SN_2: $SN_2"
    fi
 
    # get mac address
    MAC_ADDR=$2
    if [ ${<!-- -->#MAC_ADDR} -ne 17 ]; then
        echo "ERROR: The format of MAC address your program is error."
        usage
        exit 1
    else
        /sbin/ifconfig eth0 down
        /sbin/ifconfig eth0 hw ether "$MAC_ADDR"
        if [ $? -eq 0 ]; then
            echo "MAC address is valid, can be programmed OTP."
            /sbin/ifconfig eth0 up
        else
            echo "MAC address is not valid."
            /sbin/ifconfig eth0 up
            usage
            exit 1
        fi
        echo "Start programming OTP..."
        MAC_ADDR_1=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[1]}'`
        MAC_ADDR_2=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[2]}'`
        MAC_ADDR_3=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[3]}'`
        MAC_ADDR_4=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[4]}'`
        MAC_ADDR_5=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[5]}'`
        MAC_ADDR_6=`echo $MAC_ADDR | awk '{split($0,addr,":");print addr[6]}'`
 
        HW_OCOTP_MAC0=0x${MAC_ADDR_3}${MAC_ADDR_4}${MAC_ADDR_5}${MAC_ADDR_6}
        HW_OCOTP_MAC1=0x0000${MAC_ADDR_1}${MAC_ADDR_2}
        echo "HW_OCOTP_MAC0: $HW_OCOTP_MAC0"
        echo "HW_OCOTP_MAC1: $HW_OCOTP_MAC1"
    fi
 
    ##
    ## Steps for program OTPC
    ##
 
    ###
    ### Write Access
    ###
    # read RC register
    echo "Read PMC Clock Generator Main Oscillator Register"
    devmem 0xffffffc20
    # enable RC clock
    echo "Enable the main RC in CKGR_MOR register"
    devmem 0xffffffc20 32 0x01370829
    # read RC register again
    echo "Read PMC Clock Generator Main Oscillator Register again"
    devmem 0xffffffc20
 
    # write OTPC_MR.NPCKT to 0
    echo "write OTPC_MR.NPCKT to 0"
    devmem 0xeff00004 32 0x00000000
    # write OTPC_MR.ADDR to its maximum value
    echo "write OTPC_MR.ADDR to its maximum value"
    devmem 0xeff00004 32 0x00ff0000
    # write OTPC_CR.READ to 1 and wait for the read completion
    echo "write OTPC_CR.READ to 1 and wait for the read completion"
    devmem 0xeff00000 32 0x71670040
    sleep 1
 
    echo "check if End of Read (EOR bit8) is 1"
    devmem 0xeff0001c
    echo "check OTPC Header Register"
    devmem 0xeff00020
    echo "check OTPC Data Register if have data of one"
    devmem 0xeff00024
 
    # Write OTPC_MR.ADDR to 0 and set NPCKT
    devmem 0xeff00004 32 0x00000010
    # write the header value in OTPC_HR
    devmem 0xeff00020 32 0x00000301
    #setDADDR to 0
    devmem 0xeff00008 32 0x00010000
    # write serial number into payload0 and payload1
    devmem 0xeff00024 32 $SN_1
    devmem 0xeff00024 32 $SN_2
    # write eth0 mac address into payload2 and payload3
    devmem 0xeff00024 32 $HW_OCOTP_MAC0
    devmem 0xeff00024 32 $HW_OCOTP_MAC1
 
    # after write, enable OTPC programming
    devmem 0xeff00000 32 0x71670001
    sleep 1
    # check if programming is end
    devmem 0xeff0001c
    sleep 1
    # read the address of new generated packet
    val=`devmem 0xeff00004`
    echo "the address of new generated packet: $val"
    val=0x${val:2:4}0000
    # clear OTPC_MR.NPCKT
    devmem 0xeff00004 32 $val
 
    ###
    ### Read data from OTP to check if write ok
    ###
    devmem 0xeff00000 32 0x00000040
    devmem 0xeff0001c
    devmem 0xeff00020
    devmem 0xeff00008 32 0x00000000
    SN_1_OTP=`devmem 0xeff00024`
    echo "SN_1_OTP = $SN_1_OTP"
 
    SN_2_OTP=`devmem 0xeff00024`
    echo "SN_2_OTP = $SN_2_OTP"
 
    HW_OCOTP_MAC0_OTP=`devmem 0xeff00024`
    echo "HW_OCOTP_MAC0_OTP = $HW_OCOTP_MAC0_OTP"
    HW_OCOTP_MAC0_OTP=${HW_OCOTP_MAC0_OTP:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC0_OTP=$((16#$HW_OCOTP_MAC0_OTP))
 
    HW_OCOTP_MAC1_OTP=`devmem 0xeff00024`
    echo "HW_OCOTP_MAC1_OTP = $HW_OCOTP_MAC1_OTP"
    HW_OCOTP_MAC1_OTP=${HW_OCOTP_MAC1_OTP:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC1_OTP=$((16#$HW_OCOTP_MAC1_OTP))
 
    ## transfer writen value from HEX to DEC
    HW_OCOTP_MAC0=${HW_OCOTP_MAC0:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC0=$((16#$HW_OCOTP_MAC0))
    HW_OCOTP_MAC1=${HW_OCOTP_MAC1:2:9}
    # transfer HEX to DEC
    HW_OCOTP_MAC1=$((16#$HW_OCOTP_MAC1))
 
    if [ "$SN_1_OTP" == "$SN_1" ] & amp; & amp; [ "$SN_2_OTP" == "$SN_2" ] & amp; & amp; [ $HW_OCOTP_MAC0_OTP -eq $HW_OCOTP_MAC0 ] & amp; & amp ; [ $HW_OCOTP_MAC1_OTP -eq $HW_OCOTP_MAC1 ]; then
        echo "Programming MAC ADDR and serial number succussful."
    else
        echo "Programming OTP FAIL."
    fi
 
    ###
    ### Lock the new generated packet
    ###
    echo "Lock the new generated packet"
    # write the address value of the header of the packet to lock in OTPC_MR.ADDR
    devmem 0xeff00004 32 $val
    # start a read by seeing OTPC_CR.READ and waiting for the read completion indicated by OTPC_ISR.EOR
    devmem 0xeff00000 32 0x00000040
    sleep 1
    # check if End of Read (EOR bit8) is 1
    devmem 0xeff0001c
    # Write 0x7167 in the OTPC_CR.KEY field and '1' in OTPC_CR.CKSGEN
    devmem 0xeff00000 32 0x71670002
    # the end of the lock operation is indicated by OTPC_ISR.EOL='1' and/or OTPC_SR.LOCK='0'
    lock_ret=`devmem 0xeff0001c`
    eol=${lock_ret:9}
    if [ $eol -eq 2 ]; then
        echo "Lock ok."
    else
        echo "Lock fail."
    fi
 
}
 
 
#Entry
if [ $# == 2 ]; then
    prog_mac $@
else
    usage
    exit 1
fi
 
exit 0

4.2. Read

Written a shell script get_Mac.sh that reads the MAC address and SN. It can be used after the board Linux system is started. Run this script to read the MAC address from the Aera corresponding to the OTP. If the reading fails, it will be randomly Generate a MAC address, and then configure the IP of the network card through the ifconfig eth0 hw ether command. The script also reads the serial number from the OTP and writes it to the file /images/data/SN.

The get_Mac.sh script source code is as follows.

#!/bin/sh
 
 
 
## add by heat
## Steps for set MAC address and get SN from OTPC
##
 
# read RC register
echo "read RC register"
/sbin/devmem 0xfffffc20
# enable RC clock
echo "enable the main RC in CKGR_MOR register"
/sbin/devmem 0xfffffc20 32 0x01370829
# read RC register again
echo "read RC register again"
/sbin/devmem 0xfffffc20
 
# write the address of header into OTPC_MR
/sbin/devmem 0xeff00004 32 0x00000000
# OTPC_CR.READ set to 1 to enable user area
/sbin/devmem 0xeff00000 32 0x00000040
# check OTPC_ISR.EOR if 0 to wait for start reading
/sbin/devmem 0xeff0001c
sleep 1
# read the header of the packet
/sbin/devmem 0xeff00020
# start address of payload
/sbin/devmem 0xeff00008 32 0x00000000
 
# start reading value from OTP
SN_TAIL=`/sbin/devmem 0xeff00024`
SN_HEAD=`/sbin/devmem 0xeff00024`
MAC0_TAIL=`/sbin/devmem 0xeff00024`
MAC0_HEAD=`/sbin/devmem 0xeff00024`
echo "SN_TAIL=$SN_TAIL"
echo "SN_HEAD=$SN_HEAD"
echo "MAC0_TAIL=$MAC0_TAIL"
echo "MAC0_HEAD=$MAC0_HEAD"
 
if [ $MAC0_TAIL == "0x00000000" ] & amp; & amp; [ $MAC0_HEAD == "0x00000000" ]; then
    echo "Cannot get correct data from OTP address 0x0, try to read from OTP address 0x29"
    # write the address of header into OTPC_MR
    /sbin/devmem 0xeff00004 32 0x00290000
    # OTPC_CR.READ set to 1 to enable user area
    /sbin/devmem 0xeff00000 32 0x00000040
    # check OTPC_ISR.EOR if 0 to wait for start reading
    /sbin/devmem 0xeff0001c
    sleep 1
    # read the header of the packet
    /sbin/devmem 0xeff00020
    # start address of payload
    /sbin/devmem 0xeff00008 32 0x00000000
 
    # start reading value from OTP
    SN_TAIL=`/sbin/devmem 0xeff00024`
    SN_HEAD=`/sbin/devmem 0xeff00024`
    MAC0_TAIL=`/sbin/devmem 0xeff00024`
    MAC0_HEAD=`/sbin/devmem 0xeff00024`
    echo "SN_TAIL=$SN_TAIL"
    echo "SN_HEAD=$SN_HEAD"
    echo "MAC0_TAIL=$MAC0_TAIL"
    echo "MAC0_HEAD=$MAC0_HEAD"
fi
 
NONE_MAC0=false
NONE_MAC1=false
## analyze eth0 MAC address
if [ $MAC0_TAIL == "0x00000000" ] & amp; & amp; [ $MAC0_HEAD == "0x00000000" ]; then
    echo "Cannot get correct MAC address form OTP"
    echo "Use Random MAC Address"
    MAC0_ADDR_RAND=`echo $RANDOM|md5sum`
    MAC0_ADDR_1=00
    MAC0_ADDR_2=1F
    MAC0_ADDR_3=55
    MAC0_ADDR_4=`echo $MAC0_ADDR_RAND|cut -c 1-2`
    MAC0_ADDR_5=`echo $MAC0_ADDR_RAND|cut -c 3-4`
    MAC0_ADDR_6=`echo $MAC0_ADDR_RAND|cut -c 5-6`
else
    case ${<!-- -->#MAC0_TAIL} in
        2)
        MAC0_OPT_SECTOR_TAIL="0x00000000"
        ;;
        3)
        MAC0_OPT_SECTOR_TAIL="0x0000000"`echo $MAC0_TAIL | cut -b 3`
        ;;
        4)
        MAC0_OPT_SECTOR_TAIL="0x000000"`echo $MAC0_TAIL | cut -b 3-4`
        ;;
        5)
        MAC0_OPT_SECTOR_TAIL="0x00000"`echo $MAC0_TAIL | cut -b 3-5`
        ;;
        6)
        MAC0_OPT_SECTOR_TAIL="0x0000"`echo $MAC0_TAIL | cut -b 3-6`
        ;;
        7)
        MAC0_OPT_SECTOR_TAIL="0x000"`echo $MAC0_TAIL | cut -b 3-7`
        ;;
        8)
        MAC0_OPT_SECTOR_TAIL="0x00"`echo $MAC0_TAIL | cut -b 3-8`
        ;;
        9)
        MAC0_OPT_SECTOR_TAIL="0x0"`echo $MAC0_TAIL | cut -b 3-9`
        ;;
        10)
        MAC0_OPT_SECTOR_TAIL=$MAC0_TAIL
        ;;
        *)
        NONE_MAC0=true
        ;;
    esac
    case ${<!-- -->#MAC0_HEAD} in
        2)
        MAC0_OPT_SECTOR_HEAD="0x0000"
        ;;
        3)
        MAC0_OPT_SECTOR_HEAD="0x000"`echo $MAC0_HEAD | cut -b 3`
        ;;
        4)
        MAC0_OPT_SECTOR_HEAD="0x00"`echo $MAC0_HEAD | cut -b 3-4`
        ;;
        5)
        MAC0_OPT_SECTOR_HEAD="0x0"`echo $MAC0_HEAD | cut -b 3-5`
        ;;
        6)
        MAC0_OPT_SECTOR_HEAD="0x0000"`echo $MAC0_HEAD | cut -b 3-6`
        ;;
        7)
        MAC0_OPT_SECTOR_HEAD="0x000"`echo $MAC0_HEAD | cut -b 3-7`
        ;;
        8)
        MAC0_OPT_SECTOR_HEAD="0x00"`echo $MAC0_HEAD | cut -b 3-8`
        ;;
        9)
        MAC0_OPT_SECTOR_HEAD="0x0"`echo $MAC0_HEAD | cut -b 3-9`
        ;;
        10)
        MAC0_OPT_SECTOR_HEAD=$MAC0_HEAD
        ;;
        *)
        NONE_MAC0=true
        ;;
    esac
 
    MAC0_ADDR_1=`echo ${<!-- -->MAC0_OPT_SECTOR_HEAD} | cut -b 7-8`
    MAC0_ADDR_2=`echo ${<!-- -->MAC0_OPT_SECTOR_HEAD} | cut -b 9-10`
    MAC0_ADDR_3=`echo ${<!-- -->MAC0_OPT_SECTOR_TAIL} | cut -b 3-4`
    MAC0_ADDR_4=`echo ${<!-- -->MAC0_OPT_SECTOR_TAIL} | cut -b 5-6`
    MAC0_ADDR_5=`echo ${<!-- -->MAC0_OPT_SECTOR_TAIL} | cut -b 7-8`
    MAC0_ADDR_6=`echo ${<!-- -->MAC0_OPT_SECTOR_TAIL} | cut -b 9-10`
 
    echo "Got correct MAC address form OTP"
 
fi
 
## set eth0 MAC address
echo "eth0 MAC: $MAC0_ADDR_1:$MAC0_ADDR_2:$MAC0_ADDR_3:$MAC0_ADDR_4:$MAC0_ADDR_5:$MAC0_ADDR_6"
/sbin/ifconfig eth0 down
/sbin/ifconfig eth0 hw ether "$MAC0_ADDR_1:$MAC0_ADDR_2:$MAC0_ADDR_3:$MAC0_ADDR_4:$MAC0_ADDR_5:$MAC0_ADDR_6"
/sbin/ifconfig eth0 up
 
## get SN from OTP
if [ $SN_TAIL == "0x00000000" ] & amp; & amp; [ $SN_HEAD == "0x00000000" ]; then
    echo "Cannot get correct SN form OTP"
else
    echo "Got correct SN form OTP"
    SN_1=`printf " x" ${<!-- -->SN_TAIL}`
    SN_2=`printf " x" ${<!-- -->SN_HEAD}`
    echo "SN: ${SN_2}${SN_1}"
    echo "${SN_2}${SN_1}" > /images/data/SN
fi

There is no special operation, just a bunch of registers. . .