// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/time64.h>
#include <linux/delay.h>
#include <linux/amlogic/rtc.h>
#include <linux/clk.h>

#define RTC_CALIBRATION
#define OSC_24MHZ	(24000000)
#define OSC_32KHZ	(32768)

static void meson_set_time(struct meson_rtc_data *rtc, u32 time_sec)
{
	writel(time_sec, rtc->reg_base + RTC_COUNTER_REG);
}

static u32 meson_read_time(struct meson_rtc_data *rtc)
{
	u32 time_val;

	time_val = readl(rtc->reg_base + RTC_REAL_TIME);
	return time_val;
}

static u32 meson_read_alarm(struct meson_rtc_data *rtc)
{
	u32 alarm_val;

	alarm_val = readl(rtc->reg_base + RTC_ALARM0_REG);
	return alarm_val;
}

static void meson_set_alarm(struct meson_rtc_data *rtc, u32 alarm_sec)
{
	writel(alarm_sec, rtc->reg_base + RTC_ALARM0_REG);
}

static int meson_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	u32 time_sec;
	time64_t time64;

	time_sec = meson_read_time(rtc);
	time64 = (time64_t)time_sec;
	dev_dbg(dev, "%s: read time = %u\n",
		__func__, time_sec);
	rtc_time64_to_tm(time64, tm);

	return 0;
}

static int meson_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	time64_t time_sec;

	time_sec = rtc_tm_to_time64(tm);
	meson_set_time(rtc, (u32)time_sec);
	dev_dbg(dev, "%s: set_time = %llu\n",
		__func__, time_sec);

	return 0;
}

static int meson_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	u32 alarm_sec;
	u32 reg_val;
	time64_t alarm_sec64;

	/* Disable alarm0 first */
	reg_val = readl(rtc->reg_base + RTC_CTRL);
	reg_val &= (~(1 << RTC_ALRM0_EN_BIT));
	writel(reg_val, rtc->reg_base + RTC_CTRL);

	if (alarm->enabled) {
		alarm_sec64 = rtc_tm_to_time64(&alarm->time);
		if (alarm_sec64 > 0x7fffffff) {
			dev_err(dev, "alarm value invalid!\n");
			return -EINVAL;
		}

		alarm_sec = (u32)alarm_sec64;
		meson_set_alarm(rtc, alarm_sec);

		/* Enable alarm0 */
		reg_val = readl(rtc->reg_base + RTC_CTRL);
		reg_val |= (1 << RTC_ALRM0_EN_BIT);
		writel(reg_val, rtc->reg_base + RTC_CTRL);
	}

	rtc->alarm_enabled = alarm->enabled;

	dev_dbg(dev, "%s: alarm->enabled=%d alarm_set=0x%llx alarm=0x%x\n",
		__func__, alarm->enabled, alarm_sec64, alarm_sec);

	return 0;
}

static int meson_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	u32 alarm_sec;
	u32 reg_val;

	alarm_sec = meson_read_alarm(rtc);
	rtc_time64_to_tm((time64_t)alarm_sec, &alarm->time);
	dev_dbg(dev, "%s: alarm->enabled=%d alarm=0x%x\n", __func__,
		alarm->enabled, alarm_sec);

	reg_val = readl(rtc->reg_base + RTC_INT_STATUS);
	alarm->enabled = rtc->alarm_enabled;
	alarm->pending = (reg_val & (1 << RTC_ALRM0_STATUS_BIT)) ? 1 : 0;

	return 0;
}

static irqreturn_t meson_rtc_handler(int irq, void *data)
{
	struct meson_rtc_data *rtc = (struct meson_rtc_data *)data;

	rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF);
	return IRQ_HANDLED;
}

