| /* |
| * Copyright (c) 2016 Nordic Semiconductor ASA |
| * Copyright (c) 2016 Vinayak Kariappa Chettimada |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <soc.h> |
| #include <errno.h> |
| #include <atomic.h> |
| #include <device.h> |
| #include <clock_control.h> |
| #include <misc/__assert.h> |
| #include <arch/arm/cortex_m/cmsis.h> |
| |
| static u8_t m16src_ref; |
| static u8_t m16src_grd; |
| static u8_t k32src_initialized; |
| |
| static int _m16src_start(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| u32_t imask; |
| bool blocking; |
| |
| /* If the clock is already started then just increment refcount. |
| * If the start and stop don't happen in pairs, a rollover will |
| * be caught and in that case system should assert. |
| */ |
| |
| /* Test for reference increment from zero and resource guard not taken. |
| */ |
| imask = irq_lock(); |
| |
| if (m16src_ref++) { |
| irq_unlock(imask); |
| goto hf_already_started; |
| } |
| |
| if (m16src_grd) { |
| m16src_ref--; |
| irq_unlock(imask); |
| return -EAGAIN; |
| } |
| |
| m16src_grd = 1; |
| |
| irq_unlock(imask); |
| |
| /* If blocking then spin-wait in CPU sleep until 16MHz clock settles. */ |
| blocking = POINTER_TO_UINT(sub_system); |
| if (blocking) { |
| u32_t intenset; |
| |
| irq_disable(POWER_CLOCK_IRQn); |
| |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| intenset = NRF_CLOCK->INTENSET; |
| NRF_CLOCK->INTENSET = CLOCK_INTENSET_HFCLKSTARTED_Msk; |
| |
| NRF_CLOCK->TASKS_HFCLKSTART = 1; |
| |
| while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { |
| __WFE(); |
| __SEV(); |
| __WFE(); |
| } |
| |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| if (!(intenset & CLOCK_INTENSET_HFCLKSTARTED_Msk)) { |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_HFCLKSTARTED_Msk; |
| } |
| |
| NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| } else { |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| NRF_CLOCK->TASKS_HFCLKSTART = 1; |
| } |
| |
| /* release resource guard */ |
| m16src_grd = 0; |
| |
| hf_already_started: |
| /* rollover should not happen as start and stop shall be |
| * called in pairs. |
| */ |
| __ASSERT_NO_MSG(m16src_ref); |
| |
| if (NRF_CLOCK->HFCLKSTAT & CLOCK_HFCLKSTAT_STATE_Msk) { |
| return 0; |
| } else { |
| return -EINPROGRESS; |
| } |
| } |
| |
| static int _m16src_stop(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| u32_t imask; |
| |
| ARG_UNUSED(sub_system); |
| |
| /* Test for started resource, if so, decrement reference and acquire |
| * resource guard. |
| */ |
| imask = irq_lock(); |
| |
| if (!m16src_ref) { |
| irq_unlock(imask); |
| return -EALREADY; |
| } |
| |
| if (--m16src_ref) { |
| irq_unlock(imask); |
| return 0; |
| } |
| |
| if (m16src_grd) { |
| m16src_ref++; |
| irq_unlock(imask); |
| return -EAGAIN; |
| } |
| |
| m16src_grd = 1; |
| |
| irq_unlock(imask); |
| |
| /* re-entrancy and mult-context safe, and reference count is zero, */ |
| |
| NRF_CLOCK->TASKS_HFCLKSTOP = 1; |
| |
| /* release resource guard */ |
| m16src_grd = 0; |
| |
| return 0; |
| } |
| |
| static int _k32src_start(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| u32_t lf_clk_src; |
| u32_t intenset; |
| u32_t imask; |
| |
| /* If the LF clock is already started, but wasn't initialized with |
| * this function, allow it to run once. This is needed because if a |
| * soft reset is triggered while watchdog is active, the LF clock will |
| * already be running, but won't be configured yet (watchdog forces LF |
| * clock to be running). |
| * |
| * That is, a hardware check won't work here, because even if the LF |
| * clock is already running it might not be initialized. We need an |
| * initialized flag. |
| */ |
| |
| imask = irq_lock(); |
| |
| if (k32src_initialized) { |
| irq_unlock(imask); |
| goto lf_already_started; |
| } |
| |
| k32src_initialized = 1; |
| |
| irq_unlock(imask); |
| |
| irq_disable(POWER_CLOCK_IRQn); |
| |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| intenset = NRF_CLOCK->INTENSET; |
| NRF_CLOCK->INTENSET = CLOCK_INTENSET_LFCLKSTARTED_Msk; |
| |
| /* Set LF Clock Source */ |
| lf_clk_src = POINTER_TO_UINT(sub_system); |
| NRF_CLOCK->LFCLKSRC = lf_clk_src; |
| |
| /* Start and spin-wait until clock settles */ |
| NRF_CLOCK->TASKS_LFCLKSTART = 1; |
| |
| while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { |
| __WFE(); |
| __SEV(); |
| __WFE(); |
| } |
| |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| if (!(intenset & CLOCK_INTENSET_LFCLKSTARTED_Msk)) { |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_LFCLKSTARTED_Msk; |
| } |
| |
| NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| |
| /* If RC selected, calibrate and start timer for consecutive |
| * calibrations. |
| */ |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_DONE_Msk | CLOCK_INTENCLR_CTTO_Msk; |
| NRF_CLOCK->EVENTS_DONE = 0; |
| NRF_CLOCK->EVENTS_CTTO = 0; |
| |
| if ((lf_clk_src & CLOCK_LFCLKSRC_SRC_Msk) == CLOCK_LFCLKSRC_SRC_RC) { |
| int err; |
| |
| /* Set the Calibration Timer Initial Value */ |
| NRF_CLOCK->CTIV = 16; /* 4s in 0.25s units */ |
| |
| /* Enable DONE and CTTO IRQs */ |
| NRF_CLOCK->INTENSET = |
| CLOCK_INTENSET_DONE_Msk | CLOCK_INTENSET_CTTO_Msk; |
| |
| /* Start HF clock, if already started then explicitly |
| * assert IRQ. |
| * NOTE: The INTENSET is used as state flag to start |
| * calibration in ISR. |
| */ |
| NRF_CLOCK->INTENSET = CLOCK_INTENSET_HFCLKSTARTED_Msk; |
| |
| err = _m16src_start(dev, false); |
| if (!err) { |
| NVIC_SetPendingIRQ(POWER_CLOCK_IRQn); |
| } else { |
| __ASSERT_NO_MSG(err == -EINPROGRESS); |
| } |
| } |
| |
| lf_already_started: |
| if (NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_STATE_Msk) { |
| return 0; |
| } else { |
| return -EINPROGRESS; |
| } |
| } |
| |
| static void _power_clock_isr(void *arg) |
| { |
| u8_t pof, hf_intenset, hf_stat, hf, lf, done, ctto; |
| struct device *dev = arg; |
| |
| pof = (NRF_POWER->EVENTS_POFWARN != 0); |
| |
| hf_intenset = |
| ((NRF_CLOCK->INTENSET & CLOCK_INTENSET_HFCLKSTARTED_Msk) != 0); |
| hf_stat = ((NRF_CLOCK->HFCLKSTAT & CLOCK_HFCLKSTAT_STATE_Msk) != 0); |
| hf = (NRF_CLOCK->EVENTS_HFCLKSTARTED != 0); |
| |
| lf = (NRF_CLOCK->EVENTS_LFCLKSTARTED != 0); |
| |
| done = (NRF_CLOCK->EVENTS_DONE != 0); |
| ctto = (NRF_CLOCK->EVENTS_CTTO != 0); |
| |
| __ASSERT_NO_MSG(pof || hf || hf_intenset || lf || done || ctto); |
| |
| if (pof) { |
| NRF_POWER->EVENTS_POFWARN = 0; |
| } |
| |
| if (hf) { |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| } |
| |
| if (hf_intenset && hf_stat) { |
| /* INTENSET is used as state flag to start calibration, |
| * hence clear it here. |
| */ |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_HFCLKSTARTED_Msk; |
| |
| /* Start Calibration */ |
| NRF_CLOCK->TASKS_CAL = 1; |
| } |
| |
| if (lf) { |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| __ASSERT_NO_MSG(0); |
| } |
| |
| if (done) { |
| int err; |
| |
| NRF_CLOCK->EVENTS_DONE = 0; |
| |
| /* Calibration done, stop 16M Xtal. */ |
| err = _m16src_stop(dev, NULL); |
| __ASSERT_NO_MSG(!err); |
| |
| /* Start timer for next calibration. */ |
| NRF_CLOCK->TASKS_CTSTART = 1; |
| } |
| |
| if (ctto) { |
| int err; |
| |
| NRF_CLOCK->EVENTS_CTTO = 0; |
| |
| /* Start HF clock, if already started |
| * then explicitly assert IRQ; we use the INTENSET |
| * as a state flag to start calibration. |
| */ |
| NRF_CLOCK->INTENSET = CLOCK_INTENSET_HFCLKSTARTED_Msk; |
| |
| err = _m16src_start(dev, false); |
| if (!err) { |
| NVIC_SetPendingIRQ(POWER_CLOCK_IRQn); |
| } else { |
| __ASSERT_NO_MSG(err == -EINPROGRESS); |
| } |
| } |
| } |
| |
| static int _clock_control_init(struct device *dev) |
| { |
| /* TODO: Initialization will be called twice, once for 32KHz and then |
| * for 16 MHz clock. The vector is also shared for other power related |
| * features. Hence, design a better way to init IRQISR when adding |
| * power peripheral driver and/or new SoC series. |
| * NOTE: Currently the operations here are idempotent. |
| */ |
| IRQ_CONNECT(NRF5_IRQ_POWER_CLOCK_IRQn, |
| CONFIG_CLOCK_CONTROL_NRF5_IRQ_PRIORITY, |
| _power_clock_isr, 0, 0); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api _m16src_clock_control_api = { |
| .on = _m16src_start, |
| .off = _m16src_stop, |
| .get_rate = NULL, |
| }; |
| |
| DEVICE_AND_API_INIT(clock_nrf5_m16src, |
| CONFIG_CLOCK_CONTROL_NRF5_M16SRC_DRV_NAME, |
| _clock_control_init, NULL, NULL, PRE_KERNEL_1, |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &_m16src_clock_control_api); |
| |
| static const struct clock_control_driver_api _k32src_clock_control_api = { |
| .on = _k32src_start, |
| .off = NULL, |
| .get_rate = NULL, |
| }; |
| |
| DEVICE_AND_API_INIT(clock_nrf5_k32src, |
| CONFIG_CLOCK_CONTROL_NRF5_K32SRC_DRV_NAME, |
| _clock_control_init, NULL, NULL, PRE_KERNEL_1, |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &_k32src_clock_control_api); |