Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Jan Kotas | 2015 | 99.95% | 1 | 50.00% |
Alexandre Belloni | 1 | 0.05% | 1 | 50.00% |
Total | 2016 | 2 |
// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2019 Cadence * * Authors: * Jan Kotas <jank@cadence.com> */ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/io.h> #include <linux/rtc.h> #include <linux/clk.h> #include <linux/bcd.h> #include <linux/bitfield.h> #include <linux/interrupt.h> #include <linux/pm_wakeirq.h> /* Registers */ #define CDNS_RTC_CTLR 0x00 #define CDNS_RTC_HMR 0x04 #define CDNS_RTC_TIMR 0x08 #define CDNS_RTC_CALR 0x0C #define CDNS_RTC_TIMAR 0x10 #define CDNS_RTC_CALAR 0x14 #define CDNS_RTC_AENR 0x18 #define CDNS_RTC_EFLR 0x1C #define CDNS_RTC_IENR 0x20 #define CDNS_RTC_IDISR 0x24 #define CDNS_RTC_IMSKR 0x28 #define CDNS_RTC_STSR 0x2C #define CDNS_RTC_KRTCR 0x30 /* Control */ #define CDNS_RTC_CTLR_TIME BIT(0) #define CDNS_RTC_CTLR_CAL BIT(1) #define CDNS_RTC_CTLR_TIME_CAL (CDNS_RTC_CTLR_TIME | CDNS_RTC_CTLR_CAL) /* Status */ #define CDNS_RTC_STSR_VT BIT(0) #define CDNS_RTC_STSR_VC BIT(1) #define CDNS_RTC_STSR_VTA BIT(2) #define CDNS_RTC_STSR_VCA BIT(3) #define CDNS_RTC_STSR_VT_VC (CDNS_RTC_STSR_VT | CDNS_RTC_STSR_VC) #define CDNS_RTC_STSR_VTA_VCA (CDNS_RTC_STSR_VTA | CDNS_RTC_STSR_VCA) /* Keep RTC */ #define CDNS_RTC_KRTCR_KRTC BIT(0) /* Alarm, Event, Interrupt */ #define CDNS_RTC_AEI_HOS BIT(0) #define CDNS_RTC_AEI_SEC BIT(1) #define CDNS_RTC_AEI_MIN BIT(2) #define CDNS_RTC_AEI_HOUR BIT(3) #define CDNS_RTC_AEI_DATE BIT(4) #define CDNS_RTC_AEI_MNTH BIT(5) #define CDNS_RTC_AEI_ALRM BIT(6) /* Time */ #define CDNS_RTC_TIME_H GENMASK(7, 0) #define CDNS_RTC_TIME_S GENMASK(14, 8) #define CDNS_RTC_TIME_M GENMASK(22, 16) #define CDNS_RTC_TIME_HR GENMASK(29, 24) #define CDNS_RTC_TIME_PM BIT(30) #define CDNS_RTC_TIME_CH BIT(31) /* Calendar */ #define CDNS_RTC_CAL_DAY GENMASK(2, 0) #define CDNS_RTC_CAL_M GENMASK(7, 3) #define CDNS_RTC_CAL_D GENMASK(13, 8) #define CDNS_RTC_CAL_Y GENMASK(23, 16) #define CDNS_RTC_CAL_C GENMASK(29, 24) #define CDNS_RTC_CAL_CH BIT(31) #define CDNS_RTC_MAX_REGS_TRIES 3 struct cdns_rtc { struct rtc_device *rtc_dev; struct clk *pclk; struct clk *ref_clk; void __iomem *regs; int irq; }; static void cdns_rtc_set_enabled(struct cdns_rtc *crtc, bool enabled) { u32 reg = enabled ? 0x0 : CDNS_RTC_CTLR_TIME_CAL; writel(reg, crtc->regs + CDNS_RTC_CTLR); } static bool cdns_rtc_get_enabled(struct cdns_rtc *crtc) { return !(readl(crtc->regs + CDNS_RTC_CTLR) & CDNS_RTC_CTLR_TIME_CAL); } static irqreturn_t cdns_rtc_irq_handler(int irq, void *id) { struct device *dev = id; struct cdns_rtc *crtc = dev_get_drvdata(dev); /* Reading the register clears it */ if (!(readl(crtc->regs + CDNS_RTC_EFLR) & CDNS_RTC_AEI_ALRM)) return IRQ_NONE; rtc_update_irq(crtc->rtc_dev, 1, RTC_IRQF | RTC_AF); return IRQ_HANDLED; } static u32 cdns_rtc_time2reg(struct rtc_time *tm) { return FIELD_PREP(CDNS_RTC_TIME_S, bin2bcd(tm->tm_sec)) | FIELD_PREP(CDNS_RTC_TIME_M, bin2bcd(tm->tm_min)) | FIELD_PREP(CDNS_RTC_TIME_HR, bin2bcd(tm->tm_hour)); } static void cdns_rtc_reg2time(u32 reg, struct rtc_time *tm) { tm->tm_sec = bcd2bin(FIELD_GET(CDNS_RTC_TIME_S, reg)); tm->tm_min = bcd2bin(FIELD_GET(CDNS_RTC_TIME_M, reg)); tm->tm_hour = bcd2bin(FIELD_GET(CDNS_RTC_TIME_HR, reg)); } static int cdns_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct cdns_rtc *crtc = dev_get_drvdata(dev); u32 reg; /* If the RTC is disabled, assume the values are invalid */ if (!cdns_rtc_get_enabled(crtc)) return -EINVAL; cdns_rtc_set_enabled(crtc, false); reg = readl(crtc->regs + CDNS_RTC_TIMR); cdns_rtc_reg2time(reg, tm); reg = readl(crtc->regs + CDNS_RTC_CALR); tm->tm_mday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_D, reg)); tm->tm_mon = bcd2bin(FIELD_GET(CDNS_RTC_CAL_M, reg)) - 1; tm->tm_year = bcd2bin(FIELD_GET(CDNS_RTC_CAL_Y, reg)) + bcd2bin(FIELD_GET(CDNS_RTC_CAL_C, reg)) * 100 - 1900; tm->tm_wday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_DAY, reg)) - 1; cdns_rtc_set_enabled(crtc, true); return 0; } static int cdns_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct cdns_rtc *crtc = dev_get_drvdata(dev); u32 timr, calr, stsr; int ret = -EIO; int year = tm->tm_year + 1900; int tries; cdns_rtc_set_enabled(crtc, false); timr = cdns_rtc_time2reg(tm); calr = FIELD_PREP(CDNS_RTC_CAL_D, bin2bcd(tm->tm_mday)) | FIELD_PREP(CDNS_RTC_CAL_M, bin2bcd(tm->tm_mon + 1)) | FIELD_PREP(CDNS_RTC_CAL_Y, bin2bcd(year % 100)) | FIELD_PREP(CDNS_RTC_CAL_C, bin2bcd(year / 100)) | FIELD_PREP(CDNS_RTC_CAL_DAY, tm->tm_wday + 1); /* Update registers, check valid flags */ for (tries = 0; tries < CDNS_RTC_MAX_REGS_TRIES; tries++) { writel(timr, crtc->regs + CDNS_RTC_TIMR); writel(calr, crtc->regs + CDNS_RTC_CALR); stsr = readl(crtc->regs + CDNS_RTC_STSR); if ((stsr & CDNS_RTC_STSR_VT_VC) == CDNS_RTC_STSR_VT_VC) { ret = 0; break; } } cdns_rtc_set_enabled(crtc, true); return ret; } static int cdns_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct cdns_rtc *crtc = dev_get_drvdata(dev); if (enabled) { writel((CDNS_RTC_AEI_SEC | CDNS_RTC_AEI_MIN | CDNS_RTC_AEI_HOUR | CDNS_RTC_AEI_DATE | CDNS_RTC_AEI_MNTH), crtc->regs + CDNS_RTC_AENR); writel(CDNS_RTC_AEI_ALRM, crtc->regs + CDNS_RTC_IENR); } else { writel(0, crtc->regs + CDNS_RTC_AENR); writel(CDNS_RTC_AEI_ALRM, crtc->regs + CDNS_RTC_IDISR); } return 0; } static int cdns_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct cdns_rtc *crtc = dev_get_drvdata(dev); u32 reg; reg = readl(crtc->regs + CDNS_RTC_TIMAR); cdns_rtc_reg2time(reg, &alarm->time); reg = readl(crtc->regs + CDNS_RTC_CALAR); alarm->time.tm_mday = bcd2bin(FIELD_GET(CDNS_RTC_CAL_D, reg)); alarm->time.tm_mon = bcd2bin(FIELD_GET(CDNS_RTC_CAL_M, reg)) - 1; return 0; } static int cdns_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct cdns_rtc *crtc = dev_get_drvdata(dev); int ret = -EIO; int tries; u32 timar, calar, stsr; cdns_rtc_alarm_irq_enable(dev, 0); timar = cdns_rtc_time2reg(&alarm->time); calar = FIELD_PREP(CDNS_RTC_CAL_D, bin2bcd(alarm->time.tm_mday)) | FIELD_PREP(CDNS_RTC_CAL_M, bin2bcd(alarm->time.tm_mon + 1)); /* Update registers, check valid alarm flags */ for (tries = 0; tries < CDNS_RTC_MAX_REGS_TRIES; tries++) { writel(timar, crtc->regs + CDNS_RTC_TIMAR); writel(calar, crtc->regs + CDNS_RTC_CALAR); stsr = readl(crtc->regs + CDNS_RTC_STSR); if ((stsr & CDNS_RTC_STSR_VTA_VCA) == CDNS_RTC_STSR_VTA_VCA) { ret = 0; break; } } if (!ret) cdns_rtc_alarm_irq_enable(dev, alarm->enabled); return ret; } static const struct rtc_class_ops cdns_rtc_ops = { .read_time = cdns_rtc_read_time, .set_time = cdns_rtc_set_time, .read_alarm = cdns_rtc_read_alarm, .set_alarm = cdns_rtc_set_alarm, .alarm_irq_enable = cdns_rtc_alarm_irq_enable, }; static int cdns_rtc_probe(struct platform_device *pdev) { struct cdns_rtc *crtc; struct resource *res; int ret; unsigned long ref_clk_freq; crtc = devm_kzalloc(&pdev->dev, sizeof(*crtc), GFP_KERNEL); if (!crtc) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); crtc->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(crtc->regs)) return PTR_ERR(crtc->regs); crtc->irq = platform_get_irq(pdev, 0); if (crtc->irq < 0) return -EINVAL; crtc->pclk = devm_clk_get(&pdev->dev, "pclk"); if (IS_ERR(crtc->pclk)) { ret = PTR_ERR(crtc->pclk); dev_err(&pdev->dev, "Failed to retrieve the peripheral clock, %d\n", ret); return ret; } crtc->ref_clk = devm_clk_get(&pdev->dev, "ref_clk"); if (IS_ERR(crtc->ref_clk)) { ret = PTR_ERR(crtc->ref_clk); dev_err(&pdev->dev, "Failed to retrieve the reference clock, %d\n", ret); return ret; } crtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(crtc->rtc_dev)) return PTR_ERR(crtc->rtc_dev); platform_set_drvdata(pdev, crtc); ret = clk_prepare_enable(crtc->pclk); if (ret) { dev_err(&pdev->dev, "Failed to enable the peripheral clock, %d\n", ret); return ret; } ret = clk_prepare_enable(crtc->ref_clk); if (ret) { dev_err(&pdev->dev, "Failed to enable the reference clock, %d\n", ret); goto err_disable_pclk; } ref_clk_freq = clk_get_rate(crtc->ref_clk); if ((ref_clk_freq != 1) && (ref_clk_freq != 100)) { dev_err(&pdev->dev, "Invalid reference clock frequency %lu Hz.\n", ref_clk_freq); ret = -EINVAL; goto err_disable_ref_clk; } ret = devm_request_irq(&pdev->dev, crtc->irq, cdns_rtc_irq_handler, 0, dev_name(&pdev->dev), &pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to request interrupt for the device, %d\n", ret); goto err_disable_ref_clk; } /* The RTC supports 01.01.1900 - 31.12.2999 */ crtc->rtc_dev->range_min = mktime64(1900, 1, 1, 0, 0, 0); crtc->rtc_dev->range_max = mktime64(2999, 12, 31, 23, 59, 59); crtc->rtc_dev->ops = &cdns_rtc_ops; device_init_wakeup(&pdev->dev, true); /* Always use 24-hour mode and keep the RTC values */ writel(0, crtc->regs + CDNS_RTC_HMR); writel(CDNS_RTC_KRTCR_KRTC, crtc->regs + CDNS_RTC_KRTCR); ret = rtc_register_device(crtc->rtc_dev); if (ret) goto err_disable_wakeup; return 0; err_disable_wakeup: device_init_wakeup(&pdev->dev, false); err_disable_ref_clk: clk_disable_unprepare(crtc->ref_clk); err_disable_pclk: clk_disable_unprepare(crtc->pclk); return ret; } static int cdns_rtc_remove(struct platform_device *pdev) { struct cdns_rtc *crtc = platform_get_drvdata(pdev); cdns_rtc_alarm_irq_enable(&pdev->dev, 0); device_init_wakeup(&pdev->dev, 0); clk_disable_unprepare(crtc->pclk); clk_disable_unprepare(crtc->ref_clk); return 0; } #ifdef CONFIG_PM_SLEEP static int cdns_rtc_suspend(struct device *dev) { struct cdns_rtc *crtc = dev_get_drvdata(dev); if (device_may_wakeup(dev)) enable_irq_wake(crtc->irq); return 0; } static int cdns_rtc_resume(struct device *dev) { struct cdns_rtc *crtc = dev_get_drvdata(dev); if (device_may_wakeup(dev)) disable_irq_wake(crtc->irq); return 0; } #endif static SIMPLE_DEV_PM_OPS(cdns_rtc_pm_ops, cdns_rtc_suspend, cdns_rtc_resume); static const struct of_device_id cdns_rtc_of_match[] = { { .compatible = "cdns,rtc-r109v3" }, { }, }; MODULE_DEVICE_TABLE(of, cdns_rtc_of_match); static struct platform_driver cdns_rtc_driver = { .driver = { .name = "cdns-rtc", .of_match_table = cdns_rtc_of_match, .pm = &cdns_rtc_pm_ops, }, .probe = cdns_rtc_probe, .remove = cdns_rtc_remove, }; module_platform_driver(cdns_rtc_driver); MODULE_AUTHOR("Jan Kotas <jank@cadence.com>"); MODULE_DESCRIPTION("Cadence RTC driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:cdns-rtc");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1