/* * drivers/char/timer_test/sunxi_timer_test.c * * Copyright(c) 2013-2015 Allwinnertech Co., Ltd. * http://www.allwinnertech.com * * Author: liugang * * sunxi timer test driver * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "sunxi_timer_test_i.h" #include /* rtc_dev_prepare */ #define DEV_NAME "timer_test_chrdev" u32 g_loopcnt[2] = {0}; /* 0 for timer, 1 for hrtimer, so as g_cur_cnt */ static atomic_t g_cur_cnt[2] = {ATOMIC_INIT(0), ATOMIC_INIT(0)}; struct timer_list g_timer; static dev_t g_devid; static struct cdev *g_ptimer_cdev; /* in /proc/devices */ static ktime_t g_ktime; static struct class *g_timer_class; static void timer_handle(unsigned long arg) { unsigned long ms = arg; struct timeval tv_cur; if (atomic_add_return(1, &g_cur_cnt[0]) >= g_loopcnt[0]) { /* print cur time in sec */ do_gettimeofday(&tv_cur); pr_info("%s: cur sec %d\n", __func__, (int)tv_cur.tv_sec); /* clear g_cur_cnt[0] */ atomic_set(&g_cur_cnt[0], 0); } /* set next trig */ mod_timer(&g_timer, jiffies + (HZ * ms) / 1000); } u32 case_timer_function(u32 interv_ms, u32 print_gap_s, u32 total_s) { int itemp = -1; struct timeval tv_start, tv_cur; pr_info("%s: interv_ms %d, print_gap_s %d, total_s %d\n", __func__, interv_ms, print_gap_s, total_s); g_loopcnt[0] = (print_gap_s * 1000) / interv_ms; atomic_set(&g_cur_cnt[0], 0); /* init and add timer */ init_timer(&g_timer); g_timer.function = &timer_handle; g_timer.data = interv_ms; g_timer.expires = jiffies + (HZ * interv_ms) / 1000; add_timer(&g_timer); /* wait for total_s */ do_gettimeofday(&tv_start); pr_info("%s: start sec %d\n", __func__, (int)tv_start.tv_sec); while (do_gettimeofday(&tv_cur), tv_cur.tv_sec - tv_start.tv_sec <= total_s) msleep_interruptible(0); /* del timer */ pr_info("%s: before del_timer_sync\n", __func__); /* del_timer(&g_timer); */ itemp = del_timer_sync(&g_timer); pr_info("%s: after del_timer_sync(return %d)\n", __func__, itemp); return 0; } static struct hrtimer g_hrtimer; static enum hrtimer_restart hrtimer_handle(struct hrtimer *cur_timer) { struct timeval tv_cur; if (atomic_add_return(1, &g_cur_cnt[1]) >= g_loopcnt[1]) { /* print cur time in sec */ do_gettimeofday(&tv_cur); pr_info("%s: cur sec %d\n", __func__, (int)tv_cur.tv_sec); /* clear g_cur_cnt[1] */ atomic_set(&g_cur_cnt[1], 0); } /* if not call this, hrtimer_handle will called again and again */ hrtimer_forward(cur_timer, cur_timer->base->get_time(), g_ktime); return HRTIMER_RESTART; } u32 case_hrtimer_function(u32 interv_us, u32 print_gap_s, u32 total_s) { int itemp = -1; struct timeval tv_start, tv_cur; pr_info("%s: interv_us %d, print_gap_s %d, total_s %d\n", __func__, interv_us, print_gap_s, total_s); g_loopcnt[1] = (print_gap_s * 1000000) / interv_us; atomic_set(&g_cur_cnt[1], 0); pr_info("%s: g_loopcnt %d\n", __func__, g_loopcnt[1]); /* init hrtimer */ g_ktime = ktime_set(0, interv_us * 1000); hrtimer_init(&g_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); g_hrtimer.function = &hrtimer_handle; /* start hrtimer */ hrtimer_start(&g_hrtimer, g_ktime, HRTIMER_MODE_REL); pr_info("%s: hrtimer_start\n", __func__); /* wait for total_s */ do_gettimeofday(&tv_start); pr_info("%s: start sec %d\n", __func__, (int)tv_start.tv_sec); while (do_gettimeofday(&tv_cur), tv_cur.tv_sec - tv_start.tv_sec <= total_s) msleep_interruptible(0); /* del httimer */ itemp = hrtimer_cancel(&g_hrtimer); pr_info("%s: hrtimer_cancel return %d\n", __func__, itemp); return 0; } static int timer_test_open(struct inode *inode, struct file *file) { return 0; } static int timer_test_release(struct inode *inode, struct file *file) { return 0; } static long timer_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long ret = 0; struct timer_test_para para; memset(¶, 0, sizeof(para)); if (copy_from_user(¶, (struct timer_test_para *)arg, sizeof(struct timer_test_para)) != 0) { pr_err("%s(%d) err\n", __func__, __LINE__); return __LINE__; } switch (cmd) { case TIMER_TEST_CMD_FUNC_NORMAL: ret = case_timer_function(para.timer_interv_us / 1000, para.print_gap_s, para.total_test_s); break; case TIMER_TEST_CMD_FUNC_HRTIMER: ret = case_hrtimer_function(para.timer_interv_us, para.print_gap_s, para.total_test_s); break; default: break; } return ret; } static const struct file_operations timer_test_fops = { .owner = THIS_MODULE, .open = timer_test_open, .release = timer_test_release, .unlocked_ioctl = timer_test_ioctl, }; static int timer_test_cdev_init(void) { int itemp = 0; itemp = alloc_chrdev_region(&g_devid, 0, 1, DEV_NAME); if (itemp) { pr_info("%s err, line %d\n", __func__, __LINE__); return -1; } g_ptimer_cdev = cdev_alloc(); if (g_ptimer_cdev == NULL) { pr_info("%s err, line %d\n", __func__, __LINE__); goto err2; } cdev_init(g_ptimer_cdev, &timer_test_fops); g_ptimer_cdev->owner = THIS_MODULE; itemp = cdev_add(g_ptimer_cdev, g_devid, 1); if (itemp < 0) { pr_info("%s err, line %d\n", __func__, __LINE__); goto err1; } g_timer_class = class_create(THIS_MODULE, DEV_NAME); if (IS_ERR(g_timer_class)) { pr_info("%s err, line %d\n", __func__, __LINE__); goto err1; } device_create(g_timer_class, NULL, g_devid, 0, DEV_NAME); pr_info("%s success\n", __func__); return 0; err1: cdev_del(g_ptimer_cdev); g_ptimer_cdev = NULL; err2: unregister_chrdev_region(g_devid, 1); return -1; } static void timer_test_cdev_deinit(void) { device_destroy(g_timer_class, g_devid); class_destroy(g_timer_class); cdev_del(g_ptimer_cdev); unregister_chrdev_region(g_devid, 1); } /** * sunxi_timer_test_init - enter the timer test module */ static int __init sunxi_timer_test_init(void) { int itemp = 0; pr_info("%s enter\n", __func__); itemp = timer_test_cdev_init(); if (itemp < 0) { pr_err("%s err, line %d\n", __func__, __LINE__); return -1; } pr_info("%s success\n", __func__); return 0; } /** * sunxi_timer_test_exit - exit the timer test module */ static void __exit sunxi_timer_test_exit(void) { pr_info("%s enter\n", __func__); timer_test_cdev_deinit(); } module_init(sunxi_timer_test_init); module_exit(sunxi_timer_test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("liugang"); MODULE_DESCRIPTION("sunxi timer test driver code");