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.
365 lines
11 KiB
365 lines
11 KiB
/* |
|
* Copyright (c) 2024 Nuvoton Technology Corporation. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nuvoton_npcm_pcc |
|
|
|
#include <soc.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/dt-bindings/clock/npcm_clock.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(clock_control_npcm, LOG_LEVEL_ERR); |
|
|
|
/* Driver config */ |
|
struct npcm_pcc_config { |
|
/* cdcg device base address */ |
|
uintptr_t base_cdcg; |
|
/* pmc device base address */ |
|
uintptr_t base_pmc; |
|
}; |
|
|
|
/* |
|
* Core Domain Clock Generator (CDCG) device registers |
|
*/ |
|
struct cdcg_reg { |
|
/* High Frequency Clock Generator (HFCG) registers */ |
|
/* 0x000: HFCG Control */ |
|
volatile uint8_t hfcgctrl; |
|
volatile uint8_t reserved1; |
|
/* 0x002: HFCG M Low Byte Value */ |
|
volatile uint8_t hfcgml; |
|
volatile uint8_t reserved2; |
|
/* 0x004: HFCG M High Byte Value */ |
|
volatile uint8_t hfcgmh; |
|
volatile uint8_t reserved3; |
|
/* 0x006: HFCG N Value */ |
|
volatile uint8_t hfcgn; |
|
volatile uint8_t reserved4; |
|
/* 0x008: HFCG Prescaler */ |
|
volatile uint8_t hfcgp; |
|
volatile uint8_t reserved5[7]; |
|
/* 0x010: HFCG Bus Clock Dividers */ |
|
volatile uint8_t hfcbcd; |
|
volatile uint8_t reserved6; |
|
/* 0x012: HFCG Bus Clock Dividers */ |
|
volatile uint8_t hfcbcd1; |
|
volatile uint8_t reserved7; |
|
/* 0x014: HFCG Bus Clock Dividers */ |
|
volatile uint8_t hfcbcd2; |
|
volatile uint8_t reserved12[8]; |
|
/* 0x01d: HFCG Bus Clock Dividers */ |
|
volatile uint8_t hfcbcd3; |
|
}; |
|
|
|
/* clock bus references */ |
|
#define NPCM_CLOCK_BUS_LFCLK 0 |
|
#define NPCM_CLOCK_BUS_OSC 1 |
|
#define NPCM_CLOCK_BUS_FIU 2 |
|
#define NPCM_CLOCK_BUS_I3C 3 |
|
#define NPCM_CLOCK_BUS_CORE 4 |
|
#define NPCM_CLOCK_BUS_APB1 5 |
|
#define NPCM_CLOCK_BUS_APB2 6 |
|
#define NPCM_CLOCK_BUS_APB3 7 |
|
#define NPCM_CLOCK_BUS_APB4 8 |
|
#define NPCM_CLOCK_BUS_AHB6 9 |
|
#define NPCM_CLOCK_BUS_FMCLK 10 |
|
#define NPCM_CLOCK_BUS_USB20_CLK 11 |
|
#define NPCM_CLOCK_BUS_SIO_CLK 12 |
|
|
|
/* clock enable/disable references */ |
|
#define NPCM_PWDWN_CTL0 0 |
|
#define NPCM_PWDWN_CTL1 1 |
|
#define NPCM_PWDWN_CTL2 2 |
|
#define NPCM_PWDWN_CTL3 3 |
|
#define NPCM_PWDWN_CTL4 4 |
|
#define NPCM_PWDWN_CTL5 5 |
|
#define NPCM_PWDWN_CTL6 6 |
|
#define NPCM_PWDWN_CTL7 7 |
|
|
|
/* CDCG register fields */ |
|
#define NPCM_HFCGCTRL_LOAD 0 |
|
#define NPCM_HFCGCTRL_LOCK 2 |
|
#define NPCM_HFCGCTRL_CLK_CHNG 7 |
|
|
|
/* Clock settings from pcc node */ |
|
/* Target OFMCLK freq */ |
|
#define OFMCLK DT_PROP(DT_NODELABEL(pcc), clock_frequency) |
|
/* Core clock prescaler */ |
|
#define FPRED_VAL (DT_PROP(DT_NODELABEL(pcc), core_prescaler) - 1) |
|
/* APB1 clock divider */ |
|
#define APB1DIV_VAL (DT_PROP(DT_NODELABEL(pcc), apb1_prescaler) - 1) |
|
/* APB2 clock divider */ |
|
#define APB2DIV_VAL (DT_PROP(DT_NODELABEL(pcc), apb2_prescaler) - 1) |
|
/* APB3 clock divider */ |
|
#define APB3DIV_VAL (DT_PROP(DT_NODELABEL(pcc), apb3_prescaler) - 1) |
|
/* AHB6 clock divider*/ |
|
#define AHB6DIV_VAL (DT_PROP(DT_NODELABEL(pcc), ahb6_prescaler) - 1) |
|
/* FIU clock divider */ |
|
#define FIUDIV_VAL (DT_PROP(DT_NODELABEL(pcc), fiu_prescaler) - 1) |
|
/* I3C clock divider */ |
|
#define I3CDIV_VAL (DT_PROP(DT_NODELABEL(pcc), i3c_prescaler) - 1) |
|
|
|
/* Core domain clock */ |
|
#define CORE_CLK (OFMCLK / DT_PROP(DT_NODELABEL(pcc), core_prescaler)) |
|
/* Low Frequency clock */ |
|
#define LFCLK 32768 |
|
/* FMUL clock */ |
|
#define FMCLK OFMCLK /* FMUL clock = OFMCLK */ |
|
/* APBs source clock */ |
|
#define APBSRC_CLK OFMCLK |
|
/* USB2.0 clock */ |
|
#define USB20_CLK 12000000 |
|
/* SIO clock */ |
|
#define SIO_CLK 48000000 |
|
/* Get APB clock freq */ |
|
#define NPCM_APB_CLOCK(no) (APBSRC_CLK / (APB##no##DIV_VAL + 1)) |
|
|
|
struct freq_multiplier_t { |
|
uint32_t ofmclk; |
|
uint8_t hfcgn; |
|
uint8_t hfcgmh; |
|
uint8_t hfcgml; |
|
}; |
|
|
|
static const struct freq_multiplier_t freq_multiplier[] = { |
|
{.ofmclk = 100000000, .hfcgn = 0x82, .hfcgmh = 0x0B, .hfcgml = 0xEC}, |
|
{.ofmclk = 96000000, .hfcgn = 0x82, .hfcgmh = 0x0B, .hfcgml = 0x72}, |
|
{.ofmclk = 80000000, .hfcgn = 0x82, .hfcgmh = 0x09, .hfcgml = 0x89}, |
|
{.ofmclk = 66000000, .hfcgn = 0x82, .hfcgmh = 0x07, .hfcgml = 0xDE}, |
|
{.ofmclk = 50000000, .hfcgn = 0x02, .hfcgmh = 0x0B, .hfcgml = 0xEC}, |
|
{.ofmclk = 48000000, .hfcgn = 0x02, .hfcgmh = 0x0B, .hfcgml = 0x72}, |
|
{.ofmclk = 40000000, .hfcgn = 0x02, .hfcgmh = 0x09, .hfcgml = 0x89}, |
|
{.ofmclk = 33000000, .hfcgn = 0x02, .hfcgmh = 0x07, .hfcgml = 0xDE}}; |
|
|
|
struct clk_cfg_t { |
|
uint32_t clock_id; |
|
uint16_t bus; |
|
}; |
|
|
|
static const struct clk_cfg_t clk_cfg[] = { |
|
{.clock_id = NPCM_CLOCK_PWM_I, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_J, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_I3CI, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_UART3, .bus = NPCM_CLOCK_BUS_APB2}, |
|
{.clock_id = NPCM_CLOCK_UART2, .bus = NPCM_CLOCK_BUS_APB2}, |
|
|
|
{.clock_id = NPCM_CLOCK_FIU, .bus = NPCM_CLOCK_BUS_FIU}, |
|
{.clock_id = NPCM_CLOCK_USB20, .bus = NPCM_CLOCK_BUS_USB20_CLK}, |
|
{.clock_id = NPCM_CLOCK_UART, .bus = NPCM_CLOCK_BUS_APB2}, |
|
|
|
{.clock_id = NPCM_CLOCK_PWM_A, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_B, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_C, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_D, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_E, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_F, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_G, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_PWM_H, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
|
|
{.clock_id = NPCM_CLOCK_SMB1, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB2, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB3, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB4, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB5, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB6, .bus = NPCM_CLOCK_BUS_APB3}, |
|
|
|
{.clock_id = NPCM_CLOCK_ITIM1, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_ITIM2, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_ITIM3, .bus = NPCM_CLOCK_BUS_LFCLK}, |
|
{.clock_id = NPCM_CLOCK_ADC, .bus = NPCM_CLOCK_BUS_APB1}, |
|
{.clock_id = NPCM_CLOCK_PECI, .bus = NPCM_CLOCK_BUS_FMCLK}, |
|
|
|
{.clock_id = NPCM_CLOCK_UART4, .bus = NPCM_CLOCK_BUS_APB2}, |
|
|
|
{.clock_id = NPCM_CLOCK_ESPI, .bus = NPCM_CLOCK_BUS_APB3}, |
|
|
|
{.clock_id = NPCM_CLOCK_SMB7, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB8, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB9, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB10, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB11, .bus = NPCM_CLOCK_BUS_APB3}, |
|
{.clock_id = NPCM_CLOCK_SMB12, .bus = NPCM_CLOCK_BUS_APB3}, |
|
}; |
|
|
|
/* PMC multi-registers */ |
|
#define NPCM_PWDWN_CTL_OFFSET(n) (((n) < 7) ? (0x07 + n) : (0x15 + (n - 7))) |
|
#define NPCM_PWDWN_CTL(base, n) (*(volatile uint8_t *)(base + NPCM_PWDWN_CTL_OFFSET(n))) |
|
#define NPCM_CLOCK_REG_OFFSET(n) ((n) >> 3) |
|
#define NPCM_CLOCK_REG_BIT_OFFSET(n) ((n) & 0x7) |
|
|
|
#define DRV_CONFIG(dev) ((const struct npcm_pcc_config *)(dev)->config) |
|
|
|
/* Clock controller local functions */ |
|
static const struct clk_cfg_t *npcm_get_cfg(clock_control_subsys_t sub_system) |
|
{ |
|
uint32_t clk_id = (uint32_t)sub_system; |
|
uint32_t i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(clk_cfg); i++) { |
|
if (clk_cfg[i].clock_id == clk_id) { |
|
return &clk_cfg[i]; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static inline int npcm_clock_control_on(const struct device *dev, clock_control_subsys_t sub_system) |
|
{ |
|
uint32_t clk_id = (uint32_t)sub_system; |
|
const struct clk_cfg_t *priv; |
|
const uint32_t pmc_base = DRV_CONFIG(dev)->base_pmc; |
|
|
|
priv = npcm_get_cfg(sub_system); |
|
if (!priv) { |
|
LOG_ERR("Unsupported clock id %d", clk_id); |
|
return -EINVAL; |
|
} |
|
|
|
/* Clear related PD (Power-Down) bit of module to turn on clock */ |
|
NPCM_PWDWN_CTL(pmc_base, NPCM_CLOCK_REG_OFFSET(priv->clock_id)) &= |
|
~(BIT(NPCM_CLOCK_REG_BIT_OFFSET(priv->clock_id))); |
|
return 0; |
|
} |
|
|
|
static inline int npcm_clock_control_off(const struct device *dev, |
|
clock_control_subsys_t sub_system) |
|
{ |
|
uint32_t clk_id = (uint32_t)sub_system; |
|
const struct clk_cfg_t *priv; |
|
const uint32_t pmc_base = DRV_CONFIG(dev)->base_pmc; |
|
|
|
priv = npcm_get_cfg(sub_system); |
|
if (!priv) { |
|
LOG_ERR("Unsupported clock id %d", clk_id); |
|
return -EINVAL; |
|
} |
|
|
|
/* Set related PD (Power-Down) bit of module to turn off clock */ |
|
NPCM_PWDWN_CTL(pmc_base, NPCM_CLOCK_REG_OFFSET(priv->clock_id)) |= |
|
(BIT(NPCM_CLOCK_REG_BIT_OFFSET(priv->clock_id))); |
|
return 0; |
|
} |
|
|
|
static int npcm_clock_control_get_subsys_rate(const struct device *dev, |
|
clock_control_subsys_t sub_system, uint32_t *rate) |
|
{ |
|
ARG_UNUSED(dev); |
|
uint32_t clk_id = (uint32_t)sub_system; |
|
const struct clk_cfg_t *priv; |
|
|
|
priv = npcm_get_cfg(sub_system); |
|
if (!priv) { |
|
LOG_ERR("Unsupported clock id %d", clk_id); |
|
return -EINVAL; |
|
} |
|
|
|
switch (priv->bus) { |
|
case NPCM_CLOCK_BUS_APB1: |
|
*rate = NPCM_APB_CLOCK(1); |
|
break; |
|
case NPCM_CLOCK_BUS_APB2: |
|
*rate = NPCM_APB_CLOCK(2); |
|
break; |
|
case NPCM_CLOCK_BUS_APB3: |
|
*rate = NPCM_APB_CLOCK(3); |
|
break; |
|
case NPCM_CLOCK_BUS_AHB6: |
|
*rate = CORE_CLK / (AHB6DIV_VAL + 1); |
|
break; |
|
case NPCM_CLOCK_BUS_FIU: |
|
*rate = CORE_CLK / (FIUDIV_VAL + 1); |
|
break; |
|
case NPCM_CLOCK_BUS_I3C: |
|
*rate = CORE_CLK / (I3CDIV_VAL + 1); |
|
break; |
|
case NPCM_CLOCK_BUS_CORE: |
|
*rate = CORE_CLK; |
|
break; |
|
case NPCM_CLOCK_BUS_LFCLK: |
|
*rate = LFCLK; |
|
break; |
|
case NPCM_CLOCK_BUS_FMCLK: |
|
*rate = FMCLK; |
|
break; |
|
case NPCM_CLOCK_BUS_USB20_CLK: |
|
*rate = USB20_CLK; |
|
break; |
|
case NPCM_CLOCK_BUS_SIO_CLK: |
|
*rate = SIO_CLK; |
|
break; |
|
default: |
|
*rate = 0U; |
|
/* Invalid parameters */ |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Clock controller driver registration */ |
|
static DEVICE_API(clock_control, npcm_clock_control_api) = { |
|
.on = npcm_clock_control_on, |
|
.off = npcm_clock_control_off, |
|
.get_rate = npcm_clock_control_get_subsys_rate, |
|
}; |
|
|
|
static int npcm_clock_control_init(const struct device *dev) |
|
{ |
|
struct cdcg_reg *const priv = (struct cdcg_reg *)(DRV_CONFIG(dev)->base_cdcg); |
|
const struct freq_multiplier_t *freq_p; |
|
int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(freq_multiplier); i++) { |
|
if (freq_multiplier[i].ofmclk == OFMCLK) { |
|
freq_p = &freq_multiplier[i]; |
|
break; |
|
} |
|
} |
|
|
|
if (i >= ARRAY_SIZE(freq_multiplier)) { |
|
LOG_ERR("Unsupported OFMCLK frequency %d", OFMCLK); |
|
return -EINVAL; |
|
} |
|
|
|
/* |
|
* Resetting the OFMCLK (even to the same value) will make the clock |
|
* unstable for a little which can affect peripheral communication like |
|
* eSPI. Skip this if not needed. |
|
*/ |
|
if (priv->hfcgn != freq_p->hfcgn || priv->hfcgml != freq_p->hfcgml || |
|
priv->hfcgmh != freq_p->hfcgmh) { |
|
/* |
|
* Configure frequency multiplier M/N values according to |
|
* the requested OFMCLK (Unit:Hz). |
|
*/ |
|
priv->hfcgn = freq_p->hfcgn; |
|
priv->hfcgml = freq_p->hfcgml; |
|
priv->hfcgmh = freq_p->hfcgmh; |
|
|
|
/* Load M and N values into the frequency multiplier */ |
|
priv->hfcgctrl |= BIT(NPCM_HFCGCTRL_LOAD); |
|
/* Wait for stable */ |
|
while (sys_test_bit(priv->hfcgctrl, NPCM_HFCGCTRL_CLK_CHNG)) { |
|
} |
|
} |
|
|
|
/* Set all clock prescalers of core and peripherals. */ |
|
priv->hfcgp = (FPRED_VAL << 4) | AHB6DIV_VAL; |
|
priv->hfcbcd = APB1DIV_VAL | (APB2DIV_VAL << 4); |
|
priv->hfcbcd1 = (I3CDIV_VAL << 2) | FIUDIV_VAL; |
|
priv->hfcbcd2 = APB3DIV_VAL; |
|
|
|
return 0; |
|
} |
|
|
|
const struct npcm_pcc_config pcc_config = { |
|
.base_cdcg = DT_INST_REG_ADDR_BY_NAME(0, cdcg), |
|
.base_pmc = DT_INST_REG_ADDR_BY_NAME(0, pmc), |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, &npcm_clock_control_init, NULL, NULL, &pcc_config, PRE_KERNEL_1, |
|
CONFIG_KERNEL_INIT_PRIORITY_OBJECTS, &npcm_clock_control_api);
|
|
|