You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by an...@apache.org on 2019/10/16 15:19:53 UTC

[mynewt-core] branch master updated: Add OTP driver and programming script

This is an automated email from the ASF dual-hosted git repository.

andk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 8fa3925  Add OTP driver and programming script
8fa3925 is described below

commit 8fa3925bd23031bd5c24a7a2dc87c2ed67ffb8b0
Author: Andy Gross <an...@juul.com>
AuthorDate: Fri Aug 9 12:51:28 2019 -0500

    Add OTP driver and programming script
    
    This patch adds a driver for the Dialog 1469x OTP IP block.  Included
    is a python script for use with an associated OTP programming application.
    
    Signed-off-by: Andy Gross <an...@juul.com>
---
 hw/bsp/dialog_da1469x-dk-pro/da1469x_serial.py  | 110 +++++
 hw/bsp/dialog_da1469x-dk-pro/otp_tool.py        | 595 ++++++++++++++++++++++++
 hw/mcu/dialog/da14699/include/mcu/mcu.h         |   3 +
 hw/mcu/dialog/da1469x/include/mcu/da1469x_otp.h |  51 ++
 hw/mcu/dialog/da1469x/src/da1469x_otp.c         | 148 ++++++
 hw/mcu/dialog/da1469x/src/system_da1469x.c      |  11 +-
 6 files changed, 910 insertions(+), 8 deletions(-)