static int meson_rtc_alarm_enable(struct device *dev, unsigned int enabled)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	u32 reg_val;

	dev_dbg(dev, "%s: enabled = %d\n", __func__,
		enabled);

	/* Operate RTC alarm0 by default */
	if (enabled) {
		/* Enable alarm0 */
		reg_val = readl(rtc->reg_base + RTC_CTRL);
		reg_val |= (1 << RTC_ALRM0_EN_BIT);
		writel(reg_val, rtc->reg_base + RTC_CTRL);
	} else {
		/* Disable alarm0 */
		reg_val = readl(rtc->reg_base + RTC_CTRL);
		reg_val &= (~(1 << RTC_ALRM0_EN_BIT));
		writel(reg_val, rtc->reg_base + RTC_CTRL);
		/* clear alarm */
		meson_set_alarm(rtc, 0);
	}

	rtc->alarm_enabled = enabled;
	return 0;
}

static void meson_rtc_clk_config(struct meson_rtc_data *rtc)
{
	u32 reg_val;

	if (rtc->freq == OSC_24MHZ) {
		reg_val = readl(rtc->reg_base + RTC_CTRL);
		/* Select RTC oscillator to 24MHz clock output */
		reg_val |= (1 << RTC_OSC_SEL_BIT);
		writel(reg_val, rtc->reg_base + RTC_CTRL);

		/* Set RTC oscillator to freq_out to freq_in/((N0*M0+N1*M1)/(M0+M1)) */
		reg_val = readl(rtc->reg_base + RTC_OSCIN_CTRL0);
		reg_val &= (~(0x3 << 28));
		reg_val |= (0x1 << 28);
		/* Enable clock_in gate of oscillator 24MHz */
		reg_val |= (1 << 31);
		/* N0 is set to 733, N1 is set to 732 by default*/
		writel(reg_val, rtc->reg_base + RTC_OSCIN_CTRL0);
		/* Set M0 to 2, M1 to 3, so freq_out = 32768 Hz*/
		reg_val = readl(rtc->reg_base + RTC_OSCIN_CTRL1);
		reg_val &= ~(0xfff);
		reg_val |= (0x1 << 0);
		reg_val &= ~(0xfff << 12);
		reg_val |= (0x2 << 12);
		writel(reg_val, rtc->reg_base + RTC_OSCIN_CTRL1);
	}
}

#ifdef RTC_CALIBRATION
static int meson_rtc_adjust_sec(struct device *dev, u32 match_counter,
				enum meson_rtc_adj ops)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev->parent);
	u32 reg_val;

	if (match_counter > 0x7ffff) {
		pr_err("%s: invalid match_counter\n", __func__);
		return -EINVAL;
	}

	reg_val = readl(rtc->reg_base + RTC_SEC_ADJUST_REG);
	/* Set sec_adjust_ctrl */
	reg_val &= (~(0x3 << 19));
	if (ops == ADJUST_DROP)
		reg_val |= (0x2 << 19);
	else if (ops == ADJUST_INSERT)
		reg_val |= (0x3 << 19);
	/* Set match_counter */
	reg_val &= (~(0x7ffff << 0));
	reg_val |= match_counter;
	/* Valid adjust */
	reg_val |= (0x1 << 23);
	writel(reg_val, rtc->reg_base + RTC_SEC_ADJUST_REG);

	return 0;
}

static int meson_rtc_set_calibration(struct device *dev, u32 calibration)
{
	enum meson_rtc_adj cal_ops;
	u32 match_counter;
	u32 sec_adjust_ctrl;
	int ret;

	match_counter = calibration & 0x7ffff;
	sec_adjust_ctrl = (calibration >> 30) & 0x3;
	cal_ops = sec_adjust_ctrl;

	if (cal_ops == ADJUST_MAX) {
		pr_err("%s: calibration ops val invalid!\n", __func__);
		return -EINVAL;
	}

	ret = meson_rtc_adjust_sec(dev, match_counter, cal_ops);
	dev_dbg(dev, "%s: Success to store RTC calibration attribute\n",
		__func__);

	return ret;
}

