mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
1451450114
PD#SWPL-141475 Problem: set alarm A second, then clear alarm using echo 0 cmd, suspend or poweroff will delay A second resume/boot Solution: add code clear driver store alarm para after set alarm Verify: t5d, t3, sc2, s4 Change-Id: Iee848ab5949edcb3b6e92027ae4410124d38fcec Signed-off-by: yiting.deng <yiting.deng@amlogic.com>
506 lines
12 KiB
C
506 lines
12 KiB
C
// 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);
|
|
}
|