1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Tegra host1x Interrupt Management 4 * 5 * Copyright (c) 2010-2021, NVIDIA Corporation. 6 */ 7 8 #include <linux/clk.h> 9 #include <linux/interrupt.h> 10 #include <linux/overflow.h> 11 #include "dev.h" 12 #include "fence.h" 13 #include "intr.h" 14 15 static void host1x_intr_add_fence_to_list(struct host1x_fence_list *list, 16 struct host1x_syncpt_fence *fence) 17 { 18 struct host1x_syncpt_fence *fence_in_list; 19 20 list_for_each_entry_reverse(fence_in_list, &list->list, list) { 21 if ((s32)wrapping_sub(u32, fence_in_list->threshold, fence->threshold) <= 0) { 22 /* Fence in list is before us, we can insert here */ 23 list_add(&fence->list, &fence_in_list->list); 24 return; 25 } 26 } 27 28 /* Add as first in list */ 29 list_add(&fence->list, &list->list); 30 } 31 32 static void host1x_intr_update_hw_state(struct host1x *host, struct host1x_syncpt *sp) 33 { 34 struct host1x_syncpt_fence *fence; 35 36 if (!list_empty(&sp->fences.list)) { 37 fence = list_first_entry(&sp->fences.list, struct host1x_syncpt_fence, list); 38 39 host1x_hw_intr_set_syncpt_threshold(host, sp->id, fence->threshold); 40 host1x_hw_intr_enable_syncpt_intr(host, sp->id); 41 } else { 42 host1x_hw_intr_disable_syncpt_intr(host, sp->id); 43 } 44 } 45 46 void host1x_intr_add_fence_locked(struct host1x *host, struct host1x_syncpt_fence *fence) 47 { 48 struct host1x_fence_list *fence_list = &fence->sp->fences; 49 50 INIT_LIST_HEAD(&fence->list); 51 52 host1x_intr_add_fence_to_list(fence_list, fence); 53 host1x_intr_update_hw_state(host, fence->sp); 54 } 55 56 bool host1x_intr_remove_fence(struct host1x *host, struct host1x_syncpt_fence *fence) 57 { 58 struct host1x_fence_list *fence_list = &fence->sp->fences; 59 unsigned long irqflags; 60 61 spin_lock_irqsave(&fence_list->lock, irqflags); 62 63 if (list_empty(&fence->list)) { 64 spin_unlock_irqrestore(&fence_list->lock, irqflags); 65 return false; 66 } 67 68 list_del_init(&fence->list); 69 host1x_intr_update_hw_state(host, fence->sp); 70 71 spin_unlock_irqrestore(&fence_list->lock, irqflags); 72 73 return true; 74 } 75 76 void host1x_intr_handle_interrupt(struct host1x *host, unsigned int id) 77 { 78 struct host1x_syncpt *sp = &host->syncpt[id]; 79 struct host1x_syncpt_fence *fence, *tmp; 80 unsigned int value; 81 82 value = host1x_syncpt_load(sp); 83 84 spin_lock(&sp->fences.lock); 85 86 list_for_each_entry_safe(fence, tmp, &sp->fences.list, list) { 87 if ((wrapping_sub(u32, value, fence->threshold) & 0x80000000U) != 0U) { 88 /* Fence is not yet expired, we are done */ 89 break; 90 } 91 92 list_del_init(&fence->list); 93 host1x_fence_signal(fence); 94 } 95 96 /* 97 * Re-enable interrupt if necessary. The ISR already disabled the interrupt, 98 * so if no fences remain, no update is needed. 99 */ 100 if (!list_empty(&sp->fences.list)) 101 host1x_intr_update_hw_state(host, sp); 102 103 spin_unlock(&sp->fences.lock); 104 } 105 106 int host1x_intr_init(struct host1x *host) 107 { 108 struct host1x_intr_irq_data *irq_data; 109 unsigned int id; 110 int i, err; 111 112 for (id = 0; id < host1x_syncpt_nb_pts(host); ++id) { 113 struct host1x_syncpt *syncpt = &host->syncpt[id]; 114 115 spin_lock_init(&syncpt->fences.lock); 116 INIT_LIST_HEAD(&syncpt->fences.list); 117 } 118 119 irq_data = devm_kcalloc(host->dev, host->num_syncpt_irqs, sizeof(irq_data[0]), GFP_KERNEL); 120 if (!irq_data) 121 return -ENOMEM; 122 123 host1x_hw_intr_disable_all_syncpt_intrs(host); 124 125 for (i = 0; i < host->num_syncpt_irqs; i++) { 126 irq_data[i].host = host; 127 irq_data[i].offset = i; 128 129 err = devm_request_irq(host->dev, host->syncpt_irqs[i], 130 host->intr_op->isr, IRQF_SHARED, 131 "host1x_syncpt", &irq_data[i]); 132 if (err < 0) 133 return err; 134 } 135 136 return 0; 137 } 138 139 void host1x_intr_deinit(struct host1x *host) 140 { 141 } 142 143 void host1x_intr_start(struct host1x *host) 144 { 145 u32 hz = clk_get_rate(host->clk); 146 int err; 147 148 mutex_lock(&host->intr_mutex); 149 err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000)); 150 if (err) { 151 mutex_unlock(&host->intr_mutex); 152 return; 153 } 154 mutex_unlock(&host->intr_mutex); 155 } 156 157 void host1x_intr_stop(struct host1x *host) 158 { 159 host1x_hw_intr_disable_all_syncpt_intrs(host); 160 } 161