static int meson_rtc_get_calibration(struct device *dev, u32 *calibration)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev->parent);
	enum meson_rtc_adj cal_ops;
	u32 match_counter;
	u32 reg_val;
	u32 sec_adjust_ctrl;

	reg_val = readl(rtc->reg_base + RTC_SEC_ADJUST_REG);
	match_counter = reg_val & 0x7ffff;
	sec_adjust_ctrl = (reg_val >> 19) & 3U;

	if (sec_adjust_ctrl == 0 || sec_adjust_ctrl == 1)
		cal_ops = ADJUST_NONE;
	else if (sec_adjust_ctrl == 2)
		cal_ops = ADJUST_DROP;
	else
		cal_ops = ADJUST_INSERT;

	*calibration = ((cal_ops & 0x3) << 30) | (match_counter & 0x7ffff);
	return 0;
}

/* The calibration value transferred from buf takes bit[18:0] to represent   */
/* match counter, while takes bit[31:30] to represent operation style       */
/* which can be set to: 0 represents no calibration; 1 represents drop one */
/* second every match counter; 2 represents insert one second every match counter */
static ssize_t rtc_calibration_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	int retval;
	int calibration = 0;

	if (sscanf(buf, " %i ", &calibration) != 1) {
		dev_err(dev, "Failed to store RTC calibration attribute\n");
		return -EINVAL;
	}

	retval = meson_rtc_set_calibration(dev, calibration);

	return retval ? retval : count;
}

static ssize_t rtc_calibration_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	int  retval = 0;
	u32  calibration = 0;

	retval = meson_rtc_get_calibration(dev, &calibration);
	if (retval < 0) {
		dev_err(dev, "Failed to read RTC calibration attribute\n");
		sprintf(buf, "0\n");
		return retval;
	}

	return sprintf(buf, "0x%x\n", calibration);
}

static DEVICE_ATTR_RW(rtc_calibration);

static struct attribute *meson_rtc_attrs[] = {
	&dev_attr_rtc_calibration.attr,
	NULL
};

static const struct attribute_group meson_rtc_sysfs_files = {
	.attrs	= meson_rtc_attrs,
};
#endif

static const struct rtc_class_ops aml_meson_rtc_ops = {
	.read_time = meson_rtc_read_time,
	.set_time = meson_rtc_set_time,
	.read_alarm = meson_rtc_read_alarm,
	.set_alarm = meson_rtc_set_alarm,
	.alarm_irq_enable = meson_rtc_alarm_enable,
};

static void meson_rtc_init(struct device *dev, struct meson_rtc_data *rtc)
{
	u32 reg_val;
	u32 rtc_enable;

	reg_val = readl(rtc->reg_base + RTC_CTRL);
	rtc_enable = ((reg_val >> RTC_ENABLE_BIT) & 1);

	dev_dbg(dev, "%s: rtc enable = %u\n", __func__, rtc_enable);
	/* If rtc is not enable, enable and init it */
	if (!rtc_enable) {
		meson_rtc_clk_config(rtc);
		/* Enable RTC */
		reg_val |= (1 << RTC_ENABLE_BIT);
		writel(reg_val, rtc->reg_base + RTC_CTRL);
		usleep_range(100, 200);
	}

	/* Mask alarm0 irq */
	reg_val = readl(rtc->reg_base + RTC_INT_MASK);
	reg_val |= (1 << RTC_ALRM0_IRQ_MSK_BIT);
	writel(reg_val, rtc->reg_base + RTC_INT_MASK);

	rtc->alarm_enabled = false;
	/* Show rtc time */
	reg_val = readl(rtc->reg_base + RTC_REAL_TIME);
	dev_dbg(dev, "%s: rtc time stamp = %u\n", __func__, reg_val);
}

