Skip to content

libutils: Add support for stm32mp2 SoC family#205

Draft
bruelc wants to merge 1 commit intoseL4:masterfrom
bruelc:master-stm32mp2
Draft

libutils: Add support for stm32mp2 SoC family#205
bruelc wants to merge 1 commit intoseL4:masterfrom
bruelc:master-stm32mp2

Conversation

@bruelc
Copy link

@bruelc bruelc commented Feb 12, 2026

Description

Add support for uart and timers for stm32mp2 soc

Link to kernel PR seL4

Testing

Testing was part of the seL4-test and seL4-bench test suites

@bruelc bruelc marked this pull request as draft February 12, 2026 10:06
@Indanz Indanz marked this pull request as ready for review February 17, 2026 14:15
@Indanz Indanz marked this pull request as draft February 17, 2026 14:15
Copy link
Contributor

@Indanz Indanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't handle 32-bit timer overflows, you probably want to register an IRQ handler for that. Alternatively, you can probably link two timers into one 64-bit timer and ignore overflows.

You also use two hard-coded timers, instead of one. Each timer has four channels which can be used for compare independently. I'd use one for the timeout function and another for overflow detection, if there is no other way. Except if it's one of those stupid timers which can't be reprogrammed on-the-fly without stopping it, in which case you do need to use two timers and document this somewhere. But the datasheet says "The TIMx_CCRx register can be updated at any time by software to control the output waveform, provided that the preload register is not enable", so it seems okay.

But if hard-coding timers like this, it doesn't make much sense to use the DTS walker either, as you know everything already. (Keep it if the code is simpler though.)

/* official device names */
enum chardev_id {
UART0,
UART1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is UART1 actually supported by this driver? If not, leave it out.

The SoC has about 10 UARTs, probably a good idea to follow the datasheet UART names in this enum and elsewhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. In the boot stdout chosen dts path, we have serial0 aliased to USART2, which confused my naming. I will rename it.


#pragma once

#define UART0_PADDR 0x400E0000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually USART2.

#pragma once

#define UART0_PADDR 0x400E0000
#define UART1_PADDR 0x400F0000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is USART3.

* Copyright 2026, STMicroelectronics
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to give an overview here of which timers you're using and how and why (if it's more than one).

Comment on lines 27 to 29
#define RCC_APB1DIVR_OFF 0x4B4
#define RCC_APB1DIVR_PADDR (RCC_PADDR + RCC_APB1DIVR_OFF)
#define RCC_APB1DIVR_SIZE 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird indentation. And also unused?

#include "../../common.h"
#include <utils/util.h>

#include "../../chardev.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate, and see Julia's comment elsewhere.

uint32_t tisel;
uint32_t pad[0xEA];
uint32_t ipidr;
} stm32_regs_t;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This structure you can move to timer.c instead, leaving a forward declaration behind. Same for most of the defines. But whatever you prefer.

Comment on lines 20 to 23
static const struct dev_defn dev_defn[] = {
UART_DEFN(0),
UART_DEFN(1),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use this construction instead of hard-coding a specific UART, any reason not to add all compatible ones?

#include "../../ltimer.h"

/*
* We use two dm timers: one to keep track of an absolute time, the other for timeouts.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're using the TIM2 and TIM3 general purpose timers. What do you mean with "dm"?

Also, each timer seems to have four channels, so why do you need to use two timers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No specific reason except that it was easier to debug and trace. But OK, now I should rework this part ; there is no need to start more than necessary.

@bruelc
Copy link
Author

bruelc commented Feb 26, 2026

Indanz, your suggestion to use a channel instead of a second timer was very good. I force-pushed a new version with your latest reviews and refactoring. Thank you.

}

static const struct dev_defn dev_defn[] = {
UART_DEFN(1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see the other compatible UARTS in this list too, if possible. And if the address is calculable, do that instead of macro string concatenation.

If the only difference is the memory address then there is no reason not to add them. No need to test the others in that case either. Even if it doesn't work because of e.g. missing clock or power domain enable, it's still better than nothing.

Comment on lines 129 to 139
if (! stm32->periodic) {
stm32_regs->dier &= ~STM32_TIM_DIER_CC1IE;
}
else
assert(0);

if (stm32_ltimer->user_cb_fn) {
stm32_ltimer->user_cb_fn(stm32_ltimer->user_cb_token, LTIMER_TIMEOUT_EVENT);
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please cleanup this bit, looks like left-over debug stuff.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please cleanup this bit, looks like left-over debug stuff.

Gloops wrong version pushed. Please forget this commit

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repushed (with pediodic support). now I'll look at your other comments :)

Comment on lines +120 to +123

if (stm32_ltimer->user_cb_fn) {
stm32_ltimer->user_cb_fn(stm32_ltimer->user_cb_token, LTIMER_OVERFLOW_EVENT);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent off.

Comment on lines +149 to +152
if (ltimer == NULL) {
ZF_LOGE("ltimer cannot be NULL");
return EINVAL;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check really necessary? Seems a bit pointless to me, something is going to go horribly wrong sooner or later if it is NULL.

Comment on lines +34 to +35
assert(stm32 != NULL);
assert(stm32->hw != NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these asserts can go away once your driver is working, it's only useful to catch initial problems.

/* TIM2 RCC */
RCC_ON(rcc);

stm32->hw->cr1 &= ~STM32_TIM_CR1_CEN;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should do blind writes to cr1 to configure it how you want in one go. Currently you never set the other bits, so you're counting on either reset behaviour or something.

Reading registers is very slow, so if you don't have to, don't (this applies more to the other places than here). Some devices don't want other bits to be changed together with the enable bit, but a lot of them don't mind.

stm32->hw->dier |= STM32_TIM_DIER_UIE; /* interrupt for overflow */
stm32->hw->ccmr1 = 0;
stm32->hw->ccer |= STM32_TIM_CCER_CC1E;
stm32->hw->dier &= ~STM32_TIM_DIER_CC1IE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the ccer and dier registers, I think you just want to set it to STM32_TIM_CCER_CC1E and STM32_TIM_DIER_UIE. Or are there reserved bits in there?

Define default serial with USART1

32bit TIM2 timer configured with 5us tick,
arr autoreload overflows timestamp and
Channel1 ccr1 compare register for timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants