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. . .