diff options
| author | James Munns <[email protected]> | 2025-12-18 14:11:10 +0100 |
|---|---|---|
| committer | James Munns <[email protected]> | 2025-12-18 15:02:24 +0100 |
| commit | c94c09c72d16fd5cc9cb7c925386e7a2d6de1dcd (patch) | |
| tree | b78a9db91e3f7a79770427acb4efe4435938f546 /embassy-mcxa/src/clocks/mod.rs | |
| parent | b5b49cbcf3a991bf6d434b0870da50f3ee722612 (diff) | |
Pre-test
Diffstat (limited to 'embassy-mcxa/src/clocks/mod.rs')
| -rw-r--r-- | embassy-mcxa/src/clocks/mod.rs | 318 |
1 files changed, 302 insertions, 16 deletions
diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index b41a1ba46..98f7075eb 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs | |||
| @@ -88,6 +88,7 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | |||
| 88 | operator.configure_sirc_clocks()?; | 88 | operator.configure_sirc_clocks()?; |
| 89 | operator.configure_fro16k_clocks()?; | 89 | operator.configure_fro16k_clocks()?; |
| 90 | operator.configure_sosc()?; | 90 | operator.configure_sosc()?; |
| 91 | operator.configure_spll()?; | ||
| 91 | 92 | ||
| 92 | // For now, just use FIRC as the main/cpu clock, which should already be | 93 | // For now, just use FIRC as the main/cpu clock, which should already be |
| 93 | // the case on reset | 94 | // the case on reset |
| @@ -825,7 +826,7 @@ impl ClockOperator<'_> { | |||
| 825 | Ok(()) | 826 | Ok(()) |
| 826 | } | 827 | } |
| 827 | 828 | ||
| 828 | /// Configure the FRO16K/clk_16k clock family | 829 | /// Configure the ROSC/FRO16K/clk_16k clock family |
| 829 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { | 830 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { |
| 830 | let Some(fro16k) = self.config.fro16k.as_ref() else { | 831 | let Some(fro16k) = self.config.fro16k.as_ref() else { |
| 831 | return Ok(()); | 832 | return Ok(()); |
| @@ -866,21 +867,30 @@ impl ClockOperator<'_> { | |||
| 866 | Ok(()) | 867 | Ok(()) |
| 867 | } | 868 | } |
| 868 | 869 | ||
| 870 | fn ensure_ldo_active(&mut self) { | ||
| 871 | // TODO: Config for the LDO? For now, just enable | ||
| 872 | // using the default settings: | ||
| 873 | // LDOBYPASS: 0/not bypassed | ||
| 874 | // VOUT_SEL: 0b100: 1.1v | ||
| 875 | // LDOEN: 0/Disabled | ||
| 876 | let already_enabled = { | ||
| 877 | let ldocsr = self.scg0.ldocsr().read(); | ||
| 878 | ldocsr.ldoen().is_enabled() && ldocsr.vout_ok().is_enabled() | ||
| 879 | }; | ||
| 880 | if !already_enabled { | ||
| 881 | self.scg0.ldocsr().modify(|_r, w| w.ldoen().enabled()); | ||
| 882 | while self.scg0.ldocsr().read().vout_ok().is_disabled() {} | ||
| 883 | } | ||
| 884 | } | ||
| 885 | |||
| 869 | /// Configure the SOSC/clk_in oscillator | 886 | /// Configure the SOSC/clk_in oscillator |
| 870 | fn configure_sosc(&mut self) -> Result<(), ClockError> { | 887 | fn configure_sosc(&mut self) -> Result<(), ClockError> { |
| 871 | let Some(parts) = self.config.sosc.as_ref() else { | 888 | let Some(parts) = self.config.sosc.as_ref() else { |
| 872 | return Ok(()); | 889 | return Ok(()); |
| 873 | }; | 890 | }; |
| 874 | 891 | ||
| 875 | let scg0 = unsafe { pac::Scg0::steal() }; | 892 | // Enable (and wait for) LDO to be active |
| 876 | 893 | self.ensure_ldo_active(); | |
| 877 | // TODO: Config for the LDO? For now, if we have Sosc, just enable | ||
| 878 | // using the default settings: | ||
| 879 | // LDOBYPASS: 0/not bypassed | ||
| 880 | // VOUT_SEL: 0b100: 1.1v | ||
| 881 | // LDOEN: 0/Disabled | ||
| 882 | scg0.ldocsr().modify(|_r, w| w.ldoen().enabled()); | ||
| 883 | while scg0.ldocsr().read().vout_ok().is_disabled() {} | ||
| 884 | 894 | ||
| 885 | // TODO: something something pins? This seems to work when the pins are | 895 | // TODO: something something pins? This seems to work when the pins are |
| 886 | // not enabled, even if GPIO hasn't been initialized at all yet. | 896 | // not enabled, even if GPIO hasn't been initialized at all yet. |
| @@ -920,14 +930,14 @@ impl ClockOperator<'_> { | |||
| 920 | }; | 930 | }; |
| 921 | 931 | ||
| 922 | // Set source/erefs and range | 932 | // Set source/erefs and range |
| 923 | scg0.sosccfg().modify(|_r, w| { | 933 | self.scg0.sosccfg().modify(|_r, w| { |
| 924 | w.erefs().variant(eref); | 934 | w.erefs().variant(eref); |
| 925 | w.range().variant(range); | 935 | w.range().variant(range); |
| 926 | w | 936 | w |
| 927 | }); | 937 | }); |
| 928 | 938 | ||
| 929 | // Disable lock | 939 | // Disable lock |
| 930 | scg0.sosccsr().modify(|_r, w| w.lk().clear_bit()); | 940 | self.scg0.sosccsr().modify(|_r, w| w.lk().clear_bit()); |
| 931 | 941 | ||
| 932 | // TODO: We could enable the SOSC clock monitor. There are some things to | 942 | // TODO: We could enable the SOSC clock monitor. There are some things to |
| 933 | // figure out first: | 943 | // figure out first: |
| @@ -938,7 +948,7 @@ impl ClockOperator<'_> { | |||
| 938 | // * We need to decide if we need an interrupt or a reset if the monitor trips | 948 | // * We need to decide if we need an interrupt or a reset if the monitor trips |
| 939 | 949 | ||
| 940 | // Apply remaining config | 950 | // Apply remaining config |
| 941 | scg0.sosccsr().modify(|_r, w| { | 951 | self.scg0.sosccsr().modify(|_r, w| { |
| 942 | // For now, just disable the monitor. See above. | 952 | // For now, just disable the monitor. See above. |
| 943 | w.sosccm().disabled(); | 953 | w.sosccm().disabled(); |
| 944 | 954 | ||
| @@ -957,8 +967,8 @@ impl ClockOperator<'_> { | |||
| 957 | }); | 967 | }); |
| 958 | 968 | ||
| 959 | // Wait for SOSC to be valid, check for errors | 969 | // Wait for SOSC to be valid, check for errors |
| 960 | while !scg0.sosccsr().read().soscvld().bit_is_set() {} | 970 | while !self.scg0.sosccsr().read().soscvld().bit_is_set() {} |
| 961 | if scg0.sosccsr().read().soscerr().is_enabled_and_error() { | 971 | if self.scg0.sosccsr().read().soscerr().is_enabled_and_error() { |
| 962 | return Err(ClockError::BadConfig { | 972 | return Err(ClockError::BadConfig { |
| 963 | clock: "clk_in", | 973 | clock: "clk_in", |
| 964 | reason: "soscerr is set", | 974 | reason: "soscerr is set", |
| @@ -966,7 +976,7 @@ impl ClockOperator<'_> { | |||
| 966 | } | 976 | } |
| 967 | 977 | ||
| 968 | // Re-lock the sosc | 978 | // Re-lock the sosc |
| 969 | scg0.sosccsr().modify(|_r, w| w.lk().set_bit()); | 979 | self.scg0.sosccsr().modify(|_r, w| w.lk().set_bit()); |
| 970 | 980 | ||
| 971 | self.clocks.clk_in = Some(Clock { | 981 | self.clocks.clk_in = Some(Clock { |
| 972 | frequency: freq, | 982 | frequency: freq, |
| @@ -975,6 +985,282 @@ impl ClockOperator<'_> { | |||
| 975 | 985 | ||
| 976 | Ok(()) | 986 | Ok(()) |
| 977 | } | 987 | } |
| 988 | |||
| 989 | fn configure_spll(&mut self) -> Result<(), ClockError> { | ||
| 990 | // # Vocab | ||
| 991 | // | ||
| 992 | // | Name | Meaning | | ||
| 993 | // | :--- | :--- | | ||
| 994 | // | Fin | Frequency of clkin | | ||
| 995 | // | clkout | Output clock of the PLL | | ||
| 996 | // | Fout | Frequency of clkout (depends on mode) | | ||
| 997 | // | clkref | PLL Reference clock, the input clock to the PFD | | ||
| 998 | // | Fref | Frequency of clkref, Fref = Fin / N | | ||
| 999 | // | Fcco | Frequency of the output clock of the CCO, Fcco = M * Fref | | ||
| 1000 | // | N | Predivider value | | ||
| 1001 | // | M | Feedback divider value | | ||
| 1002 | // | P | Postdivider value | | ||
| 1003 | // | Tpon | PLL start-up time | | ||
| 1004 | |||
| 1005 | let Some(cfg) = self.config.spll.as_ref() else { | ||
| 1006 | return Ok(()); | ||
| 1007 | }; | ||
| 1008 | |||
| 1009 | let res = match cfg.source { | ||
| 1010 | config::SpllSource::Sosc => self | ||
| 1011 | .clocks | ||
| 1012 | .clk_in | ||
| 1013 | .as_ref() | ||
| 1014 | .map(|c| (c, pac::scg0::spllctrl::Source::Sosc)) | ||
| 1015 | .ok_or("sosc not active"), | ||
| 1016 | config::SpllSource::Firc => self | ||
| 1017 | .clocks | ||
| 1018 | .clk_45m | ||
| 1019 | .as_ref() | ||
| 1020 | .map(|c| (c, pac::scg0::spllctrl::Source::Firc)) | ||
| 1021 | .ok_or("firc not active"), | ||
| 1022 | config::SpllSource::Sirc => self | ||
| 1023 | .clocks | ||
| 1024 | .fro_12m | ||
| 1025 | .as_ref() | ||
| 1026 | .map(|c| (c, pac::scg0::spllctrl::Source::Sirc)) | ||
| 1027 | .ok_or("sirc not active"), | ||
| 1028 | }; | ||
| 1029 | let (clk, variant) = res.map_err(|s| ClockError::BadConfig { | ||
| 1030 | clock: "spll", | ||
| 1031 | reason: s, | ||
| 1032 | })?; | ||
| 1033 | if !clk.power.meets_requirement_of(&cfg.power) { | ||
| 1034 | return Err(ClockError::BadConfig { clock: "spll", reason: "needs low power source" }); | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | // Bandwidth calc | ||
| 1038 | // | ||
| 1039 | // > In normal applications, you must calculate the bandwidth manually by using the feedback divider M (ranging from 1 to 216-1), | ||
| 1040 | // > Equation 1, and Equation 2. The PLL is automatically stable in such case. In normal applications, SPLLCTRL[BANDDIRECT] must | ||
| 1041 | // > be 0; in this case, the bandwidth changes as a function of M. | ||
| 1042 | let f_in = clk.frequency; | ||
| 1043 | let bp_pre: bool; | ||
| 1044 | let bp_post: bool; | ||
| 1045 | let bp_post2: bool; | ||
| 1046 | let m: u16; | ||
| 1047 | let p: Option<u8>; | ||
| 1048 | let n: Option<u8>; | ||
| 1049 | |||
| 1050 | // Calculate both Fout and Fcco so we can ensure they don't overflow | ||
| 1051 | // and are in range | ||
| 1052 | let fout: Option<u32>; | ||
| 1053 | let fcco: Option<u32>; | ||
| 1054 | |||
| 1055 | match cfg.mode { | ||
| 1056 | // Fout = M x Fin | ||
| 1057 | config::SpllMode::Mode1a { m_mult } => { | ||
| 1058 | bp_pre = true; | ||
| 1059 | bp_post = true; | ||
| 1060 | bp_post2 = false; | ||
| 1061 | m = m_mult; | ||
| 1062 | p = None; | ||
| 1063 | n = None; | ||
| 1064 | fcco = f_in.checked_mul(m_mult as u32); | ||
| 1065 | fout = fcco; | ||
| 1066 | } | ||
| 1067 | // if !bypass_p2_div: Fout = (M / (2 x P)) x Fin | ||
| 1068 | // if bypass_p2_div: Fout = (M / P ) x Fin | ||
| 1069 | config::SpllMode::Mode1b { | ||
| 1070 | m_mult, | ||
| 1071 | p_div, | ||
| 1072 | bypass_p2_div, | ||
| 1073 | } => { | ||
| 1074 | bp_pre = true; | ||
| 1075 | bp_post = false; | ||
| 1076 | bp_post2 = bypass_p2_div; | ||
| 1077 | m = m_mult; | ||
| 1078 | p = Some(p_div); | ||
| 1079 | n = None; | ||
| 1080 | let mut div = p_div as u32; | ||
| 1081 | if !bypass_p2_div { | ||
| 1082 | div *= 2; | ||
| 1083 | } | ||
| 1084 | fcco = f_in.checked_mul(m_mult as u32); | ||
| 1085 | fout = (f_in / div).checked_mul(m_mult as u32); | ||
| 1086 | } | ||
| 1087 | // Fout = (M / N) x Fin | ||
| 1088 | config::SpllMode::Mode1c { m_mult, n_div } => { | ||
| 1089 | bp_pre = false; | ||
| 1090 | bp_post = true; | ||
| 1091 | bp_post2 = false; | ||
| 1092 | m = m_mult; | ||
| 1093 | p = None; | ||
| 1094 | n = Some(n_div); | ||
| 1095 | fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); | ||
| 1096 | fout = fcco; | ||
| 1097 | } | ||
| 1098 | // if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin | ||
| 1099 | // if bypass_p2_div: Fout = (M / ( N x P )) x Fin | ||
| 1100 | config::SpllMode::Mode1d { | ||
| 1101 | m_mult, | ||
| 1102 | n_div, | ||
| 1103 | p_div, | ||
| 1104 | bypass_p2_div, | ||
| 1105 | } => { | ||
| 1106 | bp_pre = false; | ||
| 1107 | bp_post = false; | ||
| 1108 | bp_post2 = bypass_p2_div; | ||
| 1109 | m = m_mult; | ||
| 1110 | p = Some(p_div); | ||
| 1111 | n = Some(n_div); | ||
| 1112 | // This can't overflow: u8 x u8 (x 2) always fits in u32 | ||
| 1113 | let mut div = (p_div as u32) * (n_div as u32); | ||
| 1114 | if !bypass_p2_div { | ||
| 1115 | div *= 2; | ||
| 1116 | } | ||
| 1117 | fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); | ||
| 1118 | fout = (f_in / div).checked_mul(m_mult as u32); | ||
| 1119 | } | ||
| 1120 | }; | ||
| 1121 | let fcco = fcco.ok_or(ClockError::BadConfig { | ||
| 1122 | clock: "spll", | ||
| 1123 | reason: "fcco invalid", | ||
| 1124 | })?; | ||
| 1125 | let fout = fout.ok_or(ClockError::BadConfig { | ||
| 1126 | clock: "spll", | ||
| 1127 | reason: "fout invalid", | ||
| 1128 | })?; | ||
| 1129 | // Fcco: 275MHz to 550MHz | ||
| 1130 | if !(275_000_000..=550_000_000).contains(&fcco) { | ||
| 1131 | return Err(ClockError::BadConfig { | ||
| 1132 | clock: "spll", | ||
| 1133 | reason: "fcco invalid", | ||
| 1134 | }); | ||
| 1135 | } | ||
| 1136 | // Fout: 4.3MHz to 2x Max CPU Frequency | ||
| 1137 | |||
| 1138 | // TODO: Different for different CPUs? | ||
| 1139 | const CPU_MAX_FREQ: u32 = 180_000_000; | ||
| 1140 | if !(4_300_000..=(2 * CPU_MAX_FREQ)).contains(&fout) { | ||
| 1141 | return Err(ClockError::BadConfig { | ||
| 1142 | clock: "spll", | ||
| 1143 | reason: "fout invalid", | ||
| 1144 | }); | ||
| 1145 | } | ||
| 1146 | |||
| 1147 | // TODO: | ||
| 1148 | assert!(clk.frequency != 0); | ||
| 1149 | assert!(m >= 1); | ||
| 1150 | if let Some(p) = p.as_ref() { | ||
| 1151 | assert!(*p >= 1); | ||
| 1152 | } | ||
| 1153 | if let Some(n) = n.as_ref() { | ||
| 1154 | assert!(*n >= 1); | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | // A = floor(m / 4) + 1 | ||
| 1158 | let selp_a = (m / 4) + 1; | ||
| 1159 | // SELP = A if A < 31 | ||
| 1160 | // = 31 if A >= 31 | ||
| 1161 | let selp = selp_a.min(31); | ||
| 1162 | |||
| 1163 | // A = 1 if M >= 8000 | ||
| 1164 | // = floor(8000 / M) if 8000 > M >= 122 | ||
| 1165 | // = 2 x floor(M / 4) / 3 if 122 > M >= 1 | ||
| 1166 | let seli_a = if m >= 8000 { | ||
| 1167 | 1 | ||
| 1168 | } else if m >= 122 { | ||
| 1169 | 8000 / m | ||
| 1170 | } else { | ||
| 1171 | (2 * (m / 4)) / 3 | ||
| 1172 | }; | ||
| 1173 | // SELI = A if A < 63 | ||
| 1174 | // = 63 if A >= 63 | ||
| 1175 | let seli = seli_a.min(63); | ||
| 1176 | // SELR must be 0. | ||
| 1177 | let selr = 0; | ||
| 1178 | |||
| 1179 | self.scg0.spllctrl().modify(|_r, w| { | ||
| 1180 | w.source().variant(variant); | ||
| 1181 | unsafe { | ||
| 1182 | w.selp().bits(selp as u8); | ||
| 1183 | w.seli().bits(seli as u8); | ||
| 1184 | w.selr().bits(selr); | ||
| 1185 | } | ||
| 1186 | w | ||
| 1187 | }); | ||
| 1188 | |||
| 1189 | if let Some(n) = n { | ||
| 1190 | self.scg0.spllndiv().modify(|_r, w| unsafe { w.ndiv().bits(n) }); | ||
| 1191 | } | ||
| 1192 | if let Some(p) = p { | ||
| 1193 | self.scg0.spllpdiv().modify(|_r, w| unsafe { w.pdiv().bits(p) }); | ||
| 1194 | } | ||
| 1195 | self.scg0.spllmdiv().modify(|_r, w| unsafe { w.mdiv().bits(m) }); | ||
| 1196 | |||
| 1197 | self.scg0.spllctrl().modify(|_r, w| { | ||
| 1198 | w.bypassprediv().bit(bp_pre); | ||
| 1199 | w.bypasspostdiv().bit(bp_post); | ||
| 1200 | w.bypasspostdiv2().bit(bp_post2); | ||
| 1201 | |||
| 1202 | // TODO: support FRM? | ||
| 1203 | w.frm().disabled(); | ||
| 1204 | |||
| 1205 | w | ||
| 1206 | }); | ||
| 1207 | |||
| 1208 | // Unlock | ||
| 1209 | self.scg0.spllcsr().modify(|_r, w| w.lk().write_enabled()); | ||
| 1210 | |||
| 1211 | // TODO: Support clock monitors? | ||
| 1212 | // self.scg0.spllcsr().modify(|_r, w| w.spllcm().?); | ||
| 1213 | |||
| 1214 | self.scg0.trim_lock().write(|w| unsafe { | ||
| 1215 | w.trim_lock_key().bits(0x5a5a); | ||
| 1216 | w.trim_unlock().not_locked() | ||
| 1217 | }); | ||
| 1218 | |||
| 1219 | // SPLLLOCK_CNFG: The lock time programmed in this register must be | ||
| 1220 | // equal to meet the PLL 500μs lock time plus the 300 refclk count startup. | ||
| 1221 | // | ||
| 1222 | // LOCK_TIME = 500μs/T ref + 300, F ref = F in /N (input frequency divided by pre-divider ratio). | ||
| 1223 | // | ||
| 1224 | // 500us is 1/2000th of a second, therefore Fref / 2000 is the number of cycles in 500us. | ||
| 1225 | let f_ref = if let Some(n) = n { f_in / (n as u32) } else { f_in }; | ||
| 1226 | let lock_time = f_ref.div_ceil(2000) + 300; | ||
| 1227 | self.scg0 | ||
| 1228 | .splllock_cnfg() | ||
| 1229 | .write(|w| unsafe { w.lock_time().bits(lock_time) }); | ||
| 1230 | |||
| 1231 | // TODO: Support Spread spectrum? | ||
| 1232 | |||
| 1233 | self.scg0.spllcsr().modify(|_r, w| { | ||
| 1234 | w.spllclken().enabled(); | ||
| 1235 | w.spllpwren().enabled(); | ||
| 1236 | w.spllsten().bit(matches!(cfg.power, PoweredClock::AlwaysEnabled)); | ||
| 1237 | w | ||
| 1238 | }); | ||
| 1239 | |||
| 1240 | // Wait for SPLL to set up | ||
| 1241 | loop { | ||
| 1242 | let csr = self.scg0.spllcsr().read(); | ||
| 1243 | if csr.spll_lock().is_enabled_and_valid() { | ||
| 1244 | if csr.spllerr().is_enabled_and_error() { | ||
| 1245 | return Err(ClockError::BadConfig { | ||
| 1246 | clock: "spll", | ||
| 1247 | reason: "spllerr is set", | ||
| 1248 | }); | ||
| 1249 | } | ||
| 1250 | break; | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | // Re-lock SPLL CSR | ||
| 1255 | self.scg0.spllcsr().modify(|_r, w| w.lk().write_disabled()); | ||
| 1256 | |||
| 1257 | self.clocks.pll1_clk = Some(Clock { | ||
| 1258 | frequency: fout, | ||
| 1259 | power: cfg.power, | ||
| 1260 | }); | ||
| 1261 | |||
| 1262 | Ok(()) | ||
| 1263 | } | ||
| 978 | } | 1264 | } |
| 979 | 1265 | ||
| 980 | // | 1266 | // |
