Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lightning ln882x support #52

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ltchiptool

Universal, easy-to-use GUI flashing/dumping tool for BK7231, RTL8710B and RTL8720C. Also contains some CLI utilities for binary firmware manipulation.
Universal, easy-to-use GUI flashing/dumping tool for BK7231, LN882H, RTL8710B and RTL8720C. Also contains some CLI utilities for binary firmware manipulation.

<div align="center">

Expand Down
4 changes: 4 additions & 0 deletions ltchiptool/soc/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def get(cls, family: Family) -> "SocInterface":
if family.is_child_of("realtek-ambz2"):
from .ambz2 import AmebaZ2Main
return AmebaZ2Main(family)
if family.is_child_of("lightning-ln882x"):
from .ln882x import LN882xMain
return LN882xMain(family)
# fmt: on
raise NotImplementedError(f"Unsupported family - {family.name}")

Expand All @@ -38,6 +41,7 @@ def get_family_names(cls) -> List[str]:
"beken-72xx",
"realtek-ambz",
"realtek-ambz2",
"lightning-ln882x",
]

#########################
Expand Down
7 changes: 7 additions & 0 deletions ltchiptool/soc/ln882x/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Etienne Le Cousin 2025-01-02.

from .main import LN882xMain

__all__ = [
"LN882xMain",
]
98 changes: 98 additions & 0 deletions ltchiptool/soc/ln882x/binary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright (c) Etienne Le Cousin 2025-01-02.

from abc import ABC
from datetime import datetime
from logging import warning
from os import stat
from os.path import basename, dirname, join, realpath
from typing import IO, List, Optional, Union
import json

from ltchiptool import SocInterface
from ltchiptool.util.fileio import chext
from ltchiptool.util.fwbinary import FirmwareBinary
from ltchiptool.util.lvm import LVM

from .util import MakeImageTool, OTATOOL


class LN882xBinary(SocInterface, ABC):
def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]:
toolchain = self.board.toolchain

bootfile = join(LVM.get().path(), f"cores", self.family.name, f"misc", self.board["build.bootfile"])
part_cfg = join(dirname(input), "flash_partition_cfg.json")

self.gen_partcfg_json(part_cfg)

# build output names
output_fw = FirmwareBinary(
location=input,
name=f"firmware",
subname="",
ext="bin",
title="Flash Image",
description="Complete image with boot for flashing at offset 0",
public=True,
)

fw_bin = chext(input, "bin")
# objcopy ELF -> raw BIN
toolchain.objcopy(input, fw_bin)

# Make firmware image
mkimage = MakeImageTool()
mkimage.boot_filepath = bootfile
mkimage.app_filepath = fw_bin
mkimage.flashimage_filepath = output_fw.path
mkimage.part_cfg_filepath = part_cfg
mkimage.ver_str = "1.0"
mkimage.swd_crp = 0
mkimage.doAllWork()

# Make ota image
ota_tool = OTATOOL()
ota_tool.input_filepath = output_fw.path
ota_tool.output_dir = dirname(input)
ota_tool.doAllWork()

output_ota = FirmwareBinary.load(
location = ota_tool.output_filepath,
obj = {
"filename": basename(ota_tool.output_filepath),
"title": "Flash OTA Image",
"description": "Compressed App image for OTA flashing",
"public": True,
}
)
_, ota_size, _ = self.board.region("ota")
if stat(ota_tool.output_filepath).st_size > ota_size:
warning(
f"OTA size too large: {ota_tool.output_filepath} > {ota_size} (0x{ota_size:X})"
)

return output_fw.group()

def gen_partcfg_json(self, output: str):
flash_layout = self.board["flash"]

# find all partitions
partitions = []
for name, layout in flash_layout.items():
part = {}
(offset, _, length) = layout.partition("+")
offset = int(offset, 16)
length = int(length, 16)
part["partition_type"] = name.upper()
part["start_addr"] = f"0x{offset:08X}"
part["size_KB"] = length // 1024
partitions.append(part)

partcfg: dict = {
"vendor_define": [], # boot and part_tab should be there but it's not needed
"user_define": partitions # so put all partitions in user define
}
# export file
with open(output, "w") as f:
json.dump(partcfg, f, indent="\t")

48 changes: 48 additions & 0 deletions ltchiptool/soc/ln882x/flash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) Etienne Le Cousin 2025-01-02.

import logging
import struct
from abc import ABC
from binascii import crc32
from logging import DEBUG, debug, warning
from typing import IO, Generator, List, Optional, Tuple, Union

from ltchiptool import SocInterface
from ltchiptool.util.flash import FlashConnection, FlashFeatures, FlashMemoryType
from ltchiptool.util.intbin import gen2bytes, inttole32
from ltchiptool.util.logging import VERBOSE, verbose
from ltchiptool.util.misc import sizeof
from ltchiptool.util.streams import ProgressCallback
from uf2tool import OTAScheme, UploadContext

LN882x_GUIDE = [
"Connect UART1 of the LN882x to the USB-TTL adapter:",
[
("PC", "LN882x"),
("RX", "TX1 (GPIOA2 / P2)"),
("TX", "RX1 (GPIOA3 / P3)"),
("", ""),
("GND", "GND"),
],
"Using a good, stable 3.3V power supply is crucial. Most flashing issues\n"
"are caused by either voltage drops during intensive flash operations,\n"
"or bad/loose wires.",
"The UART adapter's 3.3V power regulator is usually not enough. Instead,\n"
"a regulated bench power supply, or a linear 1117-type regulator is recommended.",
"To enter download mode, the chip has to be rebooted while the flashing program\n"
"is trying to establish communication.\n"
"In order to do that, you need to bridge CEN/BOOT pin (GPIOA9) to GND with a wire.",
]