static int meson_rtc_probe(struct platform_device *pdev)
{
	struct meson_rtc_data *rtc;
	struct device_node *node = pdev->dev.of_node;
	int ret;
	u32 freq = 0;

	if (!node) {
		dev_err(&pdev->dev,
			"meson-rtc requires of_node!\n");
		return -EINVAL;
	}

	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
	if (!rtc)
		return -ENOMEM;

	rtc->irq = platform_get_irq(pdev, 0);
	if (rtc->irq < 0)
		return rtc->irq;

	rtc->reg_base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(rtc->reg_base)) {
		pr_err("resource ioremap failed\n");
		return PTR_ERR(rtc->reg_base);
	}

	rtc->clock = of_clk_get_by_name(pdev->dev.of_node, "rtc_clk");
	if (IS_ERR_OR_NULL(rtc->clock)) {
		pr_err("invalid rtc clock\n");
		return -EINVAL;
	}

	ret = of_property_read_u32(pdev->dev.of_node,
				   "osc_freq", &freq);
	if (!ret) {
		if (freq != OSC_24MHZ && freq != OSC_32KHZ) {
			pr_err("%s: Invalid clock configuration\n",
				__func__);
			return -EINVAL;
		}
		dev_dbg(&pdev->dev, "%s: Select %dHz oscillator as rtc source clock\n",
			__func__, freq);
		rtc->freq = freq;
	} else {
		pr_err("%s: Get rtc clock frequency failed!\n", __func__);
		return -EINVAL;
	}

	clk_set_rate(rtc->clock, rtc->freq);
	clk_prepare_enable(rtc->clock);

	meson_rtc_init(&pdev->dev, rtc);

	device_init_wakeup(&pdev->dev, 1);

	platform_set_drvdata(pdev, rtc);

	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
	if (IS_ERR(rtc->rtc_dev))
		return PTR_ERR(rtc->rtc_dev);

	ret = devm_request_irq(&pdev->dev, rtc->irq, meson_rtc_handler,
			       IRQF_ONESHOT, "meson-rtc alarm", rtc);
	if (ret) {
		dev_err(&pdev->dev, "IRQ%d request failed, ret = %d\n",
			rtc->irq, ret);
		return ret;
	}

	rtc->rtc_dev->ops = &aml_meson_rtc_ops;

#ifdef RTC_CALIBRATION
	ret = rtc_add_group(rtc->rtc_dev, &meson_rtc_sysfs_files);
	if (ret)
		return ret;
#endif

	ret = devm_rtc_register_device(rtc->rtc_dev);
	if (ret)
		return ret;

	dev_dbg(&pdev->dev, "%s: probe done\n", __func__);
	return 0;
}

static int meson_rtc_suspend(struct device *dev)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);
	u32 reg_val;

	if (device_may_wakeup(dev)) {
		enable_irq_wake(rtc->irq);
		/* Unmask alarm0 irq */
		reg_val = readl(rtc->reg_base + RTC_INT_MASK);
		reg_val &= (~(1 << RTC_ALRM0_IRQ_MSK_BIT));
		writel(reg_val, rtc->reg_base + RTC_INT_MASK);
	}

	return 0;
}

static int meson_rtc_resume(struct device *dev)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(dev);

	if (device_may_wakeup(dev))
		disable_irq_wake(rtc->irq);

	return 0;
}

static SIMPLE_DEV_PM_OPS(meson_rtc_pm_ops,
			 meson_rtc_suspend, meson_rtc_resume);

static void meson_rtc_shutdown(struct platform_device *pdev)
{
	struct meson_rtc_data *rtc = dev_get_drvdata(&pdev->dev);
	u32 reg_val;

	if (device_may_wakeup(&pdev->dev)) {
		enable_irq_wake(rtc->irq);
		/* Unmask alarm0 irq */
		reg_val = readl(rtc->reg_base + RTC_INT_MASK);
		reg_val &= (~(1 << RTC_ALRM0_IRQ_MSK_BIT));
		writel(reg_val, rtc->reg_base + RTC_INT_MASK);
	}
}

static const struct of_device_id meson_rtc_dt_match[] = {
	{ .compatible = "amlogic,meson-rtc"},
	{}
};
MODULE_DEVICE_TABLE(of, meson_rtc_dt_match);

static struct platform_driver meson_rtc_driver = {
	.probe = meson_rtc_probe,
	.driver = {
		.name = "meson-rtc",
		.of_match_table = meson_rtc_dt_match,
		.pm = &meson_rtc_pm_ops,
	},
	.shutdown = meson_rtc_shutdown,
};

int __init rtc_init(void)
{
	return platform_driver_register(&meson_rtc_driver);
}

void __exit rtc_exit(void)
{
	platform_driver_unregister(&meson_rtc_driver);
}
