diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-06 00:21:08 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-04-06 00:21:08 -0400 |
commit | 167569343fac74ec6825a3ab982f795b5880e63e (patch) | |
tree | 965adb59fbe10d9f45a7fb90cb1ec1bc18d4613c /drivers/clocksource | |
parent | b240b419db5d624ce7a5a397d6f62a1a686009ec (diff) | |
parent | cd903711fd9dce808b5cc07e509135886d962b0c (diff) |
Merge tag 'armsoc-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC platform updates from Arnd Bergmann:
"This release brings up a new platform based on the old ARM9 core: the
Nuvoton NPCM is used as a baseboard management controller, competing
with the better known ASpeed AST2xx series.
Another important change is the addition of ARMv7-A based chips in
mach-stm32. The older parts in this platform are ARMv7-M based
microcontrollers, now they are expanding to general-purpose workloads.
The other changes are the usual defconfig updates to enable additional
drivers, lesser bugfixes. The largest updates as often are the ongoing
OMAP cleanups, but we also have a number of changes for the older PXA
and davinci platforms this time.
For the Renesas shmobile/r-car platform, some new infrastructure is
needed to make the watchdog work correctly.
Supporting Multiprocessing on Allwinner A80 required a significant
amount of new code, but is not doing anything unexpected"
* tag 'armsoc-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (179 commits)
arm: npcm: modify configuration for the NPCM7xx BMC.
MAINTAINERS: update entry for ARM/berlin
ARM: omap2: fix am43xx build without L2X0
ARM: davinci: da8xx: simplify CFGCHIP regmap_config
ARM: davinci: da8xx: fix oops in USB PHY driver due to stack allocated platform_data
ARM: multi_v7_defconfig: add NXP FlexCAN IP support
ARM: multi_v7_defconfig: enable thermal driver for i.MX devices
ARM: multi_v7_defconfig: add RN5T618 PMIC family support
ARM: multi_v7_defconfig: add NXP graphics drivers
ARM: multi_v7_defconfig: add GPMI NAND controller support
ARM: multi_v7_defconfig: add OCOTP driver for NXP SoCs
ARM: multi_v7_defconfig: configure I2C driver built-in
arm64: defconfig: add CONFIG_UNIPHIER_THERMAL and CONFIG_SNI_AVE
ARM: imx: fix imx6sll-only build
ARM: imx: select ARM_CPU_SUSPEND for CPU_IDLE as well
ARM: mxs_defconfig: Re-sync defconfig
ARM: imx_v4_v5_defconfig: Use the generic fsl-asoc-card driver
ARM: imx_v4_v5_defconfig: Re-sync defconfig
arm64: defconfig: enable stmmac ethernet to defconfig
ARM: EXYNOS: Simplify code in coupled CPU idle hot path
...
Diffstat (limited to 'drivers/clocksource')
-rw-r--r-- | drivers/clocksource/Kconfig | 3 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 1 | ||||
-rw-r--r-- | drivers/clocksource/timer-ti-dm.c | 1000 |
3 files changed, 1004 insertions, 0 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 6021a5af21da..9ee2888275c1 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig | |||
@@ -21,6 +21,9 @@ config CLKEVT_I8253 | |||
21 | config I8253_LOCK | 21 | config I8253_LOCK |
22 | bool | 22 | bool |
23 | 23 | ||
24 | config OMAP_DM_TIMER | ||
25 | bool | ||
26 | |||
24 | config CLKBLD_I8253 | 27 | config CLKBLD_I8253 |
25 | def_bool y if CLKSRC_I8253 || CLKEVT_I8253 || I8253_LOCK | 28 | def_bool y if CLKSRC_I8253 || CLKEVT_I8253 || I8253_LOCK |
26 | 29 | ||
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index f0cb07637a65..e8e76dfef00b 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile | |||
@@ -16,6 +16,7 @@ obj-$(CONFIG_EM_TIMER_STI) += em_sti.o | |||
16 | obj-$(CONFIG_CLKBLD_I8253) += i8253.o | 16 | obj-$(CONFIG_CLKBLD_I8253) += i8253.o |
17 | obj-$(CONFIG_CLKSRC_MMIO) += mmio.o | 17 | obj-$(CONFIG_CLKSRC_MMIO) += mmio.o |
18 | obj-$(CONFIG_DIGICOLOR_TIMER) += timer-digicolor.o | 18 | obj-$(CONFIG_DIGICOLOR_TIMER) += timer-digicolor.o |
19 | obj-$(CONFIG_OMAP_DM_TIMER) += timer-ti-dm.o | ||
19 | obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o | 20 | obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o |
20 | obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o | 21 | obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o |
21 | obj-$(CONFIG_FTTMR010_TIMER) += timer-fttmr010.o | 22 | obj-$(CONFIG_FTTMR010_TIMER) += timer-fttmr010.o |
diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c new file mode 100644 index 000000000000..4cce6b224b87 --- /dev/null +++ b/drivers/clocksource/timer-ti-dm.c | |||
@@ -0,0 +1,1000 @@ | |||
1 | /* | ||
2 | * linux/arch/arm/plat-omap/dmtimer.c | ||
3 | * | ||
4 | * OMAP Dual-Mode Timers | ||
5 | * | ||
6 | * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ | ||
7 | * Tarun Kanti DebBarma <tarun.kanti@ti.com> | ||
8 | * Thara Gopinath <thara@ti.com> | ||
9 | * | ||
10 | * dmtimer adaptation to platform_driver. | ||
11 | * | ||
12 | * Copyright (C) 2005 Nokia Corporation | ||
13 | * OMAP2 support by Juha Yrjola | ||
14 | * API improvements and OMAP2 clock framework support by Timo Teras | ||
15 | * | ||
16 | * Copyright (C) 2009 Texas Instruments | ||
17 | * Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com> | ||
18 | * | ||
19 | * This program is free software; you can redistribute it and/or modify it | ||
20 | * under the terms of the GNU General Public License as published by the | ||
21 | * Free Software Foundation; either version 2 of the License, or (at your | ||
22 | * option) any later version. | ||
23 | * | ||
24 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | ||
25 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
26 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN | ||
27 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
29 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
31 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
32 | * | ||
33 | * You should have received a copy of the GNU General Public License along | ||
34 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
35 | * 675 Mass Ave, Cambridge, MA 02139, USA. | ||
36 | */ | ||
37 | |||
38 | #include <linux/clk.h> | ||
39 | #include <linux/clk-provider.h> | ||
40 | #include <linux/module.h> | ||
41 | #include <linux/io.h> | ||
42 | #include <linux/device.h> | ||
43 | #include <linux/err.h> | ||
44 | #include <linux/pm_runtime.h> | ||
45 | #include <linux/of.h> | ||
46 | #include <linux/of_device.h> | ||
47 | #include <linux/platform_device.h> | ||
48 | #include <linux/platform_data/dmtimer-omap.h> | ||
49 | |||
50 | #include <clocksource/timer-ti-dm.h> | ||
51 | |||
52 | static u32 omap_reserved_systimers; | ||
53 | static LIST_HEAD(omap_timer_list); | ||
54 | static DEFINE_SPINLOCK(dm_timer_lock); | ||
55 | |||
56 | enum { | ||
57 | REQUEST_ANY = 0, | ||
58 | REQUEST_BY_ID, | ||
59 | REQUEST_BY_CAP, | ||
60 | REQUEST_BY_NODE, | ||
61 | }; | ||
62 | |||
63 | /** | ||
64 | * omap_dm_timer_read_reg - read timer registers in posted and non-posted mode | ||
65 | * @timer: timer pointer over which read operation to perform | ||
66 | * @reg: lowest byte holds the register offset | ||
67 | * | ||
68 | * The posted mode bit is encoded in reg. Note that in posted mode write | ||
69 | * pending bit must be checked. Otherwise a read of a non completed write | ||
70 | * will produce an error. | ||
71 | */ | ||
72 | static inline u32 omap_dm_timer_read_reg(struct omap_dm_timer *timer, u32 reg) | ||
73 | { | ||
74 | WARN_ON((reg & 0xff) < _OMAP_TIMER_WAKEUP_EN_OFFSET); | ||
75 | return __omap_dm_timer_read(timer, reg, timer->posted); | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * omap_dm_timer_write_reg - write timer registers in posted and non-posted mode | ||
80 | * @timer: timer pointer over which write operation is to perform | ||
81 | * @reg: lowest byte holds the register offset | ||
82 | * @value: data to write into the register | ||
83 | * | ||
84 | * The posted mode bit is encoded in reg. Note that in posted mode the write | ||
85 | * pending bit must be checked. Otherwise a write on a register which has a | ||
86 | * pending write will be lost. | ||
87 | */ | ||
88 | static void omap_dm_timer_write_reg(struct omap_dm_timer *timer, u32 reg, | ||
89 | u32 value) | ||
90 | { | ||
91 | WARN_ON((reg & 0xff) < _OMAP_TIMER_WAKEUP_EN_OFFSET); | ||
92 | __omap_dm_timer_write(timer, reg, value, timer->posted); | ||
93 | } | ||
94 | |||
95 | static void omap_timer_restore_context(struct omap_dm_timer *timer) | ||
96 | { | ||
97 | omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG, | ||
98 | timer->context.twer); | ||
99 | omap_dm_timer_write_reg(timer, OMAP_TIMER_COUNTER_REG, | ||
100 | timer->context.tcrr); | ||
101 | omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, | ||
102 | timer->context.tldr); | ||
103 | omap_dm_timer_write_reg(timer, OMAP_TIMER_MATCH_REG, | ||
104 | timer->context.tmar); | ||
105 | omap_dm_timer_write_reg(timer, OMAP_TIMER_IF_CTRL_REG, | ||
106 | timer->context.tsicr); | ||
107 | writel_relaxed(timer->context.tier, timer->irq_ena); | ||
108 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, | ||
109 | timer->context.tclr); | ||
110 | } | ||
111 | |||
112 | static int omap_dm_timer_reset(struct omap_dm_timer *timer) | ||
113 | { | ||
114 | u32 l, timeout = 100000; | ||
115 | |||
116 | if (timer->revision != 1) | ||
117 | return -EINVAL; | ||
118 | |||
119 | omap_dm_timer_write_reg(timer, OMAP_TIMER_IF_CTRL_REG, 0x06); | ||
120 | |||
121 | do { | ||
122 | l = __omap_dm_timer_read(timer, | ||
123 | OMAP_TIMER_V1_SYS_STAT_OFFSET, 0); | ||
124 | } while (!l && timeout--); | ||
125 | |||
126 | if (!timeout) { | ||
127 | dev_err(&timer->pdev->dev, "Timer failed to reset\n"); | ||
128 | return -ETIMEDOUT; | ||
129 | } | ||
130 | |||
131 | /* Configure timer for smart-idle mode */ | ||
132 | l = __omap_dm_timer_read(timer, OMAP_TIMER_OCP_CFG_OFFSET, 0); | ||
133 | l |= 0x2 << 0x3; | ||
134 | __omap_dm_timer_write(timer, OMAP_TIMER_OCP_CFG_OFFSET, l, 0); | ||
135 | |||
136 | timer->posted = 0; | ||
137 | |||
138 | return 0; | ||
139 | } | ||
140 | |||
141 | static int omap_dm_timer_of_set_source(struct omap_dm_timer *timer) | ||
142 | { | ||
143 | int ret; | ||
144 | struct clk *parent; | ||
145 | |||
146 | /* | ||
147 | * FIXME: OMAP1 devices do not use the clock framework for dmtimers so | ||
148 | * do not call clk_get() for these devices. | ||
149 | */ | ||
150 | if (!timer->fclk) | ||
151 | return -ENODEV; | ||
152 | |||
153 | parent = clk_get(&timer->pdev->dev, NULL); | ||
154 | if (IS_ERR(parent)) | ||
155 | return -ENODEV; | ||
156 | |||
157 | ret = clk_set_parent(timer->fclk, parent); | ||
158 | if (ret < 0) | ||
159 | pr_err("%s: failed to set parent\n", __func__); | ||
160 | |||
161 | clk_put(parent); | ||
162 | |||
163 | return ret; | ||
164 | } | ||
165 | |||
166 | static int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source) | ||
167 | { | ||
168 | int ret; | ||
169 | const char *parent_name; | ||
170 | struct clk *parent; | ||
171 | struct dmtimer_platform_data *pdata; | ||
172 | |||
173 | if (unlikely(!timer) || IS_ERR(timer->fclk)) | ||
174 | return -EINVAL; | ||
175 | |||
176 | switch (source) { | ||
177 | case OMAP_TIMER_SRC_SYS_CLK: | ||
178 | parent_name = "timer_sys_ck"; | ||
179 | break; | ||
180 | case OMAP_TIMER_SRC_32_KHZ: | ||
181 | parent_name = "timer_32k_ck"; | ||
182 | break; | ||
183 | case OMAP_TIMER_SRC_EXT_CLK: | ||
184 | parent_name = "timer_ext_ck"; | ||
185 | break; | ||
186 | default: | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | |||
190 | pdata = timer->pdev->dev.platform_data; | ||
191 | |||
192 | /* | ||
193 | * FIXME: Used for OMAP1 devices only because they do not currently | ||
194 | * use the clock framework to set the parent clock. To be removed | ||
195 | * once OMAP1 migrated to using clock framework for dmtimers | ||
196 | */ | ||
197 | if (pdata && pdata->set_timer_src) | ||
198 | return pdata->set_timer_src(timer->pdev, source); | ||
199 | |||
200 | #if defined(CONFIG_COMMON_CLK) | ||
201 | /* Check if the clock has configurable parents */ | ||
202 | if (clk_hw_get_num_parents(__clk_get_hw(timer->fclk)) < 2) | ||
203 | return 0; | ||
204 | #endif | ||
205 | |||
206 | parent = clk_get(&timer->pdev->dev, parent_name); | ||
207 | if (IS_ERR(parent)) { | ||
208 | pr_err("%s: %s not found\n", __func__, parent_name); | ||
209 | return -EINVAL; | ||
210 | } | ||
211 | |||
212 | ret = clk_set_parent(timer->fclk, parent); | ||
213 | if (ret < 0) | ||
214 | pr_err("%s: failed to set %s as parent\n", __func__, | ||
215 | parent_name); | ||
216 | |||
217 | clk_put(parent); | ||
218 | |||
219 | return ret; | ||
220 | } | ||
221 | |||
222 | static void omap_dm_timer_enable(struct omap_dm_timer *timer) | ||
223 | { | ||
224 | int c; | ||
225 | |||
226 | pm_runtime_get_sync(&timer->pdev->dev); | ||
227 | |||
228 | if (!(timer->capability & OMAP_TIMER_ALWON)) { | ||
229 | if (timer->get_context_loss_count) { | ||
230 | c = timer->get_context_loss_count(&timer->pdev->dev); | ||
231 | if (c != timer->ctx_loss_count) { | ||
232 | omap_timer_restore_context(timer); | ||
233 | timer->ctx_loss_count = c; | ||
234 | } | ||
235 | } else { | ||
236 | omap_timer_restore_context(timer); | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | |||
241 | static void omap_dm_timer_disable(struct omap_dm_timer *timer) | ||
242 | { | ||
243 | pm_runtime_put_sync(&timer->pdev->dev); | ||
244 | } | ||
245 | |||
246 | static int omap_dm_timer_prepare(struct omap_dm_timer *timer) | ||
247 | { | ||
248 | int rc; | ||
249 | |||
250 | /* | ||
251 | * FIXME: OMAP1 devices do not use the clock framework for dmtimers so | ||
252 | * do not call clk_get() for these devices. | ||
253 | */ | ||
254 | if (!(timer->capability & OMAP_TIMER_NEEDS_RESET)) { | ||
255 | timer->fclk = clk_get(&timer->pdev->dev, "fck"); | ||
256 | if (WARN_ON_ONCE(IS_ERR(timer->fclk))) { | ||
257 | dev_err(&timer->pdev->dev, ": No fclk handle.\n"); | ||
258 | return -EINVAL; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | omap_dm_timer_enable(timer); | ||
263 | |||
264 | if (timer->capability & OMAP_TIMER_NEEDS_RESET) { | ||
265 | rc = omap_dm_timer_reset(timer); | ||
266 | if (rc) { | ||
267 | omap_dm_timer_disable(timer); | ||
268 | return rc; | ||
269 | } | ||
270 | } | ||
271 | |||
272 | __omap_dm_timer_enable_posted(timer); | ||
273 | omap_dm_timer_disable(timer); | ||
274 | |||
275 | rc = omap_dm_timer_of_set_source(timer); | ||
276 | if (rc == -ENODEV) | ||
277 | return omap_dm_timer_set_source(timer, OMAP_TIMER_SRC_32_KHZ); | ||
278 | |||
279 | return rc; | ||
280 | } | ||
281 | |||
282 | static inline u32 omap_dm_timer_reserved_systimer(int id) | ||
283 | { | ||
284 | return (omap_reserved_systimers & (1 << (id - 1))) ? 1 : 0; | ||
285 | } | ||
286 | |||
287 | int omap_dm_timer_reserve_systimer(int id) | ||
288 | { | ||
289 | if (omap_dm_timer_reserved_systimer(id)) | ||
290 | return -ENODEV; | ||
291 | |||
292 | omap_reserved_systimers |= (1 << (id - 1)); | ||
293 | |||
294 | return 0; | ||
295 | } | ||
296 | |||
297 | static struct omap_dm_timer *_omap_dm_timer_request(int req_type, void *data) | ||
298 | { | ||
299 | struct omap_dm_timer *timer = NULL, *t; | ||
300 | struct device_node *np = NULL; | ||
301 | unsigned long flags; | ||
302 | u32 cap = 0; | ||
303 | int id = 0; | ||
304 | |||
305 | switch (req_type) { | ||
306 | case REQUEST_BY_ID: | ||
307 | id = *(int *)data; | ||
308 | break; | ||
309 | case REQUEST_BY_CAP: | ||
310 | cap = *(u32 *)data; | ||
311 | break; | ||
312 | case REQUEST_BY_NODE: | ||
313 | np = (struct device_node *)data; | ||
314 | break; | ||
315 | default: | ||
316 | /* REQUEST_ANY */ | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | spin_lock_irqsave(&dm_timer_lock, flags); | ||
321 | list_for_each_entry(t, &omap_timer_list, node) { | ||
322 | if (t->reserved) | ||
323 | continue; | ||
324 | |||
325 | switch (req_type) { | ||
326 | case REQUEST_BY_ID: | ||
327 | if (id == t->pdev->id) { | ||
328 | timer = t; | ||
329 | timer->reserved = 1; | ||
330 | goto found; | ||
331 | } | ||
332 | break; | ||
333 | case REQUEST_BY_CAP: | ||
334 | if (cap == (t->capability & cap)) { | ||
335 | /* | ||
336 | * If timer is not NULL, we have already found | ||
337 | * one timer. But it was not an exact match | ||
338 | * because it had more capabilities than what | ||
339 | * was required. Therefore, unreserve the last | ||
340 | * timer found and see if this one is a better | ||
341 | * match. | ||
342 | */ | ||
343 | if (timer) | ||
344 | timer->reserved = 0; | ||
345 | timer = t; | ||
346 | timer->reserved = 1; | ||
347 | |||
348 | /* Exit loop early if we find an exact match */ | ||
349 | if (t->capability == cap) | ||
350 | goto found; | ||
351 | } | ||
352 | break; | ||
353 | case REQUEST_BY_NODE: | ||
354 | if (np == t->pdev->dev.of_node) { | ||
355 | timer = t; | ||
356 | timer->reserved = 1; | ||
357 | goto found; | ||
358 | } | ||
359 | break; | ||
360 | default: | ||
361 | /* REQUEST_ANY */ | ||
362 | timer = t; | ||
363 | timer->reserved = 1; | ||
364 | goto found; | ||
365 | } | ||
366 | } | ||
367 | found: | ||
368 | spin_unlock_irqrestore(&dm_timer_lock, flags); | ||
369 | |||
370 | if (timer && omap_dm_timer_prepare(timer)) { | ||
371 | timer->reserved = 0; | ||
372 | timer = NULL; | ||
373 | } | ||
374 | |||
375 | if (!timer) | ||
376 | pr_debug("%s: timer request failed!\n", __func__); | ||
377 | |||
378 | return timer; | ||
379 | } | ||
380 | |||
381 | static struct omap_dm_timer *omap_dm_timer_request(void) | ||
382 | { | ||
383 | return _omap_dm_timer_request(REQUEST_ANY, NULL); | ||
384 | } | ||
385 | |||
386 | static struct omap_dm_timer *omap_dm_timer_request_specific(int id) | ||
387 | { | ||
388 | /* Requesting timer by ID is not supported when device tree is used */ | ||
389 | if (of_have_populated_dt()) { | ||
390 | pr_warn("%s: Please use omap_dm_timer_request_by_node()\n", | ||
391 | __func__); | ||
392 | return NULL; | ||
393 | } | ||
394 | |||
395 | return _omap_dm_timer_request(REQUEST_BY_ID, &id); | ||
396 | } | ||
397 | |||
398 | /** | ||
399 | * omap_dm_timer_request_by_cap - Request a timer by capability | ||
400 | * @cap: Bit mask of capabilities to match | ||
401 | * | ||
402 | * Find a timer based upon capabilities bit mask. Callers of this function | ||
403 | * should use the definitions found in the plat/dmtimer.h file under the | ||
404 | * comment "timer capabilities used in hwmod database". Returns pointer to | ||
405 | * timer handle on success and a NULL pointer on failure. | ||
406 | */ | ||
407 | struct omap_dm_timer *omap_dm_timer_request_by_cap(u32 cap) | ||
408 | { | ||
409 | return _omap_dm_timer_request(REQUEST_BY_CAP, &cap); | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * omap_dm_timer_request_by_node - Request a timer by device-tree node | ||
414 | * @np: Pointer to device-tree timer node | ||
415 | * | ||
416 | * Request a timer based upon a device node pointer. Returns pointer to | ||
417 | * timer handle on success and a NULL pointer on failure. | ||
418 | */ | ||
419 | static struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np) | ||
420 | { | ||
421 | if (!np) | ||
422 | return NULL; | ||
423 | |||
424 | return _omap_dm_timer_request(REQUEST_BY_NODE, np); | ||
425 | } | ||
426 | |||
427 | static int omap_dm_timer_free(struct omap_dm_timer *timer) | ||
428 | { | ||
429 | if (unlikely(!timer)) | ||
430 | return -EINVAL; | ||
431 | |||
432 | clk_put(timer->fclk); | ||
433 | |||
434 | WARN_ON(!timer->reserved); | ||
435 | timer->reserved = 0; | ||
436 | return 0; | ||
437 | } | ||
438 | |||
439 | int omap_dm_timer_get_irq(struct omap_dm_timer *timer) | ||
440 | { | ||
441 | if (timer) | ||
442 | return timer->irq; | ||
443 | return -EINVAL; | ||
444 | } | ||
445 | |||
446 | #if defined(CONFIG_ARCH_OMAP1) | ||
447 | #include <mach/hardware.h> | ||
448 | |||
449 | static struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer) | ||
450 | { | ||
451 | return NULL; | ||
452 | } | ||
453 | |||
454 | /** | ||
455 | * omap_dm_timer_modify_idlect_mask - Check if any running timers use ARMXOR | ||
456 | * @inputmask: current value of idlect mask | ||
457 | */ | ||
458 | __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask) | ||
459 | { | ||
460 | int i = 0; | ||
461 | struct omap_dm_timer *timer = NULL; | ||
462 | unsigned long flags; | ||
463 | |||
464 | /* If ARMXOR cannot be idled this function call is unnecessary */ | ||
465 | if (!(inputmask & (1 << 1))) | ||
466 | return inputmask; | ||
467 | |||
468 | /* If any active timer is using ARMXOR return modified mask */ | ||
469 | spin_lock_irqsave(&dm_timer_lock, flags); | ||
470 | list_for_each_entry(timer, &omap_timer_list, node) { | ||
471 | u32 l; | ||
472 | |||
473 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
474 | if (l & OMAP_TIMER_CTRL_ST) { | ||
475 | if (((omap_readl(MOD_CONF_CTRL_1) >> (i * 2)) & 0x03) == 0) | ||
476 | inputmask &= ~(1 << 1); | ||
477 | else | ||
478 | inputmask &= ~(1 << 2); | ||
479 | } | ||
480 | i++; | ||
481 | } | ||
482 | spin_unlock_irqrestore(&dm_timer_lock, flags); | ||
483 | |||
484 | return inputmask; | ||
485 | } | ||
486 | |||
487 | #else | ||
488 | |||
489 | static struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer) | ||
490 | { | ||
491 | if (timer && !IS_ERR(timer->fclk)) | ||
492 | return timer->fclk; | ||
493 | return NULL; | ||
494 | } | ||
495 | |||
496 | __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask) | ||
497 | { | ||
498 | BUG(); | ||
499 | |||
500 | return 0; | ||
501 | } | ||
502 | |||
503 | #endif | ||
504 | |||
505 | int omap_dm_timer_trigger(struct omap_dm_timer *timer) | ||
506 | { | ||
507 | if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) { | ||
508 | pr_err("%s: timer not available or enabled.\n", __func__); | ||
509 | return -EINVAL; | ||
510 | } | ||
511 | |||
512 | omap_dm_timer_write_reg(timer, OMAP_TIMER_TRIGGER_REG, 0); | ||
513 | return 0; | ||
514 | } | ||
515 | |||
516 | static int omap_dm_timer_start(struct omap_dm_timer *timer) | ||
517 | { | ||
518 | u32 l; | ||
519 | |||
520 | if (unlikely(!timer)) | ||
521 | return -EINVAL; | ||
522 | |||
523 | omap_dm_timer_enable(timer); | ||
524 | |||
525 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
526 | if (!(l & OMAP_TIMER_CTRL_ST)) { | ||
527 | l |= OMAP_TIMER_CTRL_ST; | ||
528 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); | ||
529 | } | ||
530 | |||
531 | /* Save the context */ | ||
532 | timer->context.tclr = l; | ||
533 | return 0; | ||
534 | } | ||
535 | |||
536 | static int omap_dm_timer_stop(struct omap_dm_timer *timer) | ||
537 | { | ||
538 | unsigned long rate = 0; | ||
539 | |||
540 | if (unlikely(!timer)) | ||
541 | return -EINVAL; | ||
542 | |||
543 | if (!(timer->capability & OMAP_TIMER_NEEDS_RESET)) | ||
544 | rate = clk_get_rate(timer->fclk); | ||
545 | |||
546 | __omap_dm_timer_stop(timer, timer->posted, rate); | ||
547 | |||
548 | /* | ||
549 | * Since the register values are computed and written within | ||
550 | * __omap_dm_timer_stop, we need to use read to retrieve the | ||
551 | * context. | ||
552 | */ | ||
553 | timer->context.tclr = | ||
554 | omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
555 | omap_dm_timer_disable(timer); | ||
556 | return 0; | ||
557 | } | ||
558 | |||
559 | static int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload, | ||
560 | unsigned int load) | ||
561 | { | ||
562 | u32 l; | ||
563 | |||
564 | if (unlikely(!timer)) | ||
565 | return -EINVAL; | ||
566 | |||
567 | omap_dm_timer_enable(timer); | ||
568 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
569 | if (autoreload) | ||
570 | l |= OMAP_TIMER_CTRL_AR; | ||
571 | else | ||
572 | l &= ~OMAP_TIMER_CTRL_AR; | ||
573 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); | ||
574 | omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, load); | ||
575 | |||
576 | omap_dm_timer_write_reg(timer, OMAP_TIMER_TRIGGER_REG, 0); | ||
577 | /* Save the context */ | ||
578 | timer->context.tclr = l; | ||
579 | timer->context.tldr = load; | ||
580 | omap_dm_timer_disable(timer); | ||
581 | return 0; | ||
582 | } | ||
583 | |||
584 | /* Optimized set_load which removes costly spin wait in timer_start */ | ||
585 | int omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload, | ||
586 | unsigned int load) | ||
587 | { | ||
588 | u32 l; | ||
589 | |||
590 | if (unlikely(!timer)) | ||
591 | return -EINVAL; | ||
592 | |||
593 | omap_dm_timer_enable(timer); | ||
594 | |||
595 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
596 | if (autoreload) { | ||
597 | l |= OMAP_TIMER_CTRL_AR; | ||
598 | omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, load); | ||
599 | } else { | ||
600 | l &= ~OMAP_TIMER_CTRL_AR; | ||
601 | } | ||
602 | l |= OMAP_TIMER_CTRL_ST; | ||
603 | |||
604 | __omap_dm_timer_load_start(timer, l, load, timer->posted); | ||
605 | |||
606 | /* Save the context */ | ||
607 | timer->context.tclr = l; | ||
608 | timer->context.tldr = load; | ||
609 | timer->context.tcrr = load; | ||
610 | return 0; | ||
611 | } | ||
612 | static int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable, | ||
613 | unsigned int match) | ||
614 | { | ||
615 | u32 l; | ||
616 | |||
617 | if (unlikely(!timer)) | ||
618 | return -EINVAL; | ||
619 | |||
620 | omap_dm_timer_enable(timer); | ||
621 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
622 | if (enable) | ||
623 | l |= OMAP_TIMER_CTRL_CE; | ||
624 | else | ||
625 | l &= ~OMAP_TIMER_CTRL_CE; | ||
626 | omap_dm_timer_write_reg(timer, OMAP_TIMER_MATCH_REG, match); | ||
627 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); | ||
628 | |||
629 | /* Save the context */ | ||
630 | timer->context.tclr = l; | ||
631 | timer->context.tmar = match; | ||
632 | omap_dm_timer_disable(timer); | ||
633 | return 0; | ||
634 | } | ||
635 | |||
636 | static int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on, | ||
637 | int toggle, int trigger) | ||
638 | { | ||
639 | u32 l; | ||
640 | |||
641 | if (unlikely(!timer)) | ||
642 | return -EINVAL; | ||
643 | |||
644 | omap_dm_timer_enable(timer); | ||
645 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
646 | l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM | | ||
647 | OMAP_TIMER_CTRL_PT | (0x03 << 10)); | ||
648 | if (def_on) | ||
649 | l |= OMAP_TIMER_CTRL_SCPWM; | ||
650 | if (toggle) | ||
651 | l |= OMAP_TIMER_CTRL_PT; | ||
652 | l |= trigger << 10; | ||
653 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); | ||
654 | |||
655 | /* Save the context */ | ||
656 | timer->context.tclr = l; | ||
657 | omap_dm_timer_disable(timer); | ||
658 | return 0; | ||
659 | } | ||
660 | |||
661 | static int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, | ||
662 | int prescaler) | ||
663 | { | ||
664 | u32 l; | ||
665 | |||
666 | if (unlikely(!timer) || prescaler < -1 || prescaler > 7) | ||
667 | return -EINVAL; | ||
668 | |||
669 | omap_dm_timer_enable(timer); | ||
670 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG); | ||
671 | l &= ~(OMAP_TIMER_CTRL_PRE | (0x07 << 2)); | ||
672 | if (prescaler >= 0) { | ||
673 | l |= OMAP_TIMER_CTRL_PRE; | ||
674 | l |= prescaler << 2; | ||
675 | } | ||
676 | omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); | ||
677 | |||
678 | /* Save the context */ | ||
679 | timer->context.tclr = l; | ||
680 | omap_dm_timer_disable(timer); | ||
681 | return 0; | ||
682 | } | ||
683 | |||
684 | static int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer, | ||
685 | unsigned int value) | ||
686 | { | ||
687 | if (unlikely(!timer)) | ||
688 | return -EINVAL; | ||
689 | |||
690 | omap_dm_timer_enable(timer); | ||
691 | __omap_dm_timer_int_enable(timer, value); | ||
692 | |||
693 | /* Save the context */ | ||
694 | timer->context.tier = value; | ||
695 | timer->context.twer = value; | ||
696 | omap_dm_timer_disable(timer); | ||
697 | return 0; | ||
698 | } | ||
699 | |||
700 | /** | ||
701 | * omap_dm_timer_set_int_disable - disable timer interrupts | ||
702 | * @timer: pointer to timer handle | ||
703 | * @mask: bit mask of interrupts to be disabled | ||
704 | * | ||
705 | * Disables the specified timer interrupts for a timer. | ||
706 | */ | ||
707 | static int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask) | ||
708 | { | ||
709 | u32 l = mask; | ||
710 | |||
711 | if (unlikely(!timer)) | ||
712 | return -EINVAL; | ||
713 | |||
714 | omap_dm_timer_enable(timer); | ||
715 | |||
716 | if (timer->revision == 1) | ||
717 | l = readl_relaxed(timer->irq_ena) & ~mask; | ||
718 | |||
719 | writel_relaxed(l, timer->irq_dis); | ||
720 | l = omap_dm_timer_read_reg(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask; | ||
721 | omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG, l); | ||
722 | |||
723 | /* Save the context */ | ||
724 | timer->context.tier &= ~mask; | ||
725 | timer->context.twer &= ~mask; | ||
726 | omap_dm_timer_disable(timer); | ||
727 | return 0; | ||
728 | } | ||
729 | |||
730 | static unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer) | ||
731 | { | ||
732 | unsigned int l; | ||
733 | |||
734 | if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) { | ||
735 | pr_err("%s: timer not available or enabled.\n", __func__); | ||
736 | return 0; | ||
737 | } | ||
738 | |||
739 | l = readl_relaxed(timer->irq_stat); | ||
740 | |||
741 | return l; | ||
742 | } | ||
743 | |||
744 | static int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value) | ||
745 | { | ||
746 | if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) | ||
747 | return -EINVAL; | ||
748 | |||
749 | __omap_dm_timer_write_status(timer, value); | ||
750 | |||
751 | return 0; | ||
752 | } | ||
753 | |||
754 | static unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer) | ||
755 | { | ||
756 | if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) { | ||
757 | pr_err("%s: timer not iavailable or enabled.\n", __func__); | ||
758 | return 0; | ||
759 | } | ||
760 | |||
761 | return __omap_dm_timer_read_counter(timer, timer->posted); | ||
762 | } | ||
763 | |||
764 | static int omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value) | ||
765 | { | ||
766 | if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) { | ||
767 | pr_err("%s: timer not available or enabled.\n", __func__); | ||
768 | return -EINVAL; | ||
769 | } | ||
770 | |||
771 | omap_dm_timer_write_reg(timer, OMAP_TIMER_COUNTER_REG, value); | ||
772 | |||
773 | /* Save the context */ | ||
774 | timer->context.tcrr = value; | ||
775 | return 0; | ||
776 | } | ||
777 | |||
778 | int omap_dm_timers_active(void) | ||
779 | { | ||
780 | struct omap_dm_timer *timer; | ||
781 | |||
782 | list_for_each_entry(timer, &omap_timer_list, node) { | ||
783 | if (!timer->reserved) | ||
784 | continue; | ||
785 | |||
786 | if (omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG) & | ||
787 | OMAP_TIMER_CTRL_ST) { | ||
788 | return 1; | ||
789 | } | ||
790 | } | ||
791 | return 0; | ||
792 | } | ||
793 | |||
794 | static const struct of_device_id omap_timer_match[]; | ||
795 | |||
796 | /** | ||
797 | * omap_dm_timer_probe - probe function called for every registered device | ||
798 | * @pdev: pointer to current timer platform device | ||
799 | * | ||
800 | * Called by driver framework at the end of device registration for all | ||
801 | * timer devices. | ||
802 | */ | ||
803 | static int omap_dm_timer_probe(struct platform_device *pdev) | ||
804 | { | ||
805 | unsigned long flags; | ||
806 | struct omap_dm_timer *timer; | ||
807 | struct resource *mem, *irq; | ||
808 | struct device *dev = &pdev->dev; | ||
809 | const struct dmtimer_platform_data *pdata; | ||
810 | int ret; | ||
811 | |||
812 | pdata = of_device_get_match_data(dev); | ||
813 | if (!pdata) | ||
814 | pdata = dev_get_platdata(dev); | ||
815 | else | ||
816 | dev->platform_data = (void *)pdata; | ||
817 | |||
818 | if (!pdata) { | ||
819 | dev_err(dev, "%s: no platform data.\n", __func__); | ||
820 | return -ENODEV; | ||
821 | } | ||
822 | |||
823 | irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
824 | if (unlikely(!irq)) { | ||
825 | dev_err(dev, "%s: no IRQ resource.\n", __func__); | ||
826 | return -ENODEV; | ||
827 | } | ||
828 | |||
829 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
830 | if (unlikely(!mem)) { | ||
831 | dev_err(dev, "%s: no memory resource.\n", __func__); | ||
832 | return -ENODEV; | ||
833 | } | ||
834 | |||
835 | timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL); | ||
836 | if (!timer) | ||
837 | return -ENOMEM; | ||
838 | |||
839 | timer->fclk = ERR_PTR(-ENODEV); | ||
840 | timer->io_base = devm_ioremap_resource(dev, mem); | ||
841 | if (IS_ERR(timer->io_base)) | ||
842 | return PTR_ERR(timer->io_base); | ||
843 | |||
844 | if (dev->of_node) { | ||
845 | if (of_find_property(dev->of_node, "ti,timer-alwon", NULL)) | ||
846 | timer->capability |= OMAP_TIMER_ALWON; | ||
847 | if (of_find_property(dev->of_node, "ti,timer-dsp", NULL)) | ||
848 | timer->capability |= OMAP_TIMER_HAS_DSP_IRQ; | ||
849 | if (of_find_property(dev->of_node, "ti,timer-pwm", NULL)) | ||
850 | timer->capability |= OMAP_TIMER_HAS_PWM; | ||
851 | if (of_find_property(dev->of_node, "ti,timer-secure", NULL)) | ||
852 | timer->capability |= OMAP_TIMER_SECURE; | ||
853 | } else { | ||
854 | timer->id = pdev->id; | ||
855 | timer->capability = pdata->timer_capability; | ||
856 | timer->reserved = omap_dm_timer_reserved_systimer(timer->id); | ||
857 | timer->get_context_loss_count = pdata->get_context_loss_count; | ||
858 | } | ||
859 | |||
860 | if (pdata) | ||
861 | timer->errata = pdata->timer_errata; | ||
862 | |||
863 | timer->irq = irq->start; | ||
864 | timer->pdev = pdev; | ||
865 | |||
866 | pm_runtime_enable(dev); | ||
867 | pm_runtime_irq_safe(dev); | ||
868 | |||
869 | if (!timer->reserved) { | ||
870 | ret = pm_runtime_get_sync(dev); | ||
871 | if (ret < 0) { | ||
872 | dev_err(dev, "%s: pm_runtime_get_sync failed!\n", | ||
873 | __func__); | ||
874 | goto err_get_sync; | ||
875 | } | ||
876 | __omap_dm_timer_init_regs(timer); | ||
877 | pm_runtime_put(dev); | ||
878 | } | ||
879 | |||
880 | /* add the timer element to the list */ | ||
881 | spin_lock_irqsave(&dm_timer_lock, flags); | ||
882 | list_add_tail(&timer->node, &omap_timer_list); | ||
883 | spin_unlock_irqrestore(&dm_timer_lock, flags); | ||
884 | |||
885 | dev_dbg(dev, "Device Probed.\n"); | ||
886 | |||
887 | return 0; | ||
888 | |||
889 | err_get_sync: | ||
890 | pm_runtime_put_noidle(dev); | ||
891 | pm_runtime_disable(dev); | ||
892 | return ret; | ||
893 | } | ||
894 | |||
895 | /** | ||
896 | * omap_dm_timer_remove - cleanup a registered timer device | ||
897 | * @pdev: pointer to current timer platform device | ||
898 | * | ||
899 | * Called by driver framework whenever a timer device is unregistered. | ||
900 | * In addition to freeing platform resources it also deletes the timer | ||
901 | * entry from the local list. | ||
902 | */ | ||
903 | static int omap_dm_timer_remove(struct platform_device *pdev) | ||
904 | { | ||
905 | struct omap_dm_timer *timer; | ||
906 | unsigned long flags; | ||
907 | int ret = -EINVAL; | ||
908 | |||
909 | spin_lock_irqsave(&dm_timer_lock, flags); | ||
910 | list_for_each_entry(timer, &omap_timer_list, node) | ||
911 | if (!strcmp(dev_name(&timer->pdev->dev), | ||
912 | dev_name(&pdev->dev))) { | ||
913 | list_del(&timer->node); | ||
914 | ret = 0; | ||
915 | break; | ||
916 | } | ||
917 | spin_unlock_irqrestore(&dm_timer_lock, flags); | ||
918 | |||
919 | pm_runtime_disable(&pdev->dev); | ||
920 | |||
921 | return ret; | ||
922 | } | ||
923 | |||
924 | const static struct omap_dm_timer_ops dmtimer_ops = { | ||
925 | .request_by_node = omap_dm_timer_request_by_node, | ||
926 | .request_specific = omap_dm_timer_request_specific, | ||
927 | .request = omap_dm_timer_request, | ||
928 | .set_source = omap_dm_timer_set_source, | ||
929 | .get_irq = omap_dm_timer_get_irq, | ||
930 | .set_int_enable = omap_dm_timer_set_int_enable, | ||
931 | .set_int_disable = omap_dm_timer_set_int_disable, | ||
932 | .free = omap_dm_timer_free, | ||
933 | .enable = omap_dm_timer_enable, | ||
934 | .disable = omap_dm_timer_disable, | ||
935 | .get_fclk = omap_dm_timer_get_fclk, | ||
936 | .start = omap_dm_timer_start, | ||
937 | .stop = omap_dm_timer_stop, | ||
938 | .set_load = omap_dm_timer_set_load, | ||
939 | .set_match = omap_dm_timer_set_match, | ||
940 | .set_pwm = omap_dm_timer_set_pwm, | ||
941 | .set_prescaler = omap_dm_timer_set_prescaler, | ||
942 | .read_counter = omap_dm_timer_read_counter, | ||
943 | .write_counter = omap_dm_timer_write_counter, | ||
944 | .read_status = omap_dm_timer_read_status, | ||
945 | .write_status = omap_dm_timer_write_status, | ||
946 | }; | ||
947 | |||
948 | static const struct dmtimer_platform_data omap3plus_pdata = { | ||
949 | .timer_errata = OMAP_TIMER_ERRATA_I103_I767, | ||
950 | .timer_ops = &dmtimer_ops, | ||
951 | }; | ||
952 | |||
953 | static const struct of_device_id omap_timer_match[] = { | ||
954 | { | ||
955 | .compatible = "ti,omap2420-timer", | ||
956 | }, | ||
957 | { | ||
958 | .compatible = "ti,omap3430-timer", | ||
959 | .data = &omap3plus_pdata, | ||
960 | }, | ||
961 | { | ||
962 | .compatible = "ti,omap4430-timer", | ||
963 | .data = &omap3plus_pdata, | ||
964 | }, | ||
965 | { | ||
966 | .compatible = "ti,omap5430-timer", | ||
967 | .data = &omap3plus_pdata, | ||
968 | }, | ||
969 | { | ||
970 | .compatible = "ti,am335x-timer", | ||
971 | .data = &omap3plus_pdata, | ||
972 | }, | ||
973 | { | ||
974 | .compatible = "ti,am335x-timer-1ms", | ||
975 | .data = &omap3plus_pdata, | ||
976 | }, | ||
977 | { | ||
978 | .compatible = "ti,dm816-timer", | ||
979 | .data = &omap3plus_pdata, | ||
980 | }, | ||
981 | {}, | ||
982 | }; | ||
983 | MODULE_DEVICE_TABLE(of, omap_timer_match); | ||
984 | |||
985 | static struct platform_driver omap_dm_timer_driver = { | ||
986 | .probe = omap_dm_timer_probe, | ||
987 | .remove = omap_dm_timer_remove, | ||
988 | .driver = { | ||
989 | .name = "omap_timer", | ||
990 | .of_match_table = of_match_ptr(omap_timer_match), | ||
991 | }, | ||
992 | }; | ||
993 | |||
994 | early_platform_init("earlytimer", &omap_dm_timer_driver); | ||
995 | module_platform_driver(omap_dm_timer_driver); | ||
996 | |||
997 | MODULE_DESCRIPTION("OMAP Dual-Mode Timer Driver"); | ||
998 | MODULE_LICENSE("GPL"); | ||
999 | MODULE_ALIAS("platform:" DRIVER_NAME); | ||
1000 | MODULE_AUTHOR("Texas Instruments Inc"); | ||