// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright 2022 Microchip
 *
 * Driver for AT91 RTC
 */

#include <assert.h>
#include <drivers/atmel_rtc.h>
#include <drivers/rtc.h>
#include <io.h>
#include <kernel/dt.h>
#include <kernel/dt_driver.h>
#include <matrix.h>
#include <mm/core_memprot.h>
#include <platform_config.h>

#define RTC_VAL(reg, val)	(((val) >> RTC_## reg ## _SHIFT) & \
				 RTC_## reg ##_MASK)

#define RTC_SET_VAL(reg, val)	SHIFT_U32((val) & RTC_## reg ##_MASK, \
					  RTC_## reg ## _SHIFT)

#define RTC_CR			0x0
#define RTC_CR_UPDCAL		BIT(1)
#define RTC_CR_UPDTIM		BIT(0)

#define RTC_MR			0x4
#define RTC_MR_HR_MODE		BIT(0)
#define RTC_MR_PERSIAN		BIT(1)
#define RTC_MR_UTC		BIT(2)
#define RTC_MR_NEGPPM		BIT(4)
#define RTC_MR_CORR_SHIFT	8
#define RTC_MR_CORR_MASK	GENMASK_32(6, 0)
#define RTC_MR_CORR(val)	RTC_VAL(val, MR_CORR)
#define RTC_MR_HIGHPPM		BIT(15)

#define RTC_TIMR		0x8
#define RTC_CALR		0xC

#define RTC_SR			0x18
#define RTC_SR_ACKUPD		BIT(0)
#define RTC_SR_SEC		BIT(2)

#define RTC_SCCR		0x1C
#define RTC_SCCR_ACKCLR		BIT(0)
#define RTC_SCCR_SECCLR		BIT(2)

#define RTC_VER			0x2C
#define RTC_VER_NVTIM		BIT(0)
#define RTC_VER_NVCAL		BIT(1)

#define RTC_TSTR0		0xB0
#define RTC_TSDR0		0xB4

#define RTC_TSSR0		0xB8
#define RTC_TSSR_DET_OFFSET	16
#define RTC_TSSR_DET_COUNT	8
#define RTC_TSSR_TST_PIN	BIT(2)
#define RTC_TSSR_JTAG		BIT(3)

/* Layout of Time registers */
#define RTC_TIME_BACKUP	BIT(31)
#define RTC_TIME_HOUR_SHIFT	16
#define RTC_TIME_HOUR_MASK	GENMASK_32(5, 0)
#define RTC_TIME_MIN_SHIFT	8
#define RTC_TIME_MIN_MASK	GENMASK_32(6, 0)
#define RTC_TIME_SEC_SHIFT	0
#define RTC_TIME_SEC_MASK	GENMASK_32(6, 0)

/* Layout of Calendar registers */
#define RTC_CAL_DATE_SHIFT	24
#define RTC_CAL_DATE_MASK	GENMASK_32(5, 0)
#define RTC_CAL_DAY_SHIFT	21
#define RTC_CAL_DAY_MASK	GENMASK_32(2, 0)
#define RTC_CAL_MONTH_SHIFT	16
#define RTC_CAL_MONTH_MASK	GENMASK_32(4, 0)
#define RTC_CAL_YEAR_SHIFT	8
#define RTC_CAL_YEAR_MASK	GENMASK_32(7, 0)
#define RTC_CAL_CENT_SHIFT	0
#define RTC_CAL_CENT_MASK	GENMASK_32(6, 0)

#define ATMEL_RTC_CORR_DIVIDEND		3906000
#define ATMEL_RTC_CORR_LOW_RATIO	20

static vaddr_t rtc_base;

static uint8_t bcd_decode(uint8_t dcb_val)
{
	return (dcb_val & 0xF) + (dcb_val >> 4) * 10;
}

static uint8_t bcd_encode(uint32_t value)
{
	return ((value / 10) << 4) + value % 10;
}

static uint32_t atmel_rtc_read(unsigned int offset)
{
	return io_read32(rtc_base + offset);
}

static void atmel_rtc_write(unsigned int offset, uint32_t val)
{
	return io_write32(rtc_base + offset, val);
}

