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