From e3055d4b886373ea4372c631de130281973cd43a Mon Sep 17 00:00:00 2001 From: Joy Cho Date: Thu, 7 Mar 2019 14:28:41 +0900 Subject: [PATCH] media: rc: Add lirc helper to support user remote wakeup - set user remote wakeup key - set ir decode protocol for wakeup Change-Id: If083545f64d4e0b9c30bed89ce4bc9ecd37ea8ac --- .../boot/dts/amlogic/meson64_odroidn2.dts | 6 + arch/arm64/configs/odroidn2_android_defconfig | 18 +- drivers/media/rc/Kconfig | 12 + drivers/media/rc/Makefile | 1 + drivers/media/rc/hk-lirc-helper.c | 238 +++++++++++++++ drivers/media/rc/hk-lirc-helper.h | 271 ++++++++++++++++++ 6 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 drivers/media/rc/hk-lirc-helper.c create mode 100644 drivers/media/rc/hk-lirc-helper.h diff --git a/arch/arm64/boot/dts/amlogic/meson64_odroidn2.dts b/arch/arm64/boot/dts/amlogic/meson64_odroidn2.dts index ec23b6b750bb..9e4c8f463a92 100644 --- a/arch/arm64/boot/dts/amlogic/meson64_odroidn2.dts +++ b/arch/arm64/boot/dts/amlogic/meson64_odroidn2.dts @@ -455,6 +455,12 @@ reg = <0x0 0xFF800000 0x0 0x400>; }; + hk-lirc-helper { + compatible = "hk-lirc-helper"; + /* Multi-format IR controller */ + reg = <0x0 0xff808040 0x0 0x44>; + status = "ok"; + }; }; /* end of / */ &meson_fb { diff --git a/arch/arm64/configs/odroidn2_android_defconfig b/arch/arm64/configs/odroidn2_android_defconfig index dc8079365c50..8cb7f16403ce 100644 --- a/arch/arm64/configs/odroidn2_android_defconfig +++ b/arch/arm64/configs/odroidn2_android_defconfig @@ -2926,7 +2926,7 @@ CONFIG_MEDIA_CAMERA_SUPPORT=y CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y # CONFIG_MEDIA_RADIO_SUPPORT is not set # CONFIG_MEDIA_SDR_SUPPORT is not set -# CONFIG_MEDIA_RC_SUPPORT is not set +CONFIG_MEDIA_RC_SUPPORT=y # CONFIG_MEDIA_CONTROLLER is not set CONFIG_VIDEO_DEV=y CONFIG_VIDEO_V4L2=y @@ -2947,6 +2947,22 @@ CONFIG_DVB_MAX_ADAPTERS=8 # # Media drivers # +CONFIG_RC_CORE=y +# CONFIG_RC_MAP is not set +# CONFIG_RC_DECODERS is not set +CONFIG_RC_DEVICES=y +# CONFIG_RC_ATI_REMOTE is not set +# CONFIG_IR_HIX5HD2 is not set +# CONFIG_IR_IMON is not set +# CONFIG_IR_MCEUSB is not set +# CONFIG_IR_REDRAT3 is not set +# CONFIG_IR_STREAMZAP is not set +# CONFIG_IR_IGORPLUGUSB is not set +# CONFIG_IR_IGUANA is not set +# CONFIG_IR_TTUSBIR is not set +# CONFIG_RC_LOOPBACK is not set +# CONFIG_IR_GPIO_CIR is not set +CONFIG_IR_HK_LIRC_HELPER=y CONFIG_MEDIA_USB_SUPPORT=y # diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index 370e16e07867..233024b3975b 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -389,4 +389,16 @@ config IR_SUNXI To compile this driver as a module, choose M here: the module will be called sunxi-ir. +config IR_HK_LIRC_HELPER + tristate "Hardkernel LIRC helper driver" + depends on RC_CORE + ---help--- + Driver to parse IR decode type from LIRC timing + and convey custom power key pattern to bl301 + Say Y if you want to use the IR helper + on Hardkernel platform. + + To compile this driver as a module, choose M here: the + module will be called meson-ir. + endif #RC_DEVICES diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 379a5c0f1379..23926721b8b6 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_IR_TTUSBIR) += ttusbir.o obj-$(CONFIG_RC_ST) += st_rc.o obj-$(CONFIG_IR_SUNXI) += sunxi-cir.o obj-$(CONFIG_IR_IMG) += img-ir/ +obj-$(CONFIG_IR_HK_LIRC_HELPER) += hk-lirc-helper.o diff --git a/drivers/media/rc/hk-lirc-helper.c b/drivers/media/rc/hk-lirc-helper.c new file mode 100644 index 000000000000..3ed68b098694 --- /dev/null +++ b/drivers/media/rc/hk-lirc-helper.c @@ -0,0 +1,238 @@ +/* + * Driver for configuration of remote wakeup functionality + * Avilable to assign user remote wakeup key + * and its decode protocol + * + * Copyright (C) 2019 Hardkernel Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "hk-lirc-helper.h" + +#define DRIVER_NAME "hk-lirc-helper" + +static u32 remotewakeup = 0xffffffff; +static int decode_type = IR_DECODE_NEC; +void __iomem *ir_reg; + +const struct remote_reg_proto *remote_reg_proto_hk[] = { + ®_nec, + ®_duokan, + ®_xmp_1, + ®_xmp_1_sw, + ®_nec_sw, + ®_rc5, + ®_rc6, + ®_legacy_nec, + ®_rc6_21bit, + NULL +}; + +void remote_wakeup_decode_type(int dec_type) +{ + decode_type = dec_type; +} +EXPORT_SYMBOL(remote_wakeup_decode_type); + +static int remote_handle_usrkey(void) +{ + scpi_send_usr_data(SCPI_CL_REMOTE, &remotewakeup, + sizeof(remotewakeup)); + return 0; +} + +static void remote_nec_convert_key(void) +{ + u32 usr_key; + u32 code_inverse; + u16 code; + u16 i, shift; + + usr_key = remotewakeup; + remotewakeup = 0xffffffff; + + /* pre-data */ + remotewakeup = ((usr_key >> 16) & 0xffff); + + /* code bit inverse */ + code = (u16)(usr_key & 0xffff); + code_inverse = 0; + for (i = 0; i < 8; i++) { + shift = 15 - (2 * i); + code_inverse |= ((code & BIT_MASK(i)) << shift); + code_inverse |= ((code & BIT_MASK(15 - i)) >> shift); + } + remotewakeup |= (code_inverse << 16); +} + +static int remote_handle_protocol(void) +{ + const struct remote_reg_proto **reg_proto = remote_reg_proto_hk; + struct remote_reg_map *reg_map; + unsigned int size; + + while ((*reg_proto) != NULL) { + if (((*reg_proto)->decode_type) == decode_type) + break; + reg_proto++; + } + + reg_map = (*reg_proto)->reg_map; + size = (*reg_proto)->reg_map_size; + while (size) { + writel(reg_map->val, (ir_reg + reg_map->reg)); + reg_map++; + size--; + } + + return 0; +} + +static int __init remote_irdecode_type(char *str) +{ + if (str == NULL) { + decode_type = IR_DECODE_NEC; + return -EINVAL; + } + + if (!strncmp(str, "DUOKAN", 6)) + decode_type = IR_DECODE_DUOKAN; + else if (!strncmp(str, "NEC_LEGACY", 10)) + decode_type = IR_DECODE_LEGACY_NEC; + else if (!strncmp(str, "NEC_SW", 6)) + decode_type = IR_DECODE_NEC_SW; + else if (!strncmp(str, "NEC", 3)) + decode_type = IR_DECODE_NEC; + else if (!strncmp(str, "XMP_1_SW", 8)) + decode_type = IR_DECODE_XMP_1_SW; + else if (!strncmp(str, "XMP_1", 5)) + decode_type = IR_DECODE_XMP_1; + else if (!strncmp(str, "RC5", 3)) + decode_type = IR_DECODE_RC5; + else if (!strncmp(str, "RC6_21BIT", 9)) + decode_type = IR_DECODE_RC6_21BIT; + else if (!strncmp(str, "RC6", 3)) + decode_type = IR_DECODE_RC6; + /* default NEC protocol for invalid strings */ + else + decode_type = IR_DECODE_NEC; + + return 0; +} +__setup("irdecodetype=", remote_irdecode_type); + +static int __init remote_wakeup_setup(char *str) +{ + int ret; + + if (str == NULL) { + pr_info("%s no string\n", __func__); + return -EINVAL; + } + + ret = kstrtouint(str, 16, &remotewakeup); + if (ret) { + remotewakeup = 0xffffffff; + return -EINVAL; + } + + return 0; +} +__setup("remotewakeup=", remote_wakeup_setup); + +static int hk_lirc_helper_notifier_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_POWER_OFF) + remote_handle_protocol(); + + return NOTIFY_DONE; +} + +static struct notifier_block hk_lirc_helper_notifier = { + .notifier_call = hk_lirc_helper_notifier_sys, +}; + +static int hk_lirc_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + int ret; + + if (!(pdev->dev.of_node)) { + dev_err(dev, "pdev->dev.of_node == NULL\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + ir_reg = ioremap(res->start, res->end - res->start); + } else { + dev_info(dev, "Failed to get ir reg\n"); + ir_reg = NULL; + } + + ret = register_reboot_notifier(&hk_lirc_helper_notifier); + if (ret) + pr_err("%s register_reboot_notifier failed\n", __func__); + + /* check user remote wakeup key */ + if (decode_type == IR_DECODE_NEC) + remote_nec_convert_key(); + remote_handle_usrkey(); + + pr_info("lirc_helper: wakeupkey 0x%x, protocol 0x%x\n", + remotewakeup, decode_type); + + return 0; +} + +static int hk_lirc_helper_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id hk_lirc_helper_match[] = { + { .compatible = DRIVER_NAME }, + { }, +}; + +static struct platform_driver hk_lirc_helper_driver = { + .probe = hk_lirc_helper_probe, + .remove = hk_lirc_helper_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = hk_lirc_helper_match, + }, +}; + +module_platform_driver(hk_lirc_helper_driver); + +MODULE_DESCRIPTION("Hardkernel LIRC helper driver"); +MODULE_AUTHOR("Joy Cho "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/rc/hk-lirc-helper.h b/drivers/media/rc/hk-lirc-helper.h new file mode 100644 index 000000000000..5581d517dca8 --- /dev/null +++ b/drivers/media/rc/hk-lirc-helper.h @@ -0,0 +1,271 @@ +/* + * Driver for configuration of remote wakeup functionality + * Avilable to assign user remote wakeup key + * and its decode protocol + * + * Copyright (C) 2019 Hardkernel Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _HK_LIRC_HELPER_H_ +#define _HK_LIRC_HELPER_H_ + +struct remote_reg_map { + unsigned int reg; + unsigned int val; +}; + +struct remote_reg_proto { + char *name; + int decode_type; + struct remote_reg_map *reg_map; + int reg_map_size; +}; + +/* + * refer to protocol type definitions + * in include/dt-bindings/input/meson_rc.h + */ +enum ir_decode_type { + IR_DECODE_UNKNOWN = 0x0, + IR_DECODE_NEC, + IR_DECODE_DUOKAN, + IR_DECODE_XMP_1, + IR_DECODE_RC5, + IR_DECODE_RC6, + IR_DECODE_XMP_1_SW, + IR_DECODE_NEC_SW, + IR_DECODE_LEGACY_NEC, + IR_DECODE_RC6_21BIT, +}; + +enum remote_reg { + REG_LDR_ACTIVE = 0x00<<2, + REG_LDR_IDLE = 0x01<<2, + REG_LDR_REPEAT = 0x02<<2, + REG_BIT_0 = 0x03<<2, + REG_REG0 = 0x04<<2, + REG_FRAME = 0x05<<2, + REG_STATUS = 0x06<<2, + REG_REG1 = 0x07<<2, + REG_REG2 = 0x08<<2, + REG_DURATN2 = 0x09<<2, + REG_DURATN3 = 0x0a<<2, + REG_FRAME1 = 0x0b<<2, + REG_STATUS1 = 0x0c<<2, + REG_STATUS2 = 0x0d<<2, + REG_REG3 = 0x0e<<2, + REG_FRAME_RSV0 = 0x0f<<2, + REG_FRAME_RSV1 = 0x10<<2 +}; + +static struct remote_reg_map regs_legacy_nec[] = { + {REG_LDR_ACTIVE, (500 << 16) | (400 << 0)}, + {REG_LDR_IDLE, 300 << 16 | 200 << 0}, + {REG_LDR_REPEAT, 150 << 16 | 80 << 0}, + {REG_BIT_0, 72 << 16 | 40 << 0 }, + {REG_REG0, 7 << 28 | (0xFA0 << 12) | 0x13}, + {REG_STATUS, (134 << 20) | (90 << 10)}, + {REG_REG1, 0xbe00}, +}; + +static struct remote_reg_map regs_default_nec[] = { + { REG_LDR_ACTIVE, (500 << 16) | (400 << 0)}, + { REG_LDR_IDLE, 300 << 16 | 200 << 0}, + { REG_LDR_REPEAT, 150 << 16 | 80 << 0}, + { REG_BIT_0, 72 << 16 | 40 << 0}, + { REG_REG0, 7 << 28 | (0xFA0 << 12) | 0x13}, + { REG_STATUS, (134 << 20) | (90 << 10)}, + { REG_REG1, 0x9f00}, + { REG_REG2, 0x00}, + { REG_DURATN2, 0x00}, + { REG_DURATN3, 0x00} +}; + +static struct remote_reg_map regs_default_duokan[] = { + { REG_LDR_ACTIVE, ((70 << 16) | (30 << 0))}, + { REG_LDR_IDLE, ((50 << 16) | (15 << 0))}, + { REG_LDR_REPEAT, ((30 << 16) | (26 << 0))}, + { REG_BIT_0, ((66 << 16) | (40 << 0))}, + { REG_REG0, ((3 << 28) | (0x4e2 << 12) | (0x13))}, + { REG_STATUS, ((80 << 20) | (66 << 10))}, + { REG_REG1, 0x9300}, + { REG_REG2, 0xb90b}, + { REG_DURATN2, ((97 << 16) | (80 << 0))}, + { REG_DURATN3, ((120 << 16) | (97 << 0))}, + { REG_REG3, 5000<<0} +}; + +static struct remote_reg_map regs_default_xmp_1_sw[] = { + { REG_LDR_ACTIVE, 0}, + { REG_LDR_IDLE, 0}, + { REG_LDR_REPEAT, 0}, + { REG_BIT_0, 0}, + { REG_REG0, ((3 << 28) | (0xFA0 << 12) | (9))}, + { REG_STATUS, 0}, + { REG_REG1, 0x8574}, + { REG_REG2, 0x02}, + { REG_DURATN2, 0}, + { REG_DURATN3, 0}, + { REG_REG3, 0} +}; + +static struct remote_reg_map regs_default_xmp_1[] = { + { REG_LDR_ACTIVE, 0}, + { REG_LDR_IDLE, 0}, + { REG_LDR_REPEAT, 0}, + { REG_BIT_0, (52 << 16) | (45<<0)}, + { REG_REG0, ((7 << 28) | (0x5DC << 12) | (0x13))}, + { REG_STATUS, (87 << 20) | (80 << 10)}, + { REG_REG1, 0x9f00}, + { REG_REG2, 0xa90e}, + /*n=10,758+137*10=2128us,2128/20= 106*/ + { REG_DURATN2, (121<<16) | (114<<0)}, + { REG_DURATN3, (7<<16) | (7<<0)}, + { REG_REG3, 0} +}; + +static struct remote_reg_map regs_default_nec_sw[] = { + { REG_LDR_ACTIVE, 0}, + { REG_LDR_IDLE, 0}, + { REG_LDR_REPEAT, 0}, + { REG_BIT_0, 0}, + { REG_REG0, ((3 << 28) | (0xFA0 << 12) | (9))}, + { REG_STATUS, 0}, + { REG_REG1, 0x8574}, + { REG_REG2, 0x02}, + { REG_DURATN2, 0}, + { REG_DURATN3, 0}, + { REG_REG3, 0} +}; + +static struct remote_reg_map regs_default_rc5[] = { + { REG_LDR_ACTIVE, 0}, + { REG_LDR_IDLE, 0}, + { REG_LDR_REPEAT, 0}, + { REG_BIT_0, 0}, + { REG_REG0, ((3 << 28) | (0x1644 << 12) | 0x13)}, + { REG_STATUS, (1 << 30)}, + { REG_REG1, ((1 << 15) | (13 << 8))}, + /*bit[0-3]: RC5; bit[8]: MSB first mode; bit[11]: compare frame method*/ + { REG_REG2, ((1 << 13) | (1 << 11) | (1 << 8) | 0x7)}, + /*Half bit for RC5 format: 888.89us*/ + { REG_DURATN2, ((49 << 16) | (40 << 0))}, + /*RC5 typically 1777.78us for whole bit*/ + { REG_DURATN3, ((94 << 16) | (83 << 0))}, + { REG_REG3, 0} +}; + +static struct remote_reg_map regs_default_rc6[] = { + {REG_LDR_ACTIVE, (210 << 16) | (125 << 0)}, + /*rca leader 4000us,200* timebase*/ + {REG_LDR_IDLE, 50 << 16 | 38 << 0}, /* leader idle 400*/ + {REG_LDR_REPEAT, 145 << 16 | 125 << 0}, /* leader repeat*/ + /* logic '0' or '00' 1500us*/ + {REG_BIT_0, 51 << 16 | 38 << 0 }, + {REG_REG0, (7 << 28)|(0xFA0 << 12)|0x13}, + /* sys clock boby time.base time = 20 body frame*/ + {REG_STATUS, (94 << 20) | (82 << 10)}, + /*20bit:9440 32bit:9f40 36bit:a340 37bit:a440*/ + {REG_REG1, 0xa440}, + /*it may get the wrong customer value and key value from register if + *the value is set to 0x4,so the register value must set to 0x104 + */ + {REG_REG2, 0x2909}, + {REG_DURATN2, ((28 << 16) | (16 << 0))}, + {REG_DURATN3, ((51 << 16) | (38 << 0))}, +}; + +static struct remote_reg_map regs_default_rc6_21bit[] = { + {REG_LDR_ACTIVE, (210 << 16) | (125 << 0)}, + /*rca leader 4000us,200* timebase*/ + {REG_LDR_IDLE, 50 << 16 | 38 << 0}, /* leader idle 400*/ + {REG_LDR_REPEAT, 145 << 16 | 125 << 0}, /* leader repeat*/ + /* logic '0' or '00' 1500us*/ + {REG_BIT_0, 51 << 16 | 38 << 0 }, + {REG_REG0, (7 << 28)|(0xFA0 << 12)|0x13}, + /* sys clock boby time.base time = 20 body frame*/ + {REG_STATUS, (94 << 20) | (82 << 10)}, + /*20bit:9440 32bit:9f40 36bit:a340 37bit:a440*/ + {REG_REG1, 0x9440}, + /*it may get the wrong customer value and key value from register if + *the value is set to 0x4,so the register value must set to 0x104 + */ + {REG_REG2, 0x2909}, + {REG_DURATN2, ((28 << 16) | (16 << 0))}, + {REG_DURATN3, ((51 << 16) | (38 << 0))}, +}; + +static struct remote_reg_proto reg_legacy_nec = { + .name = "LEGACY_NEC", + .decode_type = IR_DECODE_LEGACY_NEC, + .reg_map = regs_legacy_nec, + .reg_map_size = ARRAY_SIZE(regs_legacy_nec), +}; + +static struct remote_reg_proto reg_nec = { + .name = "NEC", + .decode_type = IR_DECODE_NEC, + .reg_map = regs_default_nec, + .reg_map_size = ARRAY_SIZE(regs_default_nec), +}; + +static struct remote_reg_proto reg_duokan = { + .name = "DUOKAN", + .decode_type = IR_DECODE_DUOKAN, + .reg_map = regs_default_duokan, + .reg_map_size = ARRAY_SIZE(regs_default_duokan), +}; + +static struct remote_reg_proto reg_xmp_1_sw = { + .name = "XMP-1-RAW", + .decode_type = IR_DECODE_XMP_1_SW, + .reg_map = regs_default_xmp_1_sw, + .reg_map_size = ARRAY_SIZE(regs_default_xmp_1_sw), +}; + +static struct remote_reg_proto reg_xmp_1 = { + .name = "XMP-1", + .decode_type = IR_DECODE_XMP_1, + .reg_map = regs_default_xmp_1, + .reg_map_size = ARRAY_SIZE(regs_default_xmp_1), +}; + +static struct remote_reg_proto reg_nec_sw = { + .name = "NEC-SW", + .decode_type = IR_DECODE_NEC_SW, + .reg_map = regs_default_nec_sw, + .reg_map_size = ARRAY_SIZE(regs_default_nec_sw), +}; + +static struct remote_reg_proto reg_rc5 = { + .name = "RC5", + .decode_type = IR_DECODE_RC5, + .reg_map = regs_default_rc5, + .reg_map_size = ARRAY_SIZE(regs_default_rc5), +}; + +static struct remote_reg_proto reg_rc6 = { + .name = "RC6", + .decode_type = IR_DECODE_RC6, + .reg_map = regs_default_rc6, + .reg_map_size = ARRAY_SIZE(regs_default_rc6), +}; + +static struct remote_reg_proto reg_rc6_21bit = { + .name = "RC6_21BIT", + .decode_type = IR_DECODE_RC6_21BIT, + .reg_map = regs_default_rc6_21bit, + .reg_map_size = ARRAY_SIZE(regs_default_rc6_21bit), +}; + +#endif /* _HK_LIRC_HELPER_H_ */