1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * MPIC timer wakeup driver |
4 | * |
5 | * Copyright 2013 Freescale Semiconductor, Inc. |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/module.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/device.h> |
14 | |
15 | #include <asm/mpic_timer.h> |
16 | #include <asm/mpic.h> |
17 | |
18 | struct fsl_mpic_timer_wakeup { |
19 | struct mpic_timer *timer; |
20 | struct work_struct free_work; |
21 | }; |
22 | |
23 | static struct fsl_mpic_timer_wakeup *fsl_wakeup; |
24 | static DEFINE_MUTEX(sysfs_lock); |
25 | |
26 | static void fsl_free_resource(struct work_struct *ws) |
27 | { |
28 | struct fsl_mpic_timer_wakeup *wakeup = |
29 | container_of(ws, struct fsl_mpic_timer_wakeup, free_work); |
30 | |
31 | mutex_lock(&sysfs_lock); |
32 | |
33 | if (wakeup->timer) { |
34 | disable_irq_wake(irq: wakeup->timer->irq); |
35 | mpic_free_timer(wakeup->timer); |
36 | } |
37 | |
38 | wakeup->timer = NULL; |
39 | mutex_unlock(lock: &sysfs_lock); |
40 | } |
41 | |
42 | static irqreturn_t fsl_mpic_timer_irq(int irq, void *dev_id) |
43 | { |
44 | struct fsl_mpic_timer_wakeup *wakeup = dev_id; |
45 | |
46 | schedule_work(work: &wakeup->free_work); |
47 | |
48 | return wakeup->timer ? IRQ_HANDLED : IRQ_NONE; |
49 | } |
50 | |
51 | static ssize_t fsl_timer_wakeup_show(struct device *dev, |
52 | struct device_attribute *attr, |
53 | char *buf) |
54 | { |
55 | time64_t interval = 0; |
56 | |
57 | mutex_lock(&sysfs_lock); |
58 | if (fsl_wakeup->timer) { |
59 | mpic_get_remain_time(fsl_wakeup->timer, &interval); |
60 | interval++; |
61 | } |
62 | mutex_unlock(lock: &sysfs_lock); |
63 | |
64 | return sprintf(buf, fmt: "%lld\n" , interval); |
65 | } |
66 | |
67 | static ssize_t fsl_timer_wakeup_store(struct device *dev, |
68 | struct device_attribute *attr, |
69 | const char *buf, |
70 | size_t count) |
71 | { |
72 | time64_t interval; |
73 | int ret; |
74 | |
75 | if (kstrtoll(s: buf, base: 0, res: &interval)) |
76 | return -EINVAL; |
77 | |
78 | mutex_lock(&sysfs_lock); |
79 | |
80 | if (fsl_wakeup->timer) { |
81 | disable_irq_wake(irq: fsl_wakeup->timer->irq); |
82 | mpic_free_timer(fsl_wakeup->timer); |
83 | fsl_wakeup->timer = NULL; |
84 | } |
85 | |
86 | if (!interval) { |
87 | mutex_unlock(lock: &sysfs_lock); |
88 | return count; |
89 | } |
90 | |
91 | fsl_wakeup->timer = mpic_request_timer(fsl_mpic_timer_irq, |
92 | fsl_wakeup, interval); |
93 | if (!fsl_wakeup->timer) { |
94 | mutex_unlock(lock: &sysfs_lock); |
95 | return -EINVAL; |
96 | } |
97 | |
98 | ret = enable_irq_wake(irq: fsl_wakeup->timer->irq); |
99 | if (ret) { |
100 | mpic_free_timer(fsl_wakeup->timer); |
101 | fsl_wakeup->timer = NULL; |
102 | mutex_unlock(lock: &sysfs_lock); |
103 | |
104 | return ret; |
105 | } |
106 | |
107 | mpic_start_timer(fsl_wakeup->timer); |
108 | |
109 | mutex_unlock(lock: &sysfs_lock); |
110 | |
111 | return count; |
112 | } |
113 | |
114 | static struct device_attribute mpic_attributes = __ATTR(timer_wakeup, 0644, |
115 | fsl_timer_wakeup_show, fsl_timer_wakeup_store); |
116 | |
117 | static int __init fsl_wakeup_sys_init(void) |
118 | { |
119 | struct device *dev_root; |
120 | int ret = -EINVAL; |
121 | |
122 | fsl_wakeup = kzalloc(size: sizeof(struct fsl_mpic_timer_wakeup), GFP_KERNEL); |
123 | if (!fsl_wakeup) |
124 | return -ENOMEM; |
125 | |
126 | INIT_WORK(&fsl_wakeup->free_work, fsl_free_resource); |
127 | |
128 | dev_root = bus_get_dev_root(bus: &mpic_subsys); |
129 | if (dev_root) { |
130 | ret = device_create_file(device: dev_root, entry: &mpic_attributes); |
131 | put_device(dev: dev_root); |
132 | if (ret) |
133 | kfree(objp: fsl_wakeup); |
134 | } |
135 | |
136 | return ret; |
137 | } |
138 | |
139 | static void __exit fsl_wakeup_sys_exit(void) |
140 | { |
141 | struct device *dev_root; |
142 | |
143 | dev_root = bus_get_dev_root(bus: &mpic_subsys); |
144 | if (dev_root) { |
145 | device_remove_file(dev: dev_root, attr: &mpic_attributes); |
146 | put_device(dev: dev_root); |
147 | } |
148 | |
149 | mutex_lock(&sysfs_lock); |
150 | |
151 | if (fsl_wakeup->timer) { |
152 | disable_irq_wake(irq: fsl_wakeup->timer->irq); |
153 | mpic_free_timer(fsl_wakeup->timer); |
154 | } |
155 | |
156 | kfree(objp: fsl_wakeup); |
157 | |
158 | mutex_unlock(lock: &sysfs_lock); |
159 | } |
160 | |
161 | module_init(fsl_wakeup_sys_init); |
162 | module_exit(fsl_wakeup_sys_exit); |
163 | |
164 | MODULE_DESCRIPTION("Freescale MPIC global timer wakeup driver" ); |
165 | MODULE_LICENSE("GPL v2" ); |
166 | MODULE_AUTHOR("Wang Dongsheng <dongsheng.wang@freescale.com>" ); |
167 | |