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
18struct fsl_mpic_timer_wakeup {
19	struct mpic_timer *timer;
20	struct work_struct free_work;
21};
22
23static struct fsl_mpic_timer_wakeup *fsl_wakeup;
24static DEFINE_MUTEX(sysfs_lock);
25
26static 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(wakeup->timer->irq);
35		mpic_free_timer(wakeup->timer);
36	}
37
38	wakeup->timer = NULL;
39	mutex_unlock(&sysfs_lock);
40}
41
42static 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(&wakeup->free_work);
47
48	return wakeup->timer ? IRQ_HANDLED : IRQ_NONE;
49}
50
51static 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(&sysfs_lock);
63
64	return sprintf(buf, "%lld\n", interval);
65}
66
67static 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(buf, 0, &interval))
76		return -EINVAL;
77
78	mutex_lock(&sysfs_lock);
79
80	if (fsl_wakeup->timer) {
81		disable_irq_wake(fsl_wakeup->timer->irq);
82		mpic_free_timer(fsl_wakeup->timer);
83		fsl_wakeup->timer = NULL;
84	}
85
86	if (!interval) {
87		mutex_unlock(&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(&sysfs_lock);
95		return -EINVAL;
96	}
97
98	ret = enable_irq_wake(fsl_wakeup->timer->irq);
99	if (ret) {
100		mpic_free_timer(fsl_wakeup->timer);
101		fsl_wakeup->timer = NULL;
102		mutex_unlock(&sysfs_lock);
103
104		return ret;
105	}
106
107	mpic_start_timer(fsl_wakeup->timer);
108
109	mutex_unlock(&sysfs_lock);
110
111	return count;
112}
113
114static struct device_attribute mpic_attributes = __ATTR(timer_wakeup, 0644,
115			fsl_timer_wakeup_show, fsl_timer_wakeup_store);
116
117static int __init fsl_wakeup_sys_init(void)
118{
119	struct device *dev_root;
120	int ret = -EINVAL;
121
122	fsl_wakeup = kzalloc(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(&mpic_subsys);
129	if (dev_root) {
130		ret = device_create_file(dev_root, &mpic_attributes);
131		put_device(dev_root);
132		if (ret)
133			kfree(fsl_wakeup);
134	}
135
136	return ret;
137}
138
139static void __exit fsl_wakeup_sys_exit(void)
140{
141	struct device *dev_root;
142
143	dev_root = bus_get_dev_root(&mpic_subsys);
144	if (dev_root) {
145		device_remove_file(dev_root, &mpic_attributes);
146		put_device(dev_root);
147	}
148
149	mutex_lock(&sysfs_lock);
150
151	if (fsl_wakeup->timer) {
152		disable_irq_wake(fsl_wakeup->timer->irq);
153		mpic_free_timer(fsl_wakeup->timer);
154	}
155
156	kfree(fsl_wakeup);
157
158	mutex_unlock(&sysfs_lock);
159}
160
161module_init(fsl_wakeup_sys_init);
162module_exit(fsl_wakeup_sys_exit);
163
164MODULE_DESCRIPTION("Freescale MPIC global timer wakeup driver");
165MODULE_LICENSE("GPL v2");
166MODULE_AUTHOR("Wang Dongsheng <dongsheng.wang@freescale.com>");
167