659 lines
17 KiB
C
659 lines
17 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/bitfield.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/time64.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/amlogic/rtc.h>
|
|
#include <linux/amlogic/aml_mbox.h>
|
|
#include "main.h"
|
|
|
|
static inline u32 gray_to_binary(u32 gray)
|
|
{
|
|
u32 bcd = gray;
|
|
int size = sizeof(bcd) * 8;
|
|
int i;
|
|
|
|
for (i = 0; (1 << i) < size; i++)
|
|
bcd ^= bcd >> (1 << i);
|
|
|
|
return bcd;
|
|
}
|
|
|
|
static inline u32 binary_to_gray(u32 bcd)
|
|
{
|
|
return bcd ^ (bcd >> 1);
|
|
}
|
|
|
|
static void meson_set_time(struct aml_rtc_data *rtc, u32 time_sec)
|
|
{
|
|
u32 time_val = time_sec;
|
|
int ret;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_SET_RTC,
|
|
&time_val, sizeof(time_val), NULL, 0, MBOX_SYNC);
|
|
if (ret < 0)
|
|
dev_err(&rtc->rtc_dev->dev, "set vrtc time failed\n");
|
|
} else {
|
|
if (rtc->time_storage_format)
|
|
time_val = binary_to_gray(time_val);
|
|
regmap_write(rtc->map, RTC_COUNTER_REG, time_val);
|
|
}
|
|
}
|
|
|
|
static u32 meson_read_time(struct aml_rtc_data *rtc)
|
|
{
|
|
u32 time_val = 0;
|
|
int ret;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_GET_RTC,
|
|
NULL, 0, &time_val, sizeof(time_val), MBOX_SYNC);
|
|
if (ret < 0)
|
|
dev_err(&rtc->rtc_dev->dev, "get vrtc time failed\n");
|
|
} else {
|
|
regmap_read(rtc->map, RTC_REAL_TIME, &time_val);
|
|
if (rtc->time_storage_format)
|
|
time_val = gray_to_binary(time_val);
|
|
}
|
|
|
|
return time_val;
|
|
}
|
|
|
|
static u32 meson_read_alarm(struct aml_rtc_data *rtc)
|
|
{
|
|
u32 alarm_val = 0, time_val;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
regmap_read(rtc->map, VRTC_ALARM_REG, &alarm_val);
|
|
} else {
|
|
regmap_read(rtc->map, RTC_ALARM0_REG, &alarm_val);
|
|
time_val = meson_read_time(rtc);
|
|
if (rtc->time_storage_format)
|
|
alarm_val = gray_to_binary(alarm_val);
|
|
alarm_val = (alarm_val > time_val) ? (alarm_val - time_val) : 0;
|
|
}
|
|
|
|
return alarm_val;
|
|
}
|
|
|
|
static void meson_set_alarm(struct aml_rtc_data *rtc, u32 alarm_sec)
|
|
{
|
|
u32 alarm_val = alarm_sec;
|
|
int ret = 0;
|
|
bool enable;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
regmap_write(rtc->map, VRTC_ALARM_REG, alarm_val);
|
|
|
|
enable = !!alarm_val;
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_RTC_ENABLE_ALRM,
|
|
&enable, sizeof(enable), NULL, 0, MBOX_SYNC);
|
|
if (ret < 0)
|
|
dev_err(&rtc->rtc_dev->dev, "enable vrtc timer failed\n");
|
|
} else {
|
|
if (alarm_val) {
|
|
alarm_val += meson_read_time(rtc);
|
|
if (rtc->time_storage_format)
|
|
alarm_val = binary_to_gray(alarm_val);
|
|
}
|
|
regmap_write(rtc->map, RTC_ALARM0_REG, alarm_val);
|
|
}
|
|
}
|
|
|
|
static void meson_enable_alarm(struct aml_rtc_data *rtc, bool enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (rtc->rtc_virtual && !enable) {
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_RTC_ENABLE_ALRM,
|
|
&enable, sizeof(enable), NULL, 0, MBOX_SYNC);
|
|
if (ret < 0)
|
|
dev_err(&rtc->rtc_dev->dev, "disable vrtc timer failed\n");
|
|
} else {
|
|
if (enable) {
|
|
regmap_update_bits(rtc->map, RTC_CTRL_REG,
|
|
RTC_ALRM0_EN, RTC_ALRM0_EN);
|
|
/* wait for alarm enable to take effect(for vlsi) */
|
|
usleep_range(100, 200);
|
|
} else {
|
|
regmap_update_bits(rtc->map, RTC_CTRL_REG, RTC_ALRM0_EN, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int aml_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
u32 time_sec;
|
|
|
|
time_sec = meson_read_time(rtc);
|
|
rtc_time64_to_tm(time_sec, tm);
|
|
dev_dbg(dev, "read time = %us\n", time_sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
u32 time_sec;
|
|
|
|
time_sec = rtc_tm_to_time64(tm);
|
|
dev_dbg(dev, "set time = %us\n", time_sec);
|
|
meson_set_time(rtc, time_sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
u32 time_sec = 0;
|
|
ktime_t alarm_sec = 0;
|
|
time64_t alarm_sec64;
|
|
|
|
if (rtc->wakealrm_enabled) {
|
|
alarm_sec64 = rtc_tm_to_time64(&alarm->time);
|
|
if (alarm_sec64 > U32_MAX) {
|
|
dev_err(dev, "alarm value invalid!\n");
|
|
return -EINVAL;
|
|
}
|
|
time_sec = meson_read_time(rtc);
|
|
if (alarm_sec64 > time_sec) {
|
|
alarm_sec = alarm_sec64 - time_sec;
|
|
/* for rtc, need to enable alarm first, then set alarm */
|
|
meson_enable_alarm(rtc, alarm->enabled);
|
|
meson_set_alarm(rtc, alarm_sec);
|
|
rtc->alarm_enabled = alarm->enabled;
|
|
|
|
dev_dbg(dev, "alarm->enabled=%d time=%u alarm=%lld\n",
|
|
alarm->enabled, time_sec, alarm_sec);
|
|
} else {
|
|
dev_info(dev, "alarm already expired!\n");
|
|
}
|
|
} else {
|
|
rtc->alarm_enabled = 0;
|
|
dev_info(dev, "rtc alarm is disabled\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
u32 alarm_sec = 0;
|
|
|
|
if (rtc->wakealrm_enabled)
|
|
alarm_sec = meson_read_alarm(rtc);
|
|
rtc_time64_to_tm((time64_t)alarm_sec, &alarm->time);
|
|
alarm->enabled = rtc->alarm_enabled;
|
|
dev_dbg(dev, "alarm->enabled=%d alarm=%u\n", alarm->enabled, alarm_sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t aml_rtc_alarm_handler(int irq, void *data)
|
|
{
|
|
struct aml_rtc_data *rtc = (struct aml_rtc_data *)data;
|
|
|
|
/* Clear alarm0 & disable alarm0 */
|
|
meson_set_alarm(rtc, 0);
|
|
meson_enable_alarm(rtc, 0);
|
|
if (!rtc->rtc_virtual) {
|
|
/* Clear alarm0 int status */
|
|
if (regmap_test_bits(rtc->map, RTC_INT_STATUS, RTC_ALRM0_IRQ_STATUS))
|
|
regmap_update_bits(rtc->map, RTC_INT_CLR,
|
|
RTC_ALRM0_IRQ_CLR, RTC_ALRM0_IRQ_CLR);
|
|
}
|
|
rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* rtc adjust function, using rtc_ops implementation */
|
|
static int aml_rtc_read_offset(struct device *dev, long *offset)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
u32 reg_val;
|
|
long val = 0;
|
|
int sign, match_counter, enable;
|
|
|
|
if (!rtc->rtc_virtual) {
|
|
regmap_read(rtc->map, RTC_SEC_ADJUST_REG, ®_val);
|
|
enable = FIELD_GET(RTC_ADJ_VALID, reg_val);
|
|
if (!enable) {
|
|
val = 0;
|
|
} else {
|
|
sign = FIELD_GET(RTC_SEC_ADJUST_CTRL, reg_val);
|
|
match_counter = FIELD_GET(RTC_MATCH_COUNTER, reg_val);
|
|
val = 1000000000 / (match_counter + 1);
|
|
if (sign == RTC_SWALLOW_SECOND)
|
|
val = -val;
|
|
}
|
|
} else {
|
|
dev_info(dev, "virtual rtc donot support read offset\n");
|
|
return -ENODEV;
|
|
}
|
|
*offset = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_rtc_set_offset(struct device *dev, long offset)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
int sign = 0, match_counter = 0, enable = 0;
|
|
u32 reg_val;
|
|
|
|
if (!rtc->rtc_virtual) {
|
|
if (offset) {
|
|
enable = 1;
|
|
sign = offset < 0 ? RTC_SWALLOW_SECOND : RTC_INSERT_SECOND;
|
|
match_counter = 1000000000 / abs(offset) - 1;
|
|
if (match_counter < 0 || match_counter > RTC_MATCH_COUNTER)
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg_val = FIELD_PREP(RTC_ADJ_VALID, enable) |
|
|
FIELD_PREP(RTC_SEC_ADJUST_CTRL, sign) |
|
|
FIELD_PREP(RTC_MATCH_COUNTER, match_counter);
|
|
regmap_update_bits(rtc->map, RTC_SEC_ADJUST_REG, RTC_MATCH_COUNTER |
|
|
RTC_SEC_ADJUST_CTRL | RTC_ADJ_VALID, reg_val);
|
|
} else {
|
|
dev_info(dev, "virtual rtc donot support set offset\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_rtc_alarm_enable(struct device *dev, unsigned int enabled)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
|
|
if (!enabled)
|
|
meson_enable_alarm(rtc, enabled);
|
|
rtc->alarm_enabled = enabled;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t div256_adj_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev->parent);
|
|
u32 reg_val;
|
|
int adj_val, adj_dsr;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
dev_info(dev, "virtual rtc donot support div256_adj\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
regmap_read(rtc->map, RTC_SEC_ADJUST_REG, ®_val);
|
|
adj_dsr = FIELD_GET(RTC_DIV256_ADJ_DSR, reg_val);
|
|
adj_val = FIELD_GET(RTC_DIV256_ADJ_VAL, reg_val);
|
|
|
|
return sprintf(buf, "%d %d\n", adj_dsr, adj_val);
|
|
}
|
|
|
|
/*
|
|
* this node looks at the action of writing, and writes once remove/insert 1/128s.
|
|
*/
|
|
static ssize_t div256_adj_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev->parent);
|
|
int val;
|
|
int adj_val, adj_dsr;
|
|
unsigned int reg_val = 0;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
dev_info(dev, "virtual rtc donot support div256_adj\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (kstrtouint(buf, 0, &val)) {
|
|
dev_err(dev, "invalid div256_daj parm=%d\n", val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
adj_val = val % 10;
|
|
adj_dsr = val / 10;
|
|
reg_val |= (FIELD_PREP(RTC_DIV256_ADJ_DSR, adj_dsr) |
|
|
FIELD_PREP(RTC_DIV256_ADJ_VAL, adj_val));
|
|
regmap_write_bits(rtc->map, RTC_SEC_ADJUST_REG,
|
|
RTC_DIV256_ADJ_DSR | RTC_DIV256_ADJ_VAL, reg_val);
|
|
/* rtc need about 30ms to adjust its time after write reg*/
|
|
mdelay(30);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(div256_adj);
|
|
|
|
static ssize_t wakealrm_enabled_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev->parent);
|
|
int enabled = rtc->wakealrm_enabled;
|
|
|
|
return sprintf(buf, "%d\n", enabled);
|
|
}
|
|
|
|
static ssize_t wakealrm_enabled_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev->parent);
|
|
int enabled;
|
|
|
|
if (kstrtoint(buf, 0, &enabled)) {
|
|
dev_err(dev, "invalid wakealrm_enabled=%d\n", enabled);
|
|
return -EINVAL;
|
|
}
|
|
rtc->wakealrm_enabled = enabled;
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(wakealrm_enabled);
|
|
|
|
static struct attribute *meson_rtc_attrs[] = {
|
|
&dev_attr_div256_adj.attr,
|
|
&dev_attr_wakealrm_enabled.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group meson_rtc_sysfs_files = {
|
|
.attrs = meson_rtc_attrs,
|
|
};
|
|
|
|
static const struct rtc_class_ops aml_rtc_ops = {
|
|
.read_time = aml_rtc_read_time,
|
|
.set_time = aml_rtc_set_time,
|
|
.read_alarm = aml_rtc_read_alarm,
|
|
.set_alarm = aml_rtc_set_alarm,
|
|
.alarm_irq_enable = aml_rtc_alarm_enable,
|
|
.read_offset = aml_rtc_read_offset,
|
|
.set_offset = aml_rtc_set_offset,
|
|
};
|
|
|
|
static void aml_rtc_init(struct device *dev, struct aml_rtc_data *rtc)
|
|
{
|
|
u32 reg_val = 0;
|
|
|
|
if (!regmap_test_bits(rtc->map, RTC_CTRL_REG, RTC_ENABLE)) {
|
|
if (clk_get_rate(rtc->rtc_clk) == OSC_24M) {
|
|
/* select 24M oscillator */
|
|
regmap_write_bits(rtc->map, RTC_CTRL_REG, RTC_OSC_SEL, RTC_OSC_SEL);
|
|
|
|
/*
|
|
* Set RTC oscillator to freq_out to freq_in/((N0*M0+N1*M1)/(M0+M1))
|
|
* Enable clock_in gate of oscillator 24MHz
|
|
* Set N0 to 733, N1 to 732
|
|
*/
|
|
reg_val = FIELD_PREP(RTC_OSCIN_IN_EN, 1)
|
|
| FIELD_PREP(RTC_OSCIN_OUT_CFG, 1)
|
|
| FIELD_PREP(RTC_OSCIN_OUT_N0M0, RTC_OSCIN_OUT_32K_N0)
|
|
| FIELD_PREP(RTC_OSCIN_OUT_N1M1, RTC_OSCIN_OUT_32K_N1);
|
|
regmap_write_bits(rtc->map, RTC_OSCIN_CTRL0, RTC_OSCIN_IN_EN
|
|
| RTC_OSCIN_OUT_CFG | RTC_OSCIN_OUT_N0M0
|
|
| RTC_OSCIN_OUT_N1M1, reg_val);
|
|
|
|
/* Set M0 to 2, M1 to 3, so freq_out = 32768 Hz*/
|
|
reg_val = FIELD_PREP(RTC_OSCIN_OUT_N0M0, RTC_OSCIN_OUT_32K_M0)
|
|
| FIELD_PREP(RTC_OSCIN_OUT_N1M1, RTC_OSCIN_OUT_32K_M1);
|
|
regmap_write_bits(rtc->map, RTC_OSCIN_CTRL1, RTC_OSCIN_OUT_N0M0
|
|
| RTC_OSCIN_OUT_N1M1, reg_val);
|
|
} else {
|
|
/* select 32K oscillator */
|
|
regmap_write_bits(rtc->map, RTC_CTRL_REG, RTC_OSC_SEL, 0);
|
|
}
|
|
/* enable rtc */
|
|
regmap_write_bits(rtc->map, RTC_CTRL_REG, RTC_ENABLE, RTC_ENABLE);
|
|
usleep_range(100, 200);
|
|
}
|
|
/* unmask alarm0 irq */
|
|
regmap_write_bits(rtc->map, RTC_INT_MASK, RTC_ALRM0_IRQ_MSK, 0);
|
|
/* disable alarm0 */
|
|
regmap_write_bits(rtc->map, RTC_CTRL_REG, RTC_ALRM0_EN, 0);
|
|
}
|
|
|
|
static int aml_rtc_probe(struct platform_device *pdev)
|
|
{
|
|
struct aml_rtc_data *rtc;
|
|
void __iomem *base;
|
|
struct resource *res;
|
|
u32 rtc_time = 0;
|
|
int ret;
|
|
|
|
rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
|
|
if (!rtc)
|
|
return -ENOMEM;
|
|
|
|
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
if (IS_ERR(base)) {
|
|
dev_err_probe(&pdev->dev, PTR_ERR(base), "resource ioremap failed\n");
|
|
return PTR_ERR(base);
|
|
}
|
|
aml_rtc_regmap_config.max_register = resource_size(res) - 4;
|
|
rtc->map = devm_regmap_init_mmio(&pdev->dev, base, &aml_rtc_regmap_config);
|
|
if (IS_ERR(rtc->map)) {
|
|
dev_err_probe(&pdev->dev, PTR_ERR(rtc->map), "regmap init failed\n");
|
|
return PTR_ERR(rtc->map);
|
|
}
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "time-storage-format-gray"))
|
|
rtc->time_storage_format = true;
|
|
else
|
|
rtc->time_storage_format = false;
|
|
|
|
/* rtc supports setting alarm flag */
|
|
if (of_property_read_bool(pdev->dev.of_node, "wakealarm-disabled"))
|
|
rtc->wakealrm_enabled = false;
|
|
else
|
|
rtc->wakealrm_enabled = true;
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "rtc-virtual"))
|
|
rtc->rtc_virtual = true;
|
|
else
|
|
rtc->rtc_virtual = false;
|
|
|
|
if (rtc->rtc_virtual) {
|
|
rtc->mbox_chan = aml_mbox_request_channel_byidx(&pdev->dev, 0);
|
|
if (IS_ERR_OR_NULL(rtc->mbox_chan))
|
|
return dev_err_probe(&pdev->dev, PTR_ERR(rtc->mbox_chan),
|
|
"failed to get mbox chan\n");
|
|
} else {
|
|
rtc->rtc_clk = devm_clk_get(&pdev->dev, "osc");
|
|
if (IS_ERR(rtc->rtc_clk))
|
|
return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_clk),
|
|
"failed to get rtc clock\n");
|
|
if (clk_get_rate(rtc->rtc_clk) != OSC_32K &&
|
|
clk_get_rate(rtc->rtc_clk) != OSC_24M) {
|
|
dev_err(&pdev->dev, "Invalid clock configuration\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rtc->sys_clk = devm_clk_get(&pdev->dev, "sys");
|
|
if (IS_ERR(rtc->sys_clk))
|
|
return dev_err_probe(&pdev->dev, PTR_ERR(rtc->sys_clk),
|
|
"failed to get rtc sys clk\n");
|
|
ret = clk_prepare_enable(rtc->sys_clk);
|
|
if (ret)
|
|
return dev_err_probe(&pdev->dev, ret, "Failed to enable clk\n");
|
|
|
|
aml_rtc_init(&pdev->dev, rtc);
|
|
|
|
rtc->mbox_chan = aml_mbox_request_channel_byidx(&pdev->dev, 0);
|
|
if (!IS_ERR_OR_NULL(rtc->mbox_chan)) {
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_GET_RTC,
|
|
NULL, 0, &rtc_time, sizeof(rtc_time), MBOX_SYNC);
|
|
if (ret < 0) {
|
|
dev_err_probe(&pdev->dev, ret, "Fail to get rtc init time\n");
|
|
goto err_clk;
|
|
}
|
|
dev_dbg(&pdev->dev, "Successfully restored rtc time %u\n", rtc_time);
|
|
meson_set_time(rtc, rtc_time);
|
|
} else {
|
|
dev_info(&pdev->dev, "No need mailbox channel\n");
|
|
rtc->mbox_chan = NULL;
|
|
}
|
|
}
|
|
|
|
rtc->irq = platform_get_irq(pdev, 0);
|
|
if (rtc->irq < 0) {
|
|
ret = dev_err_probe(&pdev->dev, rtc->irq, "Failed to get rtc irq\n");
|
|
goto err_clk;
|
|
} else {
|
|
ret = devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL, aml_rtc_alarm_handler,
|
|
IRQF_ONESHOT, "aml-rtc alarm", rtc);
|
|
if (ret) {
|
|
dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
|
|
goto err_clk;
|
|
}
|
|
}
|
|
|
|
device_init_wakeup(&pdev->dev, true);
|
|
platform_set_drvdata(pdev, rtc);
|
|
|
|
rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
|
|
if (IS_ERR(rtc->rtc_dev)) {
|
|
ret = PTR_ERR(rtc->rtc_dev);
|
|
goto err_clk;
|
|
}
|
|
|
|
rtc->rtc_dev->ops = &aml_rtc_ops;
|
|
|
|
ret = rtc_add_group(rtc->rtc_dev, &meson_rtc_sysfs_files);
|
|
if (ret) {
|
|
dev_err_probe(&pdev->dev, ret, "Failed to add RTC group: %d\n", ret);
|
|
goto err_clk;
|
|
}
|
|
|
|
ret = devm_rtc_register_device(rtc->rtc_dev);
|
|
if (ret) {
|
|
dev_err_probe(&pdev->dev, ret, "Failed to register RTC device: %d\n", ret);
|
|
goto err_clk;
|
|
}
|
|
|
|
return 0;
|
|
err_clk:
|
|
if (!rtc->rtc_virtual)
|
|
clk_disable_unprepare(rtc->sys_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __maybe_unused aml_rtc_suspend(struct device *dev)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (!rtc->wakealrm_enabled)
|
|
return ret;
|
|
|
|
if (device_may_wakeup(dev))
|
|
ret = enable_irq_wake(rtc->irq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __maybe_unused aml_rtc_resume(struct device *dev)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (!rtc->wakealrm_enabled)
|
|
return ret;
|
|
|
|
if (device_may_wakeup(dev))
|
|
ret = disable_irq_wake(rtc->irq);
|
|
/* alarm fired, handler donot executed, trigger it munally */
|
|
/* clean 'rtc->alarm' to allow a new .set_alarm to the upper RTC layer */
|
|
if (!meson_read_alarm(rtc))
|
|
aml_rtc_alarm_handler(rtc->irq, (void *)rtc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(aml_rtc_pm_ops,
|
|
aml_rtc_suspend, aml_rtc_resume);
|
|
|
|
static void aml_rtc_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct aml_rtc_data *rtc = dev_get_drvdata(&pdev->dev);
|
|
struct rtc_wkalrm aie_timer;
|
|
u32 rtc_time;
|
|
u64 alarm_sec = 0;
|
|
int ret;
|
|
|
|
/* Temporarily use: store rtc time when reboot, prevent rtc time being */
|
|
/* reset by reboot, it can be removed if rtc can preserve time in reboot */
|
|
/* procedure, only rtc needed */
|
|
rtc_time = meson_read_time(rtc);
|
|
if (!rtc->rtc_virtual && rtc->mbox_chan) {
|
|
ret = aml_mbox_transfer_data(rtc->mbox_chan, MBOX_CMD_SET_RTC,
|
|
&rtc_time, sizeof(rtc_time), NULL, 0, MBOX_SYNC);
|
|
if (ret < 0)
|
|
dev_err(&pdev->dev, "Fail to set rtc time\n");
|
|
}
|
|
|
|
if (!rtc->wakealrm_enabled)
|
|
return;
|
|
|
|
/* only aie timer can wakeup system when poweroff */
|
|
ret = rtc_read_alarm(rtc->rtc_dev, &aie_timer);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to read rtc alarm\n");
|
|
return;
|
|
}
|
|
alarm_sec = rtc_tm_to_time64(&aie_timer.time);
|
|
if (alarm_sec > rtc_time && aie_timer.enabled) {
|
|
alarm_sec -= rtc_time;
|
|
/* enable rtc alarm */
|
|
meson_enable_alarm(rtc, 1);
|
|
/* set alarm to reg */
|
|
meson_set_alarm(rtc, alarm_sec);
|
|
|
|
dev_info(&pdev->dev, "system will wakeup %llus later\n", alarm_sec);
|
|
}
|
|
}
|
|
|
|
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 = aml_rtc_probe,
|
|
.driver = {
|
|
.name = "meson-rtc",
|
|
.of_match_table = meson_rtc_dt_match,
|
|
.pm = &aml_rtc_pm_ops,
|
|
},
|
|
.shutdown = aml_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);
|
|
}
|