1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Faraday Technology FTRTC010 driver |
4 | * |
5 | * Copyright (C) 2009 Janos Laube <janos.dev@gmail.com> |
6 | * |
7 | * Original code for older kernel 2.6.15 are from Stormlinksemi |
8 | * first update from Janos Laube for > 2.6.29 kernels |
9 | * |
10 | * checkpatch fixes and usage of rtc-lib code |
11 | * Hans Ulli Kroll <ulli.kroll@googlemail.com> |
12 | */ |
13 | |
14 | #include <linux/rtc.h> |
15 | #include <linux/io.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/kernel.h> |
19 | #include <linux/module.h> |
20 | #include <linux/mod_devicetable.h> |
21 | #include <linux/clk.h> |
22 | |
23 | #define DRV_NAME "rtc-ftrtc010" |
24 | |
25 | MODULE_AUTHOR("Hans Ulli Kroll <ulli.kroll@googlemail.com>" ); |
26 | MODULE_DESCRIPTION("RTC driver for Gemini SoC" ); |
27 | MODULE_LICENSE("GPL" ); |
28 | MODULE_ALIAS("platform:" DRV_NAME); |
29 | |
30 | struct ftrtc010_rtc { |
31 | struct rtc_device *rtc_dev; |
32 | void __iomem *rtc_base; |
33 | int rtc_irq; |
34 | struct clk *pclk; |
35 | struct clk *extclk; |
36 | }; |
37 | |
38 | enum ftrtc010_rtc_offsets { |
39 | FTRTC010_RTC_SECOND = 0x00, |
40 | FTRTC010_RTC_MINUTE = 0x04, |
41 | FTRTC010_RTC_HOUR = 0x08, |
42 | FTRTC010_RTC_DAYS = 0x0C, |
43 | FTRTC010_RTC_ALARM_SECOND = 0x10, |
44 | FTRTC010_RTC_ALARM_MINUTE = 0x14, |
45 | FTRTC010_RTC_ALARM_HOUR = 0x18, |
46 | FTRTC010_RTC_RECORD = 0x1C, |
47 | FTRTC010_RTC_CR = 0x20, |
48 | }; |
49 | |
50 | static irqreturn_t ftrtc010_rtc_interrupt(int irq, void *dev) |
51 | { |
52 | return IRQ_HANDLED; |
53 | } |
54 | |
55 | /* |
56 | * Looks like the RTC in the Gemini SoC is (totaly) broken |
57 | * We can't read/write directly the time from RTC registers. |
58 | * We must do some "offset" calculation to get the real time |
59 | * |
60 | * This FIX works pretty fine and Stormlinksemi aka Cortina-Networks does |
61 | * the same thing, without the rtc-lib.c calls. |
62 | */ |
63 | |
64 | static int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm) |
65 | { |
66 | struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); |
67 | |
68 | u32 days, hour, min, sec, offset; |
69 | timeu64_t time; |
70 | |
71 | sec = readl(addr: rtc->rtc_base + FTRTC010_RTC_SECOND); |
72 | min = readl(addr: rtc->rtc_base + FTRTC010_RTC_MINUTE); |
73 | hour = readl(addr: rtc->rtc_base + FTRTC010_RTC_HOUR); |
74 | days = readl(addr: rtc->rtc_base + FTRTC010_RTC_DAYS); |
75 | offset = readl(addr: rtc->rtc_base + FTRTC010_RTC_RECORD); |
76 | |
77 | time = offset + days * 86400 + hour * 3600 + min * 60 + sec; |
78 | |
79 | rtc_time64_to_tm(time, tm); |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | static int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm) |
85 | { |
86 | struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); |
87 | u32 sec, min, hour, day, offset; |
88 | timeu64_t time; |
89 | |
90 | time = rtc_tm_to_time64(tm); |
91 | |
92 | sec = readl(addr: rtc->rtc_base + FTRTC010_RTC_SECOND); |
93 | min = readl(addr: rtc->rtc_base + FTRTC010_RTC_MINUTE); |
94 | hour = readl(addr: rtc->rtc_base + FTRTC010_RTC_HOUR); |
95 | day = readl(addr: rtc->rtc_base + FTRTC010_RTC_DAYS); |
96 | |
97 | offset = time - (day * 86400 + hour * 3600 + min * 60 + sec); |
98 | |
99 | writel(val: offset, addr: rtc->rtc_base + FTRTC010_RTC_RECORD); |
100 | writel(val: 0x01, addr: rtc->rtc_base + FTRTC010_RTC_CR); |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | static const struct rtc_class_ops ftrtc010_rtc_ops = { |
106 | .read_time = ftrtc010_rtc_read_time, |
107 | .set_time = ftrtc010_rtc_set_time, |
108 | }; |
109 | |
110 | static int ftrtc010_rtc_probe(struct platform_device *pdev) |
111 | { |
112 | u32 days, hour, min, sec; |
113 | struct ftrtc010_rtc *rtc; |
114 | struct device *dev = &pdev->dev; |
115 | struct resource *res; |
116 | int ret; |
117 | |
118 | rtc = devm_kzalloc(dev: &pdev->dev, size: sizeof(*rtc), GFP_KERNEL); |
119 | if (unlikely(!rtc)) |
120 | return -ENOMEM; |
121 | platform_set_drvdata(pdev, data: rtc); |
122 | |
123 | rtc->pclk = devm_clk_get(dev, id: "PCLK" ); |
124 | if (IS_ERR(ptr: rtc->pclk)) { |
125 | dev_err(dev, "could not get PCLK\n" ); |
126 | } else { |
127 | ret = clk_prepare_enable(clk: rtc->pclk); |
128 | if (ret) { |
129 | dev_err(dev, "failed to enable PCLK\n" ); |
130 | return ret; |
131 | } |
132 | } |
133 | rtc->extclk = devm_clk_get(dev, id: "EXTCLK" ); |
134 | if (IS_ERR(ptr: rtc->extclk)) { |
135 | dev_err(dev, "could not get EXTCLK\n" ); |
136 | } else { |
137 | ret = clk_prepare_enable(clk: rtc->extclk); |
138 | if (ret) { |
139 | dev_err(dev, "failed to enable EXTCLK\n" ); |
140 | goto err_disable_pclk; |
141 | } |
142 | } |
143 | |
144 | rtc->rtc_irq = platform_get_irq(pdev, 0); |
145 | if (rtc->rtc_irq < 0) { |
146 | ret = rtc->rtc_irq; |
147 | goto err_disable_extclk; |
148 | } |
149 | |
150 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
151 | if (!res) { |
152 | ret = -ENODEV; |
153 | goto err_disable_extclk; |
154 | } |
155 | |
156 | rtc->rtc_base = devm_ioremap(dev, offset: res->start, |
157 | size: resource_size(res)); |
158 | if (!rtc->rtc_base) { |
159 | ret = -ENOMEM; |
160 | goto err_disable_extclk; |
161 | } |
162 | |
163 | rtc->rtc_dev = devm_rtc_allocate_device(dev); |
164 | if (IS_ERR(ptr: rtc->rtc_dev)) { |
165 | ret = PTR_ERR(ptr: rtc->rtc_dev); |
166 | goto err_disable_extclk; |
167 | } |
168 | |
169 | rtc->rtc_dev->ops = &ftrtc010_rtc_ops; |
170 | |
171 | sec = readl(addr: rtc->rtc_base + FTRTC010_RTC_SECOND); |
172 | min = readl(addr: rtc->rtc_base + FTRTC010_RTC_MINUTE); |
173 | hour = readl(addr: rtc->rtc_base + FTRTC010_RTC_HOUR); |
174 | days = readl(addr: rtc->rtc_base + FTRTC010_RTC_DAYS); |
175 | |
176 | rtc->rtc_dev->range_min = (u64)days * 86400 + hour * 3600 + |
177 | min * 60 + sec; |
178 | rtc->rtc_dev->range_max = U32_MAX + rtc->rtc_dev->range_min; |
179 | |
180 | ret = devm_request_irq(dev, irq: rtc->rtc_irq, handler: ftrtc010_rtc_interrupt, |
181 | IRQF_SHARED, devname: pdev->name, dev_id: dev); |
182 | if (unlikely(ret)) |
183 | goto err_disable_extclk; |
184 | |
185 | return devm_rtc_register_device(rtc->rtc_dev); |
186 | |
187 | err_disable_extclk: |
188 | clk_disable_unprepare(clk: rtc->extclk); |
189 | err_disable_pclk: |
190 | clk_disable_unprepare(clk: rtc->pclk); |
191 | return ret; |
192 | } |
193 | |
194 | static void ftrtc010_rtc_remove(struct platform_device *pdev) |
195 | { |
196 | struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev); |
197 | |
198 | if (!IS_ERR(ptr: rtc->extclk)) |
199 | clk_disable_unprepare(clk: rtc->extclk); |
200 | if (!IS_ERR(ptr: rtc->pclk)) |
201 | clk_disable_unprepare(clk: rtc->pclk); |
202 | } |
203 | |
204 | static const struct of_device_id ftrtc010_rtc_dt_match[] = { |
205 | { .compatible = "cortina,gemini-rtc" }, |
206 | { .compatible = "faraday,ftrtc010" }, |
207 | { } |
208 | }; |
209 | MODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match); |
210 | |
211 | static struct platform_driver ftrtc010_rtc_driver = { |
212 | .driver = { |
213 | .name = DRV_NAME, |
214 | .of_match_table = ftrtc010_rtc_dt_match, |
215 | }, |
216 | .probe = ftrtc010_rtc_probe, |
217 | .remove_new = ftrtc010_rtc_remove, |
218 | }; |
219 | |
220 | module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); |
221 | |