18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for the remote control of SAA7146 based AV7110 cards
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 1999-2003 Holger Waechtler <holger@convergence.de>
68c2ecf20Sopenharmony_ci * Copyright (C) 2003-2007 Oliver Endriss <o.endriss@gmx.de>
78c2ecf20Sopenharmony_ci * Copyright (C) 2019 Sean Young <sean@mess.org>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <media/rc-core.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "av7110.h"
148c2ecf20Sopenharmony_ci#include "av7110_hw.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define IR_RC5		0
178c2ecf20Sopenharmony_ci#define IR_RCMM		1
188c2ecf20Sopenharmony_ci#define IR_RC5_EXT	2 /* internal only */
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/* interrupt handler */
218c2ecf20Sopenharmony_civoid av7110_ir_handler(struct av7110 *av7110, u32 ircom)
228c2ecf20Sopenharmony_ci{
238c2ecf20Sopenharmony_ci	struct rc_dev *rcdev = av7110->ir.rcdev;
248c2ecf20Sopenharmony_ci	enum rc_proto proto;
258c2ecf20Sopenharmony_ci	u32 command, addr, scancode;
268c2ecf20Sopenharmony_ci	u32 toggle;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	dprintk(4, "ir command = %08x\n", ircom);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	if (rcdev) {
318c2ecf20Sopenharmony_ci		switch (av7110->ir.ir_config) {
328c2ecf20Sopenharmony_ci		case IR_RC5: /* RC5: 5 bits device address, 6 bits command */
338c2ecf20Sopenharmony_ci			command = ircom & 0x3f;
348c2ecf20Sopenharmony_ci			addr = (ircom >> 6) & 0x1f;
358c2ecf20Sopenharmony_ci			scancode = RC_SCANCODE_RC5(addr, command);
368c2ecf20Sopenharmony_ci			toggle = ircom & 0x0800;
378c2ecf20Sopenharmony_ci			proto = RC_PROTO_RC5;
388c2ecf20Sopenharmony_ci			break;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci		case IR_RCMM: /* RCMM: 32 bits scancode */
418c2ecf20Sopenharmony_ci			scancode = ircom & ~0x8000;
428c2ecf20Sopenharmony_ci			toggle = ircom & 0x8000;
438c2ecf20Sopenharmony_ci			proto = RC_PROTO_RCMM32;
448c2ecf20Sopenharmony_ci			break;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci		case IR_RC5_EXT:
478c2ecf20Sopenharmony_ci			/*
488c2ecf20Sopenharmony_ci			 * extended RC5: 5 bits device address, 7 bits command
498c2ecf20Sopenharmony_ci			 *
508c2ecf20Sopenharmony_ci			 * Extended RC5 uses only one start bit. The second
518c2ecf20Sopenharmony_ci			 * start bit is re-assigned bit 6 of the command bit.
528c2ecf20Sopenharmony_ci			 */
538c2ecf20Sopenharmony_ci			command = ircom & 0x3f;
548c2ecf20Sopenharmony_ci			addr = (ircom >> 6) & 0x1f;
558c2ecf20Sopenharmony_ci			if (!(ircom & 0x1000))
568c2ecf20Sopenharmony_ci				command |= 0x40;
578c2ecf20Sopenharmony_ci			scancode = RC_SCANCODE_RC5(addr, command);
588c2ecf20Sopenharmony_ci			toggle = ircom & 0x0800;
598c2ecf20Sopenharmony_ci			proto = RC_PROTO_RC5;
608c2ecf20Sopenharmony_ci			break;
618c2ecf20Sopenharmony_ci		default:
628c2ecf20Sopenharmony_ci			dprintk(2, "unknown ir config %d\n",
638c2ecf20Sopenharmony_ci				av7110->ir.ir_config);
648c2ecf20Sopenharmony_ci			return;
658c2ecf20Sopenharmony_ci		}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci		rc_keydown(rcdev, proto, scancode, toggle != 0);
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ciint av7110_set_ir_config(struct av7110 *av7110)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	dprintk(4, "ir config = %08x\n", av7110->ir.ir_config);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1,
768c2ecf20Sopenharmony_ci			     av7110->ir.ir_config);
778c2ecf20Sopenharmony_ci}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic int change_protocol(struct rc_dev *rcdev, u64 *rc_type)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct av7110 *av7110 = rcdev->priv;
828c2ecf20Sopenharmony_ci	u32 ir_config;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (*rc_type & RC_PROTO_BIT_RCMM32) {
858c2ecf20Sopenharmony_ci		ir_config = IR_RCMM;
868c2ecf20Sopenharmony_ci		*rc_type = RC_PROTO_BIT_RCMM32;
878c2ecf20Sopenharmony_ci	} else if (*rc_type & RC_PROTO_BIT_RC5) {
888c2ecf20Sopenharmony_ci		if (FW_VERSION(av7110->arm_app) >= 0x2620)
898c2ecf20Sopenharmony_ci			ir_config = IR_RC5_EXT;
908c2ecf20Sopenharmony_ci		else
918c2ecf20Sopenharmony_ci			ir_config = IR_RC5;
928c2ecf20Sopenharmony_ci		*rc_type = RC_PROTO_BIT_RC5;
938c2ecf20Sopenharmony_ci	} else {
948c2ecf20Sopenharmony_ci		return -EINVAL;
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (ir_config == av7110->ir.ir_config)
988c2ecf20Sopenharmony_ci		return 0;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	av7110->ir.ir_config = ir_config;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	return av7110_set_ir_config(av7110);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ciint av7110_ir_init(struct av7110 *av7110)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	struct rc_dev *rcdev;
1088c2ecf20Sopenharmony_ci	struct pci_dev *pci;
1098c2ecf20Sopenharmony_ci	int ret;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	rcdev = rc_allocate_device(RC_DRIVER_SCANCODE);
1128c2ecf20Sopenharmony_ci	if (!rcdev)
1138c2ecf20Sopenharmony_ci		return -ENOMEM;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	pci = av7110->dev->pci;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	snprintf(av7110->ir.input_phys, sizeof(av7110->ir.input_phys),
1188c2ecf20Sopenharmony_ci		 "pci-%s/ir0", pci_name(pci));
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	rcdev->device_name = av7110->card_name;
1218c2ecf20Sopenharmony_ci	rcdev->driver_name = KBUILD_MODNAME;
1228c2ecf20Sopenharmony_ci	rcdev->input_phys = av7110->ir.input_phys;
1238c2ecf20Sopenharmony_ci	rcdev->input_id.bustype = BUS_PCI;
1248c2ecf20Sopenharmony_ci	rcdev->input_id.version = 2;
1258c2ecf20Sopenharmony_ci	if (pci->subsystem_vendor) {
1268c2ecf20Sopenharmony_ci		rcdev->input_id.vendor	= pci->subsystem_vendor;
1278c2ecf20Sopenharmony_ci		rcdev->input_id.product = pci->subsystem_device;
1288c2ecf20Sopenharmony_ci	} else {
1298c2ecf20Sopenharmony_ci		rcdev->input_id.vendor	= pci->vendor;
1308c2ecf20Sopenharmony_ci		rcdev->input_id.product = pci->device;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	rcdev->dev.parent = &pci->dev;
1348c2ecf20Sopenharmony_ci	rcdev->allowed_protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RCMM32;
1358c2ecf20Sopenharmony_ci	rcdev->change_protocol = change_protocol;
1368c2ecf20Sopenharmony_ci	rcdev->map_name = RC_MAP_HAUPPAUGE;
1378c2ecf20Sopenharmony_ci	rcdev->priv = av7110;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	av7110->ir.rcdev = rcdev;
1408c2ecf20Sopenharmony_ci	av7110->ir.ir_config = IR_RC5;
1418c2ecf20Sopenharmony_ci	av7110_set_ir_config(av7110);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	ret = rc_register_device(rcdev);
1448c2ecf20Sopenharmony_ci	if (ret) {
1458c2ecf20Sopenharmony_ci		av7110->ir.rcdev = NULL;
1468c2ecf20Sopenharmony_ci		rc_free_device(rcdev);
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	return ret;
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_civoid av7110_ir_exit(struct av7110 *av7110)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	rc_unregister_device(av7110->ir.rcdev);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
1588c2ecf20Sopenharmony_ci//MODULE_LICENSE("GPL");
159