// SPDX-License-Identifier: GPL-2.0+
/*
 * Watchdog driver for the sl28cpld
 *
 * Copyright (c) 2021 Michael Walle <michael@walle.cc>
 */

#include <dm.h>
#include <wdt.h>
#include <sl28cpld.h>
#include <div64.h>

#define SL28CPLD_WDT_CTRL		0x00
#define  WDT_CTRL_EN0			BIT(0)
#define  WDT_CTRL_EN1			BIT(1)
#define  WDT_CTRL_EN_MASK		GENMASK(1, 0)
#define  WDT_CTRL_LOCK			BIT(2)
#define  WDT_CTRL_ASSERT_SYS_RESET	BIT(6)
#define  WDT_CTRL_ASSERT_WDT_TIMEOUT	BIT(7)
#define SL28CPLD_WDT_TIMEOUT		0x01
#define SL28CPLD_WDT_KICK		0x02
#define  WDT_KICK_VALUE			0x6b

static int sl28cpld_wdt_reset(struct udevice *dev)
{
	return sl28cpld_write(dev, SL28CPLD_WDT_KICK, WDT_KICK_VALUE);
}

static int sl28cpld_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
{
	int ret, val;

	val = sl28cpld_read(dev, SL28CPLD_WDT_CTRL);
	if (val < 0)
		return val;

	/* (1) disable watchdog */
	val &= ~WDT_CTRL_EN_MASK;
	ret = sl28cpld_write(dev, SL28CPLD_WDT_CTRL, val);
	if (ret)
		return ret;

	/* (2) set timeout */
	ret = sl28cpld_write(dev, SL28CPLD_WDT_TIMEOUT, lldiv(timeout, 1000));
	if (ret)
		return ret;

	/* (3) kick it, will reset timer to the timeout value */
	ret = sl28cpld_wdt_reset(dev);
	if (ret)
		return ret;

	/* (4) enable either recovery or normal one */
	if (flags & BIT(0))
		val |= WDT_CTRL_EN1;
	else
		val |= WDT_CTRL_EN0;

	if (flags & BIT(1))
		val |= WDT_CTRL_LOCK;

	if (flags & BIT(2))
		val &= ~WDT_CTRL_ASSERT_SYS_RESET;
	else
		val |= WDT_CTRL_ASSERT_SYS_RESET;

	if (flags & BIT(3))
		val |= WDT_CTRL_ASSERT_WDT_TIMEOUT;
	else
		val &= ~WDT_CTRL_ASSERT_WDT_TIMEOUT;

	return sl28cpld_write(dev, SL28CPLD_WDT_CTRL, val);
}

static int sl28cpld_wdt_stop(struct udevice *dev)
{
	int val;

	val = sl28cpld_read(dev, SL28CPLD_WDT_CTRL);
	if (val < 0)
		return val;

	return sl28cpld_write(dev, SL28CPLD_WDT_CTRL, val & ~WDT_CTRL_EN_MASK);
}

static int sl28cpld_wdt_expire_now(struct udevice *dev, ulong flags)
{
	return sl28cpld_wdt_start(dev, 0, flags);
}

static const struct wdt_ops sl28cpld_wdt_ops = {
	.start = sl28cpld_wdt_start,
	.reset = sl28cpld_wdt_reset,
	.stop = sl28cpld_wdt_stop,
	.expire_now = sl28cpld_wdt_expire_now,
};

static const struct udevice_id sl28cpld_wdt_ids[] = {
	{ .compatible = "kontron,sl28cpld-wdt", },
	{}
};

U_BOOT_DRIVER(sl28cpld_wdt) = {
	.name = "sl28cpld-wdt",
	.id = UCLASS_WDT,
	.of_match = sl28cpld_wdt_ids,
	.ops = &sl28cpld_wdt_ops,
};
