7
31
Fork
You've already forked blackmagic
23

Need support for more TI Tiva TM4C129X "Snowflake" parts. #2280

Closed
opened 2026年06月29日 16:56:16 +02:00 by rbsexton · 6 comments

I've been using these parts for well over a decade and they're good, if perhaps not popular with amateur makers. I built product around the original Stellaris parts. Right now I'm porting Zephyr to the TM4C129X, so for the first time in forever I need an external debugger.

This is a close relative of other parts in the Stellaris Lineage, although it has a notable difference in that the flash memory controller has a different erase page size. You can program it with the wrong page size, but it goes very slowly. One future enhancement is that it's got a fast programming mode. Maybe later.

I've already modified a local copy of the FW for the Snowflake part on my desk. I have a working probe.

I'm working up a MR. I'm having a little trouble getting it rebased on to the current main, but I'll resolve that soon.

Here's the summary.

Table Driven device information

The existing case switching code should move to a table driven solution to make it easier to add other related parts. That also simplified the probe code and probably makes it shorter by eliminating complex case statements with embedded function calls.


/* Stellaris Fury/DustDevil */
#define LMI_DID1_LM3S3748 0x1049U
#define LMI_DID1_LM3S5732 0x1096U
#define LMI_DID1_LM3S8962 0x10a6U
/* Tiva-C Blizzard (TM4C123) */
#define LMI_DID1_TM4C123GH6PM 0x10a1U
#define LMI_DID1_TM4C1230C3PM 0x1022U
/* Tiva-C Snowflake (TM4C129x) */
#define LMI_DID1_TM4C1294NCPDT 0x101fU
#define LMI_DID1_TM4C129XNCZAD 0x1032U
#define LMI_DID1_TM4C1294KCPDT 0x1034U

 typedef struct {
	uint16_t did1;
	uint32_t ram_size;
	uint32_t flash_size;
	uint32_t block_size;
	const char *driver;
	uint32_t target_options;
	uint32_t dp_quirks;
} lmi_device_s;
/* To add a new part: verify the DID1 upper 16 bits from the device datasheet and add a row. */
static const lmi_device_s lmi_devices[] = {
	/* Stellaris Fury/DustDevil — 1 KiB erase blocks */
	{LMI_DID1_LM3S3748, 0x10000U, 0x020000U, 0x400U, "Stellaris", 0, 0},
	{LMI_DID1_LM3S5732, 0x10000U, 0x020000U, 0x400U, "Stellaris", 0, 0},
	{LMI_DID1_LM3S8962, 0x10000U, 0x040000U, 0x400U, "Stellaris", 0, 0},
	/* Tiva-C Blizzard (TM4C123) — 1 KiB erase blocks */
	{LMI_DID1_TM4C123GH6PM, 0x10000U, 0x080000U, 0x400U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP},
	{LMI_DID1_TM4C1230C3PM, 0x06000U, 0x010000U, 0x400U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP},
	/* Tiva-C Snowflake (TM4C129x) — 16 KiB erase blocks */
	{LMI_DID1_TM4C1294KCPDT, 0x40000U, 0x080000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP},
	{LMI_DID1_TM4C1294NCPDT, 0x40000U, 0x100000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP},
	{LMI_DID1_TM4C129XNCZAD, 0x40000U, 0x100000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP},
};

This works unchanged with the existing lmi_flash_erase(), which erases in blocks.

Order of operations discrepancy in the flash stub.

Per the data sheet, the device requires Data, Address, Control. The existing stub writes Address, Data, Control. Existing devices obviously work, that might just be luck.

Also, the existing stub is names stm32f1_flash_write_stub(), which seems wrong.

Correct per data sheet:


