-
Notifications
You must be signed in to change notification settings - Fork 15
[ln882h] Add Lightning LN882H support #52
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
Merged
Merged
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
00606f1
ln882h support: first version
lamauny 26af2ea
removed ln882x sdk python scirpts from cli & cleanup
lamauny e4053ba
ln882x : added generation of partcfg json file & ota file
lamauny 6730162
removed test files, oops
lamauny e2aa33c
ln882x: cleanup flash tools because not implemented
lamauny b83eca2
ln882x: fix check ota size
lamauny 6d024eb
[ln882x] update firmware binaries generation with sparate partition i…
lamauny 8b8482a
[ln882h] changed ln882x->ln882h; first flash tools release
lamauny 9b100a0
[ln882h] reformat
lamauny 35f85bb
[ln882h] reformat
lamauny 6e53696
Merge branch 'libretiny-eu:master' into feature/ln882x
lamauny c183945
[ln822h] add ramcode binary
lamauny f25d661
Reformat Python files
kuba2k2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 LN882hMain | ||
|
|
||
| __all__ = [ | ||
| "LN882hMain", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| # Copyright (c) Etienne Le Cousin 2025-01-02. | ||
|
|
||
| from abc import ABC | ||
| from logging import warning | ||
| from os import stat | ||
| from os.path import dirname, isfile | ||
| from shutil import copyfile | ||
| from typing import List | ||
|
|
||
| from ltchiptool import SocInterface | ||
| from ltchiptool.util.fileio import chext, chname | ||
| from ltchiptool.util.fwbinary import FirmwareBinary | ||
|
|
||
| from .util import OTATOOL, MakeImageTool | ||
| from .util.models import PartDescInfo, part_type_str2num | ||
|
|
||
|
|
||
| class LN882hBinary(SocInterface, ABC): | ||
| def elf2bin(self, input: str, ota_idx: int) -> List[FirmwareBinary]: | ||
| toolchain = self.board.toolchain | ||
| flash_layout = self.board["flash"] | ||
|
|
||
| # find bootloader image | ||
| input_boot = chname(input, "boot.bin") | ||
| if not isfile(input_boot): | ||
| raise FileNotFoundError("Bootloader image not found") | ||
|
|
||
| # build output names | ||
| output = FirmwareBinary( | ||
| location=input, | ||
| name="firmware", | ||
| offset=0, | ||
| title="Flash Image", | ||
| description="Complete image with boot for flashing at offset 0", | ||
| public=True, | ||
| ) | ||
| out_boot = FirmwareBinary( | ||
| location=input, | ||
| name="boot", | ||
| offset=self.board.region("boot")[0], | ||
| title="Bootloader Image", | ||
| ) | ||
| out_ptab = FirmwareBinary( | ||
| location=input, | ||
| name="part_tab", | ||
| offset=self.board.region("part_tab")[0], | ||
| title="Partition Table", | ||
| ) | ||
| out_app = FirmwareBinary( | ||
| location=input, | ||
| name="app", | ||
| offset=self.board.region("app")[0], | ||
| title="Application Image", | ||
| description="Firmware partition image for direct flashing", | ||
| public=True, | ||
| ) | ||
| out_ota = FirmwareBinary( | ||
| location=input, | ||
| name="ota", | ||
| offset=self.board.region("ota")[0], | ||
| title="OTA Image", | ||
| description="Compressed App image for OTA flashing", | ||
| public=True, | ||
| ) | ||
| # print graph element | ||
| output.graph(1) | ||
|
|
||
| input_bin = chext(input, "bin") | ||
| # objcopy ELF -> raw BIN | ||
| toolchain.objcopy(input, input_bin) | ||
|
|
||
| # Make Image Tool | ||
| # fmt: off | ||
| mkimage = MakeImageTool() | ||
| mkimage.boot_filepath = input_boot | ||
| mkimage.app_filepath = input_bin | ||
| mkimage.flashimage_filepath = output.path | ||
| mkimage.ver_str = "1.0" | ||
| mkimage.swd_crp = 0 | ||
| mkimage.readPartCfg = lambda : True | ||
| # fmt: off | ||
|
|
||
| # find all partitions | ||
| for name, layout in flash_layout.items(): | ||
| (offset, _, length) = layout.partition("+") | ||
| part_info = PartDescInfo( | ||
| parttype = part_type_str2num(name.upper()), | ||
| startaddr = int(offset, 16), | ||
| partsize = int(length, 16) | ||
| ) | ||
| mkimage._MakeImageTool__part_desc_info_list.append(part_info) | ||
|
|
||
| if not mkimage.doAllWork(): | ||
| raise RuntimeError("MakeImageTool: Fail to generate image") | ||
|
|
||
| # write all parts to files | ||
| with out_boot.write() as f: | ||
| f.write(mkimage._MakeImageTool__partbuf_bootram) | ||
| with out_ptab.write() as f: | ||
| f.write(mkimage._MakeImageTool__partbuf_parttab) | ||
| with out_app.write() as f: | ||
| f.write(mkimage._MakeImageTool__partbuf_app) | ||
|
|
||
| # Make ota image | ||
| ota_tool = OTATOOL() | ||
| ota_tool.input_filepath = output.path | ||
| ota_tool.output_dir = dirname(input) | ||
| if not ota_tool.doAllWork(): | ||
| raise RuntimeError("MakeImageTool: Fail to generate OTA image") | ||
|
|
||
| copyfile(ota_tool.output_filepath, out_ota.path) | ||
| _, ota_size, _ = self.board.region("ota") | ||
| if stat(out_ota.path).st_size > ota_size: | ||
| warning( | ||
| f"OTA size too large: {out_ota.filename} > {ota_size} (0x{ota_size:X})" | ||
| ) | ||
|
|
||
| return output.group() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| # Copyright (c) Etienne Le Cousin 2025-01-02. | ||
|
|
||
| from abc import ABC | ||
| from binascii import crc32 | ||
| 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 | ||
|
|
||
| from .util.ln882htool import LN882hTool | ||
|
|
||
| LN882H_GUIDE = [ | ||
| "Connect UART1 of the LN882h to the USB-TTL adapter:", | ||
| [ | ||
| ("PC", "LN882h"), | ||
| ("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 BOOT pin (GPIOA9) to GND with a wire.", | ||
| ] | ||
|
|
||
|
|
||
| class LN882hFlash(SocInterface, ABC): | ||
| ln882h: Optional[LN882hTool] = None | ||
| info: List[Tuple[str, str]] = None | ||
|
|
||
| def flash_get_features(self) -> FlashFeatures: | ||
| return FlashFeatures() | ||
|
|
||
| def flash_get_guide(self) -> List[Union[str, list]]: | ||
| return LN882H_GUIDE | ||
|
|
||
| def flash_get_docs_url(self) -> Optional[str]: | ||
| return "https://docs.libretiny.eu/link/flashing-ln882h" | ||
|
|
||
| def flash_set_connection(self, connection: FlashConnection) -> None: | ||
| if self.conn: | ||
| self.flash_disconnect() | ||
| self.conn = connection | ||
| self.conn.fill_baudrate(115200) | ||
|
|
||
| def flash_build_protocol(self, force: bool = False) -> None: | ||
| if not force and self.ln882h: | ||
| return | ||
| self.flash_disconnect() | ||
| self.ln882h = LN882hTool( | ||
| port=self.conn.port, | ||
| baudrate=self.conn.link_baudrate, | ||
| ) | ||
| self.flash_change_timeout(self.conn.timeout, self.conn.link_timeout) | ||
|
|
||
| def flash_change_timeout(self, timeout: float = 0.0, link_timeout: float = 0.0): | ||
| self.flash_build_protocol() | ||
| if timeout: | ||
| self.ln882h.read_timeout = timeout | ||
| self.conn.timeout = timeout | ||
| if link_timeout: | ||
| self.ln882h.link_timeout = link_timeout | ||
| self.conn.link_timeout = link_timeout | ||
|
|
||
| def flash_connect(self, callback: ProgressCallback = ProgressCallback()) -> None: | ||
| if self.ln882h and self.conn.linked: | ||
| return | ||
| self.flash_build_protocol() | ||
| assert self.ln882h | ||
| self.ln882h.link() | ||
|
|
||
| def cb(i, n, t, sent): | ||
| callback.on_update(sent - cb.total_sent) | ||
| cb.total_sent = sent | ||
|
|
||
| cb.total_sent = 0 | ||
|
|
||
| callback.on_message(f"Loading Ram Code") | ||
| self.ln882h.ram_boot(cb) | ||
| self.conn.linked = True | ||
|
|
||
| def flash_disconnect(self) -> None: | ||
| if self.ln882h: | ||
| self.ln882h.close() | ||
| self.ln882h = None | ||
| if self.conn: | ||
| self.conn.linked = False | ||
|
|
||
| def flash_get_chip_info(self) -> List[Tuple[str, str]]: | ||
| if self.info: | ||
| return self.info | ||
| self.flash_connect() | ||
| assert self.ln882h | ||
|
|
||
| flash_info = self.ln882h.command("flash_info")[-1] | ||
| flash_info = dict(s.split(":") for s in flash_info.split(",")) | ||
|
|
||
| self.info = [ | ||
| ("Flash ID", flash_info["id"]), | ||
| ("Flash Size", flash_info["flash size"]), | ||
| ("Flash UUID", self.ln882h.command("flash_uid")[-1][10:]), | ||
| ("OTP MAC", self.ln882h.command("get_mac_in_flash_otp")[-2]), | ||
| ] | ||
| return self.info | ||
|
|
||
| def flash_get_chip_info_string(self) -> str: | ||
| self.flash_connect() | ||
| assert self.ln882h | ||
| return "LN882H" | ||
|
|
||
| def flash_get_size(self, memory: FlashMemoryType = FlashMemoryType.FLASH) -> int: | ||
| self.flash_connect() | ||
| assert self.ln882h | ||
| if memory == FlashMemoryType.EFUSE: | ||
| raise NotImplementedError("Memory type not readable via UART") | ||
| else: | ||
| # It appears that flash size is coded in the low byte of flash ID as 2^X | ||
| # Ex: LN882HKI id=0xEB6015 --> 0x15 = 21 --> flash_size = 2^21 = 2MB | ||
| flash_info = self.ln882h.command("flash_info")[-1] | ||
| flash_info = dict(s.split(":") for s in flash_info.split(",")) | ||
| flash_size = 1 << (int(flash_info["id"], 16) & 0xFF) | ||
| return flash_size | ||
|
|
||
| def flash_read_raw( | ||
| self, | ||
| offset: int, | ||
| length: int, | ||
| verify: bool = True, | ||
| memory: FlashMemoryType = FlashMemoryType.FLASH, | ||
| callback: ProgressCallback = ProgressCallback(), | ||
| ) -> Generator[bytes, None, None]: | ||
| self.flash_connect() | ||
| assert self.ln882h | ||
| if memory == FlashMemoryType.EFUSE: | ||
| raise NotImplementedError("Memory type not readable via UART") | ||
| else: | ||
| gen = self.ln882h.flash_read( | ||
| offset=offset, | ||
| length=length, | ||
| verify=verify, | ||
| ) | ||
| yield from callback.update_with(gen) | ||
|
|
||
| def flash_write_raw( | ||
| self, | ||
| offset: int, | ||
| length: int, | ||
| data: IO[bytes], | ||
| verify: bool = True, | ||
| callback: ProgressCallback = ProgressCallback(), | ||
| ) -> None: | ||
| self.flash_connect() | ||
| assert self.ln882h | ||
|
|
||
| def cb(i, n, t, sent): | ||
| callback.on_update(sent - cb.total_sent) | ||
| cb.total_sent = sent | ||
|
|
||
| cb.total_sent = 0 | ||
|
|
||
| self.ln882h.flash_write( | ||
| offset=offset, | ||
| stream=data, | ||
| callback=cb, | ||
| ) | ||
|
|
||
| def flash_write_uf2( | ||
| self, | ||
| ctx: UploadContext, | ||
| verify: bool = True, | ||
| callback: ProgressCallback = ProgressCallback(), | ||
| ) -> None: | ||
| # collect continuous blocks of data (before linking, as this takes time) | ||
| parts = ctx.collect_data(OTAScheme.FLASHER_SINGLE) | ||
| callback.on_total(sum(len(part.getvalue()) for part in parts.values())) | ||
|
|
||
| # connect to chip | ||
| self.flash_connect() | ||
|
|
||
| # write blocks to flash | ||
| for offset, data in parts.items(): | ||
| length = len(data.getvalue()) | ||
| data.seek(0) | ||
| callback.on_message(f"Writing (0x{offset:06X})") | ||
| gen = self.flash_write_raw( | ||
| offset=offset, | ||
| data=data, | ||
| length=length, | ||
| callback=callback, | ||
| ) | ||
|
|
||
| callback.on_message("Booting firmware") | ||
| # reboot the chip | ||
| self.ln882h.disconnect() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.