-
Notifications
You must be signed in to change notification settings - Fork 8.2k
[WIP] drivers: mfd: add support for stm32 timers #20293
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
Changes from all commits
63737d4
45dd127
9e473a1
8857934
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,4 +95,6 @@ source "drivers/eeprom/Kconfig" | |
|
|
||
| source "drivers/peci/Kconfig" | ||
|
|
||
| source "drivers/mfd/Kconfig" | ||
|
|
||
| endmenu | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| zephyr_sources_ifdef(CONFIG_MFD_TIMER_STM32 mfd_timer_stm32.c) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # MFD configuration options | ||
|
|
||
| # Copyright (c) 2019 Max van Kessel | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| menu "Multifunction Device Drivers" | ||
|
|
||
| config MFD_TIMER_STM32 | ||
| bool "STM32 MCU timer driver" | ||
| depends on SOC_FAMILY_STM32 | ||
| help | ||
| Enable This option enables the base timer driver for STM32 family of | ||
| processors. Say y if you wish to use timer interface on STM32 | ||
| MCU. | ||
|
|
||
| endmenu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,277 @@ | ||
| /* | ||
| * Copyright (c) 2019 Max van Kessel | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include <errno.h> | ||
| #include <soc.h> | ||
| #include <device.h> | ||
| #include <drivers/clock_control/stm32_clock_control.h> | ||
| #include <mfd/mfd_timer_stm32.h> | ||
|
|
||
| #define DEV_CFG(dev) \ | ||
| ((const struct mfd_timer_stm32_config * const) \ | ||
| (dev)->config->config_info) | ||
| #define DEV_DATA(dev) \ | ||
| ((struct mfd_timer_stm32_data * const)(dev)->driver_data) | ||
|
|
||
| struct mfd_timer_stm32_config { | ||
| u32_t tim_base; /**< Timer base */ | ||
| struct stm32_pclken pclken; /**< subsystem driving this peripheral */ | ||
|
|
||
| u8_t align_mode; | ||
| u8_t dir; | ||
| u8_t msm; | ||
| u8_t slave_mode; | ||
| u8_t slave_trig; | ||
| u8_t master_trig; | ||
| u32_t prescaler; | ||
| }; | ||
|
|
||
| enum { | ||
| ALIGN_EDGE = 0, | ||
| ALIGN_CENTER_1, | ||
| ALIGN_CENTER_2, | ||
| ALIGN_CENTER_3 | ||
| }; | ||
|
|
||
| static inline void tim_stm32_get_clock(struct device *dev) | ||
| { | ||
| struct mfd_timer_stm32_data *data = DEV_DATA(dev); | ||
| struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); | ||
|
|
||
| __ASSERT_NO_MSG(clk); | ||
|
|
||
| data->clock = clk; | ||
| } | ||
|
|
||
| static u32_t tim_stm32_get_rate(u32_t bus_clk, | ||
| clock_control_subsys_t *sub_system) | ||
| { | ||
| struct stm32_pclken *pclken = (struct stm32_pclken *)(sub_system); | ||
| u32_t tim_clk, apb_psc; | ||
|
|
||
| if (pclken->bus == STM32_CLOCK_BUS_APB1) { | ||
| apb_psc = CONFIG_CLOCK_STM32_APB1_PRESCALER; | ||
| } | ||
| #if !defined(CONFIG_SOC_SERIES_STM32F0X) && !defined(CONFIG_SOC_SERIES_STM32G0X) | ||
| else { | ||
| apb_psc = CONFIG_CLOCK_STM32_APB2_PRESCALER; | ||
| } | ||
| #endif | ||
|
|
||
| /* | ||
| * If the APB prescaler equals 1, the timer clock frequencies | ||
| * are set to the same frequency as that of the APB domain. | ||
| * Otherwise, they are set to twice (×2) the frequency of the | ||
| * APB domain. | ||
| */ | ||
| if (apb_psc == 1U) { | ||
| tim_clk = bus_clk; | ||
| } else { | ||
| tim_clk = bus_clk * 2U; | ||
| } | ||
|
|
||
| return tim_clk; | ||
| } | ||
|
|
||
| static int init(struct device *dev) | ||
| { | ||
| int err = -EIO; | ||
| const struct mfd_timer_stm32_config *cfg = DEV_CFG(dev); | ||
| struct mfd_timer_stm32_data *data = DEV_DATA(dev); | ||
|
|
||
| tim_stm32_get_clock(dev); | ||
|
|
||
| /* enable clock */ | ||
| if (clock_control_on(data->clock, | ||
| (clock_control_subsys_t *)&cfg->pclken) == 0) { | ||
| err = 0; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd return |
||
| } | ||
|
|
||
| if (err == 0) { | ||
| TIM_TypeDef *tim = (TIM_TypeDef *)cfg->tim_base; | ||
| u32_t mode = 0; | ||
|
|
||
| data->tim = tim; | ||
|
|
||
| /* TODO: create binding for me */ | ||
| LL_TIM_SetClockSource(tim, LL_TIM_CLOCKSOURCE_INTERNAL); | ||
|
|
||
| if (cfg->prescaler > 0) { | ||
| LL_TIM_SetPrescaler(tim, cfg->prescaler - 1); | ||
| } else { | ||
| LL_TIM_SetPrescaler(tim, 0); | ||
| } | ||
|
|
||
| /* TODO find a more consistent solution for all soc's */ | ||
| if (cfg->align_mode == ALIGN_EDGE) { | ||
| mode = cfg->dir << TIM_CR1_DIR_Pos; | ||
| } else { | ||
| mode = cfg->align_mode << TIM_CR1_CMS_Pos; | ||
| } | ||
|
|
||
| LL_TIM_SetCounterMode(tim, mode); | ||
|
|
||
| if (cfg->msm > 0) { | ||
| /* Trigger input delayed to allow synchronization */ | ||
| LL_TIM_EnableMasterSlaveMode(tim); | ||
| } | ||
|
|
||
| mode = cfg->slave_mode << TIM_SMCR_SMS_Pos; | ||
| LL_TIM_SetSlaveMode(tim, mode); | ||
|
|
||
| if (cfg->slave_mode != 0) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if shift ( |
||
| mode = cfg->slave_trig << TIM_SMCR_TS_Pos; | ||
| LL_TIM_SetTriggerInput(tim, mode); | ||
| } | ||
|
|
||
| mode = cfg->master_trig << TIM_CR2_MMS_Pos; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example of the mentioned idea: https://github.com/zephyrproject-rtos/zephyr/blob/master/drivers/serial/uart_stm32.c#L139 |
||
| LL_TIM_SetTriggerOutput(tim, mode); | ||
|
|
||
| /* TODO: create binding for me ?*/ | ||
| LL_TIM_DisableARRPreload(tim); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should be configurable as |
||
| } | ||
|
|
||
| return err; | ||
| } | ||
|
|
||
| static void enable(struct device *dev) | ||
| { | ||
| const struct mfd_timer_stm32_config *cfg = DEV_CFG(dev); | ||
|
|
||
| /* Timer is enabled by master timer if slave mode is set */ | ||
| if ((cfg->slave_mode << TIM_SMCR_SMS_Pos) != LL_TIM_SLAVEMODE_TRIGGER) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd just save |
||
| struct mfd_timer_stm32_data *data = DEV_DATA(dev); | ||
|
|
||
| LL_TIM_EnableCounter(data->tim); | ||
| } | ||
| } | ||
|
|
||
| static void disable(struct device *dev) | ||
| { | ||
| struct mfd_timer_stm32_data *data = DEV_DATA(dev); | ||
|
|
||
| LL_TIM_DisableCounter(data->tim); | ||
| } | ||
|
|
||
| static int get_cycles_per_sec(struct device *dev, u64_t *cycles) | ||
| { | ||
| int err = -EINVAL; | ||
| const struct mfd_timer_stm32_config *cfg = DEV_CFG(dev); | ||
| struct mfd_timer_stm32_data *data = DEV_DATA(dev); | ||
| u32_t bus_clk, tim_clk; | ||
|
|
||
| if (cycles != NULL) { | ||
| /* Timer clock depends on APB prescaler */ | ||
| err = clock_control_get_rate(data->clock, | ||
| (clock_control_subsys_t *)&cfg->pclken, | ||
| &bus_clk); | ||
|
|
||
| if (err >= 0) { | ||
| tim_clk = tim_stm32_get_rate(bus_clk, | ||
| (clock_control_subsys_t *)&cfg->pclken); | ||
|
|
||
| *cycles = (u64_t) (tim_clk / (cfg->prescaler)); | ||
|
|
||
| err = 0; | ||
| } | ||
| } | ||
| return err; | ||
| } | ||
|
|
||
| static const struct mfd_timer_stm32 api = { | ||
| .enable = enable, | ||
| .disable = disable, | ||
| .get_cycles_per_sec = get_cycles_per_sec, | ||
| }; | ||
|
|
||
| #define TIMER_DEVICE_INIT(n) \ | ||
| static struct mfd_timer_stm32_data mfd_timer_stm32_dev_data_ ## n; \ | ||
| static const struct mfd_timer_stm32_config mfd_timer_stm32_dev_cfg_ ## n = { \ | ||
| .tim_base = DT_INST_## n ##_ST_STM32_TIMERS_BASE_ADDRESS, \ | ||
| .align_mode = DT_INST_## n ##_ST_STM32_TIMERS_ST_ALIGN_MODE_ENUM,\ | ||
| .dir = DT_INST_## n ##_ST_STM32_TIMERS_ST_COUNTER_DIR_ENUM, \ | ||
| .msm = DT_INST_## n ##_ST_STM32_TIMERS_ST_MASTER_SLAVE_MODE, \ | ||
| .slave_mode = DT_INST_## n ##_ST_STM32_TIMERS_ST_SLAVE_MODE, \ | ||
| .slave_trig = DT_INST_## n ##_ST_STM32_TIMERS_ST_SLAVE_TRIGGER_IN, \ | ||
| .master_trig = DT_INST_## n ##_ST_STM32_TIMERS_ST_MASTER_TRIGGER_OUT,\ | ||
| .prescaler = DT_INST_## n ##_ST_STM32_TIMERS_ST_PRESCALER, \ | ||
| .pclken = { \ | ||
| .bus = DT_INST_## n ##_ST_STM32_TIMERS_CLOCK_BUS, \ | ||
| .enr = DT_INST_## n ##_ST_STM32_TIMERS_CLOCK_BITS }, \ | ||
| }; \ | ||
| \ | ||
| DEVICE_AND_API_INIT(timer_stm32_ ## n, \ | ||
| DT_INST_## n ##_ST_STM32_TIMERS_LABEL, \ | ||
| &init, \ | ||
| &mfd_timer_stm32_dev_data_ ## n, \ | ||
| &mfd_timer_stm32_dev_cfg_ ## n, \ | ||
| POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ | ||
| &api) | ||
|
|
||
| #ifdef DT_INST_0_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(0); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_1_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(1); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_2_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(2); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_3_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(3); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_4_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(4); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_5_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(5); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_6_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(6); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_7_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(7); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_8_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(8); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_9_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(9); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_10_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(10); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_11_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(11); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_12_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(12); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_13_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(13); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_14_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(14); | ||
| #endif | ||
|
|
||
| #ifdef DT_INST_15_ST_STM32_TIMERS | ||
| TIMER_DEVICE_INIT(15); | ||
| #endif | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * Copyright (c) 2019 Max van Kessel | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #ifndef ZEPHYR_DRIVERS_MFD_MFD_TIMER_STM32_H_ | ||
| #define ZEPHYR_DRIVERS_MFD_MFD_TIMER_STM32_H_ | ||
|
|
||
| #include <errno.h> | ||
| #include <zephyr/types.h> | ||
| #include <device.h> | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| /** | ||
| * @brief MFD timer data structure | ||
| */ | ||
| struct mfd_timer_stm32_data { | ||
| TIM_TypeDef *tim; /**< Base address */ | ||
| struct device *clock; /**< Clock device */ | ||
| }; | ||
|
|
||
| typedef void (*mfd_timer_stm32_enable_t)(struct device *dev); | ||
|
|
||
| typedef void (*mfd_timer_stm32_disable_t)(struct device *dev); | ||
|
|
||
| typedef int (*mfd_timer_stm32_get_cycles_per_sec_t)(struct device *dev, | ||
| u64_t *cycles); | ||
| struct mfd_timer_stm32 { | ||
| mfd_timer_stm32_enable_t enable; | ||
| mfd_timer_stm32_disable_t disable; | ||
| mfd_timer_stm32_get_cycles_per_sec_t get_cycles_per_sec; | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Enable stm32 timer device | ||
| * @param dev The device to enable | ||
| */ | ||
| static inline void mfd_timer_stm32_enable(struct device *dev) | ||
| { | ||
| const struct mfd_timer_stm32 *api = (const struct mfd_timer_stm32 *) | ||
| dev->driver_api; | ||
| return api->enable(dev); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Disable stm32 timer device | ||
| * @param dev The device to disable | ||
| */ | ||
| static inline void mfd_timer_stm32_disable(struct device *dev) | ||
| { | ||
| const struct mfd_timer_stm32 *api = (const struct mfd_timer_stm32 *) | ||
| dev->driver_api; | ||
| return api->disable(dev); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Get the clock rate (cycles per second) | ||
| * @param dev Pointer to timer device structure | ||
| * @param cycles Pointer to the memory to store clock rate | ||
| * (cycles per second) | ||
| * @retval 0 for success | ||
| * @retval negative errno code | ||
| */ | ||
| static inline int mfd_timer_stm32_get_cycles_per_sec(struct device *dev, | ||
| u64_t *cycles) | ||
| { | ||
| const struct mfd_timer_stm32 *api = (const struct mfd_timer_stm32 *) | ||
| dev->driver_api; | ||
| return api->get_cycles_per_sec(dev, cycles); | ||
| } | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| #endif /* ZEPHYR_DRIVERS_MFD_MFD_TIMER_STM32_H_ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to support H7 series PSC needs to use
D2PRE1/D2PRE2(see #23590)