void __attribute__((naked))
lmi_flash_write_stub(const uint32_t *const dest, const uint32_t *const src, const uint32_t size)
{
	for (uint32_t i = 0; i < (size / 4U); ++i) {
		/* §8.2.3.10 (TM4C129X datasheet): write FMD before FMA, then trigger via FMC */
		/* Also checked against 6965 and 9B92 Sandstorm/Fury */ 
		LMI_FLASH_FMD = src[i];
		LMI_FLASH_FMA = (uintptr_t)(dest + i);
		LMI_FLASH_FMC = LMI_FLASH_FMC_WRKEY | LMI_FLASH_FMC_WRITE;
		while (LMI_FLASH_FMC & LMI_FLASH_FMC_WRITE)
			continue;
	}
	stub_exit(0);
}
I've been using these parts for well over a decade and they're good, if perhaps not popular with amateur makers. I built product around the original Stellaris parts. Right now I'm porting Zephyr to the TM4C129X, so for the first time in forever I need an external debugger. This is a close relative of other parts in the Stellaris Lineage, although it has a notable difference in that the flash memory controller has a different erase page size. You can program it with the wrong page size, but it goes very slowly. One future enhancement is that it's got a fast programming mode. Maybe later. I've already modified a local copy of the FW for the Snowflake part on my desk. I have a working probe. I'm working up a MR. I'm having a little trouble getting it rebased on to the current main, but I'll resolve that soon. Here's the summary. ### Table Driven device information The existing case switching code should move to a table driven solution to make it easier to add other related parts. That also simplified the probe code and probably makes it shorter by eliminating complex case statements with embedded function calls. ```C /* Stellaris Fury/DustDevil */ #define LMI_DID1_LM3S3748 0x1049U #define LMI_DID1_LM3S5732 0x1096U #define LMI_DID1_LM3S8962 0x10a6U /* Tiva-C Blizzard (TM4C123) */ #define LMI_DID1_TM4C123GH6PM 0x10a1U #define LMI_DID1_TM4C1230C3PM 0x1022U /* Tiva-C Snowflake (TM4C129x) */ #define LMI_DID1_TM4C1294NCPDT 0x101fU #define LMI_DID1_TM4C129XNCZAD 0x1032U #define LMI_DID1_TM4C1294KCPDT 0x1034U typedef struct { uint16_t did1; uint32_t ram_size; uint32_t flash_size; uint32_t block_size; const char *driver; uint32_t target_options; uint32_t dp_quirks; } lmi_device_s; /* To add a new part: verify the DID1 upper 16 bits from the device datasheet and add a row. */ static const lmi_device_s lmi_devices[] = { /* Stellaris Fury/DustDevil — 1 KiB erase blocks */ {LMI_DID1_LM3S3748, 0x10000U, 0x020000U, 0x400U, "Stellaris", 0, 0}, {LMI_DID1_LM3S5732, 0x10000U, 0x020000U, 0x400U, "Stellaris", 0, 0}, {LMI_DID1_LM3S8962, 0x10000U, 0x040000U, 0x400U, "Stellaris", 0, 0}, /* Tiva-C Blizzard (TM4C123) — 1 KiB erase blocks */ {LMI_DID1_TM4C123GH6PM, 0x10000U, 0x080000U, 0x400U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP}, {LMI_DID1_TM4C1230C3PM, 0x06000U, 0x010000U, 0x400U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP}, /* Tiva-C Snowflake (TM4C129x) — 16 KiB erase blocks */ {LMI_DID1_TM4C1294KCPDT, 0x40000U, 0x080000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP}, {LMI_DID1_TM4C1294NCPDT, 0x40000U, 0x100000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP}, {LMI_DID1_TM4C129XNCZAD, 0x40000U, 0x100000U, 0x4000U, "Tiva-C", TOPT_INHIBIT_NRST, ADIV5_DP_QUIRK_DUPED_AP}, }; ``` This works unchanged with the existing *lmi_flash_erase()*, which erases in blocks. ### Order of operations discrepancy in the flash stub. Per the data sheet, the device requires Data, Address, Control. The existing stub writes Address, Data, Control. Existing devices obviously work, that might just be luck. Also, the existing stub is names stm32f1_flash_write_stub(), which seems wrong. Correct per data sheet: ```C void __attribute__((naked)) lmi_flash_write_stub(const uint32_t *const dest, const uint32_t *const src, const uint32_t size) { for (uint32_t i = 0; i < (size / 4U); ++i) { /* §8.2.3.10 (TM4C129X datasheet): write FMD before FMA, then trigger via FMC */ /* Also checked against 6965 and 9B92 Sandstorm/Fury */ LMI_FLASH_FMD = src[i]; LMI_FLASH_FMA = (uintptr_t)(dest + i); LMI_FLASH_FMC = LMI_FLASH_FMC_WRKEY | LMI_FLASH_FMC_WRITE; while (LMI_FLASH_FMC & LMI_FLASH_FMC_WRITE) continue; } stub_exit(0); } ```

Wrt the order of operations, the reason the stub works is nothing happens in the controller until the control register is written - thus you can write the data and address registers in either order. This isn't just luck, this is how the hardware design works. The control register write is the only point the part actually looks at the other two and does anything with them.

We look forward to this additional support landing - exciting!

Wrt the order of operations, the reason the stub works is nothing happens in the controller until the control register is written - thus you can write the data and address registers in either order. This isn't just luck, this is how the hardware design works. The control register write is the only point the part actually looks at the other two and does anything with them. We look forward to this additional support landing - exciting!

I didn't write the HDL for the Snowflake Flash memory controller so I can't say with any authority whether 'write them in any order' is true for all use cases on these devices. The language of the Snowflake data sheet is oddly specific, so I've opted to adhere strictly to it.

I didn't write the HDL for the Snowflake Flash memory controller so I can't say with any authority whether 'write them in any order' is true for all use cases on these devices. The language of the Snowflake data sheet is oddly specific, so I've opted to adhere strictly to it.

Vendors often get oddly specific like that without there being underlying hardware reason - usually because they want to highlight that a couple of registers have to be written in some order before another..

It would be quite die-space-intensive to build a Flash controller that actually had a strict ordering on 3 registers rather than two just being setup, and the third actually executing the action. Such a restriction doesn't make sense from a silicon design perspective - which is why we're saying it really won't matter if it's Address then Data, or Data then Address. The most logical hardware design has the two registers feeding into the addressing and programming machinery and getting latched/fed into that machinery when writing the go bit for the write in the control register, executing the action that's been set up.

Vendors often get oddly specific like that without there being underlying hardware reason - usually because they want to highlight that a couple of registers have to be written in some order before another.. It would be quite die-space-intensive to build a Flash controller that actually had a strict ordering on 3 registers rather than two just being setup, and the third actually executing the action. Such a restriction doesn't make sense from a silicon design perspective - which is why we're saying it really won't matter if it's Address then Data, or Data then Address. The most logical hardware design has the two registers feeding into the addressing and programming machinery and getting latched/fed into that machinery when writing the go bit for the write in the control register, executing the action that's been set up.

I agree with you about the language in the data sheet. It's likely to be overly cautious.

I can think of a handful of ways for this IP to be order-sensitive at the cost of a few flip-flops. I've written IP like that myself. The foundry flash IP that I'm most familiar with has interesting timing requirements, so I don't find it all that improbable that you could have register order sensitivity.

I also know that I've seen these very sorts of register order sensitivities in my own companies' SOCs. There are errata for devices in this family related to write timing.

I try to write the code the way the vendor tells me to write it, and thats the professional expectation in my company. I'm inclined to adhere to the data sheet regardless of my profession options about how the IP probably works.

I agree with you about the language in the data sheet. It's likely to be overly cautious. I can think of a handful of ways for this IP to be order-sensitive at the cost of a few flip-flops. I've written IP like that myself. The foundry flash IP that I'm most familiar with has interesting timing requirements, so I don't find it all that improbable that you could have register order sensitivity. I also know that I've seen these very sorts of register order sensitivities in my own companies' SOCs. There are errata for devices in this family related to write timing. I try to write the code the way the vendor tells me to write it, and thats the professional expectation in my company. I'm inclined to adhere to the data sheet regardless of my profession options about how the IP probably works.

