18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MPIC timer wakeup driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2013 Freescale Semiconductor, Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/slab.h> 108c2ecf20Sopenharmony_ci#include <linux/errno.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/device.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <asm/mpic_timer.h> 168c2ecf20Sopenharmony_ci#include <asm/mpic.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct fsl_mpic_timer_wakeup { 198c2ecf20Sopenharmony_ci struct mpic_timer *timer; 208c2ecf20Sopenharmony_ci struct work_struct free_work; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic struct fsl_mpic_timer_wakeup *fsl_wakeup; 248c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(sysfs_lock); 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic void fsl_free_resource(struct work_struct *ws) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct fsl_mpic_timer_wakeup *wakeup = 298c2ecf20Sopenharmony_ci container_of(ws, struct fsl_mpic_timer_wakeup, free_work); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci mutex_lock(&sysfs_lock); 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci if (wakeup->timer) { 348c2ecf20Sopenharmony_ci disable_irq_wake(wakeup->timer->irq); 358c2ecf20Sopenharmony_ci mpic_free_timer(wakeup->timer); 368c2ecf20Sopenharmony_ci } 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci wakeup->timer = NULL; 398c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic irqreturn_t fsl_mpic_timer_irq(int irq, void *dev_id) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct fsl_mpic_timer_wakeup *wakeup = dev_id; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci schedule_work(&wakeup->free_work); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci return wakeup->timer ? IRQ_HANDLED : IRQ_NONE; 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic ssize_t fsl_timer_wakeup_show(struct device *dev, 528c2ecf20Sopenharmony_ci struct device_attribute *attr, 538c2ecf20Sopenharmony_ci char *buf) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci time64_t interval = 0; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci mutex_lock(&sysfs_lock); 588c2ecf20Sopenharmony_ci if (fsl_wakeup->timer) { 598c2ecf20Sopenharmony_ci mpic_get_remain_time(fsl_wakeup->timer, &interval); 608c2ecf20Sopenharmony_ci interval++; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci return sprintf(buf, "%lld\n", interval); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic ssize_t fsl_timer_wakeup_store(struct device *dev, 688c2ecf20Sopenharmony_ci struct device_attribute *attr, 698c2ecf20Sopenharmony_ci const char *buf, 708c2ecf20Sopenharmony_ci size_t count) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci time64_t interval; 738c2ecf20Sopenharmony_ci int ret; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (kstrtoll(buf, 0, &interval)) 768c2ecf20Sopenharmony_ci return -EINVAL; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci mutex_lock(&sysfs_lock); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (fsl_wakeup->timer) { 818c2ecf20Sopenharmony_ci disable_irq_wake(fsl_wakeup->timer->irq); 828c2ecf20Sopenharmony_ci mpic_free_timer(fsl_wakeup->timer); 838c2ecf20Sopenharmony_ci fsl_wakeup->timer = NULL; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (!interval) { 878c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 888c2ecf20Sopenharmony_ci return count; 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci fsl_wakeup->timer = mpic_request_timer(fsl_mpic_timer_irq, 928c2ecf20Sopenharmony_ci fsl_wakeup, interval); 938c2ecf20Sopenharmony_ci if (!fsl_wakeup->timer) { 948c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 958c2ecf20Sopenharmony_ci return -EINVAL; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci ret = enable_irq_wake(fsl_wakeup->timer->irq); 998c2ecf20Sopenharmony_ci if (ret) { 1008c2ecf20Sopenharmony_ci mpic_free_timer(fsl_wakeup->timer); 1018c2ecf20Sopenharmony_ci fsl_wakeup->timer = NULL; 1028c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return ret; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci mpic_start_timer(fsl_wakeup->timer); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci return count; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic struct device_attribute mpic_attributes = __ATTR(timer_wakeup, 0644, 1158c2ecf20Sopenharmony_ci fsl_timer_wakeup_show, fsl_timer_wakeup_store); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic int __init fsl_wakeup_sys_init(void) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci int ret; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci fsl_wakeup = kzalloc(sizeof(struct fsl_mpic_timer_wakeup), GFP_KERNEL); 1228c2ecf20Sopenharmony_ci if (!fsl_wakeup) 1238c2ecf20Sopenharmony_ci return -ENOMEM; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci INIT_WORK(&fsl_wakeup->free_work, fsl_free_resource); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci ret = device_create_file(mpic_subsys.dev_root, &mpic_attributes); 1288c2ecf20Sopenharmony_ci if (ret) 1298c2ecf20Sopenharmony_ci kfree(fsl_wakeup); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return ret; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic void __exit fsl_wakeup_sys_exit(void) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci device_remove_file(mpic_subsys.dev_root, &mpic_attributes); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci mutex_lock(&sysfs_lock); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (fsl_wakeup->timer) { 1418c2ecf20Sopenharmony_ci disable_irq_wake(fsl_wakeup->timer->irq); 1428c2ecf20Sopenharmony_ci mpic_free_timer(fsl_wakeup->timer); 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci kfree(fsl_wakeup); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci mutex_unlock(&sysfs_lock); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cimodule_init(fsl_wakeup_sys_init); 1518c2ecf20Sopenharmony_cimodule_exit(fsl_wakeup_sys_exit); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Freescale MPIC global timer wakeup driver"); 1548c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1558c2ecf20Sopenharmony_ciMODULE_AUTHOR("Wang Dongsheng <dongsheng.wang@freescale.com>"); 156