|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
| 2 | +/* |
| 3 | + * Renesas RZ/N1 Real Time Clock interface for Linux |
| 4 | + * |
| 5 | + * Copyright: |
| 6 | + * - 2014 Renesas Electronics Europe Limited |
| 7 | + * - 2022 Schneider Electric |
| 8 | + * |
| 9 | + * Authors: |
| 10 | + |
| 11 | + * - Miquel Raynal <[email protected]> |
| 12 | + */ |
| 13 | + |
| 14 | +#include <linux/bcd.h> |
| 15 | +#include <linux/init.h> |
| 16 | +#include <linux/iopoll.h> |
| 17 | +#include <linux/module.h> |
| 18 | +#include <linux/of_device.h> |
| 19 | +#include <linux/platform_device.h> |
| 20 | +#include <linux/pm_runtime.h> |
| 21 | +#include <linux/rtc.h> |
| 22 | + |
| 23 | +#define RZN1_RTC_CTL0 0x00 |
| 24 | +#define RZN1_RTC_CTL0_SLSB_SUBU 0 |
| 25 | +#define RZN1_RTC_CTL0_SLSB_SCMP BIT(4) |
| 26 | +#define RZN1_RTC_CTL0_AMPM BIT(5) |
| 27 | +#define RZN1_RTC_CTL0_CE BIT(7) |
| 28 | + |
| 29 | +#define RZN1_RTC_CTL1 0x04 |
| 30 | +#define RZN1_RTC_CTL1_ALME BIT(4) |
| 31 | + |
| 32 | +#define RZN1_RTC_CTL2 0x08 |
| 33 | +#define RZN1_RTC_CTL2_WAIT BIT(0) |
| 34 | +#define RZN1_RTC_CTL2_WST BIT(1) |
| 35 | +#define RZN1_RTC_CTL2_WUST BIT(5) |
| 36 | +#define RZN1_RTC_CTL2_STOPPED (RZN1_RTC_CTL2_WAIT | RZN1_RTC_CTL2_WST) |
| 37 | + |
| 38 | +#define RZN1_RTC_SEC 0x14 |
| 39 | +#define RZN1_RTC_MIN 0x18 |
| 40 | +#define RZN1_RTC_HOUR 0x1c |
| 41 | +#define RZN1_RTC_WEEK 0x20 |
| 42 | +#define RZN1_RTC_DAY 0x24 |
| 43 | +#define RZN1_RTC_MONTH 0x28 |
| 44 | +#define RZN1_RTC_YEAR 0x2c |
| 45 | + |
| 46 | +#define RZN1_RTC_SUBU 0x38 |
| 47 | +#define RZN1_RTC_SUBU_DEV BIT(7) |
| 48 | +#define RZN1_RTC_SUBU_DECR BIT(6) |
| 49 | + |
| 50 | +#define RZN1_RTC_ALM 0x40 |
| 51 | +#define RZN1_RTC_ALH 0x44 |
| 52 | +#define RZN1_RTC_ALW 0x48 |
| 53 | + |
| 54 | +#define RZN1_RTC_SECC 0x4c |
| 55 | +#define RZN1_RTC_MINC 0x50 |
| 56 | +#define RZN1_RTC_HOURC 0x54 |
| 57 | +#define RZN1_RTC_WEEKC 0x58 |
| 58 | +#define RZN1_RTC_DAYC 0x5c |
| 59 | +#define RZN1_RTC_MONTHC 0x60 |
| 60 | +#define RZN1_RTC_YEARC 0x64 |
| 61 | + |
| 62 | +struct rzn1_rtc { |
| 63 | + struct rtc_device *rtcdev; |
| 64 | + void __iomem *base; |
| 65 | +}; |
| 66 | + |
| 67 | +static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm) |
| 68 | +{ |
| 69 | + tm->tm_sec = readl(rtc->base + RZN1_RTC_SECC); |
| 70 | + tm->tm_min = readl(rtc->base + RZN1_RTC_MINC); |
| 71 | + tm->tm_hour = readl(rtc->base + RZN1_RTC_HOURC); |
| 72 | + tm->tm_wday = readl(rtc->base + RZN1_RTC_WEEKC); |
| 73 | + tm->tm_mday = readl(rtc->base + RZN1_RTC_DAYC); |
| 74 | + tm->tm_mon = readl(rtc->base + RZN1_RTC_MONTHC); |
| 75 | + tm->tm_year = readl(rtc->base + RZN1_RTC_YEARC); |
| 76 | +} |
| 77 | + |
| 78 | +static unsigned int rzn1_rtc_tm_to_wday(struct rtc_time *tm) |
| 79 | +{ |
| 80 | + time64_t time; |
| 81 | + unsigned int days; |
| 82 | + u32 secs; |
| 83 | + |
| 84 | + time = rtc_tm_to_time64(tm); |
| 85 | + days = div_s64_rem(time, 86400, &secs); |
| 86 | + |
| 87 | + /* day of the week, 1970-01-01 was a Thursday */ |
| 88 | + return (days + 4) % 7; |
| 89 | +} |
| 90 | + |
| 91 | +static int rzn1_rtc_read_time(struct device *dev, struct rtc_time *tm) |
| 92 | +{ |
| 93 | + struct rzn1_rtc *rtc = dev_get_drvdata(dev); |
| 94 | + u32 val, secs; |
| 95 | + |
| 96 | + /* |
| 97 | + * The RTC was not started or is stopped and thus does not carry the |
| 98 | + * proper time/date. |
| 99 | + */ |
| 100 | + val = readl(rtc->base + RZN1_RTC_CTL2); |
| 101 | + if (val & RZN1_RTC_CTL2_STOPPED) |
| 102 | + return -EINVAL; |
| 103 | + |
| 104 | + rzn1_rtc_get_time_snapshot(rtc, tm); |
| 105 | + secs = readl(rtc->base + RZN1_RTC_SECC); |
| 106 | + if (tm->tm_sec != secs) |
| 107 | + rzn1_rtc_get_time_snapshot(rtc, tm); |
| 108 | + |
| 109 | + tm->tm_sec = bcd2bin(tm->tm_sec); |
| 110 | + tm->tm_min = bcd2bin(tm->tm_min); |
| 111 | + tm->tm_hour = bcd2bin(tm->tm_hour); |
| 112 | + tm->tm_wday = bcd2bin(tm->tm_wday); |
| 113 | + tm->tm_mday = bcd2bin(tm->tm_mday); |
| 114 | + tm->tm_mon = bcd2bin(tm->tm_mon); |
| 115 | + tm->tm_year = bcd2bin(tm->tm_year); |
| 116 | + |
| 117 | + return 0; |
| 118 | +} |
| 119 | + |
| 120 | +static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm) |
| 121 | +{ |
| 122 | + struct rzn1_rtc *rtc = dev_get_drvdata(dev); |
| 123 | + u32 val; |
| 124 | + int ret; |
| 125 | + |
| 126 | + tm->tm_sec = bin2bcd(tm->tm_sec); |
| 127 | + tm->tm_min = bin2bcd(tm->tm_min); |
| 128 | + tm->tm_hour = bin2bcd(tm->tm_hour); |
| 129 | + tm->tm_wday = bin2bcd(rzn1_rtc_tm_to_wday(tm)); |
| 130 | + tm->tm_mday = bin2bcd(tm->tm_mday); |
| 131 | + tm->tm_mon = bin2bcd(tm->tm_mon); |
| 132 | + tm->tm_year = bin2bcd(tm->tm_year); |
| 133 | + |
| 134 | + val = readl(rtc->base + RZN1_RTC_CTL2); |
| 135 | + if (!(val & RZN1_RTC_CTL2_STOPPED)) { |
| 136 | + /* Hold the counter if it was counting up */ |
| 137 | + writel(RZN1_RTC_CTL2_WAIT, rtc->base + RZN1_RTC_CTL2); |
| 138 | + |
| 139 | + /* Wait for the counter to stop: two 32k clock cycles */ |
| 140 | + usleep_range(61, 100); |
| 141 | + ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, val, |
| 142 | + val & RZN1_RTC_CTL2_WST, 0, 100); |
| 143 | + if (ret) |
| 144 | + return ret; |
| 145 | + } |
| 146 | + |
| 147 | + writel(tm->tm_sec, rtc->base + RZN1_RTC_SEC); |
| 148 | + writel(tm->tm_min, rtc->base + RZN1_RTC_MIN); |
| 149 | + writel(tm->tm_hour, rtc->base + RZN1_RTC_HOUR); |
| 150 | + writel(tm->tm_wday, rtc->base + RZN1_RTC_WEEK); |
| 151 | + writel(tm->tm_mday, rtc->base + RZN1_RTC_DAY); |
| 152 | + writel(tm->tm_mon, rtc->base + RZN1_RTC_MONTH); |
| 153 | + writel(tm->tm_year, rtc->base + RZN1_RTC_YEAR); |
| 154 | + writel(0, rtc->base + RZN1_RTC_CTL2); |
| 155 | + |
| 156 | + return 0; |
| 157 | +} |
| 158 | + |
| 159 | +static const struct rtc_class_ops rzn1_rtc_ops = { |
| 160 | + .read_time = rzn1_rtc_read_time, |
| 161 | + .set_time = rzn1_rtc_set_time, |
| 162 | +}; |
| 163 | + |
| 164 | +static int rzn1_rtc_probe(struct platform_device *pdev) |
| 165 | +{ |
| 166 | + struct rzn1_rtc *rtc; |
| 167 | + int ret; |
| 168 | + |
| 169 | + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); |
| 170 | + if (!rtc) |
| 171 | + return -ENOMEM; |
| 172 | + |
| 173 | + platform_set_drvdata(pdev, rtc); |
| 174 | + |
| 175 | + rtc->base = devm_platform_ioremap_resource(pdev, 0); |
| 176 | + if (IS_ERR(rtc->base)) |
| 177 | + return dev_err_probe(&pdev->dev, PTR_ERR(rtc->base), "Missing reg\n"); |
| 178 | + |
| 179 | + rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev); |
| 180 | + if (IS_ERR(rtc->rtcdev)) |
| 181 | + return PTR_ERR(rtc); |
| 182 | + |
| 183 | + rtc->rtcdev->range_min = RTC_TIMESTAMP_BEGIN_2000; |
| 184 | + rtc->rtcdev->range_max = RTC_TIMESTAMP_END_2099; |
| 185 | + rtc->rtcdev->ops = &rzn1_rtc_ops; |
| 186 | + clear_bit(RTC_FEATURE_ALARM, rtc->rtcdev->features); |
| 187 | + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features); |
| 188 | + |
| 189 | + devm_pm_runtime_enable(&pdev->dev); |
| 190 | + ret = pm_runtime_resume_and_get(&pdev->dev); |
| 191 | + if (ret < 0) |
| 192 | + return ret; |
| 193 | + |
| 194 | + /* |
| 195 | + * Ensure the clock counter is enabled. |
| 196 | + * Set 24-hour mode and possible oscillator offset compensation in SUBU mode. |
| 197 | + */ |
| 198 | + writel(RZN1_RTC_CTL0_CE | RZN1_RTC_CTL0_AMPM | RZN1_RTC_CTL0_SLSB_SUBU, |
| 199 | + rtc->base + RZN1_RTC_CTL0); |
| 200 | + |
| 201 | + /* Disable all interrupts */ |
| 202 | + writel(0, rtc->base + RZN1_RTC_CTL1); |
| 203 | + |
| 204 | + ret = devm_rtc_register_device(rtc->rtcdev); |
| 205 | + if (ret) |
| 206 | + goto dis_runtime_pm; |
| 207 | + |
| 208 | + return 0; |
| 209 | + |
| 210 | +dis_runtime_pm: |
| 211 | + pm_runtime_put(&pdev->dev); |
| 212 | + |
| 213 | + return ret; |
| 214 | +} |
| 215 | + |
| 216 | +static int rzn1_rtc_remove(struct platform_device *pdev) |
| 217 | +{ |
| 218 | + pm_runtime_put(&pdev->dev); |
| 219 | + |
| 220 | + return 0; |
| 221 | +} |
| 222 | + |
| 223 | +static const struct of_device_id rzn1_rtc_of_match[] = { |
| 224 | + { .compatible = "renesas,rzn1-rtc" }, |
| 225 | + {}, |
| 226 | +}; |
| 227 | +MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); |
| 228 | + |
| 229 | +static struct platform_driver rzn1_rtc_driver = { |
| 230 | + .probe = rzn1_rtc_probe, |
| 231 | + .remove = rzn1_rtc_remove, |
| 232 | + .driver = { |
| 233 | + .name = "rzn1-rtc", |
| 234 | + .owner = THIS_MODULE, |
| 235 | + .of_match_table = rzn1_rtc_of_match, |
| 236 | + }, |
| 237 | +}; |
| 238 | +module_platform_driver(rzn1_rtc_driver); |
| 239 | + |
| 240 | +MODULE_AUTHOR( "Michel Pollet <[email protected]"); |
| 241 | +MODULE_AUTHOR( "Miquel Raynal <[email protected]"); |
| 242 | +MODULE_DESCRIPTION("RZ/N1 RTC driver"); |
| 243 | +MODULE_LICENSE("GPL"); |
0 commit comments