root/drivers/staging/media/av7110/av7110_ir.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for the remote control of SAA7146 based AV7110 cards
 *
 * Copyright (C) 1999-2003 Holger Waechtler <holger@convergence.de>
 * Copyright (C) 2003-2007 Oliver Endriss <o.endriss@gmx.de>
 * Copyright (C) 2019 Sean Young <sean@mess.org>
 */

#include <linux/kernel.h>
#include <media/rc-core.h>

#include "av7110.h"
#include "av7110_hw.h"

#define IR_RC5          0
#define IR_RCMM         1
#define IR_RC5_EXT      2 /* internal only */

/* interrupt handler */
void av7110_ir_handler(struct av7110 *av7110, u32 ircom)
{
        struct rc_dev *rcdev = av7110->ir.rcdev;
        enum rc_proto proto;
        u32 command, addr, scancode;
        u32 toggle;

        dprintk(4, "ir command = %08x\n", ircom);

        if (rcdev) {
                switch (av7110->ir.ir_config) {
                case IR_RC5: /* RC5: 5 bits device address, 6 bits command */
                        command = ircom & 0x3f;
                        addr = (ircom >> 6) & 0x1f;
                        scancode = RC_SCANCODE_RC5(addr, command);
                        toggle = ircom & 0x0800;
                        proto = RC_PROTO_RC5;
                        break;

                case IR_RCMM: /* RCMM: 32 bits scancode */
                        scancode = ircom & ~0x8000;
                        toggle = ircom & 0x8000;
                        proto = RC_PROTO_RCMM32;
                        break;

                case IR_RC5_EXT:
                        /*
                         * extended RC5: 5 bits device address, 7 bits command
                         *
                         * Extended RC5 uses only one start bit. The second
                         * start bit is re-assigned bit 6 of the command bit.
                         */
                        command = ircom & 0x3f;
                        addr = (ircom >> 6) & 0x1f;
                        if (!(ircom & 0x1000))
                                command |= 0x40;
                        scancode = RC_SCANCODE_RC5(addr, command);
                        toggle = ircom & 0x0800;
                        proto = RC_PROTO_RC5;
                        break;
                default:
                        dprintk(2, "unknown ir config %d\n", av7110->ir.ir_config);
                        return;
                }

                rc_keydown(rcdev, proto, scancode, toggle != 0);
        }
}

int av7110_set_ir_config(struct av7110 *av7110)
{
        dprintk(4, "ir config = %08x\n", av7110->ir.ir_config);

        return av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1,
                             av7110->ir.ir_config);
}

static int change_protocol(struct rc_dev *rcdev, u64 *rc_type)
{
        struct av7110 *av7110 = rcdev->priv;
        u32 ir_config;

        if (*rc_type & RC_PROTO_BIT_RCMM32) {
                ir_config = IR_RCMM;
                *rc_type = RC_PROTO_BIT_RCMM32;
        } else if (*rc_type & RC_PROTO_BIT_RC5) {
                if (FW_VERSION(av7110->arm_app) >= 0x2620)
                        ir_config = IR_RC5_EXT;
                else
                        ir_config = IR_RC5;
                *rc_type = RC_PROTO_BIT_RC5;
        } else {
                return -EINVAL;
        }

        if (ir_config == av7110->ir.ir_config)
                return 0;

        av7110->ir.ir_config = ir_config;

        return av7110_set_ir_config(av7110);
}

int av7110_ir_init(struct av7110 *av7110)
{
        struct rc_dev *rcdev;
        struct pci_dev *pci;
        int ret;

        rcdev = rc_allocate_device(RC_DRIVER_SCANCODE);
        if (!rcdev)
                return -ENOMEM;

        pci = av7110->dev->pci;

        snprintf(av7110->ir.input_phys, sizeof(av7110->ir.input_phys),
                 "pci-%s/ir0", pci_name(pci));

        rcdev->device_name = av7110->card_name;
        rcdev->driver_name = KBUILD_MODNAME;
        rcdev->input_phys = av7110->ir.input_phys;
        rcdev->input_id.bustype = BUS_PCI;
        rcdev->input_id.version = 2;
        if (pci->subsystem_vendor) {
                rcdev->input_id.vendor  = pci->subsystem_vendor;
                rcdev->input_id.product = pci->subsystem_device;
        } else {
                rcdev->input_id.vendor  = pci->vendor;
                rcdev->input_id.product = pci->device;
        }

        rcdev->dev.parent = &pci->dev;
        rcdev->allowed_protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RCMM32;
        rcdev->change_protocol = change_protocol;
        rcdev->map_name = RC_MAP_HAUPPAUGE;
        rcdev->priv = av7110;

        av7110->ir.rcdev = rcdev;
        av7110->ir.ir_config = IR_RC5;
        av7110_set_ir_config(av7110);

        ret = rc_register_device(rcdev);
        if (ret) {
                av7110->ir.rcdev = NULL;
                rc_free_device(rcdev);
        }

        return ret;
}

void av7110_ir_exit(struct av7110 *av7110)
{
        rc_unregister_device(av7110->ir.rcdev);
}

//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
//MODULE_LICENSE("GPL");