1. 介绍
SAM9X60 处理器有部分OTP(One Time Programming) Aera 可用于存储user data,这样的话我们就可以将板卡 MAC Address和 SN 序列号写到固定的OTP User Area中。
为什么要使用 OTP 区域存储MAC地址和序列号呢?答案是为了省钱。如果不使用OTP的话,可能要考虑采用 eeprom 等掉电非易失的存储保存这些固定信息,这样的话就多了一颗料的钱,还得占用I2C总线,PCB布线等。
这里提一句,NXP i.MX6系列的处理器,对 OTP 区域的划分比Microchip的处理器要详细和更容易访问一些,没有那么多弯弯绕绕,感兴趣的小伙伴可以看一下i.MX6 Reference Manual.
2. MAC地址和SN 格式
假如格式如下:
3. SAM9X60 OTP Address
SAM9X60 处理器OTP相关的介绍。
Memory Mapping
查看SAM9X60 芯片手册 ‘23. OTP Memory Controller(OTPC)’章节的相关内容。
每一个Packet包含1个32-bit的Header和若干个32-bit的Payload(data)。
Payload 的 SIZE 自己可以在Header的寄存器中定义。
我们这里要存储1个MAC地址,1个SN序列号的话,为了方便操作和录入,这里就使用4个32bit的payload。Header地址从0x00开始,然后地址0x01 ~ 0x04分别代表payload0~payload4。
3.1. 写操作流程
OTPC_MR (0x4)写 0xFF0000 - devmem 0xEFF00004 32 0xFF0000
OTPC_CR (0x0)写 0x40
读 OTPC_ISR (0x1C)查看这个位EOR (bit8)是否是1
读 OTPC_HR (0x20)查看寄存器中的值是否有1,
读 OTPC_DR (0x24)查看寄存器中的值是否有1 (或者读 OTPC_SR (0x0C)寄存器,查看 ONEF bit9 这一位是否为1)
OTPC_MR (0x4)写 0x00000010 ,将OTPC_MR.ADDR 设置为 0,表示从0x0地址开始生成新的packet.这个操作可能触发自动 flush
OTPC_HR (0x20)写 0x301 ,表示我们要使用4个payload, packet类型是 REGULAR 普通的用户数据。
OTPC_AR (0x08)写 0x10000, 表示地址从0x0开始,并且再每次写入数据后,该地址会自动递增,就不用我们再手动增加写入的数据地址了。
OTPC_DR (0x24)写入第一个数据 0x41300001
OTPC_DR (0x24)写入第二个数据 0x00000220
OTPC_DR (0x24)写入第三个数据 0x55508000
OTPC_DR (0x24)写入第四个数据 0x0000001F
OTPC_DR (0x24)写入第五个数据 0x55508001
OTPC_DR (0x24)写入第六个数据 0x0000001F
数据写完之后,OTPC_CR (0x0)写 0x71670001 ,使能programming.
查看写操作是否结束,读 OTPC_ISR (0x1C) EOP bit0是否是1,或者读 OTPC_SR (0x0C) PGM bit0 是否是0
3.2. OTP Lock操作流程
数据写完之后进行Lock,操作如下:
3.3. 读操作流程
OTPC_MR (0x4) 寄存器中先写入 header 的地址 ,写入0x0
OTPC_CR.READ (0x0)必须设置为1,OTPC_CR这个寄存器是write-only的,仅仅写入 0x40即可,这样就可以读 user area 了。
等待 OTPC_ISR(0x1C)中 EOR(bit8) 这个位变为1 或者等待 OTPC_SR(0x0C)READ (bit6)这个位变成0,这就意味着整个packet 已经转移到临时寄存器中了
读 OTPC_HR (0x20)头寄存器,读取每一个paylaod word,paylaod word的地址必须已经写入到 OTPC_AR(0x08).DADDR 中。 OTPC_AR.DADDR的地址在读取 OTPC_DR 后会自动递增,所以在连续读取paylaod word数据时,只需要去读 OTPC_DR即可。
4. MAC Address/SN Programming and Read
4.1. Programming
编写的用于板卡 MAC&SN 录入的shell 脚本如下,仅供参考。
#!/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 adddress you 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 0xfffffc20
# enable RC clock
echo "Enable the main RC in CKGR_MOR register"
devmem 0xfffffc20 32 0x01370829
# read RC register again
echo "Read PMC Clock Generator Main Oscillator Register again"
devmem 0xfffffc20
# 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
# set DADDR 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" ] && [ "$SN_2_OTP" == "$SN_2" ] && [ $HW_OCOTP_MAC0_OTP -eq $HW_OCOTP_MAC0 ] && [ $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 seeting 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
又写了一个读取MAC地址和SN的shell脚本 get_Mac.sh,可以用于板子 Linux系统启动后,运行该脚本从OTP相应的Aera中读取MAC address,如果读取失败会随机生成一个MAC地址,然后通过ifconfig eth0 hw ether 命令配置网卡的IP。脚本还会从OTP读取序列号,并写入到文件 /images/data/SN 中。
get_Mac.sh 脚本源码如下。
#!/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" ] && [ $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
## analyse eth0 MAC address
if [ $MAC0_TAIL == "0x00000000" ] && [ $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" ] && [ $SN_HEAD == "0x00000000" ]; then
echo "Cannot get correct SN form OTP"
else
echo "Got correct SN form OTP"
SN_1=`printf "%08x" ${SN_TAIL}`
SN_2=`printf "%03x" ${SN_HEAD}`
echo "SN: ${SN_2}${SN_1}"
echo "${SN_2}${SN_1}" > /images/data/SN
fi
没有做什么特别的操作,就是操作一堆寄存器。。。