| /* Copyright (c) 2012, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/clocksource.h> |
| #include <linux/clockchips.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <mach/event_timer.h> |
| |
| #define __INIT_HEAD(x) { .head = RB_ROOT,\ |
| .next = NULL, } |
| |
| #define DEFINE_TIME_HEAD(x) struct timerqueue_head x = __INIT_HEAD(x) |
| |
| /** |
| * struct event_timer_info - basic event timer structure |
| * @node: timerqueue node to track time ordered data structure |
| * of event timers |
| * @timer: hrtimer created for this event. |
| * @function : callback function for event timer. |
| * @data : callback data for event timer. |
| */ |
| struct event_timer_info { |
| struct timerqueue_node node; |
| void (*function)(void *); |
| void *data; |
| }; |
| |
| static DEFINE_TIME_HEAD(timer_head); |
| static DEFINE_SPINLOCK(event_timer_lock); |
| static DEFINE_SPINLOCK(event_setup_lock); |
| static struct hrtimer event_hrtimer; |
| static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer); |
| |
| static int msm_event_debug_mask; |
| module_param_named( |
| debug_mask, msm_event_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP |
| ); |
| |
| enum { |
| MSM_EVENT_TIMER_DEBUG = 1U << 0, |
| }; |
| |
| |
| /** |
| * add_event_timer() : Add a wakeup event. Intended to be called |
| * by clients once. Returns a handle to be used |
| * for future transactions. |
| * @function : The callback function will be called when event |
| * timer expires. |
| * @data: callback data provided by client. |
| */ |
| struct event_timer_info *add_event_timer(void (*function)(void *), void *data) |
| { |
| struct event_timer_info *event_info = |
| kzalloc(sizeof(struct event_timer_info), GFP_KERNEL); |
| |
| if (!event_info) |
| return NULL; |
| |
| event_info->function = function; |
| event_info->data = data; |
| /* Init rb node and hr timer */ |
| timerqueue_init(&event_info->node); |
| pr_debug("%s: New Event Added. Event 0x%x.", |
| __func__, |
| (unsigned int)event_info); |
| |
| return event_info; |
| } |
| |
| /** |
| * is_event_next(): Helper function to check if the event is the next |
| * next expiring event |
| * @event : handle to the event to be checked. |
| */ |
| static bool is_event_next(struct event_timer_info *event) |
| { |
| struct event_timer_info *next_event; |
| struct timerqueue_node *next; |
| bool ret = false; |
| |
| next = timerqueue_getnext(&timer_head); |
| if (!next) |
| goto exit_is_next_event; |
| |
| next_event = container_of(next, struct event_timer_info, node); |
| if (!next_event) |
| goto exit_is_next_event; |
| |
| if (next_event == event) |
| ret = true; |
| |
| exit_is_next_event: |
| return ret; |
| } |
| |
| /** |
| * is_event_active(): Helper function to check if the timer for a given event |
| * has been started. |
| * @event : handle to the event to be checked. |
| */ |
| static bool is_event_active(struct event_timer_info *event) |
| { |
| struct timerqueue_node *next; |
| struct event_timer_info *next_event; |
| bool ret = false; |
| |
| for (next = timerqueue_getnext(&timer_head); next; |
| next = timerqueue_iterate_next(next)) { |
| next_event = container_of(next, struct event_timer_info, node); |
| |
| if (event == next_event) { |
| ret = true; |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * create_httimer(): Helper function to setup hrtimer. |
| */ |
| static void create_hrtimer(ktime_t expires) |
| { |
| static bool timer_initialized; |
| |
| if (!timer_initialized) { |
| hrtimer_init(&event_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); |
| timer_initialized = true; |
| } |
| |
| event_hrtimer.function = event_hrtimer_cb; |
| hrtimer_start(&event_hrtimer, expires, HRTIMER_MODE_ABS); |
| } |
| |
| /** |
| * event_hrtimer_cb() : Callback function for hr timer. |
| * Make the client CB from here and remove the event |
| * from the time ordered queue. |
| */ |
| static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer) |
| { |
| struct event_timer_info *event; |
| struct timerqueue_node *next; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&event_timer_lock, flags); |
| next = timerqueue_getnext(&timer_head); |
| |
| while (next && (ktime_to_ns(next->expires) |
| <= ktime_to_ns(hrtimer->node.expires))) { |
| if (!next) |
| goto hrtimer_cb_exit; |
| |
| event = container_of(next, struct event_timer_info, node); |
| if (!event) |
| goto hrtimer_cb_exit; |
| |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Deleting event 0x%x @ %lu", __func__, |
| (unsigned int)event, |
| (unsigned long)ktime_to_ns(next->expires)); |
| |
| timerqueue_del(&timer_head, &event->node); |
| |
| if (event->function) |
| event->function(event->data); |
| next = timerqueue_getnext(&timer_head); |
| } |
| |
| if (next) |
| create_hrtimer(next->expires); |
| |
| spin_unlock_irqrestore(&event_timer_lock, flags); |
| hrtimer_cb_exit: |
| return HRTIMER_NORESTART; |
| } |
| |
| /** |
| * create_timer_smp(): Helper function used setting up timer on core 0. |
| */ |
| static void create_timer_smp(void *data) |
| { |
| unsigned long flags; |
| struct event_timer_info *event = |
| (struct event_timer_info *)data; |
| struct timerqueue_node *next; |
| |
| spin_lock_irqsave(&event_timer_lock, flags); |
| if (is_event_active(event)) |
| timerqueue_del(&timer_head, &event->node); |
| |
| next = timerqueue_getnext(&timer_head); |
| timerqueue_add(&timer_head, &event->node); |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Adding Event 0x%x for %lu", __func__, |
| (unsigned int)event, |
| (unsigned long)ktime_to_ns(event->node.expires)); |
| |
| if (!next || |
| (next && (ktime_to_ns(event->node.expires) < |
| ktime_to_ns(next->expires)))) { |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Setting timer for %lu", __func__, |
| (unsigned long)ktime_to_ns(event->node.expires)); |
| create_hrtimer(event->node.expires); |
| } |
| spin_unlock_irqrestore(&event_timer_lock, flags); |
| } |
| |
| /** |
| * setup_timer() : Helper function to setup timer on primary |
| * core during hrtimer callback. |
| * @event: event handle causing the wakeup. |
| */ |
| static void setup_event_hrtimer(struct event_timer_info *event) |
| { |
| smp_call_function_single(0, create_timer_smp, event, 1); |
| } |
| |
| /** |
| * activate_event_timer() : Set the expiration time for an event in absolute |
| * ktime. This is a oneshot event timer, clients |
| * should call this again to set another expiration. |
| * @event : event handle. |
| * @event_time : event time in absolute ktime. |
| */ |
| void activate_event_timer(struct event_timer_info *event, ktime_t event_time) |
| { |
| if (!event) |
| return; |
| |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Adding event timer @ %lu", __func__, |
| (unsigned long)ktime_to_us(event_time)); |
| |
| spin_lock(&event_setup_lock); |
| event->node.expires = event_time; |
| /* Start hr timer and add event to rb tree */ |
| setup_event_hrtimer(event); |
| spin_unlock(&event_setup_lock); |
| } |
| |
| |
| /** |
| * deactivate_event_timer() : Deactivate an event timer, this removes the event from |
| * the time ordered queue of event timers. |
| * @event: event handle. |
| */ |
| void deactivate_event_timer(struct event_timer_info *event) |
| { |
| unsigned long flags; |
| |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Deactivate timer", __func__); |
| |
| spin_lock_irqsave(&event_timer_lock, flags); |
| if (is_event_active(event)) { |
| if (is_event_next(event)) |
| hrtimer_try_to_cancel(&event_hrtimer); |
| |
| timerqueue_del(&timer_head, &event->node); |
| } |
| spin_unlock_irqrestore(&event_timer_lock, flags); |
| } |
| |
| /** |
| * destroy_event_timer() : Free the event info data structure allocated during |
| * add_event_timer(). |
| * @event: event handle. |
| */ |
| void destroy_event_timer(struct event_timer_info *event) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&event_timer_lock, flags); |
| if (is_event_active(event)) { |
| if (is_event_next(event)) |
| hrtimer_try_to_cancel(&event_hrtimer); |
| |
| timerqueue_del(&timer_head, &event->node); |
| } |
| spin_unlock_irqrestore(&event_timer_lock, flags); |
| kfree(event); |
| } |
| |
| /** |
| * get_next_event_timer() - Get the next wakeup event. Returns |
| * a ktime value of the next expiring event. |
| */ |
| ktime_t get_next_event_time(void) |
| { |
| unsigned long flags; |
| struct timerqueue_node *next; |
| ktime_t next_event = ns_to_ktime(0); |
| |
| spin_lock_irqsave(&event_timer_lock, flags); |
| next = timerqueue_getnext(&timer_head); |
| spin_unlock_irqrestore(&event_timer_lock, flags); |
| |
| if (!next) |
| return next_event; |
| |
| next_event = hrtimer_get_remaining(&event_hrtimer); |
| if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) |
| pr_info("%s: Next Event %lu", __func__, |
| (unsigned long)ktime_to_us(next_event)); |
| |
| return next_event; |
| } |