summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c')
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c127
1 files changed, 120 insertions, 7 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
index 4c34e2bd0487..d4dae1f12d62 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
@@ -21,18 +21,131 @@
*
* Authors: Ben Skeggs
*/
-#include <subdev/timer.h>
+#include "priv.h"
+
+u64
+nvkm_timer_read(struct nvkm_timer *tmr)
+{
+ return tmr->func->read(tmr);
+}
+
+void
+nvkm_timer_alarm_trigger(struct nvkm_timer *tmr)
+{
+ struct nvkm_alarm *alarm, *atemp;
+ unsigned long flags;
+ LIST_HEAD(exec);
+
+ /* move any due alarms off the pending list */
+ spin_lock_irqsave(&tmr->lock, flags);
+ list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) {
+ if (alarm->timestamp <= nvkm_timer_read(tmr))
+ list_move_tail(&alarm->head, &exec);
+ }
+
+ /* reschedule interrupt for next alarm time */
+ if (!list_empty(&tmr->alarms)) {
+ alarm = list_first_entry(&tmr->alarms, typeof(*alarm), head);
+ tmr->func->alarm_init(tmr, alarm->timestamp);
+ } else {
+ tmr->func->alarm_fini(tmr);
+ }
+ spin_unlock_irqrestore(&tmr->lock, flags);
+
+ /* execute any pending alarm handlers */
+ list_for_each_entry_safe(alarm, atemp, &exec, head) {
+ list_del_init(&alarm->head);
+ alarm->func(alarm);
+ }
+}
void
-nvkm_timer_alarm(void *obj, u32 nsec, struct nvkm_alarm *alarm)
+nvkm_timer_alarm(struct nvkm_timer *tmr, u32 nsec, struct nvkm_alarm *alarm)
{
- struct nvkm_timer *tmr = nvkm_timer(obj);
- tmr->alarm(tmr, nsec, alarm);
+ struct nvkm_alarm *list;
+ unsigned long flags;
+
+ alarm->timestamp = nvkm_timer_read(tmr) + nsec;
+
+ /* append new alarm to list, in soonest-alarm-first order */
+ spin_lock_irqsave(&tmr->lock, flags);
+ if (!nsec) {
+ if (!list_empty(&alarm->head))
+ list_del(&alarm->head);
+ } else {
+ list_for_each_entry(list, &tmr->alarms, head) {
+ if (list->timestamp > alarm->timestamp)
+ break;
+ }
+ list_add_tail(&alarm->head, &list->head);
+ }
+ spin_unlock_irqrestore(&tmr->lock, flags);
+
+ /* process pending alarms */
+ nvkm_timer_alarm_trigger(tmr);
}
void
-nvkm_timer_alarm_cancel(void *obj, struct nvkm_alarm *alarm)
+nvkm_timer_alarm_cancel(struct nvkm_timer *tmr, struct nvkm_alarm *alarm)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&tmr->lock, flags);
+ list_del_init(&alarm->head);
+ spin_unlock_irqrestore(&tmr->lock, flags);
+}
+
+static void
+nvkm_timer_intr(struct nvkm_subdev *subdev)
{
- struct nvkm_timer *tmr = nvkm_timer(obj);
- tmr->alarm_cancel(tmr, alarm);
+ struct nvkm_timer *tmr = nvkm_timer(subdev);
+ tmr->func->intr(tmr);
+}
+
+static int
+nvkm_timer_fini(struct nvkm_subdev *subdev, bool suspend)
+{
+ struct nvkm_timer *tmr = nvkm_timer(subdev);
+ tmr->func->alarm_fini(tmr);
+ return 0;
+}
+
+static int
+nvkm_timer_init(struct nvkm_subdev *subdev)
+{
+ struct nvkm_timer *tmr = nvkm_timer(subdev);
+ if (tmr->func->init)
+ tmr->func->init(tmr);
+ tmr->func->time(tmr, ktime_to_ns(ktime_get()));
+ nvkm_timer_alarm_trigger(tmr);
+ return 0;
+}
+
+static void *
+nvkm_timer_dtor(struct nvkm_subdev *subdev)
+{
+ return nvkm_timer(subdev);
+}
+
+static const struct nvkm_subdev_func
+nvkm_timer = {
+ .dtor = nvkm_timer_dtor,
+ .init = nvkm_timer_init,
+ .fini = nvkm_timer_fini,
+ .intr = nvkm_timer_intr,
+};
+
+int
+nvkm_timer_new_(const struct nvkm_timer_func *func, struct nvkm_device *device,
+ int index, struct nvkm_timer **ptmr)
+{
+ struct nvkm_timer *tmr;
+
+ if (!(tmr = *ptmr = kzalloc(sizeof(*tmr), GFP_KERNEL)))
+ return -ENOMEM;
+
+ nvkm_subdev_ctor(&nvkm_timer, device, index, 0, &tmr->subdev);
+ tmr->func = func;
+ INIT_LIST_HEAD(&tmr->alarms);
+ spin_lock_init(&tmr->lock);
+ return 0;
}