root/drivers/media/rc/img-ir/img-ir-raw.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * ImgTec IR Raw Decoder found in PowerDown Controller.
 *
 * Copyright 2010-2014 Imagination Technologies Ltd.
 *
 * This ties into the input subsystem using the RC-core in raw mode. Raw IR
 * signal edges are reported and decoded by generic software decoders.
 */

#include <linux/spinlock.h>
#include <media/rc-core.h>
#include "img-ir.h"

#define ECHO_TIMEOUT_MS 150     /* ms between echos */

/* must be called with priv->lock held */
static void img_ir_refresh_raw(struct img_ir_priv *priv, u32 irq_status)
{
        struct img_ir_priv_raw *raw = &priv->raw;
        struct rc_dev *rc_dev = priv->raw.rdev;
        int multiple;
        u32 ir_status;

        /* find whether both rise and fall was detected */
        multiple = ((irq_status & IMG_IR_IRQ_EDGE) == IMG_IR_IRQ_EDGE);
        /*
         * If so, we need to see if the level has actually changed.
         * If it's just noise that we didn't have time to process,
         * there's no point reporting it.
         */
        ir_status = img_ir_read(priv, IMG_IR_STATUS) & IMG_IR_IRRXD;
        if (multiple && ir_status == raw->last_status)
                return;
        raw->last_status = ir_status;

        /* report the edge to the IR raw decoders */
        if (ir_status) /* low */
                ir_raw_event_store_edge(rc_dev, false);
        else /* high */
                ir_raw_event_store_edge(rc_dev, true);
        ir_raw_event_handle(rc_dev);
}

/* called with priv->lock held */
void img_ir_isr_raw(struct img_ir_priv *priv, u32 irq_status)
{
        struct img_ir_priv_raw *raw = &priv->raw;

        /* check not removing */
        if (!raw->rdev)
                return;

        img_ir_refresh_raw(priv, irq_status);

        /* start / push back the echo timer */
        mod_timer(&raw->timer, jiffies + msecs_to_jiffies(ECHO_TIMEOUT_MS));
}

/*
 * Echo timer callback function.
 * The raw decoders expect to get a final sample even if there are no edges, in
 * order to be assured of the final space. If there are no edges for a certain
 * time we use this timer to emit a final sample to satisfy them.
 */
static void img_ir_echo_timer(struct timer_list *t)
{
        struct img_ir_priv *priv = timer_container_of(priv, t, raw.timer);

        spin_lock_irq(&priv->lock);

        /* check not removing */
        if (priv->raw.rdev)
                /*
                 * It's safe to pass irq_status=0 since it's only used to check
                 * for double edges.
                 */
                img_ir_refresh_raw(priv, 0);

        spin_unlock_irq(&priv->lock);
}

void img_ir_setup_raw(struct img_ir_priv *priv)
{
        u32 irq_en;

        if (!priv->raw.rdev)
                return;

        /* clear and enable edge interrupts */
        spin_lock_irq(&priv->lock);
        irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE);
        irq_en |= IMG_IR_IRQ_EDGE;
        img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
        img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en);
        spin_unlock_irq(&priv->lock);
}

int img_ir_probe_raw(struct img_ir_priv *priv)
{
        struct img_ir_priv_raw *raw = &priv->raw;
        struct rc_dev *rdev;
        int error;

        /* Set up the echo timer */
        timer_setup(&raw->timer, img_ir_echo_timer, 0);

        /* Allocate raw decoder */
        raw->rdev = rdev = rc_allocate_device(RC_DRIVER_IR_RAW);
        if (!rdev) {
                dev_err(priv->dev, "cannot allocate raw input device\n");
                return -ENOMEM;
        }
        rdev->priv = priv;
        rdev->map_name = RC_MAP_EMPTY;
        rdev->device_name = "IMG Infrared Decoder Raw";

        /* Register raw decoder */
        error = rc_register_device(rdev);
        if (error) {
                dev_err(priv->dev, "failed to register raw IR input device\n");
                rc_free_device(rdev);
                raw->rdev = NULL;
                return error;
        }

        return 0;
}

void img_ir_remove_raw(struct img_ir_priv *priv)
{
        struct img_ir_priv_raw *raw = &priv->raw;
        struct rc_dev *rdev = raw->rdev;
        u32 irq_en;

        if (!rdev)
                return;

        /* switch off and disable raw (edge) interrupts */
        spin_lock_irq(&priv->lock);
        raw->rdev = NULL;
        irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE);
        irq_en &= ~IMG_IR_IRQ_EDGE;
        img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en);
        img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
        spin_unlock_irq(&priv->lock);

        rc_unregister_device(rdev);

        timer_delete_sync(&raw->timer);
}