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