What we're generally trying to say is that.. unless you have found an actual example where the order actually matters and the stub is wrong, we would prefer not changing the stub - it's presently working, and how the register descriptions themselves in the datasheet say it should, and performs well.. changing the stub on a maybe just in case and then having to re-test all supported parts is a large maintenance burden if there's no actual counter-example.

What we're generally trying to say is that.. unless you have found an actual example where the order actually matters and the stub is wrong, we would prefer not changing the stub - it's presently working, and how the register descriptions themselves in the datasheet say it should, and performs well.. changing the stub on a maybe just in case and then having to re-test all supported parts is a large maintenance burden if there's no actual counter-example.

Fair enough! I'll remove the order of operations change and re-test.

Fair enough! I'll remove the order of operations change and re-test.
Sign in to join this conversation.
No Branch/Tag specified
main
fix/pre-bmp-v3-cross-file-cleanup
feature/bmda-remote-comms
ALTracer/feature/aarch64-ident
feature/better-meson-optimisation-handling
feature/am335x-support
feature/esp32-c3-support
feature/cortex-ar-software-breakpoints
feature/unit-testing
feature/windows-usb-serial-interface-naming
fix/bmp-external-spi
ALTracer/feature/bluepillplus-platform
ALTracer/feature/at32f43x-unrdp
feature/const-correctness
ALTracer/feature/fault_handlers
ALTracer/feature/hazard3-ice40-support
fix/ci-cleanup
ALTracer/fix/gdb-10-12-thread
ALTracer/feature/blackpill-f4-adc
ALTracer/fix/cortex-desc-allocfail-report
ALTracer/feature/spi-perf
ALTracer/feature/calibrate_swd
ALTracer/feature/blank-check
feature/avr
v2.0
v1.9
v1.10
v1.8
v2.1.0-rc1
v2.0.0
v2.0.0-rc2
v2.0.0-rc1
v1.9.3
v1.10.2
v1.10.1
v1.10.0
v1.10.0-rc1
v1.10.0-rc0
v1.9.2
v1.8.3
v1.9.1
v1.9.0
v1.9.0-rc1
v1.9.0-rc0
v1.8.2
v1.8.1
v1.8.0
v1.7.1
v1.7
v1.6.2-rc1
1.6.2-rc0
v1.6.1
v1.6
v1.6-rc1
v1.6-rc0
production_01
production_00
Labels
Clear labels
BMD App
Black Magic Debug App (aka. PC hosted) (not firmware)
BMP Firmware
Black Magic Probe Firmware (not PC hosted software)
Bug
Confirmed bug
Build system
Build system
Can't reproduce
Maintainers can't reproduce this problem
CI
Continuous Integration System
Contribution wanted
User contributions welcome
Documentation
Project documentation
Draft
Work in progress draft
Duplicate
This issue or pull request already exists
Enhancement
General project improvement
Feedback wanted
Requires additional submitter feedback
Foreign Host Board
Non Native hardware to runing Black Magic firmware on
GDB
Issue/PR related to GDB
Good first issue
Good for newcommers
HwIssue Mitigation
Solving or mitigating a Hardware issue in Software
Information Needed
Maintainers need more information
NativeHardware
Official Black Magic Debug Hardware
New Host Board
New hardware to run Black Magic firmware on
New Target
New debug target
Off Topic
Something that does not involve the project in any way
Potential Bug
A potential, unconfirmed or very special circumstance bug
Regression
Bug caused by a regression
User Interest Needed
More user interest required before consideration
User Testing Needed
Looking for user testing reports
Won't fix
Outside of the project scope or works as intended
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
blackmagic-debug/blackmagic#2280
Reference in a new issue
blackmagic-debug/blackmagic
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?