root/drivers/gpu/host1x/intr.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Tegra host1x Interrupt Management
 *
 * Copyright (c) 2010-2021, NVIDIA Corporation.
 */

#include <linux/clk.h>
#include <linux/interrupt.h>
#include "dev.h"
#include "fence.h"
#include "intr.h"

static void host1x_intr_add_fence_to_list(struct host1x_fence_list *list,
                                          struct host1x_syncpt_fence *fence)
{
        struct host1x_syncpt_fence *fence_in_list;

        list_for_each_entry_reverse(fence_in_list, &list->list, list) {
                if ((s32)(fence_in_list->threshold - fence->threshold) <= 0) {
                        /* Fence in list is before us, we can insert here */
                        list_add(&fence->list, &fence_in_list->list);
                        return;
                }
        }

        /* Add as first in list */
        list_add(&fence->list, &list->list);
}

static void host1x_intr_update_hw_state(struct host1x *host, struct host1x_syncpt *sp)
{
        struct host1x_syncpt_fence *fence;

        if (!list_empty(&sp->fences.list)) {
                fence = list_first_entry(&sp->fences.list, struct host1x_syncpt_fence, list);

                host1x_hw_intr_set_syncpt_threshold(host, sp->id, fence->threshold);
                host1x_hw_intr_enable_syncpt_intr(host, sp->id);
        } else {
                host1x_hw_intr_disable_syncpt_intr(host, sp->id);
        }
}

void host1x_intr_add_fence_locked(struct host1x *host, struct host1x_syncpt_fence *fence)
{
        struct host1x_fence_list *fence_list = &fence->sp->fences;

        INIT_LIST_HEAD(&fence->list);

        host1x_intr_add_fence_to_list(fence_list, fence);
        host1x_intr_update_hw_state(host, fence->sp);
}

bool host1x_intr_remove_fence(struct host1x *host, struct host1x_syncpt_fence *fence)
{
        struct host1x_fence_list *fence_list = &fence->sp->fences;
        unsigned long irqflags;

        spin_lock_irqsave(&fence_list->lock, irqflags);

        if (list_empty(&fence->list)) {
                spin_unlock_irqrestore(&fence_list->lock, irqflags);
                return false;
        }

        list_del_init(&fence->list);
        host1x_intr_update_hw_state(host, fence->sp);

        spin_unlock_irqrestore(&fence_list->lock, irqflags);

        return true;
}

void host1x_intr_handle_interrupt(struct host1x *host, unsigned int id)
{
        struct host1x_syncpt *sp = &host->syncpt[id];
        struct host1x_syncpt_fence *fence, *tmp;
        unsigned int value;

        value = host1x_syncpt_load(sp);

        spin_lock(&sp->fences.lock);

        list_for_each_entry_safe(fence, tmp, &sp->fences.list, list) {
                if (((value - fence->threshold) & 0x80000000U) != 0U) {
                        /* Fence is not yet expired, we are done */
                        break;
                }

                list_del_init(&fence->list);
                host1x_fence_signal(fence);
        }

        /*
         * Re-enable interrupt if necessary. The ISR already disabled the interrupt,
         * so if no fences remain, no update is needed.
         */
        if (!list_empty(&sp->fences.list))
                host1x_intr_update_hw_state(host, sp);

        spin_unlock(&sp->fences.lock);
}

int host1x_intr_init(struct host1x *host)
{
        struct host1x_intr_irq_data *irq_data;
        unsigned int id;
        int i, err;

        for (id = 0; id < host1x_syncpt_nb_pts(host); ++id) {
                struct host1x_syncpt *syncpt = &host->syncpt[id];

                spin_lock_init(&syncpt->fences.lock);
                INIT_LIST_HEAD(&syncpt->fences.list);
        }

        irq_data = devm_kcalloc(host->dev, host->num_syncpt_irqs, sizeof(irq_data[0]), GFP_KERNEL);
        if (!irq_data)
                return -ENOMEM;

        host1x_hw_intr_disable_all_syncpt_intrs(host);

        for (i = 0; i < host->num_syncpt_irqs; i++) {
                irq_data[i].host = host;
                irq_data[i].offset = i;

                err = devm_request_irq(host->dev, host->syncpt_irqs[i],
                                       host->intr_op->isr, IRQF_SHARED,
                                       "host1x_syncpt", &irq_data[i]);
                if (err < 0)
                        return err;
        }

        return 0;
}

void host1x_intr_deinit(struct host1x *host)
{
}

void host1x_intr_start(struct host1x *host)
{
        u32 hz = clk_get_rate(host->clk);
        int err;

        mutex_lock(&host->intr_mutex);
        err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000));
        if (err) {
                mutex_unlock(&host->intr_mutex);
                return;
        }
        mutex_unlock(&host->intr_mutex);
}

void host1x_intr_stop(struct host1x *host)
{
        host1x_hw_intr_disable_all_syncpt_intrs(host);
}