From 6ed1001786e90dd46d92d0cdf83606cd8b8a6530 Mon Sep 17 00:00:00 2001 From: Yun Cai Date: Tue, 14 Mar 2017 16:18:01 +0800 Subject: [PATCH] vrtc: add vrtc driver PD#138714: add amlogic vrtc driver Change-Id: I8aee09041d2d20f06113c1ac0ce310f0c8c68384 Signed-off-by: Yun Cai --- MAINTAINERS | 3 + arch/arm64/boot/dts/amlogic/mesongxl.dtsi | 8 + arch/arm64/boot/dts/amlogic/mesongxm.dtsi | 8 + arch/arm64/configs/meson64_defconfig | 1 + drivers/amlogic/Kconfig | 1 + drivers/amlogic/Makefile | 2 +- drivers/amlogic/mailbox/scpi_protocol.c | 35 +++ drivers/amlogic/vrtc/Kconfig | 11 + drivers/amlogic/vrtc/Makefile | 4 + drivers/amlogic/vrtc/aml_vrtc.c | 288 ++++++++++++++++++++++ include/linux/amlogic/scpi_protocol.h | 2 + 11 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 drivers/amlogic/vrtc/Kconfig create mode 100644 drivers/amlogic/vrtc/Makefile create mode 100644 drivers/amlogic/vrtc/aml_vrtc.c diff --git a/MAINTAINERS b/MAINTAINERS index 6c1397abb5fd..fc5a1ceb9228 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 +F: drivers/amlogic/vrtc/ diff --git a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi index b5c9a444ba97..a8bdc9489760 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi @@ -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"; + }; }; diff --git a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi index 9f20a26bf3fc..cca1415528c1 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi @@ -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"; + }; }; diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index d3b0bc01c8ed..44ce4b7c2caa 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -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 diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index 00426eb9f098..a7097a9632e0 100644 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -53,5 +53,6 @@ source "drivers/amlogic/media/Kconfig" source "drivers/amlogic/mmc/Kconfig" +source "drivers/amlogic/vrtc/Kconfig" endmenu endif diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index baf4d779abc2..4dfeb189d238 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -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/ diff --git a/drivers/amlogic/mailbox/scpi_protocol.c b/drivers/amlogic/mailbox/scpi_protocol.c index 8f00249691eb..744462059628 100644 --- a/drivers/amlogic/mailbox/scpi_protocol.c +++ b/drivers/amlogic/mailbox/scpi_protocol.c @@ -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); diff --git a/drivers/amlogic/vrtc/Kconfig b/drivers/amlogic/vrtc/Kconfig new file mode 100644 index 000000000000..9b40939a18b0 --- /dev/null +++ b/drivers/amlogic/vrtc/Kconfig @@ -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. + + diff --git a/drivers/amlogic/vrtc/Makefile b/drivers/amlogic/vrtc/Makefile new file mode 100644 index 000000000000..b002dff14c9b --- /dev/null +++ b/drivers/amlogic/vrtc/Makefile @@ -0,0 +1,4 @@ +# +#Makefile for the vrtc dirver +# +obj-$(CONFIG_AMLOGIC_VRTC) += aml_vrtc.o diff --git a/drivers/amlogic/vrtc/aml_vrtc.c b/drivers/amlogic/vrtc/aml_vrtc.c new file mode 100644 index 000000000000..60547821f93b --- /dev/null +++ b/drivers/amlogic/vrtc/aml_vrtc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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"); diff --git a/include/linux/amlogic/scpi_protocol.h b/include/linux/amlogic/scpi_protocol.h index 8b8f2d759a05..7395c2631c96 100644 --- a/include/linux/amlogic/scpi_protocol.h +++ b/include/linux/amlogic/scpi_protocol.h @@ -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_*/