mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-08 20:07:46 +09:00
vrtc: add vrtc driver
PD#138714: add amlogic vrtc driver Change-Id: I8aee09041d2d20f06113c1ac0ce310f0c8c68384 Signed-off-by: Yun Cai <yun.cai@amlogic.com>
This commit is contained in:
@@ -13601,3 +13601,6 @@ F: include/linux/amlogic/sd.h
|
||||
F: include/linux/mmc/emmc_partitions.h
|
||||
F: include/linux/mmc/host.h
|
||||
|
||||
AMLOGIC VRTC DRIVER
|
||||
M: Yun Cai <yun.cai@amlogic.com>
|
||||
F: drivers/amlogic/vrtc/
|
||||
|
||||
@@ -1079,5 +1079,13 @@
|
||||
sys_reset = <0x84000009>;
|
||||
sys_poweroff = <0x84000008>;
|
||||
};
|
||||
|
||||
rtc{
|
||||
compatible = "amlogic, aml_vrtc";
|
||||
alarm_reg_addr = <0xc81000a8>;
|
||||
timer_e_addr = <0xc1109988>;
|
||||
init_date = "2017/01/01";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1125,5 +1125,13 @@
|
||||
sys_reset = <0x84000009>;
|
||||
sys_poweroff = <0x84000008>;
|
||||
};
|
||||
|
||||
rtc{
|
||||
compatible = "amlogic, aml_vrtc";
|
||||
alarm_reg_addr = <0xc81000a8>;
|
||||
timer_e_addr = <0xc1109988>;
|
||||
init_date = "2017/01/01";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -210,6 +210,7 @@ CONFIG_AMLOGIC_MEDIA_FB_OSD_VSYNC_RDMA=y
|
||||
CONFIG_AMLOGIC_MEDIA_FB_OSD2_ENABLE=y
|
||||
CONFIG_AMLOGIC_MEDIA_FB_OSD2_CURSOR=y
|
||||
CONFIG_AMLOGIC_MMC=y
|
||||
CONFIG_AMLOGIC_VRTC=y
|
||||
CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
|
||||
@@ -53,5 +53,6 @@ source "drivers/amlogic/media/Kconfig"
|
||||
|
||||
source "drivers/amlogic/mmc/Kconfig"
|
||||
|
||||
source "drivers/amlogic/vrtc/Kconfig"
|
||||
endmenu
|
||||
endif
|
||||
|
||||
@@ -47,6 +47,6 @@ obj-$(CONFIG_AMLOGIC_PWM) += pwm/
|
||||
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_ENABLE) += media/
|
||||
|
||||
|
||||
obj-$(CONFIG_AMLOGIC_MMC) += mmc/
|
||||
|
||||
obj-$(CONFIG_AMLOGIC_VRTC) += vrtc/
|
||||
|
||||
@@ -426,3 +426,38 @@ int scpi_send_usr_data(u32 client_id, u32 *val, u32 size)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(scpi_send_usr_data);
|
||||
|
||||
int scpi_get_vrtc(u32 *p_vrtc)
|
||||
{
|
||||
struct scpi_data_buf sdata;
|
||||
struct mhu_data_buf mdata;
|
||||
u32 temp = 0;
|
||||
struct __packed {
|
||||
u32 status;
|
||||
u32 vrtc;
|
||||
} buf;
|
||||
|
||||
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
|
||||
SCPI_CMD_GET_RTC, temp, buf);
|
||||
if (scpi_execute_cmd(&sdata))
|
||||
return -EPERM;
|
||||
|
||||
*p_vrtc = buf.vrtc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(scpi_get_vrtc);
|
||||
|
||||
int scpi_set_vrtc(u32 vrtc_val)
|
||||
{
|
||||
struct scpi_data_buf sdata;
|
||||
struct mhu_data_buf mdata;
|
||||
int state;
|
||||
|
||||
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
|
||||
SCPI_CMD_SET_RTC, vrtc_val, state);
|
||||
if (scpi_execute_cmd(&sdata))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(scpi_set_vrtc);
|
||||
|
||||
11
drivers/amlogic/vrtc/Kconfig
Normal file
11
drivers/amlogic/vrtc/Kconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# Amlogic VRTC
|
||||
|
||||
config AMLOGIC_VRTC
|
||||
bool "meson VRTC support"
|
||||
default n
|
||||
select RTC_CLASS
|
||||
help
|
||||
This is the Amlogic virtual Real Time Clock driver,
|
||||
which only has alarm function.
|
||||
|
||||
|
||||
4
drivers/amlogic/vrtc/Makefile
Normal file
4
drivers/amlogic/vrtc/Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
#Makefile for the vrtc dirver
|
||||
#
|
||||
obj-$(CONFIG_AMLOGIC_VRTC) += aml_vrtc.o
|
||||
288
drivers/amlogic/vrtc/aml_vrtc.c
Normal file
288
drivers/amlogic/vrtc/aml_vrtc.c
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* drivers/amlogic/vrtc/aml_vrtc.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/amlogic/cpu_version.h>
|
||||
#include <linux/pm_wakeup.h>
|
||||
#include <linux/amlogic/scpi_protocol.h>
|
||||
|
||||
static void __iomem *alarm_reg_vaddr;
|
||||
static void __iomem *tel_reg_vaddr, *teh_reg_vaddr;
|
||||
static unsigned int vrtc_init_date;
|
||||
|
||||
#define TIME_LEN 10
|
||||
static int parse_init_date(const char *date)
|
||||
{
|
||||
char local_str[TIME_LEN + 1];
|
||||
char *year_s, *month_s, *day_s, *str;
|
||||
unsigned int year_d, month_d, day_d;
|
||||
int ret;
|
||||
|
||||
if (strlen(date) != 10)
|
||||
return -1;
|
||||
memset(local_str, 0, TIME_LEN + 1);
|
||||
strncpy(local_str, date, TIME_LEN);
|
||||
str = local_str;
|
||||
year_s = strsep(&str, "/");
|
||||
if (!year_s)
|
||||
return -1;
|
||||
month_s = strsep(&str, "/");
|
||||
if (!month_s)
|
||||
return -1;
|
||||
day_s = str;
|
||||
pr_debug("year: %s\nmonth: %s\nday: %s\n", year_s, month_s, day_s);
|
||||
ret = kstrtou32(year_s, 10, &year_d);
|
||||
if (ret < 0 || year_d > 2100 || year_d < 1900)
|
||||
return -1;
|
||||
ret = kstrtou32(month_s, 10, &month_d);
|
||||
if (ret < 0 || month_d > 12)
|
||||
return -1;
|
||||
ret = kstrtou32(day_s, 10, &day_d);
|
||||
if (ret < 0 || day_d > 31)
|
||||
return -1;
|
||||
vrtc_init_date = mktime(year_d, month_d, day_d, 0, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 read_te(void)
|
||||
{
|
||||
u32 time;
|
||||
unsigned long te = 0;
|
||||
|
||||
time = 0;
|
||||
|
||||
if (tel_reg_vaddr && teh_reg_vaddr) {
|
||||
te = readl(teh_reg_vaddr);
|
||||
te <<= 32;
|
||||
te += readl(tel_reg_vaddr);
|
||||
time = (u32)(te / 1000000);
|
||||
pr_debug("time_e: %us\n", time);
|
||||
time += vrtc_init_date;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
static int aml_vrtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
u32 time_t = read_te();
|
||||
|
||||
rtc_time_to_tm(time_t, tm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aml_rtc_write_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long time_t;
|
||||
unsigned long te = 0;
|
||||
unsigned int time;
|
||||
|
||||
rtc_tm_to_time(tm, &time_t);
|
||||
pr_debug("aml_rtc : write the rtc time, time is %ld\n", time_t);
|
||||
|
||||
time = 0;
|
||||
if (tel_reg_vaddr && teh_reg_vaddr) {
|
||||
te = readl(teh_reg_vaddr);
|
||||
te <<= 32;
|
||||
te += readl(tel_reg_vaddr);
|
||||
time = (u32)(te / 1000000);
|
||||
pr_debug("time_e: %us\n", time);
|
||||
}
|
||||
vrtc_init_date = (unsigned int)time_t - time;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_wakeup_time(unsigned long time)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (alarm_reg_vaddr) {
|
||||
writel(time, alarm_reg_vaddr);
|
||||
ret = 0;
|
||||
pr_debug("set_wakeup_time: %lu\n", time);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aml_vrtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
unsigned long alarm_secs, cur_secs;
|
||||
int ret;
|
||||
struct rtc_device *vrtc;
|
||||
|
||||
struct rtc_time cur_rtc_time;
|
||||
|
||||
vrtc = dev_get_drvdata(dev);
|
||||
|
||||
if (alarm->enabled) {
|
||||
ret = rtc_tm_to_time(&alarm->time, &alarm_secs);
|
||||
if (ret)
|
||||
return ret;
|
||||
aml_vrtc_read_time(dev, &cur_rtc_time);
|
||||
ret = rtc_tm_to_time(&cur_rtc_time, &cur_secs);
|
||||
if (alarm_secs >= cur_secs) {
|
||||
alarm_secs = alarm_secs - cur_secs;
|
||||
ret = set_wakeup_time(alarm_secs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
pr_debug("system will wakeup %lus later\n", alarm_secs);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops aml_vrtc_ops = {
|
||||
.read_time = aml_vrtc_read_time,
|
||||
.set_time = aml_rtc_write_time,
|
||||
.set_alarm = aml_vrtc_set_alarm,
|
||||
};
|
||||
|
||||
static int aml_vrtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *vrtc;
|
||||
int ret;
|
||||
u32 paddr = 0;
|
||||
const char *str;
|
||||
u32 vrtc_val;
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node,
|
||||
"alarm_reg_addr", &paddr);
|
||||
if (!ret) {
|
||||
pr_debug("alarm_reg_paddr: 0x%x\n", paddr);
|
||||
alarm_reg_vaddr = ioremap(paddr, 0x4);
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node,
|
||||
"timer_e_addr", &paddr);
|
||||
if (!ret) {
|
||||
pr_debug("timer_e_paddr: 0x%x\n", paddr);
|
||||
tel_reg_vaddr = ioremap(paddr, 0x4);
|
||||
teh_reg_vaddr = ioremap(paddr + 4, 0x4);
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node,
|
||||
"timer_e_addr", &paddr);
|
||||
ret = of_property_read_string(pdev->dev.of_node, "init_date", &str);
|
||||
if (!ret) {
|
||||
pr_debug("init_date: %s\n", str);
|
||||
if (!scpi_get_vrtc(&vrtc_val)) {
|
||||
vrtc_init_date = vrtc_val;
|
||||
pr_debug("get vrtc: %us\n", vrtc_init_date);
|
||||
} else
|
||||
parse_init_date(str);
|
||||
}
|
||||
device_init_wakeup(&pdev->dev, 1);
|
||||
vrtc = rtc_device_register("aml_vrtc", &pdev->dev,
|
||||
&aml_vrtc_ops, THIS_MODULE);
|
||||
if (!vrtc)
|
||||
return -1;
|
||||
platform_set_drvdata(pdev, vrtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aml_vrtc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct rtc_device *vrtc = platform_get_drvdata(dev);
|
||||
|
||||
rtc_device_unregister(vrtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aml_vrtc_resume(struct platform_device *pdev)
|
||||
{
|
||||
set_wakeup_time(0);
|
||||
|
||||
/* If timeE < 20, EE domain is thutdown, timerE is not
|
||||
* work during suspend. we need get vrtc value.
|
||||
*/
|
||||
if (read_te() < 20)
|
||||
if (!scpi_get_vrtc(&vrtc_init_date))
|
||||
pr_debug("get vrtc: %us\n", vrtc_init_date);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aml_vrtc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
u32 vrtc_val;
|
||||
|
||||
vrtc_val = read_te();
|
||||
|
||||
if (scpi_set_vrtc(vrtc_val))
|
||||
pr_debug("vrtc setting fail.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void aml_vrtc_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
u32 vrtc_val;
|
||||
struct timespec new_system;
|
||||
|
||||
vrtc_val = read_te();
|
||||
getnstimeofday(&new_system);
|
||||
|
||||
vrtc_val = (u32)new_system.tv_sec;
|
||||
|
||||
if (scpi_set_vrtc(vrtc_val))
|
||||
pr_debug("vrtc setting fail.\n");
|
||||
}
|
||||
|
||||
|
||||
static const struct of_device_id aml_vrtc_dt_match[] = {
|
||||
{ .compatible = "amlogic, aml_vrtc"},
|
||||
{},
|
||||
};
|
||||
|
||||
struct platform_driver aml_vrtc_driver = {
|
||||
.driver = {
|
||||
.name = "aml_vrtc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = aml_vrtc_dt_match,
|
||||
},
|
||||
.probe = aml_vrtc_probe,
|
||||
.remove = aml_vrtc_remove,
|
||||
.resume = aml_vrtc_resume,
|
||||
.suspend = aml_vrtc_suspend,
|
||||
.shutdown = aml_vrtc_shutdown,
|
||||
};
|
||||
|
||||
static int __init aml_vrtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&aml_vrtc_driver);
|
||||
}
|
||||
|
||||
static void __init aml_vrtc_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&aml_vrtc_driver);
|
||||
}
|
||||
|
||||
module_init(aml_vrtc_init);
|
||||
module_exit(aml_vrtc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Amlogic internal vrtc driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -87,4 +87,6 @@ struct scpi_dvfs_info *scpi_dvfs_get_opps(u8 domain);
|
||||
int scpi_get_sensor(char *name);
|
||||
int scpi_get_sensor_value(u16 sensor, u32 *val);
|
||||
int scpi_send_usr_data(u32 client_id, u32 *val, u32 size);
|
||||
int scpi_get_vrtc(u32 *p_vrtc);
|
||||
int scpi_set_vrtc(u32 vrtc_val);
|
||||
#endif /*_SCPI_PROTOCOL_H_*/
|
||||
|
||||
Reference in New Issue
Block a user