static void atmel_decode_date(unsigned int time_reg, unsigned int cal_reg,
			      struct optee_rtc_time *tm)
{
	uint32_t time = 0;
	uint32_t date = 0;

	/* Must read twice in case it changes */
	do {
		time = atmel_rtc_read(time_reg);
		date = atmel_rtc_read(cal_reg);
	} while ((time != atmel_rtc_read(time_reg)) ||
		 (date != atmel_rtc_read(cal_reg)));

	tm->tm_wday = bcd_decode(RTC_VAL(CAL_DAY, date)) - 1;
	tm->tm_mday = bcd_decode(RTC_VAL(CAL_DATE, date));
	tm->tm_mon = bcd_decode(RTC_VAL(CAL_MONTH, date)) - 1;
	tm->tm_year = bcd_decode(RTC_VAL(CAL_CENT, date)) * 100;
	tm->tm_year += bcd_decode(RTC_VAL(CAL_YEAR, date));

	tm->tm_hour = bcd_decode(RTC_VAL(TIME_HOUR, time));
	tm->tm_min = bcd_decode(RTC_VAL(TIME_MIN, time));
	tm->tm_sec = bcd_decode(RTC_VAL(TIME_SEC, time));
}

static TEE_Result atmel_rtc_get_time(struct rtc *rtc __unused,
				     struct optee_rtc_time *tm)
{
	atmel_decode_date(RTC_TIMR, RTC_CALR, tm);

	return TEE_SUCCESS;
}

TEE_Result atmel_rtc_get_tamper_timestamp(struct optee_rtc_time *tm)
{
	if (!rtc_base)
		return TEE_ERROR_NOT_SUPPORTED;

	atmel_decode_date(RTC_TSTR0, RTC_TSDR0, tm);

	return TEE_SUCCESS;
}

static TEE_Result atmel_rtc_set_time(struct rtc *rtc __unused,
				     struct optee_rtc_time *tm)
{
	uint32_t cr = 0;
	uint32_t sr = 0;
	uint32_t err = 0;

	/* First, wait for UPDCAL/UPDTIM to be 0 */
	do {
		cr = atmel_rtc_read(RTC_CR);
	} while (cr & (RTC_CR_UPDCAL | RTC_CR_UPDTIM));

	/* Stop Time/Calendar for update */
	atmel_rtc_write(RTC_CR, cr | RTC_CR_UPDCAL | RTC_CR_UPDTIM);

	do {
		sr = atmel_rtc_read(RTC_SR);
	} while (!(sr & RTC_SR_ACKUPD));

	atmel_rtc_write(RTC_SCCR, RTC_SCCR_ACKCLR);

	atmel_rtc_write(RTC_TIMR,
			RTC_SET_VAL(TIME_SEC, bcd_encode(tm->tm_sec)) |
			RTC_SET_VAL(TIME_MIN, bcd_encode(tm->tm_min)) |
			RTC_SET_VAL(TIME_HOUR, bcd_encode(tm->tm_hour)));

	atmel_rtc_write(RTC_CALR,
			RTC_SET_VAL(CAL_CENT,
				    bcd_encode(tm->tm_year / 100)) |
			RTC_SET_VAL(CAL_YEAR, bcd_encode(tm->tm_year % 100)) |
			RTC_SET_VAL(CAL_MONTH, bcd_encode(tm->tm_mon + 1)) |
			RTC_SET_VAL(CAL_DAY, bcd_encode(tm->tm_wday + 1)) |
			RTC_SET_VAL(CAL_DATE, bcd_encode(tm->tm_mday)));

	err = atmel_rtc_read(RTC_VER);
	if (err) {
		if (err & RTC_VER_NVTIM)
			DMSG("Invalid time programmed");
		if (err & RTC_VER_NVCAL)
			DMSG("Invalid date programmed");

		return TEE_ERROR_BAD_PARAMETERS;
	}

	/* Restart Time/Calendar */
	atmel_rtc_write(RTC_CR, cr);

	return TEE_SUCCESS;
}

