1 /*
2  * Copyright (c) 2023 Nuvoton Technology Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT nuvoton_numaker_scc
8 
9 #include <zephyr/drivers/clock_control.h>
10 #include <zephyr/drivers/clock_control/clock_control_numaker.h>
11 #include <zephyr/logging/log.h>
12 #include <NuMicro.h>
13 
14 LOG_MODULE_REGISTER(clock_control_numaker_scc, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
15 
16 struct numaker_scc_config {
17 	uint32_t clk_base;
18 	int hxt;
19 	int lxt;
20 	int hirc48;
21 	uint32_t clk_pclkdiv;
22 	uint32_t core_clock;
23 };
24 
numaker_scc_on(const struct device * dev,clock_control_subsys_t subsys)25 static inline int numaker_scc_on(const struct device *dev, clock_control_subsys_t subsys)
26 {
27 	ARG_UNUSED(dev);
28 
29 	struct numaker_scc_subsys *scc_subsys = (struct numaker_scc_subsys *)subsys;
30 
31 	if (scc_subsys->subsys_id == NUMAKER_SCC_SUBSYS_ID_PCC) {
32 		SYS_UnlockReg();
33 		CLK_EnableModuleClock(scc_subsys->pcc.clk_modidx);
34 		SYS_LockReg();
35 	} else {
36 		return -EINVAL;
37 	}
38 
39 	return 0;
40 }
41 
numaker_scc_off(const struct device * dev,clock_control_subsys_t subsys)42 static inline int numaker_scc_off(const struct device *dev, clock_control_subsys_t subsys)
43 {
44 	ARG_UNUSED(dev);
45 
46 	struct numaker_scc_subsys *scc_subsys = (struct numaker_scc_subsys *)subsys;
47 
48 	if (scc_subsys->subsys_id == NUMAKER_SCC_SUBSYS_ID_PCC) {
49 		SYS_UnlockReg();
50 		CLK_DisableModuleClock(scc_subsys->pcc.clk_modidx);
51 		SYS_LockReg();
52 	} else {
53 		return -EINVAL;
54 	}
55 
56 	return 0;
57 }
58 
numaker_scc_get_rate(const struct device * dev,clock_control_subsys_t subsys,uint32_t * rate)59 static inline int numaker_scc_get_rate(const struct device *dev, clock_control_subsys_t subsys,
60 				       uint32_t *rate)
61 {
62 	ARG_UNUSED(dev);
63 	ARG_UNUSED(subsys);
64 	ARG_UNUSED(rate);
65 	return -ENOTSUP;
66 }
67 
numaker_scc_set_rate(const struct device * dev,clock_control_subsys_t subsys,clock_control_subsys_rate_t rate)68 static inline int numaker_scc_set_rate(const struct device *dev, clock_control_subsys_t subsys,
69 				       clock_control_subsys_rate_t rate)
70 {
71 	ARG_UNUSED(dev);
72 	ARG_UNUSED(subsys);
73 	ARG_UNUSED(rate);
74 	return -ENOTSUP;
75 }
76 
numaker_scc_configure(const struct device * dev,clock_control_subsys_t subsys,void * data)77 static inline int numaker_scc_configure(const struct device *dev, clock_control_subsys_t subsys,
78 					void *data)
79 {
80 	ARG_UNUSED(dev);
81 	ARG_UNUSED(data);
82 
83 	struct numaker_scc_subsys *scc_subsys = (struct numaker_scc_subsys *)subsys;
84 
85 	if (scc_subsys->subsys_id == NUMAKER_SCC_SUBSYS_ID_PCC) {
86 		SYS_UnlockReg();
87 		CLK_SetModuleClock(scc_subsys->pcc.clk_modidx, scc_subsys->pcc.clk_src,
88 				   scc_subsys->pcc.clk_div);
89 		SYS_LockReg();
90 	} else {
91 		return -EINVAL;
92 	}
93 
94 	return 0;
95 }
96 
97 /* System clock controller driver registration */
98 static struct clock_control_driver_api numaker_scc_api = {
99 	.on = numaker_scc_on,
100 	.off = numaker_scc_off,
101 	.get_rate = numaker_scc_get_rate,
102 	.set_rate = numaker_scc_set_rate,
103 	.configure = numaker_scc_configure,
104 };
105 
106 /* At most one compatible with status "okay" */
107 BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) <= 1,
108 	     "Requires at most one compatible with status \"okay\"");
109 
110 #define LOG_OSC_SW(osc, sw)                                                                        \
111 	if (sw == NUMAKER_SCC_CLKSW_ENABLE) {                                                      \
112 		LOG_DBG("Enable " #osc);                                                           \
113 	} else if (sw == NUMAKER_SCC_CLKSW_DISABLE) {                                              \
114 		LOG_DBG("Disable " #osc);                                                          \
115 	}
116 
numaker_scc_init(const struct device * dev)117 static int numaker_scc_init(const struct device *dev)
118 {
119 	const struct numaker_scc_config *cfg = dev->config;
120 
121 	LOG_DBG("CLK base: 0x%08x", cfg->clk_base);
122 #if DT_NODE_HAS_PROP(DT_NODELABEL(scc), hxt)
123 	LOG_OSC_SW(HXT, cfg->hxt);
124 #endif
125 #if DT_NODE_HAS_PROP(DT_NODELABEL(scc), lxt)
126 	LOG_OSC_SW(LXT, cfg->lxt);
127 #endif
128 #if DT_NODE_HAS_PROP(DT_NODELABEL(scc), hirc48)
129 	LOG_OSC_SW(HIRC48, cfg->hirc48);
130 #endif
131 #if DT_NODE_HAS_PROP(DT_NODELABEL(scc), clk_pclkdiv)
132 	LOG_DBG("CLK_PCLKDIV: 0x%08x", cfg->clk_pclkdiv);
133 #endif
134 #if DT_NODE_HAS_PROP(DT_NODELABEL(scc), core_clock)
135 	LOG_DBG("Core clock: %d (Hz)", cfg->core_clock);
136 #endif
137 
138 	/*
139 	 * z_arm_platform_init() will respect above configurations and
140 	 * actually take charge of system clock control initialization.
141 	 */
142 
143 	SystemCoreClockUpdate();
144 	LOG_DBG("SystemCoreClock: %d (Hz)", SystemCoreClock);
145 
146 	return 0;
147 }
148 
149 #define NUMICRO_SCC_INIT(inst)                                                                     \
150 	static const struct numaker_scc_config numaker_scc_config_##inst = {                       \
151 		.clk_base = DT_INST_REG_ADDR(inst),                                                \
152 		.hxt = DT_INST_ENUM_IDX_OR(inst, hxt, NUMAKER_SCC_CLKSW_UNTOUCHED),                \
153 		.lxt = DT_INST_ENUM_IDX_OR(inst, lxt, NUMAKER_SCC_CLKSW_UNTOUCHED),                \
154 		.hirc48 = DT_INST_ENUM_IDX_OR(inst, hirc48, NUMAKER_SCC_CLKSW_UNTOUCHED),          \
155 		.clk_pclkdiv = DT_INST_PROP_OR(inst, clk_pclkdiv, 0),                              \
156 		.core_clock = DT_INST_PROP_OR(inst, core_clock, 0),                                \
157 	};                                                                                         \
158                                                                                                    \
159 	DEVICE_DT_INST_DEFINE(inst, &numaker_scc_init, NULL, NULL, &numaker_scc_config_##inst,     \
160 			      PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &numaker_scc_api);
161 
162 DT_INST_FOREACH_STATUS_OKAY(NUMICRO_SCC_INIT);
163