Files
carbon 0545e9dc6d init version 2024-05-07
commit d1edce71135cc6d98c0a4b5729774542b676e769
Author: sophgo-forum-service <forum_service@sophgo.com>
Date:   Fri Mar 15 16:07:33 2024 +0800

    [fix] recommend using ssh method to clone repo.
    [fix] fix sensor driver repo branch name.
2024-05-07 19:36:36 +08:00

511 lines
14 KiB
C

/*
* An RTC driver for the CVITEK RTC.
*/
#include <linux/kernel.h>
#include <linux/bcd.h>
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/of.h>
#include <linux/version.h>
#include <asm/div64.h>
#include <linux/io.h>
/* CVITEK RTC registers */
#define CVI_RTC_ANA_CALIB 0x0
#define CVI_RTC_SEC_PULSE_GEN 0x4
#define CVI_RTC_ALARM_TIME 0x8
#define CVI_RTC_ALARM_ENABLE 0xC
#define CVI_RTC_SET_SEC_CNTR_VALUE 0x10
#define CVI_RTC_SET_SEC_CNTR_TRIG 0x14
#define CVI_RTC_SEC_CNTR_VALUE 0x18
#define CVI_RTC_APB_RDATA_SEL 0x3C
#define CVI_RTC_POR_DB_MAGIC_KEY 0x68
#define CVI_RTC_EN_PWR_WAKEUP 0xBC
#define CVI_RTC_PWR_DET_SEL 0x140
/* CVITEK RTC MACRO registers */
#define RTC_MACRO_DA_CLEAR_ALL 0x480
#define RTC_MACRO_DA_SOC_READY 0x48C
#define RTC_MACRO_RO_T 0x4A8
#define RTC_MACRO_RG_SET_T 0x498
/* CVITEK RTC CTRL registers */
#define CVI_RTC_FC_COARSE_EN 0x40
#define CVI_RTC_FC_COARSE_CAL 0x44
#define CVI_RTC_FC_FINE_EN 0x48
#define CVI_RTC_FC_FINE_CAL 0x50
#define RTC_SEC_MAX_VAL 0xFFFFFFFF
#define CVI_RTC_HANDLE_IRQ
#define CV_RTC_FINE_CALIB /* use rtc 32k calibration flow */
struct cvi_rtc_info {
struct platform_device *pdev;
struct rtc_device *rtc_dev;
void __iomem *rtc_base; /* NULL if not initialized. */
void __iomem *rtc_ctrl_base; /* NULL if not initialized. */
struct clk *clk;
int cvi_rtc_irq; /* alarm and periodic irq */
spinlock_t cvi_rtc_lock;
struct delayed_work cvi_rtc_work;
};
static int cvi_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
unsigned long sec;
unsigned long sec_ro_t;
unsigned long sl_irq_flags;
spin_lock_irqsave(&info->cvi_rtc_lock, sl_irq_flags);
sec = readl(info->rtc_base + CVI_RTC_SEC_CNTR_VALUE);
sec_ro_t = readl(info->rtc_base + RTC_MACRO_RO_T);
if (sec_ro_t > 0x30000000) {
sec = sec_ro_t;
// Writeback to SEC CVI_RTC_SEC_CNTR_VALUE
writel(sec, info->rtc_base + CVI_RTC_SET_SEC_CNTR_VALUE);
writel(1, info->rtc_base + CVI_RTC_SET_SEC_CNTR_TRIG);
} else if (sec < 0x30000000) {
dev_err(NULL, "RTC invalid time\n");
}
spin_unlock_irqrestore(&info->cvi_rtc_lock, sl_irq_flags);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
rtc_time64_to_tm(sec, tm);
#else
rtc_time_to_tm(sec, tm);
#endif
dev_vdbg(dev, "%s %lu\n", __func__, sec);
dev_notice(dev, "time read as %lu. %d/%d/%d %d:%02u:%02u\n",
sec,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec
);
return 0;
}
static int cvi_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
unsigned long sec;
int ret;
unsigned long sl_irq_flags;
/* convert tm to seconds. */
ret = rtc_valid_tm(tm);
if (ret)
return ret;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
sec = rtc_tm_to_time64(tm);
#else
rtc_tm_to_time(tm, &sec);
#endif
dev_vdbg(dev, "%s %lu\n", __func__, sec);
dev_notice(dev, "time set to %lu. %d/%d/%d %d:%02u:%02u\n",
sec,
tm->tm_mon+1,
tm->tm_mday,
tm->tm_year+1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec
);
spin_lock_irqsave(&info->cvi_rtc_lock, sl_irq_flags);
writel(sec, info->rtc_base + CVI_RTC_SET_SEC_CNTR_VALUE);
writel(1, info->rtc_base + CVI_RTC_SET_SEC_CNTR_TRIG);
writel(sec, info->rtc_base + RTC_MACRO_RG_SET_T);
writel(1, info->rtc_base + RTC_MACRO_DA_CLEAR_ALL);
writel(1, info->rtc_base + RTC_MACRO_DA_SOC_READY);
writel(0, info->rtc_base + RTC_MACRO_DA_CLEAR_ALL);
writel(0, info->rtc_base + RTC_MACRO_RG_SET_T);
writel(0, info->rtc_base + RTC_MACRO_DA_SOC_READY);
spin_unlock_irqrestore(&info->cvi_rtc_lock, sl_irq_flags);
return ret;
}
static int cvi_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
dev_notice(dev, "CVITEK current time %d\n", readl(info->rtc_base + CVI_RTC_SEC_CNTR_VALUE));
dev_notice(dev, "CVITEK current alarm %d\n", readl(info->rtc_base + CVI_RTC_ALARM_TIME));
alarm->enabled = readl(info->rtc_base + CVI_RTC_ALARM_ENABLE) & 0x1;
#if (KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE)
rtc_time64_to_tm(readl(info->rtc_base + CVI_RTC_ALARM_TIME), &alarm->time);
#else
rtc_time_to_tm(readl(info->rtc_base + CVI_RTC_ALARM_TIME), &alarm->time);
#endif
return 0;
}
static int cvi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
writel(enabled ? 0x1 : 0x0, info->rtc_base + CVI_RTC_ALARM_ENABLE);
return 0;
}
static int cvi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
unsigned long alarm_time;
uint32_t wakeup_src_mask;
alarm_time = rtc_tm_to_time64(&alarm->time);
dev_notice(dev, "CVITEK set alarm %lu\n", alarm_time);
dev_notice(dev, "CVITEK current time %d\n", readl(info->rtc_base + CVI_RTC_SEC_CNTR_VALUE));
if (alarm_time > RTC_SEC_MAX_VAL)
return -EINVAL;
writel(0x0, info->rtc_base + CVI_RTC_ALARM_ENABLE);
dev_notice(dev, "CVITEK CVI_RTC_ALARM_ENABLE %x\n", readl(info->rtc_base + CVI_RTC_ALARM_ENABLE));
udelay(200);
writel((u32)alarm_time, info->rtc_base + CVI_RTC_ALARM_TIME);
writel(0x1, info->rtc_base + CVI_RTC_APB_RDATA_SEL);
writel(0x1, info->rtc_base + CVI_RTC_ALARM_ENABLE);
wakeup_src_mask = readl(info->rtc_base + CVI_RTC_EN_PWR_WAKEUP) | 0x30;
writel(wakeup_src_mask, info->rtc_base + CVI_RTC_EN_PWR_WAKEUP);
readl(info->rtc_base + CVI_RTC_SEC_CNTR_VALUE);
dev_notice(dev, "CVITEK CVI_RTC_ALARM_ENABLE %x\n", readl(info->rtc_base + CVI_RTC_ALARM_ENABLE));
return 0;
}
static int cvi_rtc_proc(struct device *dev, struct seq_file *seq)
{
if (!dev || !dev->driver)
return 0;
seq_printf(seq, "name\t\t: %s\n", dev_name(dev));
return 0;
}
#if defined(CVI_RTC_HANDLE_IRQ)
static irqreturn_t cvi_rtc_irq_handler(int irq, void *data)
{
struct cvi_rtc_info *info = dev_get_drvdata((struct device *)data);
writel(0x0, info->rtc_base + CVI_RTC_ALARM_ENABLE);//Clear interrupt bit
schedule_delayed_work(&info->cvi_rtc_work, 0);
return IRQ_HANDLED;
}
static void cvi_rtc_irq_work(struct work_struct *work)
{
struct cvi_rtc_info *info = container_of(work, struct cvi_rtc_info, cvi_rtc_work.work);
struct rtc_device *rtc = info->rtc_dev;
struct rtc_wkalrm alrm;
/* Update the alarm_IRQ state in /proc */
rtc_read_alarm(rtc, &alrm);
alrm.enabled = 0;
rtc_set_alarm(rtc, &alrm);
}
#endif
static const struct rtc_class_ops cvi_rtc_ops = {
.read_time = cvi_rtc_read_time,
.set_time = cvi_rtc_set_time,
.read_alarm = cvi_rtc_read_alarm,
.set_alarm = cvi_rtc_set_alarm,
.proc = cvi_rtc_proc,
.alarm_irq_enable = cvi_rtc_alarm_irq_enable,
};
static const struct of_device_id cvi_rtc_dt_match[] = {
{ .compatible = "cvitek,rtc", },
{}
};
MODULE_DEVICE_TABLE(of, cvi_rtc_dt_match);
static void rtc_enable_sec_counter(struct cvi_rtc_info *info)
{
uint32_t value = 0;
value = readl(info->rtc_base + CVI_RTC_SEC_PULSE_GEN) & ~(1 << 31);
writel(value, info->rtc_base + CVI_RTC_SEC_PULSE_GEN);
value = readl(info->rtc_base + CVI_RTC_ANA_CALIB) & ~(1 << 31);
writel(value, info->rtc_base + CVI_RTC_ANA_CALIB);
readl(info->rtc_base + CVI_RTC_SEC_CNTR_VALUE);
writel(0x0, info->rtc_base + CVI_RTC_ALARM_ENABLE);
}
#if defined(CV_RTC_FINE_CALIB)
static void rtc_32k_coarse_value_calib(struct cvi_rtc_info *info)
{
uint32_t analog_calib_value = 0;
uint32_t fc_coarse_time1 = 0;
uint32_t fc_coarse_time2 = 0;
uint32_t fc_coarse_value = 0;
uint32_t offset = 128;
uint32_t value = 0;
writel(0x10100, info->rtc_base + CVI_RTC_ANA_CALIB);
udelay(200);
// Select 32K OSC tuning value source from rtc_sys
value = readl(info->rtc_base + CVI_RTC_SEC_PULSE_GEN) & ~(1 << 31);
writel(value, info->rtc_base + CVI_RTC_SEC_PULSE_GEN);
analog_calib_value = readl(info->rtc_base + CVI_RTC_ANA_CALIB);
// dev_notice(NULL, "RTC_ANA_CALIB: 0x%x\n", analog_calib_value);
writel(1, info->rtc_ctrl_base + CVI_RTC_FC_COARSE_EN);
while (1) {
fc_coarse_time1 = readl(info->rtc_ctrl_base + CVI_RTC_FC_COARSE_CAL);
fc_coarse_time1 >>= 16;
// dev_notice(NULL, "fc_coarse_time1 = 0x%x\n", fc_coarse_time1);
// dev_notice(NULL, "fc_coarse_time2 = 0x%x\n", fc_coarse_time2);
while (fc_coarse_time2 <= fc_coarse_time1) {
fc_coarse_time2 = readl(info->rtc_ctrl_base + CVI_RTC_FC_COARSE_CAL);
fc_coarse_time2 >>= 16;
// dev_notice(NULL, "fc_coarse_time2 = 0x%x\n", fc_coarse_time2);
}
udelay(400);
fc_coarse_value = readl(info->rtc_ctrl_base + CVI_RTC_FC_COARSE_CAL);
fc_coarse_value &= 0xFFFF;
// dev_notice(NULL, "fc_coarse_value = 0x%x\n", fc_coarse_value);
if (fc_coarse_value > 770) {
analog_calib_value += offset;
offset >>= 1;
writel(analog_calib_value, info->rtc_base + CVI_RTC_ANA_CALIB);
} else if (fc_coarse_value < 755) {
analog_calib_value -= offset;
offset >>= 1;
writel(analog_calib_value, info->rtc_base + CVI_RTC_ANA_CALIB);
} else {
writel(0, info->rtc_ctrl_base + CVI_RTC_FC_COARSE_EN);
// dev_notice(NULL, "RTC coarse calib done\n");
break;
}
if (offset == 0) {
dev_err(NULL, "RTC calib failed\n");
break;
}
// dev_notice(NULL, "RTC_ANA_CALIB: 0x%x\n", analog_calib_value);
}
}
static void rtc_32k_fine_value_calib(struct cvi_rtc_info *info)
{
uint32_t fc_fine_time1 = 0;
uint32_t fc_fine_time2 = 0;
uint32_t fc_fine_value = 0;
uint64_t freq = 256000000000;
uint32_t sec_cnt;
uint32_t frac_ext = 10000;
writel(1, info->rtc_ctrl_base + CVI_RTC_FC_FINE_EN);
fc_fine_time1 = readl(info->rtc_ctrl_base + CVI_RTC_FC_FINE_CAL);
fc_fine_time1 >>= 24;
// dev_notice(NULL, "fc_fine_time1 = 0x%x\n", fc_fine_time1);
while (fc_fine_time2 <= fc_fine_time1) {
fc_fine_time2 = readl(info->rtc_ctrl_base + CVI_RTC_FC_FINE_CAL);
fc_fine_time2 >>= 24;
// dev_notice(NULL, "fc_fine_time2 = 0x%x\n", fc_fine_time2);
}
fc_fine_value = readl(info->rtc_ctrl_base + CVI_RTC_FC_FINE_CAL);
fc_fine_value &= 0xFFFFFF;
// dev_notice(NULL, "fc_fine_value = 0x%x\n", fc_fine_value);
// Frequency = 256 / (RTC_FC_FINE_VALUE x 40ns)
// freq = 256000000000 / 40;
// freq = (freq * frac_ext) / fc_fine_value;
// dev_notice(NULL, "freq = %u\n", freq);
// sec_cnt = ((freq / frac_ext) << 8) + (((freq % frac_ext) * 256) / frac_ext & 0xFF);
// dev_notice(NULL, "sec_cnt = 0x%x\n", sec_cnt);
/* Fix a data size overflow error. From jinyu.zhao */
do_div(freq, 40);
freq = freq * frac_ext;
do_div(freq, fc_fine_value);
sec_cnt = ((do_div(freq, frac_ext) * 256) / frac_ext & 0xFF) + (freq << 8);
writel(sec_cnt, info->rtc_base + CVI_RTC_SEC_PULSE_GEN);
writel(0, info->rtc_ctrl_base + CVI_RTC_FC_FINE_EN);
}
#endif
static int __init cvi_rtc_probe(struct platform_device *pdev)
{
struct cvi_rtc_info *info;
struct resource *res;
int ret;
info = devm_kzalloc(&pdev->dev, sizeof(struct cvi_rtc_info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->rtc_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->rtc_base))
return PTR_ERR(info->rtc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
info->rtc_ctrl_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->rtc_ctrl_base))
return PTR_ERR(info->rtc_ctrl_base);
info->cvi_rtc_irq = platform_get_irq(pdev, 0);
if (info->cvi_rtc_irq <= 0)
return -EBUSY;
info->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(info->clk))
return PTR_ERR(info->clk);
ret = clk_prepare_enable(info->clk);
if (ret < 0)
return ret;
/* set context info. */
info->pdev = pdev;
spin_lock_init(&info->cvi_rtc_lock);
platform_set_drvdata(pdev, info);
device_init_wakeup(&pdev->dev, 1);
info->rtc_dev = devm_rtc_device_register(&pdev->dev,
dev_name(&pdev->dev), &cvi_rtc_ops,
THIS_MODULE);
if (IS_ERR(info->rtc_dev)) {
ret = PTR_ERR(info->rtc_dev);
dev_err(&pdev->dev, "Unable to register device (err=%d).\n",
ret);
goto disable_clk;
}
#if defined(CVI_RTC_HANDLE_IRQ)
ret = devm_request_irq(&pdev->dev, info->cvi_rtc_irq,
cvi_rtc_irq_handler, IRQF_TRIGGER_HIGH,
dev_name(&pdev->dev), &pdev->dev);
if (ret) {
dev_err(&pdev->dev,
"Unable to request interrupt for device (err=%d).\n",
ret);
goto disable_clk;
}
INIT_DELAYED_WORK(&info->cvi_rtc_work, cvi_rtc_irq_work);
#endif
#if defined(CV_RTC_FINE_CALIB)
if ((readl(info->rtc_ctrl_base + 0x8) & 0x400) == 0x0) {
/* Enable calibration only when use internal osc */
rtc_32k_coarse_value_calib(info);
rtc_32k_fine_value_calib(info);
dev_notice(&pdev->dev, "rtc 32k calibration has been completed\n");
} else {
dev_notice(&pdev->dev, "Disable calibration because using external xtal\n");
}
#endif
rtc_enable_sec_counter(info);
dev_notice(&pdev->dev, "CVITEK real time clock\n");
return 0;
disable_clk:
clk_disable_unprepare(info->clk);
return ret;
}
static int cvi_rtc_remove(struct platform_device *pdev)
{
struct cvi_rtc_info *info = platform_get_drvdata(pdev);
clk_disable_unprepare(info->clk);
#if defined(CVI_RTC_HANDLE_IRQ)
cancel_delayed_work(&info->cvi_rtc_work);
#endif
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int cvi_rtc_suspend(struct device *dev)
{
return 0;
}
static int cvi_rtc_resume(struct device *dev)
{
struct cvi_rtc_info *info = dev_get_drvdata(dev);
writel(0x0, info->rtc_base + CVI_RTC_ALARM_ENABLE);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(cvi_rtc_pm_ops, cvi_rtc_suspend, cvi_rtc_resume);
static void cvi_rtc_shutdown(struct platform_device *pdev)
{
dev_vdbg(&pdev->dev, "disabling interrupts.\n");
cvi_rtc_alarm_irq_enable(&pdev->dev, 0);
}
MODULE_ALIAS("platform:cvi_rtc");
static struct platform_driver cvi_rtc_driver = {
.remove = cvi_rtc_remove,
.shutdown = cvi_rtc_shutdown,
.driver = {
.name = "cvi_rtc",
.of_match_table = cvi_rtc_dt_match,
.pm = &cvi_rtc_pm_ops,
},
};
module_platform_driver_probe(cvi_rtc_driver, cvi_rtc_probe);
MODULE_AUTHOR("Mark Hsieh <mark.hsieh@cvitek.com>");
MODULE_DESCRIPTION("driver for CVITEK RTC");
MODULE_LICENSE("GPL");