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