Skip to content
Closed
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
1 change: 1 addition & 0 deletions drivers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_definitions(-D__ZEPHYR_SUPERVISOR__)

add_subdirectory(console)
add_subdirectory(interrupt_controller)
add_subdirectory(mfd)

add_subdirectory_if_kconfig(adc)
add_subdirectory_if_kconfig(clock_control)
Expand Down
2 changes: 2 additions & 0 deletions drivers/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ source "drivers/eeprom/Kconfig"

source "drivers/peci/Kconfig"

source "drivers/mfd/Kconfig"

endmenu
3 changes: 3 additions & 0 deletions drivers/mfd/CMakeLists.txt
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)
16 changes: 16 additions & 0 deletions drivers/mfd/Kconfig
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
277 changes: 277 additions & 0 deletions drivers/mfd/mfd_timer_stm32.c
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;
Copy link
Member

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)

}
#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;
Copy link
Member

@gmarull gmarull Mar 20, 2020

Choose a reason for hiding this comment

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

I'd return -EIO here (avoids if below)

}

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) {
Copy link
Member

Choose a reason for hiding this comment

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

if shift (<<) approach is changed as suggested, cfg->slave_mode != LL_TIM_SLAVEMODE_DISABLED would be more readable.

mode = cfg->slave_trig << TIM_SMCR_TS_Pos;
LL_TIM_SetTriggerInput(tim, mode);
}

mode = cfg->master_trig << TIM_CR2_MMS_Pos;
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using cfg->var << SHIFT strategy I'd implement some helper functions to convert from dt binding values to actual definitions in the LL headers. I think it would be more portable.

Copy link
Member

Choose a reason for hiding this comment

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

LL_TIM_SetTriggerOutput(tim, mode);

/* TODO: create binding for me ?*/
LL_TIM_DisableARRPreload(tim);
Copy link
Member

Choose a reason for hiding this comment

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

That should be configurable as TODO suggests

}

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) {
Copy link
Member

@gmarull gmarull Mar 20, 2020

Choose a reason for hiding this comment

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

I'd just save LL_TIM_SLAVEMODE_TRIGGER to cfg->slave_mode, see next comment

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

80 changes: 80 additions & 0 deletions drivers/mfd/mfd_timer_stm32.h
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_ */
Loading