blob: 1ea26e9b72aa305a5ee8f541ca68fcbfcdfbe346 [file] [log] [blame]
/*
* 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
}