diff --git a/hw/bsp/dialog_da1469x-dk-pro/da1469x_serial.py b/hw/bsp/dialog_da1469x-dk-pro/da1469x_serial.py
new file mode 100755
index 0000000..383718c
--- /dev/null
+++ b/hw/bsp/dialog_da1469x-dk-pro/da1469x_serial.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+import serial
+import io
+import click
+import os
+
+
+@click.argument('infile')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Close out OTP configuration script')
+def load(infile, uart):
+    try:
+        ser = serial.Serial(port=uart, baudrate=115200, timeout=10,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    try:
+        f = open(infile, "rb")
+    except IOError:
+        raise SystemExit("Failed to open input file")
+
+    data = f.read()
+    crc = 0
+    for i in range(len(data)):
+        crc = crc ^ data[i]
+
+    # serial load protocol is the following:
+    # - on reset, board sends 0x2 and listens for 100ms or so
+    # - host sends 0x1 followed by length of file to transfer
+    # - board responds with 0x6
+    # - host sends file
+    # - board responds with bytewise XOR of file
+    # - If XOR matches, host sends 0x6 as final ack
+    # - board boots image after receiving ACK from host
+
+#    if len(msg) == 0:
+#        raise SystemExit("Read timed out, exiting")
+
+    print("Please reset board to enter ROM uart recovery")
+
+    while True:
+        msg = ser.read(1)
+        print(msg)
+        if msg[0] == 0x2:
+            print("Detected serial boot protocol")
+            msg = bytes([0x1])
+            msg += bytes([len(data) & 0xff])
+            msg += bytes([len(data) >> 8])
+            ser.write(msg)
+
+            msg = ser.read()
+            if len(msg) == 0:
+                raise SystemExit("Failed to receive SOH ACK, aborting")
+
+            if msg[0] != 0x6:
+                raise SystemExit("Received invalid response, aborting")
+
+            print("Loading application to RAM")
+            try:
+                ser.write(data)
+            except serial.SerialException:
+                raise SystemExit("Failed to write executable, aborting")
+
+            msg = ser.read()
+            if len(msg) == 0:
+                raise SystemExit("Failed to receive XOR of bytes")
+
+            if crc != msg[0]:
+                raise SystemExit("Failed CRC check")
+
+            msg = bytes([0x6])
+
+            try:
+                ser.write(msg)
+            except serial.SerialException:
+                raise SystemExit("Failed to write final ACK, aborting")
+            print("Successfully loaded RAM, board will now boot executable")
+            break
+        else:
+            continue
+
+
+@click.group()
+def cli():
+    pass
+
+
+cli.add_command(load)
+
+
+if __name__ == '__main__':
+    cli()
diff --git a/hw/bsp/dialog_da1469x-dk-pro/otp_tool.py b/hw/bsp/dialog_da1469x-dk-pro/otp_tool.py
new file mode 100755
index 0000000..8984e67
--- /dev/null
+++ b/hw/bsp/dialog_da1469x-dk-pro/otp_tool.py
@@ -0,0 +1,595 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import serial
+import io
+import click
+import struct
+import binascii
+from typing import NamedTuple
+import os
+from cryptography.hazmat.primitives import serialization
+
+# Add in path to mcuboot.  This is required to pull in keys module from imgtool
+import sys
+sys.path.append(os.path.join(os.getcwd(), "repos", "mcuboot", "scripts",
+                "imgtool"))
+import keys as keys
+
+
+class Cmd(object):
+    OTP_READ_KEY = 0
+    OTP_WRITE_KEY = 1
+    FLASH_READ = 2
+    FLASH_WRITE = 3
+    OTP_READ_CONFIG = 4
+    OTP_APPEND_VALUE = 5
+    OTP_INIT = 6
+    FLASH_ERASE = 7
+
+
+class cmd_no_payload(NamedTuple):
+    som: int
+    cmd: int
+
+
+class cmd_read_key(NamedTuple):
+    som: int
+    cmd: int
+    segment: int
+    slot: int
+
+
+class cmd_write_key(NamedTuple):
+    som: int
+    cmd: int
+    segment: int
+    slot: int
+
+
+class cmd_append_value(NamedTuple):
+    som: int
+    cmd: int
+    length: int
+
+
+class cmd_response(NamedTuple):
+    som: int
+    cmd: int
+    status: int
+    length: int
+
+
+class cmd_flash_op(NamedTuple):
+    som: int
+    cmd: int
+    index: int
+    offset: int
+    length: int
+
+
+def crc16(data: bytearray, offset, length):
+    if data is None or offset < 0:
+        return
+
+    if offset > len(data) - 1 or offset + length > len(data):
+        return 0
+
+    crc = 0
+    for i in range(length):
+        crc ^= data[offset + i] << 8
+
+        for _ in range(8):
+            if (crc & 0x8000) > 0:
+                crc = (crc << 1) ^ 0x1021
+            else:
+                crc = crc << 1
+    return crc & 0xFFFF
+
+
+def validate_slot_index(ctx, param, value):
+    if value in range(8):
+        return value
+    else:
+        raise click.BadParameter("Slot value has to be between 0 and 7")
+
+
+def read_exact(device, length):
+    data = device.read(length)
+    if len(data) != length:
+        raise SystemExit("Failed to receive expected response, exiting")
+    return data
+
+
+@click.option('-i', '--index', required=True, type=int, help='key slot index',
+              callback=validate_slot_index)
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.option('-s', '--segment', type=click.Choice(['signature', 'data',
+              'qspi']), help='OTP segment', required=True)
+@click.command(help='Read a single key from OTP key segment')
+def otp_read_key(index, segment, uart):
+    seg_map = {'signature': 0, 'data': 1, 'qspi': 2}
+
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    cmd = cmd_read_key(0xaa55aa55, Cmd.OTP_READ_KEY, seg_map[segment], index)
+    data = struct.pack('IIII', *cmd)
+
+    try:
+        ser.write(data)
+    except serial.SerialException:
+        raise SystemExit("Failed to write to %s" % uart)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status == 0:
+        key = ser.read(32)
+        print("key:" + key.hex())
+    else:
+        raise SystemExit("Error reading key with status %s" %
+                         hex(response.status))
+
+
+def isBase64(data):
+    try:
+        return base64.b64encode(base64.b64decode(data)) == data
+    except Exception:
+        return False
+
+
+@click.argument('infile')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.option('-i', '--index', required=True, type=int, help='key slot index',
+              callback=validate_slot_index)
+@click.option('-s', '--segment', type=click.Choice(['signature', 'data',
+              'qspi']), help='OTP segment', required=True,)
+@click.command(help='Write a single key to OTP key segment')
+def otp_write_key(infile, index, segment, uart):
+
+    key = bytearray()
+    try:
+        with open(infile, "rb") as f:
+            if segment == 'signature':
+                # read key from ED25519 pem file
+                sig = keys.load(infile)
+                buf = sig._get_public().public_bytes(
+                            encoding=serialization.Encoding.Raw,
+                            format=serialization.PublicFormat.Raw)
+            else:
+                # read key from base64 encoded AES key file
+                buf = f.read()
+                if isBase64(buf):
+                    if len(buf) != 32:
+                        raise SystemExit("AES key file has incorrect length")
+            key = struct.unpack('IIIIIIII', buf)
+    except IOError:
+        raise SystemExit("Failed to read key from file")
+
+    seg_map = {'signature': 0, 'data': 1, 'qspi': 2}
+
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    cmd = cmd_write_key(0xaa55aa55, Cmd.OTP_WRITE_KEY, seg_map[segment], index)
+    # swap endianness of data to little endian
+
+    msg = struct.pack('IIII', *cmd)
+    key_bytes = struct.pack('>IIIIIIII', *key)
+    msg += (key_bytes)
+    msg += struct.pack('H', crc16(key_bytes, 0, 32))
+
+    ser.write(msg)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status == 0:
+        print("Key successfully updated")
+    else:
+        raise SystemExit('Error writing key with status %s ' %
+                         hex(response.status))
+
+
+def generate_payload(data):
+    msg = bytearray()
+
+    for entry in data:
+        msg += entry.to_bytes(4, 'little')
+
+    return msg
+
+
+@ click.argument('outfile')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Read data from OTP configuration script area')
+def otp_read_config(uart, outfile):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    # length is unused, so set to 0
+    cmd = cmd_append_value(0xaa55aa55, Cmd.OTP_READ_CONFIG, 0)
+    data = struct.pack('III', *cmd)
+
+    try:
+        ser.write(data)
+    except serial.SerialException:
+        raise SystemExit("Failed to write to %s" % uart)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status == 0:
+        data = ser.read(response.length)
+        if len(data) != response.length:
+            raise SystemExit("Failed to receive data, exiting")
+
+        print("Successfully read configuration script, writing to file: " + outfile)
+    try:
+        with open(outfile, "wb") as f:
+            f.write(data)
+    except IOError:
+        raise SystemExit("Failed to write config data to file")
+
+
+@click.argument('outfile')
+@click.option('-a', '--offset', type=str, required=True,
+              help='flash address offset from base, hexadecimal')
+@click.option('-l', '--length', type=int, required=True, help='length to read')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Read from flash')
+def flash_read(uart, length, outfile, offset):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    try:
+        f = open(outfile, "wb")
+    except IOError:
+        raise SystemExit("Failed to open output file")
+
+    bytes_left = length
+    offset = int(offset, 16)
+    retry = 3
+    while bytes_left > 0:
+        if bytes_left > 4096:
+            trans_length = 4096
+        else:
+            trans_length = bytes_left
+
+        cmd = cmd_flash_op(0xaa55aa55, Cmd.FLASH_READ, 0, offset, trans_length)
+        data = struct.pack('IIIII', *cmd)
+
+        try:
+            ser.write(data)
+        except serial.SerialException:
+            raise SystemExit("Failed to write cmd to %s" % uart)
+
+        data = read_exact(ser, 16)
+
+        response = cmd_response._make(struct.unpack_from('IIII', data))
+        if response.status == 0:
+            data = ser.read(response.length)
+            if len(data) != response.length:
+                raise SystemExit("Failed to receive response, exiting")
+
+            # data has a crc on the end
+            crc_computed = crc16(data[:-2], 0, response.length - 2)
+            crc = struct.unpack('!H', data[response.length -2 :])[0]
+            if crc == crc_computed:
+                retry = 0
+            else:
+                if retry == 0:
+                    raise SystemExit("Data corruption retries exceeded, exiting")
+
+                print("Data crc failed, retrying\n");
+                retry -= 1
+                continue
+
+            f.write(data[:-2])
+
+        else:
+            raise SystemExit("Error in read response, exiting")
+            break
+
+        bytes_left -= trans_length
+        offset += trans_length
+
+    print("Successfully read flash, wrote contents to " + outfile)
+
+
+@click.option('-a', '--offset', type=str, required=True,
+              help='flash address offset, in hex')
+@click.option('-l', '--length', type=int, required=True, help='size to erase')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Write to flash')
+def flash_erase(uart, offset, length):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=60,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    # length is unused, so set to 0
+    offset = int(offset, 16)
+    cmd = cmd_flash_op(0xaa55aa55, Cmd.FLASH_ERASE, 0, offset, length)
+    msg = struct.pack('IIIII', *cmd)
+
+    try:
+        ser.write(msg)
+    except serial.SerialException:
+        raise SystemExit("Failed to write to %s" % uart)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status != 0:
+            raise SystemExit("Failed to erase flash, exiting")
+
+    print("Successfully erased flash")
+
+@click.argument('infile')
+@click.option('-a', '--offset', type=str, required=True,
+              help='flash address offset, in hex')
+@click.option('-b', '--block-size', type=int, default=4096, help='block size')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Write to flash')
+def flash_write(uart, infile, offset, block_size):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    try:
+        f = open(infile, "rb")
+    except IOError:
+        raise SystemExit("Failed to open input file")
+
+    length = os.path.getsize(infile)
+
+    bytes_left = length
+    offset = int(offset, 16)
+    while bytes_left > 0:
+        if bytes_left > block_size:
+            trans_length = block_size
+        else:
+            trans_length = bytes_left
+
+        cmd = cmd_flash_op(0xaa55aa55, Cmd.FLASH_WRITE, 0, offset,
+                           trans_length + 2)
+        data = struct.pack('IIIII', *cmd)
+
+        f_bytes = f.read(trans_length)
+        data += f_bytes
+        data += struct.pack('H', crc16(f_bytes, 0, trans_length))
+
+        try:
+            ser.write(data)
+        except serial.SerialException:
+            raise SystemExit("Failed to write cmd to %s" % uart)
+
+        data = read_exact(ser, 16)
+
+        response = cmd_response._make(struct.unpack_from('IIII', data))
+        if response.status != 0:
+            raise SystemExit("Flash write failed w/ %s, exiting" %
+                             hex(response.status))
+
+        bytes_left -= trans_length
+        offset += trans_length
+
+    print("Successfully wrote flash")
+
+
+def send_otp_config_payload(uart, data):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    data_bytes = generate_payload(data)
+
+    # length is unused, so set to 0
+    cmd = cmd_append_value(0xaa55aa55, Cmd.OTP_APPEND_VALUE,
+                           len(data_bytes) + 2)
+    msg = struct.pack('III', *cmd)
+
+    msg += data_bytes
+    msg += struct.pack('H', crc16(data_bytes, 0, len(data_bytes)))
+
+    try:
+        ser.write(msg)
+    except serial.SerialException:
+        raise SystemExit("Failed to write to %s" % uart)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status == 0:
+        data = ser.read(response.length)
+        if len(data) != response.length:
+            raise SystemExit("Failed to receive data, exiting")
+
+
+@click.command(help='Append register value to OTP configuration script')
+@click.option('-a', '--address', type=str, required=True,
+              help='register address in hexadecimal')
+@click.option('-v', '--value', type=str, required=True,
+              help='register value in hexadecimal')
+@click.option('-u', '--uart', required=True, help='uart port')
+def otp_append_register(uart, address, value):
+
+    send_otp_config_payload(uart, [int(address, 16), int(value, 16)])
+
+
+@click.option('-t', '--trim', type=str, required=True,
+              multiple=True, help='trim value')
+@click.option('-i', '--index', type=int, required=True,
+              help='Trim value id')
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Append trim value to OTP configuration script')
+def otp_append_trim(uart, trim, index):
+
+    data = []
+    data.append(0x90000000 + (len(trim) << 8) + index)
+    for entry in trim:
+        data.append(int(entry, 16))
+
+    send_otp_config_payload(uart, data)
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Disable development mode')
+def disable_development_mode(uart):
+
+    send_otp_config_payload(uart, [0x70000000])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Enable secure boot')
+def enable_secure_boot(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x1])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Write lock OTP QSPI key area')
+def disable_qspi_key_write(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x40])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Read lock OTP QSPI key area')
+def disable_qspi_key_read(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x80])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Write lock OTP user key area')
+def disable_user_key_write(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x10])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Read lock OTP user key area')
+def disable_user_key_read(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x20])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Write lock OTP signature key area')
+def disable_signature_key_write(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x8])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Disable CMAC debugger')
+def disable_cmac_debugger(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x4])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Disable SWD debugger')
+def disable_swd_debugger(uart):
+
+    send_otp_config_payload(uart, [0x500000cc, 0x2])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Close out OTP configuration script')
+def close_config_script(uart):
+
+    send_otp_config_payload(uart, [0x0])
+
+
+@click.option('-u', '--uart', required=True, help='uart port')
+@click.command(help='Initialize blank OTP Config script')
+def init_config_script(uart):
+    try:
+        ser = serial.Serial(port=uart, baudrate=1000000, timeout=1,
+                            bytesize=8, stopbits=serial.STOPBITS_ONE)
+    except serial.SerialException:
+        raise SystemExit("Failed to open serial port")
+
+    # length is unused, so set to 0
+    cmd = cmd_append_value(0xaa55aa55, Cmd.OTP_INIT, 0)
+    msg = struct.pack('III', *cmd)
+
+    try:
+        ser.write(msg)
+    except serial.SerialException:
+        raise SystemExit("Failed to write to %s" % uart)
+
+    data = read_exact(ser, 16)
+
+    response = cmd_response._make(struct.unpack_from('IIII', data))
+    if response.status != 0:
+        raise SystemExit('Failed to initialize OTP with status %d'
+                         % response.status)
+    print("Successfully initialized blank OTP")
+
+
+@click.group()
+def cli():
+    pass
+
+
+cli.add_command(otp_read_config)
+cli.add_command(otp_read_key)
+cli.add_command(otp_write_key)
+cli.add_command(flash_read)
+cli.add_command(flash_write)
+cli.add_command(flash_erase)
+cli.add_command(otp_append_register)
+cli.add_command(otp_append_trim)
+cli.add_command(disable_development_mode)
+cli.add_command(enable_secure_boot)
+cli.add_command(disable_qspi_key_write)
+cli.add_command(disable_qspi_key_read)
+cli.add_command(disable_user_key_write)
+cli.add_command(disable_user_key_read)
+cli.add_command(disable_signature_key_write)
+cli.add_command(disable_cmac_debugger)
+cli.add_command(disable_swd_debugger)
+cli.add_command(close_config_script)
+cli.add_command(init_config_script)
+
+
+if __name__ == '__main__':
+    cli()
diff --git a/hw/mcu/dialog/da14699/include/mcu/mcu.h b/hw/mcu/dialog/da14699/include/mcu/mcu.h
index ef0ee33..3114bfb 100644
--- a/hw/mcu/dialog/da14699/include/mcu/mcu.h
+++ b/hw/mcu/dialog/da14699/include/mcu/mcu.h
@@ -150,6 +150,9 @@ void mcu_gpio_exit_sleep(void);
 #define MCU_MEM_SYSRAM_START_ADDRESS    (0x20000000)
 #define MCU_MEM_SYSRAM_END_ADDRESS      (0x20080000)
 
