blob: 1d034c215ece72dfbb42ab6c1eb3ae4b8877ae8f [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2019, Broadcom
*/
#include <drivers/sp805_wdt.h>
#include <initcall.h>
#include <io.h>
#include <keep.h>
#include <kernel/interrupt.h>
#include <mm/core_memprot.h>
#include <stdint.h>
#include <trace.h>
static vaddr_t chip_to_base(struct wdt_chip *chip)
{
struct sp805_wdt_data *pd =
container_of(chip, struct sp805_wdt_data, chip);
return io_pa_or_va(&pd->base);
}
static TEE_Result sp805_setload(struct wdt_chip *chip, unsigned long timeout)
{
struct sp805_wdt_data *pd =
container_of(chip, struct sp805_wdt_data, chip);
uint32_t load = 0;
/*
* sp805 runs counter with given value twice, after the end of first
* counter it gives an interrupt and then starts counter again. If
* interrupt already occurred then it resets the system. This is why
* load is half of what should be required.
*/
if (MUL_OVERFLOW(timeout, pd->clk_rate, &load))
return TEE_ERROR_SECURITY;
load = (load / 2) - 1;
if (load < WDT_LOAD_MIN)
load = WDT_LOAD_MIN;
pd->load_val = load;
return TEE_SUCCESS;
}
static void sp805_config(struct wdt_chip *chip, bool enable)
{
struct sp805_wdt_data *pd =
container_of(chip, struct sp805_wdt_data, chip);
vaddr_t base = chip_to_base(chip);
io_write32(base + WDT_LOCK_OFFSET, WDT_UNLOCK_KEY);
io_write32(base + WDT_LOAD_OFFSET, pd->load_val);
io_write32(base + WDT_INTCLR_OFFSET, WDT_INT_CLR);
if (enable)
io_write32(base + WDT_CONTROL_OFFSET,
WDT_INT_EN | WDT_RESET_EN);
io_write32(base + WDT_LOCK_OFFSET, WDT_LOCK_KEY);
/* Flush posted writes. */
(void)io_read32(base + WDT_LOCK_OFFSET);
}
static void sp805_ping(struct wdt_chip *chip)
{
sp805_config(chip, false);
}
static void sp805_enable(struct wdt_chip *chip)
{
sp805_config(chip, true);
}
static void sp805_disable(struct wdt_chip *chip)
{
vaddr_t base = chip_to_base(chip);
io_write32(base + WDT_LOCK_OFFSET, WDT_UNLOCK_KEY);
io_write32(base + WDT_CONTROL_OFFSET, 0);
io_write32(base + WDT_LOCK_OFFSET, WDT_LOCK_KEY);
/* Flush posted writes. */
(void)io_read32(base + WDT_LOCK_OFFSET);
}
static enum itr_return wdt_itr_cb(struct itr_handler *h)
{
struct wdt_chip *chip = h->data;
struct sp805_wdt_data *pd =
container_of(chip, struct sp805_wdt_data, chip);
if (pd->itr_handler)
pd->itr_handler(chip);
return ITRR_HANDLED;
}
KEEP_PAGER(wdt_itr_cb);
TEE_Result sp805_register_itr_handler(struct sp805_wdt_data *pd,
uint32_t itr_num, uint32_t itr_flags,
sp805_itr_handler_func_t itr_handler)
{
struct itr_handler *wdt_itr;
assert(!pd->chip.wdt_itr);
wdt_itr = calloc(1, sizeof(*wdt_itr));
if (!wdt_itr)
return TEE_ERROR_OUT_OF_MEMORY;
wdt_itr->it = itr_num;
wdt_itr->flags = itr_flags;
wdt_itr->handler = wdt_itr_cb;
wdt_itr->data = &pd->chip;
pd->itr_handler = itr_handler;
pd->chip.wdt_itr = wdt_itr;
itr_add(wdt_itr);
itr_enable(wdt_itr->it);
return TEE_SUCCESS;
}
static const struct wdt_ops sp805_wdt_ops = {
.start = sp805_enable,
.stop = sp805_disable,
.ping = sp805_ping,
.set_timeout = sp805_setload,
};
KEEP_PAGER(sp805_wdt_ops);
TEE_Result sp805_wdt_init(struct sp805_wdt_data *pd, paddr_t base,
uint32_t clk_rate, uint32_t timeout)
{
assert(pd);
pd->base.pa = base;
pd->clk_rate = clk_rate;
pd->chip.ops = &sp805_wdt_ops;
return sp805_setload(&pd->chip, timeout);
}