nt9856x/BSP/linux-kernel/drivers/rtc/rtc-na51055-drtc.c
2023-05-17 15:33:39 +08:00

483 lines
11 KiB
C
Executable File

/*
* driver/rtc/rtc-na51055-dtrc.c
*
* Author: howard_chang@novatek.com.tw
* Created: Feb 26, 2019
* Copyright: Novatek Inc.
*
*/
#include <linux/module.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <mach/nvt-io.h>
#include <plat/drtc_reg.h>
#include <plat/drtc_int.h>
#include <plat/hardware.h>
#include <linux/clk.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <linux/delay.h>
#include <linux/of.h>
#define DRV_VERSION "1.00.000"
static struct semaphore drtc_sem;
static unsigned int _REGIOBASE;
#define loc_cpu() down(&drtc_sem);
#define unl_cpu() up(&drtc_sem);
static const unsigned short drtc_ydays[2][13] = {
/* Normal years */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};
struct nvt_drtc_priv {
struct rtc_device *rtc;
struct clk *clk;
};
static void drtc_setreg(uint32_t offset, REGVALUE value)
{
nvt_writel(value, _REGIOBASE + offset);
}
static REGVALUE drtc_getreg(uint32_t offset)
{
return nvt_readl(_REGIOBASE + offset);
}
/*
//add by Y_S WU 2014.5.22 CTS default time
static struct rtc_time default_tm = {
.tm_year = (2014 - 1900), // year 2014
.tm_mon = 1, // month 2
.tm_mday = 5, // day 5
.tm_hour = 12,
.tm_min = 0,
.tm_sec = 0
};
*/
static void nvt_drtc_enable_configuration(void)
{
uint32_t count = 0;
union DRTC_CTRL_REG ctrl_reg;
ctrl_reg.reg = drtc_getreg(DRTC_CTRL_REG_OFS);
ctrl_reg.bit.time_en = 0;
drtc_setreg(DRTC_CTRL_REG_OFS, ctrl_reg.reg);
/* check hardware stop */
do {
ctrl_reg.reg = drtc_getreg(DRTC_CTRL_REG_OFS);
if (ctrl_reg.bit.time_en)
usleep_range(10, 100);
else
break;
} while(count++ < 100);
if (ctrl_reg.bit.time_en)
pr_err("time counter stop timeout!\n");
ctrl_reg.bit.time_en = 1;
drtc_setreg(DRTC_CTRL_REG_OFS, ctrl_reg.reg);
}
#ifdef CONFIG_RTC_INTF_DEV
static int nvt_drtc_ioctl(struct device *dev, unsigned int cmd, \
unsigned long arg)
{
/*
switch(cmd) {
case RTC_AIE_ON:
pr_info("RTC_AIE_ON\n");
break;
case RTC_AIE_OFF:
pr_info("RTC_AIE_OFF\n");
break;
case RTC_UIE_ON:
pr_info("RTC_UIE_ON\n");
break;
case RTC_UIE_OFF:
pr_info("RTC_UIE_OFF\n");
break;
case RTC_WIE_ON:
pr_info("RTC_WIE_ON\n");
break;
case RTC_WIE_OFF:
pr_info("RTC_WIE_OFF\n");
break;
case RTC_ALM_SET:
pr_info("RTC_ALM_SET\n");
break;
case RTC_ALM_READ:
pr_info("RTC_ALM_READ\n");
break;
case RTC_RD_TIME:
pr_info("RTC_RD_TIME\n");
break;
case RTC_SET_TIME:
pr_info("RTC_SET_TIME\n");
break;
case RTC_IRQP_READ:
pr_info("RTC_IRQP_READ\n");
break;
case RTC_IRQP_SET:
pr_info("RTC_IRQP_SET\n");
break;
case RTC_EPOCH_READ:
pr_info("RTC_EPOCH_READ\n");
break;
case RTC_EPOCH_SET:
pr_info("RTC_EPOCH_SET\n");
break;
case RTC_WKALM_SET:
pr_info("RTC_WKALM_SET\n");
break;
case RTC_WKALM_RD:
pr_info("RTC_WKALM_RD\n");
break;
case RTC_PLL_SET:
pr_info("RTC_PLL_SET\n");
break;
case RTC_PLL_GET:
pr_info("RTC_PLL_GET\n");
break;
default:
pr_info("unknown rtc ioctl :0X%X\n",cmd);
}
*/
return 0;
}
#else
#define nvt_drtc_ioctl NULL
#endif
#ifdef CONFIG_PROC_FS
static int nvt_drtc_proc(struct device *dev, struct seq_file *seq)
{
return 0;
}
#else
#define nvt_drtc_proc NULL
#endif
static int nvt_drtc_read_time(struct device *dev, struct rtc_time *tm)
{
uint32_t days, months, years, month_days;
union DRTC_TIMER_REG timer_reg;
union DRTC_DAY_REG day_reg;
timer_reg.reg = drtc_getreg(DRTC_TIMER_REG_OFS);
day_reg.reg = drtc_getreg(DRTC_DAY_REG_OFS);
days = day_reg.bit.day;
for (years = 0; days >= drtc_ydays[is_leap_year(years + 1900)][12]; \
years++) {
days -= drtc_ydays[is_leap_year(years + 1900)][12];
}
for (months = 1; months < 13; months++) {
if (days <= drtc_ydays[is_leap_year(years + 1900)][months]) {
days -= drtc_ydays[is_leap_year(years + 1900)][months-1];
months--;
break;
}
}
month_days = drtc_ydays[is_leap_year(years + 1900)][months+1] - \
drtc_ydays[is_leap_year(years + 1900)][months];
if (days == month_days) {
months++;
days = 1;
} else
days++; /*Align linux time format*/
tm->tm_sec = timer_reg.bit.sec;
tm->tm_min = timer_reg.bit.min;
tm->tm_hour = timer_reg.bit.hour;
tm->tm_mday = days;
tm->tm_mon = months;
tm->tm_year = years;
pr_debug("after read time: sec = %d, min = %d, hour = %d, mday = %d," \
"mon = %d, year = %d, wday = %d, yday = %d," \
"\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \
tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday);
return rtc_valid_tm(tm);
}
static int nvt_drtc_set_time(struct device *dev, struct rtc_time *tm)
{
int year_looper, ret;
uint32_t days = 0;
union DRTC_TIMER_REG timer_reg;
union DRTC_DAY_REG day_reg;
pr_debug("kernel set time: sec = %d, min = %d, hour = %d, mday = %d," \
"mon = %d, year = %d, wday = %d, yday = %d," \
"\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \
tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday);
ret = rtc_valid_tm(tm);
if (ret < 0)
return ret;
for (year_looper = 0; year_looper < tm->tm_year; year_looper++)
days += drtc_ydays[is_leap_year(year_looper + 1900)][12];
days += drtc_ydays[is_leap_year(year_looper + 1900)][tm->tm_mon];
tm->tm_mday--; /*subtract the day which is not ended*/
days += tm->tm_mday;
loc_cpu();
timer_reg.reg = 0;
timer_reg.bit.sec = tm->tm_sec;
timer_reg.bit.min = tm->tm_min;
timer_reg.bit.hour = tm->tm_hour;
drtc_setreg(DRTC_TIMER_REG_OFS, timer_reg.reg);
day_reg.reg = drtc_getreg(DRTC_DAY_REG_OFS);
day_reg.bit.day = days;
drtc_setreg(DRTC_DAY_REG_OFS, day_reg.reg);
nvt_drtc_enable_configuration();
unl_cpu();
return ret;
}
static int nvt_drtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct rtc_time *tm = &alrm->time;
uint32_t days, months, years, month_days;
union DRTC_ALARM_TIMER_REG alm_timer_reg;
union DRTC_ALARM_DAY_REG alm_day_reg;
alm_timer_reg.reg = drtc_getreg(DRTC_ALARM_TIMER_REG_OFS);
alm_day_reg.reg = drtc_getreg(DRTC_ALARM_DAY_REG_OFS);
days = alm_day_reg.bit.day;
for (years = 0; days >= drtc_ydays[is_leap_year(years + 1900)][12]; \
years++) {
days -= drtc_ydays[is_leap_year(years + 1900)][12];
}
for (months = 1; months < 13; months++) {
if (days <= drtc_ydays[is_leap_year(years + 1900)][months]) {
days -= drtc_ydays[is_leap_year(years + 1900)][months-1];
months--;
break;
}
}
month_days = drtc_ydays[is_leap_year(years + 1900)][months+1] - \
drtc_ydays[is_leap_year(years + 1900)][months];
if (days == month_days) {
months++;
days = 1;
} else
days++; /*Align linux time format*/
tm->tm_sec = alm_timer_reg.bit.sec;
tm->tm_min = alm_timer_reg.bit.min;
tm->tm_hour = alm_timer_reg.bit.hour;
tm->tm_mday = days;
tm->tm_mon = months;
tm->tm_year = years;
pr_debug("read alarm time: sec = %d, min = %d, hour = %d, mday = %d," \
"mon = %d, year = %d, wday = %d, yday = %d," \
"\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \
tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday);
return rtc_valid_tm(tm);
}
static int nvt_drtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct rtc_time *tm = &alrm->time;
int year_looper, ret;
uint32_t current_days = 0, alarm_days = 0;
union DRTC_ALARM_TIMER_REG alm_timer_reg;
union DRTC_ALARM_DAY_REG alm_day_reg;
union DRTC_DAY_REG day_reg;
day_reg.reg = drtc_getreg(DRTC_DAY_REG_OFS);
current_days = day_reg.bit.day;
pr_debug("set alarm time: sec = %d, min = %d, hour = %d, mday = %d," \
"mon = %d, year = %d, wday = %d, yday = %d," \
"\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \
tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday);
ret = rtc_valid_tm(tm);
if (ret < 0)
return ret;
for (year_looper = 0; year_looper < tm->tm_year; year_looper++)
alarm_days += drtc_ydays[is_leap_year(year_looper + 1900)][12];
alarm_days += drtc_ydays[is_leap_year(year_looper + 1900)][tm->tm_mon];
tm->tm_mday--; /*subtract the day which is not ended*/
alarm_days += tm->tm_mday;
/*Check date parameter for maximum register setting*/
if (alarm_days < current_days) {
pr_err("Invalid parameter!\n");
return E_PAR;
}
alm_timer_reg.reg = 0;
alm_timer_reg.bit.sec = tm->tm_sec;
alm_timer_reg.bit.min = tm->tm_min;
alm_timer_reg.bit.hour = tm->tm_hour;
drtc_setreg(DRTC_ALARM_TIMER_REG_OFS, alm_timer_reg.reg);
alm_day_reg.reg = 0;
alm_day_reg.bit.day = alarm_days;
drtc_setreg(DRTC_ALARM_DAY_REG_OFS, alm_day_reg.reg);
unl_cpu();
return ret;
}
static const struct rtc_class_ops nvt_drtc_ops = {
.ioctl = nvt_drtc_ioctl,
.proc = nvt_drtc_proc,
.read_time = nvt_drtc_read_time,
.set_time = nvt_drtc_set_time,
.read_alarm = nvt_drtc_read_alarm,
.set_alarm = nvt_drtc_set_alarm,
};
static int nvt_drtc_probe(struct platform_device *pdev)
{
struct rtc_device *drtc;
struct nvt_drtc_priv *priv;
struct resource *memres = NULL;
int ret = 0;
/* setup resource */
memres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (unlikely(!memres)) {
dev_err(&pdev->dev, "failed to get resource\n");
return -ENXIO;
}
_REGIOBASE = (u32) devm_ioremap_resource(&pdev->dev, memres);;
if (unlikely(_REGIOBASE == 0)) {
dev_err(&pdev->dev, "failed to get io memory\n");
goto out;
}
priv = kzalloc(sizeof(struct nvt_drtc_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
device_init_wakeup(&pdev->dev, 1);
drtc = rtc_device_register("nvt_drtc", &pdev->dev,
&nvt_drtc_ops, THIS_MODULE);
if (IS_ERR(drtc)) {
ret = PTR_ERR(drtc);
goto out;
}
priv->rtc = drtc;
#ifdef NVT_DRTC_CLK_SUPPORT
/* resource will auto free when device detached */
priv->clk = devm_clk_get(&pdev->dev, dev_name(&pdev->dev));
if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "failed to find drtc clock\n");
return PTR_ERR(priv->clk);
}
clk_prepare_enable(priv->clk);
#endif
sema_init(&drtc_sem, 1);
return 0;
out:
if (priv->rtc)
rtc_device_unregister(priv->rtc);
kfree(priv);
return ret;
}
static int nvt_drtc_remove(struct platform_device *pdev)
{
struct nvt_drtc_priv *priv = platform_get_drvdata(pdev);
rtc_device_unregister(priv->rtc);
kfree(priv);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id nvt_drtc_of_dt_ids[] = {
{ .compatible = "nvt,nvt_drtc", },
{},
};
#endif
static struct platform_driver nvt_drtc_platform_driver = {
.driver = {
.name = "nvt_drtc",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = nvt_drtc_of_dt_ids,
#endif
},
.probe = nvt_drtc_probe,
.remove = nvt_drtc_remove,
};
static int __init nvt_drtc_init(void)
{
int ret;
ret = platform_driver_register(&nvt_drtc_platform_driver);
return ret;
}
static void __exit nvt_drtc_exit(void)
{
platform_driver_unregister(&nvt_drtc_platform_driver);
}
MODULE_AUTHOR("Novatek");
MODULE_DESCRIPTION("nvt DRTC driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
MODULE_ALIAS("platform:drtc-nvt");
module_init(nvt_drtc_init);
module_exit(nvt_drtc_exit);