root/drivers/media/pci/mgb4/mgb4_trigger.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2021-2023 Digiteq Automotive
 *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
 *
 * This module handles the IIO trigger device. The card has two signal inputs
 * for event triggers that can be used to record events related to the video
 * stream. A standard linux IIO device with triggered buffer capability is
 * created and configured that can be used to fetch the events with the same
 * clock source as the video frames.
 */

#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/pci.h>
#include <linux/dma/amd_xdma.h>
#include <linux/types.h>
#include "mgb4_core.h"
#include "mgb4_trigger.h"

struct trigger_data {
        struct mgb4_dev *mgbdev;
        struct iio_trigger *trig;
};

static int trigger_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan, int *val,
                            int *val2, long mask)
{
        struct trigger_data *st = iio_priv(indio_dev);

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                if (iio_buffer_enabled(indio_dev))
                        return -EBUSY;
                *val = mgb4_read_reg(&st->mgbdev->video, 0xA0);

                return IIO_VAL_INT;
        }

        return -EINVAL;
}

static int trigger_set_state(struct iio_trigger *trig, bool state)
{
        struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
        struct trigger_data *st = iio_priv(indio_dev);
        int irq = xdma_get_user_irq(st->mgbdev->xdev, 11);

        if (state)
                xdma_enable_user_irq(st->mgbdev->xdev, irq);
        else
                xdma_disable_user_irq(st->mgbdev->xdev, irq);

        return 0;
}

static const struct iio_trigger_ops trigger_ops = {
        .set_trigger_state = &trigger_set_state,
};

static const struct iio_info trigger_info = {
        .read_raw         = trigger_read_raw,
};

#define TRIGGER_CHANNEL(_si) {                    \
        .type = IIO_ACTIVITY,                         \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
        .scan_index = _si,                            \
        .scan_type = {                                \
                .sign = 'u',                              \
                .realbits = 32,                           \
                .storagebits = 32,                        \
                .shift = 0,                               \
                .endianness = IIO_CPU                     \
        },                                            \
}

static const struct iio_chan_spec trigger_channels[] = {
        TRIGGER_CHANNEL(0),
        IIO_CHAN_SOFT_TIMESTAMP(1),
};

static irqreturn_t trigger_handler(int irq, void *p)
{
        struct iio_poll_func *pf = p;
        struct iio_dev *indio_dev = pf->indio_dev;
        struct trigger_data *st = iio_priv(indio_dev);
        struct {
                u32 data;
                aligned_s64 ts;
        } scan = { };

        scan.data = mgb4_read_reg(&st->mgbdev->video, 0xA0);
        mgb4_write_reg(&st->mgbdev->video, 0xA0, scan.data);

        iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp);
        iio_trigger_notify_done(indio_dev->trig);

        mgb4_write_reg(&st->mgbdev->video, 0xB4, 1U << 11);

        return IRQ_HANDLED;
}

static int probe_trigger(struct iio_dev *indio_dev, int irq)
{
        int ret;
        struct trigger_data *st = iio_priv(indio_dev);

        st->trig = iio_trigger_alloc(&st->mgbdev->pdev->dev, "%s-dev%d",
                                     indio_dev->name, iio_device_id(indio_dev));
        if (!st->trig)
                return -ENOMEM;

        ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll, IRQF_NO_THREAD,
                          "mgb4-trigger", st->trig);
        if (ret)
                goto error_free_trig;

        st->trig->ops = &trigger_ops;
        iio_trigger_set_drvdata(st->trig, indio_dev);
        ret = iio_trigger_register(st->trig);
        if (ret)
                goto error_free_irq;

        indio_dev->trig = iio_trigger_get(st->trig);

        return 0;

error_free_irq:
        free_irq(irq, st->trig);
error_free_trig:
        iio_trigger_free(st->trig);

        return ret;
}

static void remove_trigger(struct iio_dev *indio_dev, int irq)
{
        struct trigger_data *st = iio_priv(indio_dev);

        iio_trigger_unregister(st->trig);
        free_irq(irq, st->trig);
        iio_trigger_free(st->trig);
}

struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev)
{
        struct iio_dev *indio_dev;
        struct trigger_data *data;
        struct pci_dev *pdev = mgbdev->pdev;
        struct device *dev = &pdev->dev;
        int rv, irq;

        indio_dev = iio_device_alloc(dev, sizeof(*data));
        if (!indio_dev)
                return NULL;

        indio_dev->info = &trigger_info;
        indio_dev->name = "mgb4";
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->channels = trigger_channels;
        indio_dev->num_channels = ARRAY_SIZE(trigger_channels);

        data = iio_priv(indio_dev);
        data->mgbdev = mgbdev;

        irq = xdma_get_user_irq(mgbdev->xdev, 11);
        rv = probe_trigger(indio_dev, irq);
        if (rv < 0) {
                dev_err(dev, "iio triggered setup failed\n");
                goto error_alloc;
        }
        rv = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
                                        trigger_handler, NULL);
        if (rv < 0) {
                dev_err(dev, "iio triggered buffer setup failed\n");
                goto error_trigger;
        }
        rv = iio_device_register(indio_dev);
        if (rv < 0) {
                dev_err(dev, "iio device register failed\n");
                goto error_buffer;
        }

        return indio_dev;

error_buffer:
        iio_triggered_buffer_cleanup(indio_dev);
error_trigger:
        remove_trigger(indio_dev, irq);
error_alloc:
        iio_device_free(indio_dev);

        return NULL;
}

void mgb4_trigger_free(struct iio_dev *indio_dev)
{
        struct trigger_data *st = iio_priv(indio_dev);

        iio_device_unregister(indio_dev);
        iio_triggered_buffer_cleanup(indio_dev);
        remove_trigger(indio_dev, xdma_get_user_irq(st->mgbdev->xdev, 11));
        iio_device_free(indio_dev);
}