diff options
| author | Caleb Jamison <[email protected]> | 2023-05-07 13:49:48 -0400 |
|---|---|---|
| committer | Caleb Jamison <[email protected]> | 2023-05-08 09:45:54 -0400 |
| commit | 1a96eae22c38afbd0c1c15d1d5dcc43bc639369b (patch) | |
| tree | 1acc7615d82950c55e4f37fd43ffa7e5552a3b87 | |
| parent | 79c60f4a7d17613269f795df867c3c83ca90cbcc (diff) | |
rp clock configuration
| -rw-r--r-- | embassy-rp/src/clocks.rs | 511 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 20 |
2 files changed, 436 insertions, 95 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 8a34b293d..4ae967aae 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs | |||
| @@ -2,10 +2,138 @@ use pac::clocks::vals::*; | |||
| 2 | 2 | ||
| 3 | use crate::{pac, reset}; | 3 | use crate::{pac, reset}; |
| 4 | 4 | ||
| 5 | const XOSC_MHZ: u32 = 12; | 5 | static mut EXTERNAL_HZ: u32 = 0; |
| 6 | |||
| 7 | pub struct ClockConfig { | ||
| 8 | rosc_config: Option<RoscConfig>, | ||
| 9 | xosc_config: Option<XoscConfig>, | ||
| 10 | ref_clk_config: (RefClkSrc, u8), | ||
| 11 | sys_clk_config: (SysClkSrc, u32), | ||
| 12 | peri_clk_src: Option<ClkPeriCtrlAuxsrc>, | ||
| 13 | usb_clk_config: Option<(ClkUsbCtrlAuxsrc, u8)>, | ||
| 14 | adc_clk_config: Option<(ClkAdcCtrlAuxsrc, u8)>, | ||
| 15 | rtc_clk_config: Option<(ClkRtcCtrlAuxsrc, u32)>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl ClockConfig { | ||
| 19 | pub fn crystal(crystal_hz: u32) -> Self { | ||
| 20 | Self { | ||
| 21 | rosc_config: Some(RoscConfig { | ||
| 22 | range: pac::rosc::vals::FreqRange::MEDIUM, | ||
| 23 | drive_strength_0: 0, | ||
| 24 | drive_strength_1: 0, | ||
| 25 | drive_strength_2: 0, | ||
| 26 | drive_strength_3: 0, | ||
| 27 | drive_strength_4: 0, | ||
| 28 | drive_strength_5: 0, | ||
| 29 | drive_strength_6: 0, | ||
| 30 | drive_strength_7: 0, | ||
| 31 | div: 16, | ||
| 32 | }), | ||
| 33 | xosc_config: Some(XoscConfig { | ||
| 34 | hz: crystal_hz, | ||
| 35 | clock_type: ExternalClock::Crystal, | ||
| 36 | sys_pll: Some(PllConfig { | ||
| 37 | refdiv: 1, | ||
| 38 | vco_freq: 1500_000_000, | ||
| 39 | post_div1: 6, | ||
| 40 | post_div2: 2, | ||
| 41 | }), | ||
| 42 | usb_pll: Some(PllConfig { | ||
| 43 | refdiv: 1, | ||
| 44 | vco_freq: 480_000_000, | ||
| 45 | post_div1: 5, | ||
| 46 | post_div2: 2, | ||
| 47 | }), | ||
| 48 | }), | ||
| 49 | ref_clk_config: (RefClkSrc::Xosc, 1), | ||
| 50 | sys_clk_config: (SysClkSrc::Aux(ClkSysCtrlAuxsrc::CLKSRC_PLL_SYS), 1), | ||
| 51 | peri_clk_src: Some(ClkPeriCtrlAuxsrc::CLK_SYS), | ||
| 52 | usb_clk_config: Some((ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS, 1)), | ||
| 53 | adc_clk_config: Some((ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB, 1)), | ||
| 54 | rtc_clk_config: Some((ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB, 1024)), | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | pub fn rosc() -> Self { | ||
| 59 | Self { | ||
| 60 | rosc_config: Some(RoscConfig { | ||
| 61 | range: pac::rosc::vals::FreqRange::HIGH, | ||
| 62 | drive_strength_0: 0, | ||
| 63 | drive_strength_1: 0, | ||
| 64 | drive_strength_2: 0, | ||
| 65 | drive_strength_3: 0, | ||
| 66 | drive_strength_4: 0, | ||
| 67 | drive_strength_5: 0, | ||
| 68 | drive_strength_6: 0, | ||
| 69 | drive_strength_7: 0, | ||
| 70 | div: 1, | ||
| 71 | }), | ||
| 72 | xosc_config: None, | ||
| 73 | ref_clk_config: (RefClkSrc::Rosc, 4), | ||
| 74 | sys_clk_config: (SysClkSrc::Aux(ClkSysCtrlAuxsrc::ROSC_CLKSRC), 1), | ||
| 75 | peri_clk_src: Some(ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH), | ||
| 76 | usb_clk_config: None, | ||
| 77 | adc_clk_config: Some((ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH, 1)), | ||
| 78 | rtc_clk_config: Some((ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH, 1024)), | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | pub enum ExternalClock { | ||
| 83 | Crystal, | ||
| 84 | Clock, | ||
| 85 | } | ||
| 86 | |||
| 87 | pub struct XoscConfig { | ||
| 88 | hz: u32, | ||
| 89 | clock_type: ExternalClock, | ||
| 90 | sys_pll: Option<PllConfig>, | ||
| 91 | usb_pll: Option<PllConfig>, | ||
| 92 | } | ||
| 93 | |||
| 94 | pub struct RoscConfig { | ||
| 95 | range: pac::rosc::vals::FreqRange, | ||
| 96 | drive_strength_0: u8, | ||
| 97 | drive_strength_1: u8, | ||
| 98 | drive_strength_2: u8, | ||
| 99 | drive_strength_3: u8, | ||
| 100 | drive_strength_4: u8, | ||
| 101 | drive_strength_5: u8, | ||
| 102 | drive_strength_6: u8, | ||
| 103 | drive_strength_7: u8, | ||
| 104 | div: u16, | ||
| 105 | } | ||
| 106 | |||
| 107 | pub struct PllConfig { | ||
| 108 | pub refdiv: u32, | ||
| 109 | pub vco_freq: u32, | ||
| 110 | pub post_div1: u8, | ||
| 111 | pub post_div2: u8, | ||
| 112 | } | ||
| 113 | |||
| 114 | pub struct RefClkConfig { | ||
| 115 | pub src: RefClkSrc, | ||
| 116 | pub div: u8, | ||
| 117 | } | ||
| 118 | |||
| 119 | pub enum RefClkSrc { | ||
| 120 | Xosc, | ||
| 121 | Rosc, | ||
| 122 | Aux(ClkRefCtrlAuxsrc), | ||
| 123 | } | ||
| 124 | |||
| 125 | pub struct SysClkConfig { | ||
| 126 | pub src: SysClkSrc, | ||
| 127 | pub div: u32, | ||
| 128 | } | ||
| 129 | |||
| 130 | pub enum SysClkSrc { | ||
| 131 | Ref, | ||
| 132 | Aux(ClkSysCtrlAuxsrc), | ||
| 133 | } | ||
| 6 | 134 | ||
| 7 | /// safety: must be called exactly once at bootup | 135 | /// safety: must be called exactly once at bootup |
| 8 | pub(crate) unsafe fn init() { | 136 | pub(crate) unsafe fn init(config: ClockConfig) { |
| 9 | // Reset everything except: | 137 | // Reset everything except: |
| 10 | // - QSPI (we're using it to run this code!) | 138 | // - QSPI (we're using it to run this code!) |
| 11 | // - PLLs (it may be suicide if that's what's clocking us) | 139 | // - PLLs (it may be suicide if that's what's clocking us) |
| @@ -15,124 +143,325 @@ pub(crate) unsafe fn init() { | |||
| 15 | peris.set_pads_qspi(false); | 143 | peris.set_pads_qspi(false); |
| 16 | peris.set_pll_sys(false); | 144 | peris.set_pll_sys(false); |
| 17 | peris.set_pll_usb(false); | 145 | peris.set_pll_usb(false); |
| 146 | // TODO investigate if usb should be unreset here | ||
| 18 | peris.set_usbctrl(false); | 147 | peris.set_usbctrl(false); |
| 19 | peris.set_syscfg(false); | 148 | peris.set_syscfg(false); |
| 20 | reset::reset(peris); | 149 | reset::reset(peris); |
| 21 | 150 | ||
| 22 | // Remove reset from peripherals which are clocked only by clk_sys and | ||
| 23 | // clk_ref. Other peripherals stay in reset until we've configured clocks. | ||
| 24 | let mut peris = reset::ALL_PERIPHERALS; | ||
| 25 | peris.set_adc(false); | ||
| 26 | peris.set_rtc(false); | ||
| 27 | peris.set_spi0(false); | ||
| 28 | peris.set_spi1(false); | ||
| 29 | peris.set_uart0(false); | ||
| 30 | peris.set_uart1(false); | ||
| 31 | peris.set_usbctrl(false); | ||
| 32 | reset::unreset_wait(peris); | ||
| 33 | |||
| 34 | // Start tick in watchdog | ||
| 35 | // xosc 12 mhz | ||
| 36 | pac::WATCHDOG.tick().write(|w| { | ||
| 37 | w.set_cycles(XOSC_MHZ as u16); | ||
| 38 | w.set_enable(true); | ||
| 39 | }); | ||
| 40 | |||
| 41 | // Disable resus that may be enabled from previous software | 151 | // Disable resus that may be enabled from previous software |
| 42 | let c = pac::CLOCKS; | 152 | let c = pac::CLOCKS; |
| 43 | c.clk_sys_resus_ctrl() | 153 | c.clk_sys_resus_ctrl() |
| 44 | .write_value(pac::clocks::regs::ClkSysResusCtrl(0)); | 154 | .write_value(pac::clocks::regs::ClkSysResusCtrl(0)); |
| 45 | 155 | ||
| 46 | // start XOSC | ||
| 47 | start_xosc(); | ||
| 48 | |||
| 49 | // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. | 156 | // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. |
| 50 | c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); | 157 | c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); |
| 51 | while c.clk_sys_selected().read() != 1 {} | 158 | while c.clk_sys_selected().read() != 1 {} |
| 52 | c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); | 159 | c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); |
| 53 | while c.clk_ref_selected().read() != 1 {} | 160 | while c.clk_ref_selected().read() != 1 {} |
| 54 | 161 | ||
| 55 | // Configure PLLs | 162 | if let Some(config) = config.rosc_config { |
| 56 | // REF FBDIV VCO POSTDIV | 163 | configure_rosc(config); |
| 57 | // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz | 164 | } |
| 58 | // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz | ||
| 59 | configure_pll(pac::PLL_SYS, 1, 1500_000_000, 6, 2); | ||
| 60 | configure_pll(pac::PLL_USB, 1, 480_000_000, 5, 2); | ||
| 61 | 165 | ||
| 62 | // CLK_REF = XOSC (12MHz) / 1 = 12MHz2Mhz | 166 | if let Some(config) = config.xosc_config { |
| 63 | c.clk_ref_ctrl().write(|w| { | 167 | EXTERNAL_HZ = config.hz; |
| 64 | w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); | ||
| 65 | }); | ||
| 66 | while c.clk_ref_selected().read() != 1 << ClkRefCtrlSrc::XOSC_CLKSRC.0 {} | ||
| 67 | c.clk_ref_div().write(|w| w.set_int(1)); | ||
| 68 | 168 | ||
| 69 | // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz | 169 | pac::WATCHDOG.tick().write(|w| { |
| 70 | c.clk_sys_ctrl().write(|w| { | 170 | w.set_cycles((config.hz / 1_000_000) as u16); |
| 71 | w.set_src(ClkSysCtrlSrc::CLK_REF); | 171 | w.set_enable(true); |
| 72 | }); | 172 | }); |
| 73 | while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF.0 {} | 173 | |
| 74 | c.clk_sys_div().write(|w| w.set_int(1)); | 174 | // start XOSC |
| 75 | c.clk_sys_ctrl().write(|w| { | 175 | match config.clock_type { |
| 76 | w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_SYS); | 176 | ExternalClock::Crystal => start_xosc(config.hz), |
| 77 | w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); | 177 | // TODO The datasheet says the xosc needs to be put into a bypass mode to use an |
| 78 | }); | 178 | // external clock, but is mum about how to do that. |
| 79 | while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX.0 {} | 179 | ExternalClock::Clock => todo!(), |
| 180 | } | ||
| 181 | |||
| 182 | if let Some(sys_pll_config) = config.sys_pll { | ||
| 183 | configure_pll(pac::PLL_SYS, config.hz, sys_pll_config); | ||
| 184 | } | ||
| 185 | if let Some(usb_pll_config) = config.usb_pll { | ||
| 186 | configure_pll(pac::PLL_USB, config.hz, usb_pll_config); | ||
| 187 | } | ||
| 188 | } | ||
| 80 | 189 | ||
| 81 | // CLK USB = PLL USB (48MHz) / 1 = 48MHz | 190 | let (src, div) = config.ref_clk_config; |
| 82 | c.clk_usb_div().write(|w| w.set_int(1)); | 191 | match src { |
| 83 | c.clk_usb_ctrl().write(|w| { | 192 | RefClkSrc::Xosc => { |
| 193 | c.clk_ref_ctrl().write(|w| { | ||
| 194 | w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); | ||
| 195 | }); | ||
| 196 | while c.clk_ref_selected().read() != 1 << ClkRefCtrlSrc::XOSC_CLKSRC.0 {} | ||
| 197 | c.clk_ref_div().write(|w| w.set_int(div)); | ||
| 198 | } | ||
| 199 | RefClkSrc::Rosc => { | ||
| 200 | c.clk_ref_ctrl().write(|w| { | ||
| 201 | w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH); | ||
| 202 | }); | ||
| 203 | while c.clk_ref_selected().read() != 1 << ClkRefCtrlSrc::ROSC_CLKSRC_PH.0 {} | ||
| 204 | c.clk_ref_div().write(|w| w.set_int(div)); | ||
| 205 | } | ||
| 206 | RefClkSrc::Aux(src) => { | ||
| 207 | c.clk_ref_ctrl().write(|w| { | ||
| 208 | w.set_auxsrc(src); | ||
| 209 | w.set_src(ClkRefCtrlSrc::CLKSRC_CLK_REF_AUX); | ||
| 210 | }); | ||
| 211 | while c.clk_ref_selected().read() != 1 << ClkRefCtrlSrc::CLKSRC_CLK_REF_AUX.0 {} | ||
| 212 | c.clk_ref_div().write(|w| w.set_int(div)); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | pac::WATCHDOG.tick().write(|w| { | ||
| 217 | w.set_cycles((clk_ref_freq() / 1_000_000) as u16); | ||
| 84 | w.set_enable(true); | 218 | w.set_enable(true); |
| 85 | w.set_auxsrc(ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB); | ||
| 86 | }); | 219 | }); |
| 87 | 220 | ||
| 88 | // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz | 221 | let (src, div) = config.sys_clk_config; |
| 89 | c.clk_adc_div().write(|w| w.set_int(1)); | 222 | match src { |
| 90 | c.clk_adc_ctrl().write(|w| { | 223 | SysClkSrc::Ref => { |
| 91 | w.set_enable(true); | 224 | c.clk_sys_ctrl().write(|w| { |
| 92 | w.set_auxsrc(ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB); | 225 | w.set_src(ClkSysCtrlSrc::CLK_REF); |
| 226 | }); | ||
| 227 | while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF.0 {} | ||
| 228 | c.clk_sys_div().write(|w| w.set_int(div)); | ||
| 229 | } | ||
| 230 | SysClkSrc::Aux(src) => { | ||
| 231 | c.clk_sys_ctrl().write(|w| { | ||
| 232 | w.set_src(ClkSysCtrlSrc::CLK_REF); | ||
| 233 | }); | ||
| 234 | while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF.0 {} | ||
| 235 | |||
| 236 | c.clk_sys_div().write(|w| w.set_int(div)); | ||
| 237 | c.clk_sys_ctrl().write(|w| { | ||
| 238 | w.set_auxsrc(src); | ||
| 239 | w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); | ||
| 240 | }); | ||
| 241 | while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX.0 {} | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | let mut peris = reset::ALL_PERIPHERALS; | ||
| 246 | |||
| 247 | if let Some(src) = config.peri_clk_src { | ||
| 248 | c.clk_peri_ctrl().write(|w| { | ||
| 249 | w.set_enable(true); | ||
| 250 | w.set_auxsrc(src); | ||
| 251 | }); | ||
| 252 | } else { | ||
| 253 | peris.set_spi0(false); | ||
| 254 | peris.set_spi1(false); | ||
| 255 | peris.set_uart0(false); | ||
| 256 | peris.set_uart1(false); | ||
| 257 | } | ||
| 258 | |||
| 259 | if let Some((src, div)) = config.usb_clk_config { | ||
| 260 | // CLK USB = PLL USB (48MHz) / 1 = 48MHz | ||
| 261 | c.clk_usb_div().write(|w| w.set_int(div)); | ||
| 262 | c.clk_usb_ctrl().write(|w| { | ||
| 263 | w.set_enable(true); | ||
| 264 | w.set_auxsrc(src); | ||
| 265 | }); | ||
| 266 | } else { | ||
| 267 | peris.set_usbctrl(false); | ||
| 268 | } | ||
| 269 | |||
| 270 | if let Some((src, div)) = config.adc_clk_config { | ||
| 271 | // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz | ||
| 272 | c.clk_adc_div().write(|w| w.set_int(div)); | ||
| 273 | c.clk_adc_ctrl().write(|w| { | ||
| 274 | w.set_enable(true); | ||
| 275 | w.set_auxsrc(src); | ||
| 276 | }); | ||
| 277 | } else { | ||
| 278 | peris.set_adc(false); | ||
| 279 | } | ||
| 280 | |||
| 281 | if let Some((src, div)) = config.rtc_clk_config { | ||
| 282 | // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz | ||
| 283 | c.clk_rtc_ctrl().modify(|w| { | ||
| 284 | w.set_enable(false); | ||
| 285 | }); | ||
| 286 | c.clk_rtc_div().write(|w| w.set_int(div)); | ||
| 287 | c.clk_rtc_ctrl().write(|w| { | ||
| 288 | w.set_enable(true); | ||
| 289 | w.set_auxsrc(src); | ||
| 290 | }); | ||
| 291 | } else { | ||
| 292 | peris.set_rtc(false); | ||
| 293 | } | ||
| 294 | |||
| 295 | // Peripheral clocks should now all be running | ||
| 296 | reset::unreset_wait(peris); | ||
| 297 | } | ||
| 298 | |||
| 299 | unsafe fn configure_rosc(config: RoscConfig) { | ||
| 300 | let p = pac::ROSC; | ||
| 301 | |||
| 302 | p.freqa().write(|w| { | ||
| 303 | w.set_passwd(pac::rosc::vals::Passwd::PASS); | ||
| 304 | w.set_ds0(config.drive_strength_0); | ||
| 305 | w.set_ds1(config.drive_strength_1); | ||
| 306 | w.set_ds2(config.drive_strength_2); | ||
| 307 | w.set_ds3(config.drive_strength_3); | ||
| 93 | }); | 308 | }); |
| 94 | 309 | ||
| 95 | // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz | 310 | p.freqb().write(|w| { |
| 96 | c.clk_rtc_ctrl().modify(|w| { | 311 | w.set_passwd(pac::rosc::vals::Passwd::PASS); |
| 97 | w.set_enable(false); | 312 | w.set_ds4(config.drive_strength_4); |
| 313 | w.set_ds5(config.drive_strength_5); | ||
| 314 | w.set_ds6(config.drive_strength_6); | ||
| 315 | w.set_ds7(config.drive_strength_7); | ||
| 98 | }); | 316 | }); |
| 99 | c.clk_rtc_div().write(|w| w.set_int(1024)); | 317 | |
| 100 | c.clk_rtc_ctrl().write(|w| { | 318 | p.div().write(|w| { |
| 101 | w.set_enable(true); | 319 | w.set_div(pac::rosc::vals::Div(config.div + pac::rosc::vals::Div::PASS.0)); |
| 102 | w.set_auxsrc(ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB); | ||
| 103 | }); | 320 | }); |
| 104 | 321 | ||
| 105 | // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable | 322 | p.ctrl().write(|w| { |
| 106 | // Normally choose clk_sys or clk_usb | 323 | w.set_enable(pac::rosc::vals::Enable::ENABLE); |
| 107 | c.clk_peri_ctrl().write(|w| { | 324 | w.set_freq_range(config.range); |
| 108 | w.set_enable(true); | ||
| 109 | w.set_auxsrc(ClkPeriCtrlAuxsrc::CLK_SYS); | ||
| 110 | }); | 325 | }); |
| 326 | } | ||
| 111 | 327 | ||
| 112 | // Peripheral clocks should now all be running | 328 | pub fn estimate_rosc_freq() -> u32 { |
| 113 | let peris = reset::ALL_PERIPHERALS; | 329 | let p = pac::ROSC; |
| 114 | reset::unreset_wait(peris); | 330 | |
| 331 | let base = match unsafe { p.ctrl().read().freq_range() } { | ||
| 332 | pac::rosc::vals::FreqRange::LOW => 84_000_000, | ||
| 333 | pac::rosc::vals::FreqRange::MEDIUM => 104_000_000, | ||
| 334 | pac::rosc::vals::FreqRange::HIGH => 140_000_000, | ||
| 335 | pac::rosc::vals::FreqRange::TOOHIGH => 208_000_000, | ||
| 336 | _ => unreachable!(), | ||
| 337 | }; | ||
| 338 | let mut div = unsafe { p.div().read().0 - pac::rosc::vals::Div::PASS.0 as u32 }; | ||
| 339 | if div == 0 { | ||
| 340 | div = 32 | ||
| 341 | } | ||
| 342 | |||
| 343 | base / div | ||
| 115 | } | 344 | } |
| 116 | 345 | ||
| 117 | pub(crate) fn _clk_sys_freq() -> u32 { | 346 | pub(crate) fn clk_sys_freq() -> u32 { |
| 118 | 125_000_000 | 347 | let c = pac::CLOCKS; |
| 348 | let ctrl = unsafe { c.clk_sys_ctrl().read() }; | ||
| 349 | |||
| 350 | let base = match ctrl.src() { | ||
| 351 | ClkSysCtrlSrc::CLK_REF => clk_ref_freq(), | ||
| 352 | ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX => { | ||
| 353 | match ctrl.auxsrc() { | ||
| 354 | ClkSysCtrlAuxsrc::CLKSRC_PLL_SYS => clk_sys_pll_freq(), | ||
| 355 | ClkSysCtrlAuxsrc::CLKSRC_PLL_USB => clk_usb_pll_freq(), | ||
| 356 | ClkSysCtrlAuxsrc::ROSC_CLKSRC => estimate_rosc_freq(), | ||
| 357 | ClkSysCtrlAuxsrc::XOSC_CLKSRC => unsafe { EXTERNAL_HZ }, | ||
| 358 | // TODO not sure how to handle clkin sources | ||
| 359 | _ => todo!(), | ||
| 360 | } | ||
| 361 | } | ||
| 362 | _ => unreachable!(), | ||
| 363 | }; | ||
| 364 | |||
| 365 | let div = unsafe { c.clk_sys_div().read() }; | ||
| 366 | let int = if div.int() == 0 { 65536 } else { div.int() }; | ||
| 367 | // TODO handle fractional clock div | ||
| 368 | let _frac = div.frac(); | ||
| 369 | |||
| 370 | base / int | ||
| 371 | } | ||
| 372 | |||
| 373 | pub(crate) fn clk_sys_pll_freq() -> u32 { | ||
| 374 | let p = pac::PLL_SYS; | ||
| 375 | |||
| 376 | let input_freq = unsafe { EXTERNAL_HZ }; | ||
| 377 | let cs = unsafe { p.cs().read() }; | ||
| 378 | |||
| 379 | let refdiv = cs.refdiv() as u32; | ||
| 380 | let fbdiv = unsafe { p.fbdiv_int().read().fbdiv_int() } as u32; | ||
| 381 | let (postdiv1, postdiv2) = unsafe { | ||
| 382 | let prim = p.prim().read(); | ||
| 383 | (prim.postdiv1() as u32, prim.postdiv2() as u32) | ||
| 384 | }; | ||
| 385 | |||
| 386 | (((input_freq / refdiv) * fbdiv) / postdiv1) / postdiv2 | ||
| 387 | } | ||
| 388 | |||
| 389 | pub(crate) fn clk_usb_pll_freq() -> u32 { | ||
| 390 | let p = pac::PLL_USB; | ||
| 391 | |||
| 392 | let input_freq = unsafe { EXTERNAL_HZ }; | ||
| 393 | let cs = unsafe { p.cs().read() }; | ||
| 394 | |||
| 395 | let refdiv = cs.refdiv() as u32; | ||
| 396 | let fbdiv = unsafe { p.fbdiv_int().read().fbdiv_int() } as u32; | ||
| 397 | let (postdiv1, postdiv2) = unsafe { | ||
| 398 | let prim = p.prim().read(); | ||
| 399 | (prim.postdiv1() as u32, prim.postdiv2() as u32) | ||
| 400 | }; | ||
| 401 | |||
| 402 | (((input_freq / refdiv) * fbdiv) / postdiv1) / postdiv2 | ||
| 119 | } | 403 | } |
| 120 | 404 | ||
| 121 | pub(crate) fn clk_peri_freq() -> u32 { | 405 | pub(crate) fn clk_peri_freq() -> u32 { |
| 122 | 125_000_000 | 406 | let c = pac::CLOCKS; |
| 407 | let src = unsafe { c.clk_peri_ctrl().read().auxsrc() }; | ||
| 408 | |||
| 409 | match src { | ||
| 410 | ClkPeriCtrlAuxsrc::CLK_SYS => clk_sys_freq(), | ||
| 411 | ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS => clk_sys_pll_freq(), | ||
| 412 | ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH => estimate_rosc_freq(), | ||
| 413 | ClkPeriCtrlAuxsrc::XOSC_CLKSRC => unsafe { EXTERNAL_HZ }, | ||
| 414 | // TODO not sure how to handle clkin sources | ||
| 415 | _ => todo!(), | ||
| 416 | } | ||
| 417 | } | ||
| 418 | |||
| 419 | pub fn clk_ref_freq() -> u32 { | ||
| 420 | let c = pac::CLOCKS; | ||
| 421 | let ctrl = unsafe { c.clk_ref_ctrl().read() }; | ||
| 422 | |||
| 423 | let base = match ctrl.src() { | ||
| 424 | ClkRefCtrlSrc::ROSC_CLKSRC_PH => estimate_rosc_freq(), | ||
| 425 | ClkRefCtrlSrc::XOSC_CLKSRC => unsafe { EXTERNAL_HZ }, | ||
| 426 | ClkRefCtrlSrc::CLKSRC_CLK_REF_AUX => todo!(), | ||
| 427 | _ => unreachable!(), | ||
| 428 | }; | ||
| 429 | |||
| 430 | let mut div = unsafe { c.clk_ref_div().read().int() } as u32; | ||
| 431 | if div == 0 { | ||
| 432 | div = 4; | ||
| 433 | } | ||
| 434 | |||
| 435 | base / div | ||
| 123 | } | 436 | } |
| 124 | 437 | ||
| 125 | pub(crate) fn clk_rtc_freq() -> u32 { | 438 | pub(crate) fn clk_rtc_freq() -> u32 { |
| 126 | 46875 | 439 | let c = pac::CLOCKS; |
| 440 | let src = unsafe { c.clk_rtc_ctrl().read().auxsrc() }; | ||
| 441 | |||
| 442 | let base = match src { | ||
| 443 | ClkRtcCtrlAuxsrc::XOSC_CLKSRC => unsafe { EXTERNAL_HZ }, | ||
| 444 | ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH => estimate_rosc_freq(), | ||
| 445 | ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB => clk_usb_pll_freq(), | ||
| 446 | ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS => clk_sys_pll_freq(), | ||
| 447 | // TODO not sure how to handle clkin sources | ||
| 448 | _ => todo!(), | ||
| 449 | }; | ||
| 450 | |||
| 451 | let div = unsafe { c.clk_rtc_div().read() }; | ||
| 452 | let int = if div.int() == 0 { 65536 } else { div.int() }; | ||
| 453 | // TODO handle fractional clock div | ||
| 454 | let _frac = div.frac(); | ||
| 455 | |||
| 456 | base / int | ||
| 127 | } | 457 | } |
| 128 | 458 | ||
| 129 | unsafe fn start_xosc() { | 459 | unsafe fn start_xosc(crystal_hz: u32) { |
| 130 | const XOSC_MHZ: u32 = 12; | ||
| 131 | pac::XOSC | 460 | pac::XOSC |
| 132 | .ctrl() | 461 | .ctrl() |
| 133 | .write(|w| w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ)); | 462 | .write(|w| w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ)); |
| 134 | 463 | ||
| 135 | let startup_delay = (((XOSC_MHZ * 1_000_000) / 1000) + 128) / 256; | 464 | let startup_delay = ((crystal_hz / 1000) + 128) / 256; |
| 136 | pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); | 465 | pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); |
| 137 | pac::XOSC.ctrl().write(|w| { | 466 | pac::XOSC.ctrl().write(|w| { |
| 138 | w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); | 467 | w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); |
| @@ -141,24 +470,24 @@ unsafe fn start_xosc() { | |||
| 141 | while !pac::XOSC.status().read().stable() {} | 470 | while !pac::XOSC.status().read().stable() {} |
| 142 | } | 471 | } |
| 143 | 472 | ||
| 144 | unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: u8, post_div2: u8) { | 473 | unsafe fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) { |
| 145 | let ref_freq = XOSC_MHZ * 1_000_000 / refdiv; | 474 | let ref_freq = input_freq / config.refdiv; |
| 146 | 475 | ||
| 147 | let fbdiv = vco_freq / ref_freq; | 476 | let fbdiv = config.vco_freq / ref_freq; |
| 148 | assert!(fbdiv >= 16 && fbdiv <= 320); | 477 | assert!(fbdiv >= 16 && fbdiv <= 320); |
| 149 | assert!(post_div1 >= 1 && post_div1 <= 7); | 478 | assert!(config.post_div1 >= 1 && config.post_div1 <= 7); |
| 150 | assert!(post_div2 >= 1 && post_div2 <= 7); | 479 | assert!(config.post_div2 >= 1 && config.post_div2 <= 7); |
| 151 | assert!(post_div2 <= post_div1); | 480 | assert!(config.post_div2 <= config.post_div1); |
| 152 | assert!(ref_freq <= (vco_freq / 16)); | 481 | assert!(ref_freq <= (config.vco_freq / 16)); |
| 153 | 482 | ||
| 154 | // do not disrupt PLL that is already correctly configured and operating | 483 | // do not disrupt PLL that is already correctly configured and operating |
| 155 | let cs = p.cs().read(); | 484 | let cs = p.cs().read(); |
| 156 | let prim = p.prim().read(); | 485 | let prim = p.prim().read(); |
| 157 | if cs.lock() | 486 | if cs.lock() |
| 158 | && cs.refdiv() == refdiv as u8 | 487 | && cs.refdiv() == config.refdiv as u8 |
| 159 | && p.fbdiv_int().read().fbdiv_int() == fbdiv as u16 | 488 | && p.fbdiv_int().read().fbdiv_int() == fbdiv as u16 |
| 160 | && prim.postdiv1() == post_div1 | 489 | && prim.postdiv1() == config.post_div1 |
| 161 | && prim.postdiv2() == post_div2 | 490 | && prim.postdiv2() == config.post_div2 |
| 162 | { | 491 | { |
| 163 | return; | 492 | return; |
| 164 | } | 493 | } |
| @@ -174,7 +503,7 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: | |||
| 174 | reset::unreset_wait(peris); | 503 | reset::unreset_wait(peris); |
| 175 | 504 | ||
| 176 | // Load VCO-related dividers before starting VCO | 505 | // Load VCO-related dividers before starting VCO |
| 177 | p.cs().write(|w| w.set_refdiv(refdiv as _)); | 506 | p.cs().write(|w| w.set_refdiv(config.refdiv as _)); |
| 178 | p.fbdiv_int().write(|w| w.set_fbdiv_int(fbdiv as _)); | 507 | p.fbdiv_int().write(|w| w.set_fbdiv_int(fbdiv as _)); |
| 179 | 508 | ||
| 180 | // Turn on PLL | 509 | // Turn on PLL |
| @@ -189,8 +518,8 @@ unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: | |||
| 189 | 518 | ||
| 190 | // Wait for PLL to lock | 519 | // Wait for PLL to lock |
| 191 | p.prim().write(|w| { | 520 | p.prim().write(|w| { |
| 192 | w.set_postdiv1(post_div1); | 521 | w.set_postdiv1(config.post_div1); |
| 193 | w.set_postdiv2(post_div2); | 522 | w.set_postdiv2(config.post_div2); |
| 194 | }); | 523 | }); |
| 195 | 524 | ||
| 196 | // Turn on post divider | 525 | // Turn on post divider |
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index cba7559df..118ce5237 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -136,23 +136,35 @@ embassy_hal_common::peripherals! { | |||
| 136 | static BOOT2: [u8; 256] = *include_bytes!("boot2.bin"); | 136 | static BOOT2: [u8; 256] = *include_bytes!("boot2.bin"); |
| 137 | 137 | ||
| 138 | pub mod config { | 138 | pub mod config { |
| 139 | use crate::clocks::ClockConfig; | ||
| 140 | |||
| 139 | #[non_exhaustive] | 141 | #[non_exhaustive] |
| 140 | pub struct Config {} | 142 | pub struct Config { |
| 143 | pub clocks: ClockConfig, | ||
| 144 | } | ||
| 141 | 145 | ||
| 142 | impl Default for Config { | 146 | impl Default for Config { |
| 143 | fn default() -> Self { | 147 | fn default() -> Self { |
| 144 | Self {} | 148 | Self { |
| 149 | clocks: ClockConfig::crystal(12_000_000), | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | impl Config { | ||
| 155 | pub fn new(clocks: ClockConfig) -> Self { | ||
| 156 | Self { clocks } | ||
| 145 | } | 157 | } |
| 146 | } | 158 | } |
| 147 | } | 159 | } |
| 148 | 160 | ||
| 149 | pub fn init(_config: config::Config) -> Peripherals { | 161 | pub fn init(config: config::Config) -> Peripherals { |
| 150 | // Do this first, so that it panics if user is calling `init` a second time | 162 | // Do this first, so that it panics if user is calling `init` a second time |
| 151 | // before doing anything important. | 163 | // before doing anything important. |
| 152 | let peripherals = Peripherals::take(); | 164 | let peripherals = Peripherals::take(); |
| 153 | 165 | ||
| 154 | unsafe { | 166 | unsafe { |
| 155 | clocks::init(); | 167 | clocks::init(config.clocks); |
| 156 | #[cfg(feature = "time-driver")] | 168 | #[cfg(feature = "time-driver")] |
| 157 | timer::init(); | 169 | timer::init(); |
| 158 | dma::init(); | 170 | dma::init(); |
