You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
5.7 KiB
214 lines
5.7 KiB
/* |
|
* Copyright (c) 2022 Teslabs Engineering S.L. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT gd_gd32_cctl |
|
|
|
#include <stdint.h> |
|
|
|
#include <zephyr/arch/cpu.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/devicetree.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/clock_control/gd32.h> |
|
|
|
#include <gd32_regs.h> |
|
|
|
/** RCU offset (from id cell) */ |
|
#define GD32_CLOCK_ID_OFFSET(id) (((id) >> 6U) & 0xFFU) |
|
/** RCU configuration bit (from id cell) */ |
|
#define GD32_CLOCK_ID_BIT(id) ((id)&0x1FU) |
|
|
|
#define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency) |
|
|
|
/** AHB prescaler exponents */ |
|
static const uint8_t ahb_exp[16] = { |
|
0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U, 6U, 7U, 8U, 9U, |
|
}; |
|
/** APB1 prescaler exponents */ |
|
static const uint8_t apb1_exp[8] = { |
|
0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U, |
|
}; |
|
/** APB2 prescaler exponents */ |
|
static const uint8_t apb2_exp[8] = { |
|
0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U, |
|
}; |
|
|
|
struct clock_control_gd32_config { |
|
uint32_t base; |
|
}; |
|
|
|
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_timer) |
|
/* timer identifiers */ |
|
#define TIMER_ID_OR_NONE(nodelabel) \ |
|
COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(nodelabel)), \ |
|
(DT_CLOCKS_CELL(DT_NODELABEL(nodelabel), id),), ()) |
|
|
|
static const uint16_t timer_ids[] = { |
|
TIMER_ID_OR_NONE(timer0) /* */ |
|
TIMER_ID_OR_NONE(timer1) /* */ |
|
TIMER_ID_OR_NONE(timer2) /* */ |
|
TIMER_ID_OR_NONE(timer3) /* */ |
|
TIMER_ID_OR_NONE(timer4) /* */ |
|
TIMER_ID_OR_NONE(timer5) /* */ |
|
TIMER_ID_OR_NONE(timer6) /* */ |
|
TIMER_ID_OR_NONE(timer7) /* */ |
|
TIMER_ID_OR_NONE(timer8) /* */ |
|
TIMER_ID_OR_NONE(timer9) /* */ |
|
TIMER_ID_OR_NONE(timer10) /* */ |
|
TIMER_ID_OR_NONE(timer11) /* */ |
|
TIMER_ID_OR_NONE(timer12) /* */ |
|
TIMER_ID_OR_NONE(timer13) /* */ |
|
TIMER_ID_OR_NONE(timer14) /* */ |
|
TIMER_ID_OR_NONE(timer15) /* */ |
|
TIMER_ID_OR_NONE(timer16) /* */ |
|
}; |
|
#endif /* DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_timer) */ |
|
|
|
static int clock_control_gd32_on(const struct device *dev, |
|
clock_control_subsys_t sys) |
|
{ |
|
const struct clock_control_gd32_config *config = dev->config; |
|
uint16_t id = *(uint16_t *)sys; |
|
|
|
sys_set_bit(config->base + GD32_CLOCK_ID_OFFSET(id), |
|
GD32_CLOCK_ID_BIT(id)); |
|
|
|
return 0; |
|
} |
|
|
|
static int clock_control_gd32_off(const struct device *dev, |
|
clock_control_subsys_t sys) |
|
{ |
|
const struct clock_control_gd32_config *config = dev->config; |
|
uint16_t id = *(uint16_t *)sys; |
|
|
|
sys_clear_bit(config->base + GD32_CLOCK_ID_OFFSET(id), |
|
GD32_CLOCK_ID_BIT(id)); |
|
|
|
return 0; |
|
} |
|
|
|
static int clock_control_gd32_get_rate(const struct device *dev, |
|
clock_control_subsys_t sys, |
|
uint32_t *rate) |
|
{ |
|
const struct clock_control_gd32_config *config = dev->config; |
|
uint16_t id = *(uint16_t *)sys; |
|
uint32_t cfg; |
|
uint8_t psc; |
|
|
|
cfg = sys_read32(config->base + RCU_CFG0_OFFSET); |
|
|
|
switch (GD32_CLOCK_ID_OFFSET(id)) { |
|
#if defined(CONFIG_SOC_SERIES_GD32F4XX) |
|
case RCU_AHB1EN_OFFSET: |
|
case RCU_AHB2EN_OFFSET: |
|
case RCU_AHB3EN_OFFSET: |
|
#else |
|
case RCU_AHBEN_OFFSET: |
|
#endif |
|
psc = (cfg & RCU_CFG0_AHBPSC_MSK) >> RCU_CFG0_AHBPSC_POS; |
|
*rate = CPU_FREQ >> ahb_exp[psc]; |
|
break; |
|
case RCU_APB1EN_OFFSET: |
|
#if !defined(CONFIG_SOC_SERIES_GD32VF103) && \ |
|
!defined(CONFIG_SOC_SERIES_GD32A50X) && \ |
|
!defined(CONFIG_SOC_SERIES_GD32L23X) |
|
case RCU_ADDAPB1EN_OFFSET: |
|
#endif |
|
psc = (cfg & RCU_CFG0_APB1PSC_MSK) >> RCU_CFG0_APB1PSC_POS; |
|
*rate = CPU_FREQ >> apb1_exp[psc]; |
|
break; |
|
case RCU_APB2EN_OFFSET: |
|
psc = (cfg & RCU_CFG0_APB2PSC_MSK) >> RCU_CFG0_APB2PSC_POS; |
|
*rate = CPU_FREQ >> apb2_exp[psc]; |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
#if DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_timer) |
|
/* handle timer clocks */ |
|
for (size_t i = 0U; i < ARRAY_SIZE(timer_ids); i++) { |
|
if (id != timer_ids[i]) { |
|
continue; |
|
} |
|
|
|
#if defined(CONFIG_SOC_SERIES_GD32F4XX) |
|
uint32_t cfg1 = sys_read32(config->base + RCU_CFG1_OFFSET); |
|
|
|
/* |
|
* The TIMERSEL bit in RCU_CFG1 controls the clock frequency of |
|
* all the timers connected to the APB1 and APB2 domains. |
|
* |
|
* Up to a certain threshold value of APB{1,2} prescaler, timer |
|
* clock equals to CK_AHB. This threshold value depends on |
|
* TIMERSEL setting (2 if TIMERSEL=0, 4 if TIMERSEL=1). Above |
|
* threshold, timer clock is set to a multiple of the APB |
|
* domain clock CK_APB{1,2} (2 if TIMERSEL=0, 4 if TIMERSEL=1). |
|
*/ |
|
|
|
/* TIMERSEL = 0 */ |
|
if ((cfg1 & RCU_CFG1_TIMERSEL_MSK) == 0U) { |
|
if (psc <= 2U) { |
|
*rate = CPU_FREQ; |
|
} else { |
|
*rate *= 2U; |
|
} |
|
/* TIMERSEL = 1 */ |
|
} else { |
|
if (psc <= 4U) { |
|
*rate = CPU_FREQ; |
|
} else { |
|
*rate *= 4U; |
|
} |
|
} |
|
#else |
|
/* |
|
* 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 the frequency of the APB |
|
* domain. |
|
*/ |
|
if (psc != 1U) { |
|
*rate *= 2U; |
|
} |
|
#endif /* CONFIG_SOC_SERIES_GD32F4XX */ |
|
} |
|
#endif /* DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_timer) */ |
|
|
|
return 0; |
|
} |
|
|
|
static enum clock_control_status |
|
clock_control_gd32_get_status(const struct device *dev, |
|
clock_control_subsys_t sys) |
|
{ |
|
const struct clock_control_gd32_config *config = dev->config; |
|
uint16_t id = *(uint16_t *)sys; |
|
|
|
if (sys_test_bit(config->base + GD32_CLOCK_ID_OFFSET(id), |
|
GD32_CLOCK_ID_BIT(id)) != 0) { |
|
return CLOCK_CONTROL_STATUS_ON; |
|
} |
|
|
|
return CLOCK_CONTROL_STATUS_OFF; |
|
} |
|
|
|
static const struct clock_control_driver_api clock_control_gd32_api = { |
|
.on = clock_control_gd32_on, |
|
.off = clock_control_gd32_off, |
|
.get_rate = clock_control_gd32_get_rate, |
|
.get_status = clock_control_gd32_get_status, |
|
}; |
|
|
|
static const struct clock_control_gd32_config config = { |
|
.base = DT_REG_ADDR(DT_INST_PARENT(0)), |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &config, PRE_KERNEL_1, |
|
CONFIG_CLOCK_CONTROL_INIT_PRIORITY, |
|
&clock_control_gd32_api);
|
|
|