+#define MCU_OTPM_BASE 0x10080000UL
+#define MCU_OTPM_SIZE 4096
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/hw/mcu/dialog/da1469x/include/mcu/da1469x_otp.h b/hw/mcu/dialog/da1469x/include/mcu/da1469x_otp.h
new file mode 100644
index 0000000..276543f
--- /dev/null
+++ b/hw/mcu/dialog/da1469x/include/mcu/da1469x_otp.h
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef __MCU_DA1469X_OTP_H_
+#define __MCU_DA1469X_OTP_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define OTP_ERR_INVALID_SIZE_ALIGNMENT -1
+#define OTP_ERR_INVALID_ADDRESS -2
+#define OTP_ERR_PROGRAM_VERIFY_FAILED -3
+
+#define OTP_SEGMENT_CONFIG          0xc00
+#define OTP_SEGMENT_QSPI_FW_KEYS    0xb00
+#define OTP_SEGMENT_USER_DATA_KEYS  0xa00
+#define OTP_SEGMENT_SIGNATURE_KEYS  0x8c0
+
+int da1469x_otp_write(uint32_t address, const void *src,
+                             uint32_t num_bytes);
+
+int da1469x_otp_read(uint32_t address, void *dst, uint32_t num_bytes);
+
+void da1469x_otp_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCU_DA1469X_OTP_H_ */
diff --git a/hw/mcu/dialog/da1469x/src/da1469x_otp.c b/hw/mcu/dialog/da1469x/src/da1469x_otp.c
new file mode 100644
index 0000000..fbc0230
--- /dev/null
+++ b/hw/mcu/dialog/da1469x/src/da1469x_otp.c
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+#include <assert.h>
+#include "syscfg/syscfg.h"
+#include <mcu/mcu.h>
+#include "da1469x_priv.h"
+#include "mcu/da1469x_clock.h"
+#include <mcu/da1469x_otp.h>
+
+enum otpc_mode_val {
+    OTPC_MODE_PDOWN = 0,
+    OTPC_MODE_DSTBY,
+    OTPC_MODE_STBY,
+    OTPC_MODE_READ,
+    OTPC_MODE_PROG,
+    OTPC_MODE_PVFY,
+    OTPC_MODE_RINI,
+};
+
+static inline void
+da1469x_otp_set_mode(enum otpc_mode_val mode)
+{
+    OTPC->OTPC_MODE_REG = (OTPC->OTPC_MODE_REG &
+                           ~OTPC_OTPC_MODE_REG_OTPC_MODE_MODE_Msk) |
+                          (mode << OTPC_OTPC_MODE_REG_OTPC_MODE_MODE_Pos);
+    while (!(OTPC->OTPC_STAT_REG & OTPC_OTPC_STAT_REG_OTPC_STAT_MRDY_Msk));
+}
+
+
+int
+da1469x_otp_read(uint32_t offset, void *dst, uint32_t num_bytes)
+{
+    uint32_t *src_addr = (uint32_t *)(MCU_OTPM_BASE + offset);
+    uint32_t *dst_addr = dst;
+
+    if (offset >= MCU_OTPM_SIZE || (offset + num_bytes) >= MCU_OTPM_SIZE) {
+       return OTP_ERR_INVALID_ADDRESS;
+    }
+
+    if (num_bytes & 0x3) {
+       return OTP_ERR_INVALID_SIZE_ALIGNMENT;
+    }
+
+    /* Enable OTP clock and set mode to standby */
+    da1469x_clock_amba_enable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+
+    da1469x_otp_set_mode(OTPC_MODE_READ);
+
+    for (; num_bytes; dst_addr++, src_addr++, num_bytes -= 4) {
+        *dst_addr = *src_addr;
+    }
+
+    da1469x_otp_set_mode(OTPC_MODE_DSTBY);
+
+    /* Disable OTP clock */
+    da1469x_clock_amba_disable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+    return 0;
+}
+
+int
+da1469x_otp_write(uint32_t offset, const void *src, uint32_t num_bytes)
+{
+    uint32_t *dst_addr = (uint32_t *)(MCU_OTPM_BASE + offset);
+    const uint32_t *src_addr = src;
+    int ret = 0;
+
+    if (offset >= MCU_OTPM_SIZE || (offset + num_bytes) >= MCU_OTPM_SIZE) {
+       return OTP_ERR_INVALID_ADDRESS;
+    }
+
+    if (num_bytes & 0x3) {
+       return OTP_ERR_INVALID_SIZE_ALIGNMENT;
+    }
+
+    /* Enable OTP clock and set mode to standby */
+    da1469x_clock_amba_enable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+
+    do {
+        da1469x_otp_set_mode(OTPC_MODE_PROG);
+
+        /* wait for programming to go idle and data buffer to be empty */
+        while (!(OTPC->OTPC_STAT_REG & OTPC_OTPC_STAT_REG_OTPC_STAT_PRDY_Msk));
+        while (!(OTPC->OTPC_STAT_REG &
+                 OTPC_OTPC_STAT_REG_OTPC_STAT_PBUF_EMPTY_Msk));
+
+        /* fill data buffer with a word and trigger via the PADDR reg */
+        OTPC->OTPC_PWORD_REG = *src_addr;
+        OTPC->OTPC_PADDR_REG = ((uint32_t)dst_addr >> 2) &
+                               OTPC_OTPC_PADDR_REG_OTPC_PADDR_Msk;
+
+        while (!(OTPC->OTPC_STAT_REG & OTPC_OTPC_STAT_REG_OTPC_STAT_PRDY_Msk));
+
+        /* set mode to verify */
+        da1469x_otp_set_mode(OTPC_MODE_PVFY);
+
+        /* read data and compare to source */
+        if (*dst_addr != *src_addr) {
+            ret = OTP_ERR_PROGRAM_VERIFY_FAILED;
+            goto fail_write;
+        }
+
+        da1469x_otp_set_mode(OTPC_MODE_STBY);
+        num_bytes -= 4;
+        src_addr++;
+        dst_addr++;
+    } while (num_bytes);
+
+fail_write:
+
+    /* Disable OTP clock */
+    da1469x_clock_amba_disable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+
+    return ret;
+}
+
+void
+da1469x_otp_init(void)
+{
+    /* Enable OTP clock and set mode to standby */
+    da1469x_clock_amba_enable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+
+    da1469x_otp_set_mode(OTPC_MODE_STBY);
+
+    /* set clk timing */
+    OTPC->OTPC_TIM1_REG = 0x0999101f;  /* 32 MHz default */
+    OTPC->OTPC_TIM2_REG = 0xa4040409;
+
+    /* Disable OTP clock */
+    da1469x_clock_amba_disable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+}
diff --git a/hw/mcu/dialog/da1469x/src/system_da1469x.c b/hw/mcu/dialog/da1469x/src/system_da1469x.c
index e75c660..89d04f6 100644
--- a/hw/mcu/dialog/da1469x/src/system_da1469x.c
+++ b/hw/mcu/dialog/da1469x/src/system_da1469x.c
@@ -22,6 +22,7 @@
 #include "mcu/da1469x_pdc.h"
 #include "mcu/da1469x_prail.h"
 #include "mcu/da1469x_clock.h"
+#include <mcu/da1469x_otp.h>
 #include "mcu/mcu.h"
 #include "da1469x_priv.h"
 
@@ -102,14 +103,8 @@ void SystemInit(void)
     CRG_TOP->PMU_CTRL_REG |= CRG_TOP_PMU_CTRL_REG_RETAIN_CACHE_Msk;
 #endif
 
-
-    /* Switch OTPC to deep standby (DSTBY) mode */
-    da1469x_clock_amba_enable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
-    OTPC->OTPC_MODE_REG = (OTPC->OTPC_MODE_REG &
-                           ~OTPC_OTPC_MODE_REG_OTPC_MODE_MODE_Msk) |
-                          (1 << OTPC_OTPC_MODE_REG_OTPC_MODE_MODE_Pos);
-    while (!(OTPC->OTPC_STAT_REG & OTPC_OTPC_STAT_REG_OTPC_STAT_MRDY_Msk));
-    da1469x_clock_amba_disable(CRG_TOP_CLK_AMBA_REG_OTP_ENABLE_Msk);
+    /* initialize OTP and place in deep standby */
+    da1469x_otp_init();
 
     /* Initialize and configure power rails */
     da1469x_prail_initialize();