379 lines
9.7 KiB
C
379 lines
9.7 KiB
C
/*
|
|
* Copyright (c) 2016-2019 Nordic Semiconductor ASA
|
|
* Copyright (c) 2016 Vinayak Kariappa Chettimada
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <soc.h>
|
|
#include <drivers/clock_control.h>
|
|
#include <drivers/clock_control/nrf_clock_control.h>
|
|
#include "nrf_clock_calibration.h"
|
|
#include <logging/log.h>
|
|
#include <hal/nrf_power.h>
|
|
|
|
LOG_MODULE_REGISTER(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
|
|
|
/* Helper logging macros which prepends device name to the log. */
|
|
#define CLOCK_LOG(lvl, dev, ...) \
|
|
LOG_##lvl("%s: " GET_ARG1(__VA_ARGS__), dev->config->name \
|
|
COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__),\
|
|
(), (, GET_ARGS_LESS_1(__VA_ARGS__))))
|
|
#define ERR(dev, ...) CLOCK_LOG(ERR, dev, __VA_ARGS__)
|
|
#define WRN(dev, ...) CLOCK_LOG(WRN, dev, __VA_ARGS__)
|
|
#define INF(dev, ...) CLOCK_LOG(INF, dev, __VA_ARGS__)
|
|
#define DBG(dev, ...) CLOCK_LOG(DBG, dev, __VA_ARGS__)
|
|
|
|
/* returns true if clock stopping or starting can be performed. If false then
|
|
* start/stop will be deferred and performed later on by handler owner.
|
|
*/
|
|
typedef bool (*nrf_clock_handler_t)(struct device *dev);
|
|
|
|
/* Clock instance structure */
|
|
struct nrf_clock_control {
|
|
sys_slist_t list; /* List of users requesting callback */
|
|
s8_t ref; /* Users counter */
|
|
bool started; /* Indicated that clock is started */
|
|
};
|
|
|
|
/* Clock instance static configuration */
|
|
struct nrf_clock_control_config {
|
|
nrf_clock_handler_t start_handler; /* Called before start */
|
|
nrf_clock_handler_t stop_handler; /* Called before stop */
|
|
nrf_clock_event_t started_evt; /* Clock started event */
|
|
nrf_clock_task_t start_tsk; /* Clock start task */
|
|
nrf_clock_task_t stop_tsk; /* Clock stop task */
|
|
};
|
|
|
|
/* Return true if given event has enabled interrupt and is triggered. Event
|
|
* is cleared.
|
|
*/
|
|
static bool clock_event_check_and_clean(nrf_clock_event_t evt, u32_t intmask)
|
|
{
|
|
bool ret = nrf_clock_event_check(evt) &&
|
|
nrf_clock_int_enable_check(intmask);
|
|
|
|
if (ret) {
|
|
nrf_clock_event_clear(evt);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum clock_control_status get_status(struct device *dev,
|
|
clock_control_subsys_t sys)
|
|
{
|
|
struct nrf_clock_control *data = dev->driver_data;
|
|
|
|
if (data->started) {
|
|
return CLOCK_CONTROL_STATUS_ON;
|
|
}
|
|
|
|
if (data->ref > 0) {
|
|
return CLOCK_CONTROL_STATUS_STARTING;
|
|
}
|
|
|
|
return CLOCK_CONTROL_STATUS_OFF;
|
|
}
|
|
|
|
static int clock_stop(struct device *dev, clock_control_subsys_t sub_system)
|
|
{
|
|
const struct nrf_clock_control_config *config =
|
|
dev->config->config_info;
|
|
struct nrf_clock_control *data = dev->driver_data;
|
|
int err = 0;
|
|
int key;
|
|
|
|
key = irq_lock();
|
|
data->ref--;
|
|
if (data->ref == 0) {
|
|
bool do_stop;
|
|
|
|
DBG(dev, "Stopping");
|
|
sys_slist_init(&data->list);
|
|
|
|
do_stop = (config->stop_handler) ?
|
|
config->stop_handler(dev) : true;
|
|
|
|
if (do_stop) {
|
|
nrf_clock_task_trigger(config->stop_tsk);
|
|
/* It may happen that clock is being stopped when it
|
|
* has just been started and start is not yet handled
|
|
* (due to irq_lock). In that case after stopping the
|
|
* clock, started event is cleared to prevent false
|
|
* interrupt being triggered.
|
|
*/
|
|
nrf_clock_event_clear(config->started_evt);
|
|
}
|
|
|
|
data->started = false;
|
|
} else if (data->ref < 0) {
|
|
data->ref = 0;
|
|
err = -EALREADY;
|
|
}
|
|
|
|
irq_unlock(key);
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool is_in_list(sys_slist_t *list, sys_snode_t *node)
|
|
{
|
|
sys_snode_t *item = sys_slist_peek_head(list);
|
|
|
|
do {
|
|
if (item == node) {
|
|
return true;
|
|
}
|
|
|
|
item = sys_slist_peek_next(item);
|
|
} while (item);
|
|
|
|
return false;
|
|
}
|
|
|
|
static int clock_async_start(struct device *dev,
|
|
clock_control_subsys_t sub_system,
|
|
struct clock_control_async_data *data)
|
|
{
|
|
const struct nrf_clock_control_config *config =
|
|
dev->config->config_info;
|
|
struct nrf_clock_control *clk_data = dev->driver_data;
|
|
int key;
|
|
s8_t ref;
|
|
|
|
__ASSERT_NO_MSG((data == NULL) ||
|
|
((data != NULL) && (data->cb != NULL)));
|
|
|
|
key = irq_lock();
|
|
ref = ++clk_data->ref;
|
|
irq_unlock(key);
|
|
|
|
if (clk_data->started) {
|
|
if (data) {
|
|
data->cb(dev, data->user_data);
|
|
}
|
|
} else {
|
|
if (ref == 1) {
|
|
bool do_start;
|
|
|
|
do_start = (config->start_handler) ?
|
|
config->start_handler(dev) : true;
|
|
if (do_start) {
|
|
nrf_clock_task_trigger(config->start_tsk);
|
|
DBG(dev, "Triggered start task");
|
|
} else if (data) {
|
|
data->cb(dev, data->user_data);
|
|
}
|
|
}
|
|
|
|
/* if node is in the list it means that it is scheduled for
|
|
* the second time.
|
|
*/
|
|
if (data) {
|
|
if (is_in_list(&clk_data->list, &data->node)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
sys_slist_append(&clk_data->list, &data->node);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int clock_start(struct device *dev, clock_control_subsys_t sub_system)
|
|
{
|
|
return clock_async_start(dev, sub_system, NULL);
|
|
}
|
|
|
|
/* Note: this function has public linkage, and MUST have this
|
|
* particular name. The platform architecture itself doesn't care,
|
|
* but there is a test (tests/kernel/arm_irq_vector_table) that needs
|
|
* to find it to it can set it in a custom vector table. Should
|
|
* probably better abstract that at some point (e.g. query and reset
|
|
* it by pointer at runtime, maybe?) so we don't have this leaky
|
|
* symbol.
|
|
*/
|
|
void nrf_power_clock_isr(void *arg);
|
|
|
|
static int hfclk_init(struct device *dev)
|
|
{
|
|
IRQ_CONNECT(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0,
|
|
DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0_PRIORITY,
|
|
nrf_power_clock_isr, 0, 0);
|
|
|
|
irq_enable(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0);
|
|
|
|
nrf_clock_lf_src_set(CLOCK_CONTROL_NRF_K32SRC);
|
|
|
|
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) {
|
|
z_nrf_clock_calibration_init(dev);
|
|
}
|
|
|
|
nrf_clock_int_enable((
|
|
NRF_CLOCK_INT_HF_STARTED_MASK |
|
|
NRF_CLOCK_INT_LF_STARTED_MASK |
|
|
COND_CODE_1(CONFIG_USB_NRF52840,
|
|
(NRF_POWER_INT_USBDETECTED_MASK |
|
|
NRF_POWER_INT_USBREMOVED_MASK |
|
|
NRF_POWER_INT_USBPWRRDY_MASK),
|
|
(0))));
|
|
|
|
sys_slist_init(&((struct nrf_clock_control *)dev->driver_data)->list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lfclk_init(struct device *dev)
|
|
{
|
|
sys_slist_init(&((struct nrf_clock_control *)dev->driver_data)->list);
|
|
return 0;
|
|
}
|
|
|
|
static const struct clock_control_driver_api clock_control_api = {
|
|
.on = clock_start,
|
|
.off = clock_stop,
|
|
.async_on = clock_async_start,
|
|
.get_status = get_status,
|
|
};
|
|
|
|
static struct nrf_clock_control hfclk;
|
|
static const struct nrf_clock_control_config hfclk_config = {
|
|
.start_tsk = NRF_CLOCK_TASK_HFCLKSTART,
|
|
.started_evt = NRF_CLOCK_EVENT_HFCLKSTARTED,
|
|
.stop_tsk = NRF_CLOCK_TASK_HFCLKSTOP
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(clock_nrf5_m16src,
|
|
DT_INST_0_NORDIC_NRF_CLOCK_LABEL "_16M",
|
|
hfclk_init, &hfclk, &hfclk_config, PRE_KERNEL_1,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&clock_control_api);
|
|
|
|
|
|
static struct nrf_clock_control lfclk;
|
|
static const struct nrf_clock_control_config lfclk_config = {
|
|
.start_tsk = NRF_CLOCK_TASK_LFCLKSTART,
|
|
.started_evt = NRF_CLOCK_EVENT_LFCLKSTARTED,
|
|
.stop_tsk = NRF_CLOCK_TASK_LFCLKSTOP,
|
|
.start_handler =
|
|
IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION) ?
|
|
z_nrf_clock_calibration_start : NULL,
|
|
.stop_handler =
|
|
IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION) ?
|
|
z_nrf_clock_calibration_stop : NULL
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(clock_nrf5_k32src,
|
|
DT_INST_0_NORDIC_NRF_CLOCK_LABEL "_32K",
|
|
lfclk_init, &lfclk, &lfclk_config, PRE_KERNEL_1,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&clock_control_api);
|
|
|
|
static void clkstarted_handle(struct device *dev)
|
|
{
|
|
struct clock_control_async_data *async_data;
|
|
struct nrf_clock_control *data = dev->driver_data;
|
|
sys_snode_t *node = sys_slist_get(&data->list);
|
|
|
|
DBG(dev, "Clock started");
|
|
data->started = true;
|
|
|
|
while (node != NULL) {
|
|
async_data = CONTAINER_OF(node,
|
|
struct clock_control_async_data, node);
|
|
async_data->cb(dev, async_data->user_data);
|
|
node = sys_slist_get(&data->list);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_USB_NRF52840)
|
|
static bool power_event_check_and_clean(nrf_power_event_t evt, u32_t intmask)
|
|
{
|
|
bool ret = nrf_power_event_check(evt) &&
|
|
nrf_power_int_enable_check(intmask);
|
|
|
|
if (ret) {
|
|
nrf_power_event_clear(evt);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static void usb_power_isr(void)
|
|
{
|
|
#if defined(CONFIG_USB_NRF52840)
|
|
extern void usb_dc_nrfx_power_event_callback(nrf_power_event_t event);
|
|
|
|
if (power_event_check_and_clean(NRF_POWER_EVENT_USBDETECTED,
|
|
NRF_POWER_INT_USBDETECTED_MASK)) {
|
|
usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBDETECTED);
|
|
}
|
|
|
|
if (power_event_check_and_clean(NRF_POWER_EVENT_USBPWRRDY,
|
|
NRF_POWER_INT_USBPWRRDY_MASK)) {
|
|
usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBPWRRDY);
|
|
}
|
|
|
|
if (power_event_check_and_clean(NRF_POWER_EVENT_USBREMOVED,
|
|
NRF_POWER_INT_USBREMOVED_MASK)) {
|
|
usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBREMOVED);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void nrf_power_clock_isr(void *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
|
|
if (clock_event_check_and_clean(NRF_CLOCK_EVENT_HFCLKSTARTED,
|
|
NRF_CLOCK_INT_HF_STARTED_MASK)) {
|
|
struct device *hfclk_dev = DEVICE_GET(clock_nrf5_m16src);
|
|
struct nrf_clock_control *data = hfclk_dev->driver_data;
|
|
|
|
/* Check needed due to anomaly 201:
|
|
* HFCLKSTARTED may be generated twice.
|
|
*/
|
|
if (!data->started) {
|
|
clkstarted_handle(hfclk_dev);
|
|
}
|
|
}
|
|
|
|
if (clock_event_check_and_clean(NRF_CLOCK_EVENT_LFCLKSTARTED,
|
|
NRF_CLOCK_INT_LF_STARTED_MASK)) {
|
|
struct device *lfclk_dev = DEVICE_GET(clock_nrf5_k32src);
|
|
|
|
if (IS_ENABLED(
|
|
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) {
|
|
z_nrf_clock_calibration_lfclk_started(lfclk_dev);
|
|
}
|
|
clkstarted_handle(lfclk_dev);
|
|
}
|
|
|
|
usb_power_isr();
|
|
|
|
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) {
|
|
z_nrf_clock_calibration_isr();
|
|
}
|
|
}
|
|
|
|
void nrf5_power_usb_power_int_enable(bool enable)
|
|
{
|
|
#ifdef CONFIG_USB_NRF52840
|
|
u32_t mask;
|
|
|
|
mask = NRF_POWER_INT_USBDETECTED_MASK |
|
|
NRF_POWER_INT_USBREMOVED_MASK |
|
|
NRF_POWER_INT_USBPWRRDY_MASK;
|
|
|
|
if (enable) {
|
|
nrf_power_int_enable(mask);
|
|
irq_enable(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0);
|
|
} else {
|
|
nrf_power_int_disable(mask);
|
|
}
|
|
#endif
|
|
}
|