1 /*
2  * Copyright (c) 2024 GARDENA GmbH
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT silabs_si32_pll
8 
9 #include <stdint.h>
10 
11 #include <zephyr/device.h>
12 #include <zephyr/devicetree.h>
13 #include <zephyr/drivers/clock_control.h>
14 
15 #include <SI32_CLKCTRL_A_Type.h>
16 #include <SI32_PLL_A_Type.h>
17 #include <si32_device.h>
18 
19 #define LOG_LEVEL LOG_LEVEL_DBG
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_REGISTER(pll);
22 
23 struct clock_control_si32_pll_config {
24 	SI32_PLL_A_Type *pll;
25 };
26 
27 struct clock_control_si32_pll_data {
28 	uint32_t freq;
29 };
30 
clock_control_si32_pll_on(const struct device * dev,clock_control_subsys_t sys)31 static int clock_control_si32_pll_on(const struct device *dev, clock_control_subsys_t sys)
32 {
33 	const struct clock_control_si32_pll_config *config = dev->config;
34 	struct clock_control_si32_pll_data *data = dev->data;
35 
36 	if (data->freq == 0) {
37 		return -ENOTSUP;
38 	}
39 
40 	uint32_t dco_range;
41 
42 	if (data->freq > 80000000) {
43 		dco_range = 4;
44 	} else if (data->freq > 76500000) {
45 		dco_range = 4;
46 	} else if (data->freq > 62000000) {
47 		dco_range = 3;
48 	} else if (data->freq > 49500000) {
49 		dco_range = 2;
50 	} else if (data->freq > 35000000) {
51 		dco_range = 1;
52 	} else if (data->freq > 23000000) {
53 		dco_range = 0;
54 	} else {
55 		return -ENOTSUP;
56 	}
57 
58 	/* lposc0div */
59 	const uint32_t source_clock_freq = 2500000;
60 	const uint32_t div_m = 100;
61 	const uint32_t div_n = (data->freq / source_clock_freq) * (div_m + 1) - 1;
62 
63 	if (div_n < 32 || div_n > 4095) {
64 		return -ENOTSUP;
65 	}
66 
67 	/* Setup PLL to lock to requested frequency */
68 	SI32_PLL_A_initialize(config->pll, 0x00, 0x00, 0x00, 0x000FFF0);
69 	SI32_PLL_A_set_numerator(config->pll, div_n);
70 	SI32_PLL_A_set_denominator(config->pll, div_m);
71 	/* TODO: support other clock sources */
72 	SI32_PLL_A_select_reference_clock_source_lp0oscdiv(config->pll);
73 
74 	/* Wait for lock */
75 	SI32_PLL_A_select_disable_dco_output(config->pll);
76 	SI32_PLL_A_set_frequency_adjuster_value(config->pll, 0xFFF);
77 	SI32_PLL_A_set_output_frequency_range(config->pll, dco_range);
78 
79 	/* Lock and block for result */
80 	SI32_PLL_A_select_dco_frequency_lock_mode(config->pll);
81 	while (!(SI32_PLL_A_is_locked(config->pll) ||
82 		 SI32_PLL_A_is_saturation_low_interrupt_pending(config->pll) ||
83 		 SI32_PLL_A_is_saturation_high_interrupt_pending(config->pll)))
84 		;
85 
86 	return 0;
87 }
88 
clock_control_si32_pll_off(const struct device * dev,clock_control_subsys_t sys)89 static int clock_control_si32_pll_off(const struct device *dev, clock_control_subsys_t sys)
90 {
91 
92 	return -ENOTSUP;
93 }
94 
clock_control_si32_pll_get_rate(const struct device * dev,clock_control_subsys_t sys,uint32_t * rate)95 static int clock_control_si32_pll_get_rate(const struct device *dev, clock_control_subsys_t sys,
96 					   uint32_t *rate)
97 {
98 	struct clock_control_si32_pll_data *data = dev->data;
99 
100 	*rate = data->freq;
101 
102 	return 0;
103 }
104 
clock_control_si32_pll_set_rate(const struct device * dev,clock_control_subsys_t sys,clock_control_subsys_rate_t rate_)105 static int clock_control_si32_pll_set_rate(const struct device *dev, clock_control_subsys_t sys,
106 					   clock_control_subsys_rate_t rate_)
107 {
108 	struct clock_control_si32_pll_data *data = dev->data;
109 	const uint32_t *rate = rate_;
110 
111 	data->freq = *rate;
112 
113 	return 0;
114 }
115 
116 static DEVICE_API(clock_control, clock_control_si32_pll_api) = {
117 	.on = clock_control_si32_pll_on,
118 	.off = clock_control_si32_pll_off,
119 	.get_rate = clock_control_si32_pll_get_rate,
120 	.set_rate = clock_control_si32_pll_set_rate,
121 };
122 
clock_control_si32_pll_init(const struct device * dev)123 static int clock_control_si32_pll_init(const struct device *dev)
124 {
125 	SI32_CLKCTRL_A_enable_apb_to_modules_0(SI32_CLKCTRL_0, SI32_CLKCTRL_A_APBCLKG0_PLL0);
126 
127 	return 0;
128 }
129 
130 static const struct clock_control_si32_pll_config config = {
131 	.pll = (SI32_PLL_A_Type *)DT_REG_ADDR(DT_NODELABEL(pll0)),
132 };
133 
134 static struct clock_control_si32_pll_data data = {
135 	.freq = 0,
136 };
137 
138 DEVICE_DT_INST_DEFINE(0, clock_control_si32_pll_init, NULL, &data, &config, PRE_KERNEL_1,
139 		      CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &clock_control_si32_pll_api);
140