class LN882xFlash(SocInterface, ABC):
info: List[Tuple[str, str]] = None

def flash_get_features(self) -> FlashFeatures:
return FlashFeatures()

def flash_get_guide(self) -> List[Union[str, list]]:
return LN882X_GUIDE

def flash_get_docs_url(self) -> Optional[str]:
return "https://docs.libretiny.eu/link/flashing-ln882x"
34 changes: 34 additions & 0 deletions ltchiptool/soc/ln882x/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) Etienne Le Cousin 2025-01-02.

from abc import ABC
from logging import info
from typing import Optional

from ltchiptool import Family
from ltchiptool.models import OTAType
from ltchiptool.soc import SocInterfaceCommon

from .binary import LN882xBinary
from .flash import LN882xFlash


class LN882xMain(
LN882xBinary,
LN882xFlash,
SocInterfaceCommon,
ABC,
):
def __init__(self, family: Family) -> None:
super().__init__()
self.family = family

def hello(self):
info("Hello from LN882x")

@property
def ota_type(self) -> Optional[OTAType]:
return OTAType.SINGLE

@property
def ota_supports_format_1(self) -> bool:
return True
9 changes: 9 additions & 0 deletions ltchiptool/soc/ln882x/util/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Etienne Le Cousin 2025-01-02.

from .makeimage import MakeImageTool
from .ota_image_generator import OTATOOL

__all__ = [
"MakeImageTool",
"OTATOOL",
]
122 changes: 122 additions & 0 deletions ltchiptool/soc/ln882x/util/boot_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#
# Copyright 2021 Shanghai Lightning Semiconductor Technology Co., LTD

# Licensed 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 zlib
import struct
from .ln_tools import *


class BootHeader:

BOOT_HEADER_SIZE = (4 + 2 + 2 + 4 * 4)

CRP_VALID_FLAG = 0x46505243

BOOT_START_ADDR = 0
BOOT_SIZE_LIMIT = (1024 * 24)

def __init__(self, other_buf) -> None:
self.__bootram_target_addr = 0
self.__bootram_bin_length = 0 # 2bytes
self.__bootram_crc_offset = 0 # 2bytes
self.__bootram_crc_value = 0
self.__bootram_vector_addr = 0
self.__crp_flag = 0
self.__boot_header_crc = 0

if not (isinstance(other_buf, bytearray) or isinstance(other_buf, bytes)):
raise TypeError("Error: other_buf MUST be a bytearray or bytes!!!")

if len(other_buf) < BootHeader.BOOT_HEADER_SIZE:
raise ValueError("Error: other_buf MUST have at least {} bytes!!!".format(BootHeader.BOOT_HEADER_SIZE))

self.__buffer = bytearray(BootHeader.BOOT_HEADER_SIZE)
self.__buffer[:] = other_buf[0:BootHeader.BOOT_HEADER_SIZE]

items = struct.unpack("<I2H4I", self.__buffer)
self.__bootram_target_addr = items[0]
self.__bootram_bin_length = items[1]
self.__bootram_crc_offset = items[2]
self.__bootram_crc_value = items[3]
self.__bootram_vector_addr = items[4]
self.__crp_flag = items[5]
self.__boot_header_crc = items[6]

def toByteArray(self) -> bytearray:
struct.pack_into("<I2H4I", self.__buffer, 0,
self.bootram_target_addr,
self.bootram_bin_length, self.bootram_crc_offset,
self.bootram_crc_value, self.bootram_vector_addr, self.crp_flag, self.boot_header_crc)
self.__boot_header_crc = zlib.crc32(self.__buffer[0:(BootHeader.BOOT_HEADER_SIZE-4)])
struct.pack_into("<I2H4I", self.__buffer, 0,
self.bootram_target_addr,
self.bootram_bin_length, self.bootram_crc_offset,
self.bootram_crc_value, self.bootram_vector_addr, self.crp_flag, self.boot_header_crc)
return self.__buffer

@property
def bootram_target_addr(self):
return self.__bootram_target_addr

@property
def bootram_bin_length(self):
return self.__bootram_bin_length

@bootram_bin_length.setter
def bootram_bin_length(self, length):
if isinstance(length, int):
self.__bootram_bin_length = length
else:
raise TypeError("length MUST be int type!!!")

@property
def bootram_crc_offset(self):
return self.__bootram_crc_offset

@property
def bootram_crc_value(self):
return self.__bootram_crc_value

@bootram_crc_value.setter
def bootram_crc_value(self, val):
if isinstance(val, int):
self.__bootram_crc_value = val
else:
raise TypeError("crc MUST be int type!!!")

@property
def bootram_vector_addr(self):
return self.__bootram_vector_addr

@property
def crp_flag(self):
return self.__crp_flag

@crp_flag.setter
def crp_flag(self, crp):
if isinstance(crp, int):
if (crp == 0) or (crp == 1) or (crp == self.CRP_VALID_FLAG):
self.__crp_flag = crp
else:
raise ValueError("Error: crp MUST be 0 or 1!!!")
else:
raise TypeError("Error: crp MUST be int type!!!")

@property
def boot_header_crc(self):
self.__boot_header_crc = zlib.crc32(self.__buffer[0:(BootHeader.BOOT_HEADER_SIZE-4)])
return self.__boot_header_crc
Loading