static TEE_Result atmel_rtc_get_offset(struct rtc *rtc __unused, long *offset)
{
	uint32_t mr = atmel_rtc_read(RTC_MR);
	long val = RTC_VAL(MR_CORR, mr);

	if (!val) {
		*offset = 0;
		return TEE_SUCCESS;
	}

	val++;

	if (!(mr & RTC_MR_HIGHPPM))
		val *= ATMEL_RTC_CORR_LOW_RATIO;

	val = UDIV_ROUND_NEAREST(ATMEL_RTC_CORR_DIVIDEND, val);

	if (!(mr & RTC_MR_NEGPPM))
		val = -val;

	*offset = val;

	return TEE_SUCCESS;
}

static TEE_Result atmel_rtc_set_offset(struct rtc *rtc  __unused, long offset)
{
	long corr = 0;
	uint32_t mr = 0;

	if (offset > ATMEL_RTC_CORR_DIVIDEND / 2)
		return TEE_ERROR_BAD_PARAMETERS;
	if (offset < -ATMEL_RTC_CORR_DIVIDEND / 2)
		return TEE_ERROR_BAD_PARAMETERS;

	mr = atmel_rtc_read(RTC_MR);
	mr &= ~(RTC_MR_NEGPPM | RTC_MR_CORR_MASK | RTC_MR_HIGHPPM);

	if (offset > 0)
		mr |= RTC_MR_NEGPPM;
	else
		offset = -offset;

	/* offset less than 764 ppb, disable correction */
	if (offset < 764) {
		atmel_rtc_write(RTC_MR, mr & ~RTC_MR_NEGPPM);

		return TEE_SUCCESS;
	}

	/*
	 * 29208 ppb is the perfect cutoff between low range and high range
	 * low range values are never better than high range value after that.
	 */
	if (offset < 29208) {
		corr = UDIV_ROUND_NEAREST(ATMEL_RTC_CORR_DIVIDEND,
					  offset * ATMEL_RTC_CORR_LOW_RATIO);
	} else {
		corr = UDIV_ROUND_NEAREST(ATMEL_RTC_CORR_DIVIDEND, offset);
		mr |= RTC_MR_HIGHPPM;
	}

	corr = MIN(corr, 128);

	mr |= ((corr - 1) & RTC_MR_CORR_MASK) << RTC_MR_CORR_SHIFT;

	atmel_rtc_write(RTC_MR, mr);

	return TEE_SUCCESS;
}

static const struct rtc_ops atmel_rtc_ops = {
	.get_time = atmel_rtc_get_time,
	.set_time = atmel_rtc_set_time,
	.get_offset = atmel_rtc_get_offset,
	.set_offset = atmel_rtc_set_offset,
};

static struct rtc atmel_rtc = {
	.ops = &atmel_rtc_ops,
	.range_min = { 1900, 1, 1, 0, 0, 0, 0 },
	.range_max = { 2099, 12, 31, 23, 59, 59, 0 },
};

/* Non-null reference for compat data */
static const uint8_t rtc_always_secure;

static TEE_Result atmel_rtc_probe(const void *fdt, int node,
				  const void *compat_data)
{
	size_t size = 0;

	if (rtc_base)
		return TEE_ERROR_GENERIC;

	if (fdt_get_status(fdt, node) != DT_STATUS_OK_SEC)
		return TEE_ERROR_BAD_PARAMETERS;

	if (compat_data != &rtc_always_secure)
		matrix_configure_periph_secure(AT91C_ID_SYS);

	if (dt_map_dev(fdt, node, &rtc_base, &size, DT_MAP_AUTO) < 0)
		return TEE_ERROR_GENERIC;

	atmel_rtc_write(RTC_CR, 0);
	/* Enable 24 hours Gregorian mode (this is a clear bits operation !) */
	io_clrbits32(rtc_base + RTC_MR, RTC_MR_PERSIAN | RTC_MR_UTC |
		     RTC_MR_HR_MODE);

	rtc_register(&atmel_rtc);

	return TEE_SUCCESS;
}

static const struct dt_device_match atmel_rtc_match_table[] = {
	{ .compatible = "atmel,sama5d2-rtc" },
	{
		.compatible = "microchip,sama7g5-rtc",
		.compat_data = &rtc_always_secure,
	},
	{ }
};

DEFINE_DT_DRIVER(atmel_rtc_dt_driver) = {
	.name = "atmel_rtc",
	.type = DT_DRIVER_NOTYPE,
	.match_table = atmel_rtc_match_table,
	.probe = atmel_rtc_probe,
};

