From e5dd86446fe3ed91b696bb4eced78631f7c7a7d4 Mon Sep 17 00:00:00 2001 From: lijian Date: Thu, 10 Jun 2021 05:15:04 +0200 Subject: media: rc: streamzap: Removed unnecessary 'return' Removed unnecessary 'return'. Signed-off-by: lijian Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/streamzap.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c index 9f3cd9fb6b6e..9cd765e31c49 100644 --- a/drivers/media/rc/streamzap.c +++ b/drivers/media/rc/streamzap.c @@ -265,8 +265,6 @@ static void streamzap_callback(struct urb *urb) ir_raw_event_handle(sz->rdev); usb_submit_urb(urb, GFP_ATOMIC); - - return; } static struct rc_dev *streamzap_init_rc_dev(struct streamzap_ir *sz) -- cgit From adb08a09230b8e7337dc162815288a189e9ee30f Mon Sep 17 00:00:00 2001 From: lijian Date: Thu, 10 Jun 2021 05:24:13 +0200 Subject: media: rc: redrat3: Fix a typo Change 'an' to 'on'. Signed-off-by: lijian Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/redrat3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 2cf3377ec63a..ac85464864b9 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -6,7 +6,7 @@ * based heavily on the work of Stephen Cox, with additional * help from RedRat Ltd. * - * This driver began life based an an old version of the first-generation + * This driver began life based on an old version of the first-generation * lirc_mceusb driver from the lirc 0.7.2 distribution. It was then * significantly rewritten by Stephen Cox with the aid of RedRat Ltd's * Chris Dodge. -- cgit From 86541f0480b574cf299563156ac22ebf266ea5ec Mon Sep 17 00:00:00 2001 From: lijian Date: Thu, 10 Jun 2021 11:27:25 +0200 Subject: media: rc: rc-main.c: deleted the repeated word deleted the repeated word 'the' in the comments. Signed-off-by: lijian Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/rc-main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 8e88dc8ea6c5..b90438a71c80 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -1940,7 +1940,7 @@ int rc_register_device(struct rc_dev *dev) kfree(path); /* - * once the the input device is registered in rc_setup_rx_device, + * once the input device is registered in rc_setup_rx_device, * userspace can open the input device and rc_open() will be called * as a result. This results in driver code being allowed to submit * keycodes with rc_keydown, so lirc must be registered first. -- cgit From 6b7f554be8c92319d7e6df92fd247ebb9beb4a45 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 3 Jul 2021 15:37:17 +0200 Subject: media: rc-loopback: return number of emitters rather than error The LIRC_SET_TRANSMITTER_MASK ioctl should return the number of emitters if an invalid list was set. Cc: stable@vger.kernel.org Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/rc-loopback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 1ba3f96ffa7d..40ab66c850f2 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -42,7 +42,7 @@ static int loop_set_tx_mask(struct rc_dev *dev, u32 mask) if ((mask & (RXMASK_REGULAR | RXMASK_LEARNING)) != mask) { dprintk("invalid tx mask: %u\n", mask); - return -EINVAL; + return 2; } dprintk("setting tx mask: %u\n", mask); -- cgit From d9d0103940e981ba424b832a7143b547d9364b5b Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 3 Jul 2021 15:36:15 +0200 Subject: media: rc-loopback: use dev_dbg() rather than handrolled debug Make use of dynamic debug. Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/rc-loopback.c | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 40ab66c850f2..5adfbb6989ee 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -16,12 +16,9 @@ #include #define DRIVER_NAME "rc-loopback" -#define dprintk(x...) if (debug) printk(KERN_INFO DRIVER_NAME ": " x) #define RXMASK_REGULAR 0x1 #define RXMASK_LEARNING 0x2 -static bool debug; - struct loopback_dev { struct rc_dev *dev; u32 txmask; @@ -41,11 +38,11 @@ static int loop_set_tx_mask(struct rc_dev *dev, u32 mask) struct loopback_dev *lodev = dev->priv; if ((mask & (RXMASK_REGULAR | RXMASK_LEARNING)) != mask) { - dprintk("invalid tx mask: %u\n", mask); + dev_dbg(&dev->dev, "invalid tx mask: %u\n", mask); return 2; } - dprintk("setting tx mask: %u\n", mask); + dev_dbg(&dev->dev, "setting tx mask: %u\n", mask); lodev->txmask = mask; return 0; } @@ -54,7 +51,7 @@ static int loop_set_tx_carrier(struct rc_dev *dev, u32 carrier) { struct loopback_dev *lodev = dev->priv; - dprintk("setting tx carrier: %u\n", carrier); + dev_dbg(&dev->dev, "setting tx carrier: %u\n", carrier); lodev->txcarrier = carrier; return 0; } @@ -64,11 +61,11 @@ static int loop_set_tx_duty_cycle(struct rc_dev *dev, u32 duty_cycle) struct loopback_dev *lodev = dev->priv; if (duty_cycle < 1 || duty_cycle > 99) { - dprintk("invalid duty cycle: %u\n", duty_cycle); + dev_dbg(&dev->dev, "invalid duty cycle: %u\n", duty_cycle); return -EINVAL; } - dprintk("setting duty cycle: %u\n", duty_cycle); + dev_dbg(&dev->dev, "setting duty cycle: %u\n", duty_cycle); lodev->txduty = duty_cycle; return 0; } @@ -78,11 +75,11 @@ static int loop_set_rx_carrier_range(struct rc_dev *dev, u32 min, u32 max) struct loopback_dev *lodev = dev->priv; if (min < 1 || min > max) { - dprintk("invalid rx carrier range %u to %u\n", min, max); + dev_dbg(&dev->dev, "invalid rx carrier range %u to %u\n", min, max); return -EINVAL; } - dprintk("setting rx carrier range %u to %u\n", min, max); + dev_dbg(&dev->dev, "setting rx carrier range %u to %u\n", min, max); lodev->rxcarriermin = min; lodev->rxcarriermax = max; return 0; @@ -97,7 +94,7 @@ static int loop_tx_ir(struct rc_dev *dev, unsigned *txbuf, unsigned count) if (lodev->txcarrier < lodev->rxcarriermin || lodev->txcarrier > lodev->rxcarriermax) { - dprintk("ignoring tx, carrier out of range\n"); + dev_dbg(&dev->dev, "ignoring tx, carrier out of range\n"); goto out; } @@ -107,7 +104,7 @@ static int loop_tx_ir(struct rc_dev *dev, unsigned *txbuf, unsigned count) rxmask = RXMASK_REGULAR; if (!(rxmask & lodev->txmask)) { - dprintk("ignoring tx, rx mask mismatch\n"); + dev_dbg(&dev->dev, "ignoring tx, rx mask mismatch\n"); goto out; } @@ -134,7 +131,7 @@ static void loop_set_idle(struct rc_dev *dev, bool enable) struct loopback_dev *lodev = dev->priv; if (lodev->idle != enable) { - dprintk("%sing idle mode\n", enable ? "enter" : "exit"); + dev_dbg(&dev->dev, "%sing idle mode\n", enable ? "enter" : "exit"); lodev->idle = enable; } } @@ -144,7 +141,7 @@ static int loop_set_learning_mode(struct rc_dev *dev, int enable) struct loopback_dev *lodev = dev->priv; if (lodev->learning != enable) { - dprintk("%sing learning mode\n", enable ? "enter" : "exit"); + dev_dbg(&dev->dev, "%sing learning mode\n", enable ? "enter" : "exit"); lodev->learning = !!enable; } @@ -156,7 +153,7 @@ static int loop_set_carrier_report(struct rc_dev *dev, int enable) struct loopback_dev *lodev = dev->priv; if (lodev->carrierreport != enable) { - dprintk("%sabling carrier reports\n", enable ? "en" : "dis"); + dev_dbg(&dev->dev, "%sabling carrier reports\n", enable ? "en" : "dis"); lodev->carrierreport = !!enable; } @@ -204,10 +201,8 @@ static int __init loop_init(void) int ret; rc = rc_allocate_device(RC_DRIVER_IR_RAW); - if (!rc) { - printk(KERN_ERR DRIVER_NAME ": rc_dev allocation failed\n"); + if (!rc) return -ENOMEM; - } rc->device_name = "rc-core loopback device"; rc->input_phys = "rc-core/virtual"; @@ -245,7 +240,7 @@ static int __init loop_init(void) ret = rc_register_device(rc); if (ret < 0) { - printk(KERN_ERR DRIVER_NAME ": rc_dev registration failed\n"); + dev_err(&rc->dev, "rc_dev registration failed\n"); rc_free_device(rc); return ret; } @@ -262,9 +257,6 @@ static void __exit loop_exit(void) module_init(loop_init); module_exit(loop_exit); -module_param(debug, bool, S_IRUGO | S_IWUSR); -MODULE_PARM_DESC(debug, "Enable debug messages"); - MODULE_DESCRIPTION("Loopback device for rc-core debugging"); MODULE_AUTHOR("David Härdeman "); MODULE_LICENSE("GPL"); -- cgit From 7300d0c2391a08d03b69100c454146b578322fa3 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 3 Jul 2021 15:51:10 +0200 Subject: media: rc-loopback: send carrier reports When carrier reports are enabled, send them over loopback. Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/rc-loopback.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 5adfbb6989ee..67782dd7ee4d 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -111,8 +111,16 @@ static int loop_tx_ir(struct rc_dev *dev, unsigned *txbuf, unsigned count) for (i = 0; i < count; i++) { rawir.pulse = i % 2 ? false : true; rawir.duration = txbuf[i]; - if (rawir.duration) - ir_raw_event_store_with_filter(dev, &rawir); + + ir_raw_event_store_with_filter(dev, &rawir); + } + + if (lodev->carrierreport) { + rawir.pulse = false; + rawir.carrier_report = true; + rawir.carrier = lodev->txcarrier; + + ir_raw_event_store(dev, &rawir); } /* Fake a silence long enough to cause us to go idle */ -- cgit From 50634548d3c360c19013f665964e6c5b8300ff05 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 5 Jul 2021 19:56:01 +0200 Subject: media: rc-loopback: max_timeout of UINT_MAX does not work Any timeout larger than LIRC_VALUE_MASK cannot work for the lirc uapi. LIRC_VALUE_MASK is about 16 seconds which is more than enough. Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/rc-loopback.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 67782dd7ee4d..40fca1226b09 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -222,9 +222,9 @@ static int __init loop_init(void) rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; rc->allowed_wakeup_protocols = RC_PROTO_BIT_ALL_IR_ENCODER; rc->encode_wakeup = true; - rc->timeout = MS_TO_US(100); /* 100 ms */ + rc->timeout = IR_DEFAULT_TIMEOUT; rc->min_timeout = 1; - rc->max_timeout = UINT_MAX; + rc->max_timeout = IR_MAX_TIMEOUT; rc->rx_resolution = 1; rc->tx_resolution = 1; rc->s_tx_mask = loop_set_tx_mask; -- cgit From 8b777edff097ca9bb564529913f3a934d59112f4 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 3 Jul 2021 11:04:40 +0200 Subject: media: rc: rename s_learning_mode() to s_wideband_receiver() The s_learning_mode() function is called in response to the ioctl LIRC_SET_WIDEBAND_RECEIVER, so rename it to s_wideband_receiver(). Learning mode is when both the wideband receiver is turned on and carrier reports are enabled. Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/ene_ir.c | 2 +- drivers/media/rc/lirc_dev.c | 6 +++--- drivers/media/rc/mceusb.c | 2 +- drivers/media/rc/rc-loopback.c | 30 +++++++++++++++--------------- 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c index 6049e5c95394..e09270916fbc 100644 --- a/drivers/media/rc/ene_ir.c +++ b/drivers/media/rc/ene_ir.c @@ -1052,7 +1052,7 @@ static int ene_probe(struct pnp_dev *pnp_dev, const struct pnp_device_id *id) rdev->device_name = "ENE eHome Infrared Remote Receiver"; if (dev->hw_learning_and_tx_capable) { - rdev->s_learning_mode = ene_set_learning_mode; + rdev->s_wideband_receiver = ene_set_learning_mode; init_completion(&dev->tx_complete); rdev->tx_ir = ene_transmit; rdev->s_tx_mask = ene_set_tx_mask; diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 116daf90c858..7f591ff5269d 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -412,7 +412,7 @@ static long lirc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) val |= LIRC_CAN_SET_REC_CARRIER | LIRC_CAN_SET_REC_CARRIER_RANGE; - if (dev->s_learning_mode) + if (dev->s_wideband_receiver) val |= LIRC_CAN_USE_WIDEBAND_RECEIVER; if (dev->s_carrier_report) @@ -519,10 +519,10 @@ static long lirc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; case LIRC_SET_WIDEBAND_RECEIVER: - if (!dev->s_learning_mode) + if (!dev->s_wideband_receiver) ret = -ENOTTY; else - ret = dev->s_learning_mode(dev, !!val); + ret = dev->s_wideband_receiver(dev, !!val); break; case LIRC_SET_MEASURE_CARRIER_MODE: diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 5642595a057e..e03dd1f0144f 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -1630,7 +1630,7 @@ static struct rc_dev *mceusb_init_rc_dev(struct mceusb_dev *ir) rc->tx_ir = mceusb_tx_ir; } if (ir->flags.rx2 > 0) { - rc->s_learning_mode = mceusb_set_rx_wideband; + rc->s_wideband_receiver = mceusb_set_rx_wideband; rc->s_carrier_report = mceusb_set_rx_carrier_report; } rc->driver_name = DRIVER_NAME; diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c index 40fca1226b09..6441879fcba1 100644 --- a/drivers/media/rc/rc-loopback.c +++ b/drivers/media/rc/rc-loopback.c @@ -15,9 +15,9 @@ #include #include -#define DRIVER_NAME "rc-loopback" -#define RXMASK_REGULAR 0x1 -#define RXMASK_LEARNING 0x2 +#define DRIVER_NAME "rc-loopback" +#define RXMASK_NARROWBAND 0x1 +#define RXMASK_WIDEBAND 0x2 struct loopback_dev { struct rc_dev *dev; @@ -25,7 +25,7 @@ struct loopback_dev { u32 txcarrier; u32 txduty; bool idle; - bool learning; + bool wideband; bool carrierreport; u32 rxcarriermin; u32 rxcarriermax; @@ -37,7 +37,7 @@ static int loop_set_tx_mask(struct rc_dev *dev, u32 mask) { struct loopback_dev *lodev = dev->priv; - if ((mask & (RXMASK_REGULAR | RXMASK_LEARNING)) != mask) { + if ((mask & (RXMASK_NARROWBAND | RXMASK_WIDEBAND)) != mask) { dev_dbg(&dev->dev, "invalid tx mask: %u\n", mask); return 2; } @@ -98,10 +98,10 @@ static int loop_tx_ir(struct rc_dev *dev, unsigned *txbuf, unsigned count) goto out; } - if (lodev->learning) - rxmask = RXMASK_LEARNING; + if (lodev->wideband) + rxmask = RXMASK_WIDEBAND; else - rxmask = RXMASK_REGULAR; + rxmask = RXMASK_NARROWBAND; if (!(rxmask & lodev->txmask)) { dev_dbg(&dev->dev, "ignoring tx, rx mask mismatch\n"); @@ -144,13 +144,13 @@ static void loop_set_idle(struct rc_dev *dev, bool enable) } } -static int loop_set_learning_mode(struct rc_dev *dev, int enable) +static int loop_set_wideband_receiver(struct rc_dev *dev, int enable) { struct loopback_dev *lodev = dev->priv; - if (lodev->learning != enable) { - dev_dbg(&dev->dev, "%sing learning mode\n", enable ? "enter" : "exit"); - lodev->learning = !!enable; + if (lodev->wideband != enable) { + dev_dbg(&dev->dev, "using %sband receiver\n", enable ? "wide" : "narrow"); + lodev->wideband = !!enable; } return 0; @@ -233,17 +233,17 @@ static int __init loop_init(void) rc->s_rx_carrier_range = loop_set_rx_carrier_range; rc->tx_ir = loop_tx_ir; rc->s_idle = loop_set_idle; - rc->s_learning_mode = loop_set_learning_mode; + rc->s_wideband_receiver = loop_set_wideband_receiver; rc->s_carrier_report = loop_set_carrier_report; rc->s_wakeup_filter = loop_set_wakeup_filter; - loopdev.txmask = RXMASK_REGULAR; + loopdev.txmask = RXMASK_NARROWBAND; loopdev.txcarrier = 36000; loopdev.txduty = 50; loopdev.rxcarriermin = 1; loopdev.rxcarriermax = ~0; loopdev.idle = true; - loopdev.learning = false; + loopdev.wideband = false; loopdev.carrierreport = false; ret = rc_register_device(rc); -- cgit From 49be1c78d575eedf862a05aebfd5ab5c24193f61 Mon Sep 17 00:00:00 2001 From: Viktor Prutyanov Date: Mon, 19 Jul 2021 19:05:06 +0200 Subject: media: rc: introduce Meson IR TX driver This patch adds the driver for Amlogic Meson IR transmitter. Some Amlogic SoCs such as A311D and T950D4 have IR transmitter (also called blaster) controller onboard. It is capable of sending IR signals with arbitrary carrier frequency and duty cycle. The driver supports 2 modulation clock sources: - xtal3 clock (xtal divided by 3) - 1us clock Signed-off-by: Viktor Prutyanov Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/media/rc/Kconfig | 10 + drivers/media/rc/Makefile | 1 + drivers/media/rc/meson-ir-tx.c | 407 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 drivers/media/rc/meson-ir-tx.c (limited to 'drivers/media/rc') diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index d0a8326b75c2..fd5a7a058714 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -246,6 +246,16 @@ config IR_MESON To compile this driver as a module, choose M here: the module will be called meson-ir. +config IR_MESON_TX + tristate "Amlogic Meson IR TX" + depends on ARCH_MESON || COMPILE_TEST + help + Say Y if you want to use the IR transmitter available on + Amlogic Meson SoCs. + + To compile this driver as a module, choose M here: the + module will be called meson-ir-tx. + config IR_MTK tristate "Mediatek IR remote receiver" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 692e9b6b203f..0db51fad27d6 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_IR_ITE_CIR) += ite-cir.o obj-$(CONFIG_IR_MCEUSB) += mceusb.o obj-$(CONFIG_IR_FINTEK) += fintek-cir.o obj-$(CONFIG_IR_MESON) += meson-ir.o +obj-$(CONFIG_IR_MESON_TX) += meson-ir-tx.o obj-$(CONFIG_IR_NUVOTON) += nuvoton-cir.o obj-$(CONFIG_IR_ENE) += ene_ir.o obj-$(CONFIG_IR_REDRAT3) += redrat3.o diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c new file mode 100644 index 000000000000..3055f8e1b6ff --- /dev/null +++ b/drivers/media/rc/meson-ir-tx.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * meson-ir-tx.c - Amlogic Meson IR TX driver + * + * Copyright (c) 2021, SberDevices. All Rights Reserved. + * + * Author: Viktor Prutyanov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "Meson IR TX" +#define DRIVER_NAME "meson-ir-tx" + +#define MIRTX_DEFAULT_CARRIER 38000 +#define MIRTX_DEFAULT_DUTY_CYCLE 50 +#define MIRTX_FIFO_THD 32 + +#define IRB_MOD_1US_CLK_RATE 1000000 + +#define IRB_FIFO_LEN 128 + +#define IRB_ADDR0 0x0 +#define IRB_ADDR1 0x4 +#define IRB_ADDR2 0x8 +#define IRB_ADDR3 0xc + +#define IRB_MAX_DELAY (1 << 10) +#define IRB_DELAY_MASK (IRB_MAX_DELAY - 1) + +/* IRCTRL_IR_BLASTER_ADDR0 */ +#define IRB_MOD_CLK(x) ((x) << 12) +#define IRB_MOD_SYS_CLK 0 +#define IRB_MOD_XTAL3_CLK 1 +#define IRB_MOD_1US_CLK 2 +#define IRB_MOD_10US_CLK 3 +#define IRB_INIT_HIGH BIT(2) +#define IRB_ENABLE BIT(0) + +/* IRCTRL_IR_BLASTER_ADDR2 */ +#define IRB_MOD_COUNT(lo, hi) ((((lo) - 1) << 16) | ((hi) - 1)) + +/* IRCTRL_IR_BLASTER_ADDR2 */ +#define IRB_WRITE_FIFO BIT(16) +#define IRB_MOD_ENABLE BIT(12) +#define IRB_TB_1US (0x0 << 10) +#define IRB_TB_10US (0x1 << 10) +#define IRB_TB_100US (0x2 << 10) +#define IRB_TB_MOD_CLK (0x3 << 10) + +/* IRCTRL_IR_BLASTER_ADDR3 */ +#define IRB_FIFO_THD_PENDING BIT(16) +#define IRB_FIFO_IRQ_ENABLE BIT(8) + +struct meson_irtx { + struct device *dev; + void __iomem *reg_base; + u32 *buf; + unsigned int buf_len; + unsigned int buf_head; + unsigned int carrier; + unsigned int duty_cycle; + /* Locks buf */ + spinlock_t lock; + struct completion completion; + unsigned long clk_rate; +}; + +static void meson_irtx_set_mod(struct meson_irtx *ir) +{ + unsigned int cnt = DIV_ROUND_CLOSEST(ir->clk_rate, ir->carrier); + unsigned int pulse_cnt = DIV_ROUND_CLOSEST(cnt * ir->duty_cycle, 100); + unsigned int space_cnt = cnt - pulse_cnt; + + dev_dbg(ir->dev, "F_mod = %uHz, T_mod = %luns, duty_cycle = %u%%\n", + ir->carrier, NSEC_PER_SEC / ir->clk_rate * cnt, + 100 * pulse_cnt / cnt); + + writel(IRB_MOD_COUNT(pulse_cnt, space_cnt), + ir->reg_base + IRB_ADDR1); +} + +static void meson_irtx_setup(struct meson_irtx *ir, unsigned int clk_nr) +{ + /* + * Disable the TX, set modulator clock tick and set initialize + * output to be high. Set up carrier frequency and duty cycle. Then + * unset initialize output. Enable FIFO interrupt, set FIFO interrupt + * threshold. Finally, enable the transmitter back. + */ + writel(~IRB_ENABLE & (IRB_MOD_CLK(clk_nr) | IRB_INIT_HIGH), + ir->reg_base + IRB_ADDR0); + meson_irtx_set_mod(ir); + writel(readl(ir->reg_base + IRB_ADDR0) & ~IRB_INIT_HIGH, + ir->reg_base + IRB_ADDR0); + writel(IRB_FIFO_IRQ_ENABLE | MIRTX_FIFO_THD, + ir->reg_base + IRB_ADDR3); + writel(readl(ir->reg_base + IRB_ADDR0) | IRB_ENABLE, + ir->reg_base + IRB_ADDR0); +} + +static u32 meson_irtx_prepare_pulse(struct meson_irtx *ir, unsigned int time) +{ + unsigned int delay; + unsigned int tb = IRB_TB_MOD_CLK; + unsigned int tb_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, ir->carrier); + + delay = (DIV_ROUND_CLOSEST(time, tb_us) - 1) & IRB_DELAY_MASK; + + return ((IRB_WRITE_FIFO | IRB_MOD_ENABLE) | tb | delay); +} + +static u32 meson_irtx_prepare_space(struct meson_irtx *ir, unsigned int time) +{ + unsigned int delay; + unsigned int tb = IRB_TB_100US; + unsigned int tb_us = 100; + + if (time <= IRB_MAX_DELAY) { + tb = IRB_TB_1US; + tb_us = 1; + } else if (time <= 10 * IRB_MAX_DELAY) { + tb = IRB_TB_10US; + tb_us = 10; + } else if (time <= 100 * IRB_MAX_DELAY) { + tb = IRB_TB_100US; + tb_us = 100; + } + + delay = (DIV_ROUND_CLOSEST(time, tb_us) - 1) & IRB_DELAY_MASK; + + return ((IRB_WRITE_FIFO & ~IRB_MOD_ENABLE) | tb | delay); +} + +static void meson_irtx_send_buffer(struct meson_irtx *ir) +{ + unsigned int nr = 0; + unsigned int max_fifo_level = IRB_FIFO_LEN - MIRTX_FIFO_THD; + + while (ir->buf_head < ir->buf_len && nr < max_fifo_level) { + writel(ir->buf[ir->buf_head], ir->reg_base + IRB_ADDR2); + + ir->buf_head++; + nr++; + } +} + +static bool meson_irtx_check_buf(struct meson_irtx *ir, + unsigned int *buf, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + unsigned int max_tb_us; + /* + * Max space timebase is 100 us. + * Pulse timebase equals to carrier period. + */ + if (i % 2 == 0) + max_tb_us = USEC_PER_SEC / ir->carrier; + else + max_tb_us = 100; + + if (buf[i] >= max_tb_us * IRB_MAX_DELAY) + return false; + } + + return true; +} + +static void meson_irtx_fill_buf(struct meson_irtx *ir, u32 *dst_buf, + unsigned int *src_buf, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + if (i % 2 == 0) + dst_buf[i] = meson_irtx_prepare_pulse(ir, src_buf[i]); + else + dst_buf[i] = meson_irtx_prepare_space(ir, src_buf[i]); + } +} + +static irqreturn_t meson_irtx_irqhandler(int irq, void *data) +{ + unsigned long flags; + struct meson_irtx *ir = data; + + writel(readl(ir->reg_base + IRB_ADDR3) & ~IRB_FIFO_THD_PENDING, + ir->reg_base + IRB_ADDR3); + + if (completion_done(&ir->completion)) + return IRQ_HANDLED; + + spin_lock_irqsave(&ir->lock, flags); + if (ir->buf_head < ir->buf_len) + meson_irtx_send_buffer(ir); + else + complete(&ir->completion); + spin_unlock_irqrestore(&ir->lock, flags); + + return IRQ_HANDLED; +} + +static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier) +{ + struct meson_irtx *ir = rc->priv; + + if (carrier == 0) + return -EINVAL; + + ir->carrier = carrier; + meson_irtx_set_mod(ir); + + return 0; +} + +static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32 duty_cycle) +{ + struct meson_irtx *ir = rc->priv; + + ir->duty_cycle = duty_cycle; + meson_irtx_set_mod(ir); + + return 0; +} + +static void meson_irtx_update_buf(struct meson_irtx *ir, u32 *buf, + unsigned int len, unsigned int head) +{ + ir->buf = buf; + ir->buf_len = len; + ir->buf_head = head; +} + +static int meson_irtx_transmit(struct rc_dev *rc, unsigned int *buf, + unsigned int len) +{ + unsigned long flags; + struct meson_irtx *ir = rc->priv; + u32 *tx_buf; + int ret = len; + + if (!meson_irtx_check_buf(ir, buf, len)) + return -EINVAL; + + tx_buf = kmalloc_array(len, sizeof(u32), GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + meson_irtx_fill_buf(ir, tx_buf, buf, len); + dev_dbg(ir->dev, "TX buffer filled, length = %u\n", len); + + spin_lock_irqsave(&ir->lock, flags); + meson_irtx_update_buf(ir, tx_buf, len, 0); + reinit_completion(&ir->completion); + meson_irtx_send_buffer(ir); + spin_unlock_irqrestore(&ir->lock, flags); + + if (!wait_for_completion_timeout(&ir->completion, + usecs_to_jiffies(IR_MAX_DURATION))) + ret = -ETIMEDOUT; + + spin_lock_irqsave(&ir->lock, flags); + kfree(ir->buf); + meson_irtx_update_buf(ir, NULL, 0, 0); + spin_unlock_irqrestore(&ir->lock, flags); + + return ret; +} + +static int meson_irtx_mod_clock_probe(struct meson_irtx *ir, + unsigned int *clk_nr) +{ + struct device_node *np = ir->dev->of_node; + struct clk *clock; + + if (!np) + return -ENODEV; + + clock = devm_clk_get(ir->dev, "xtal"); + if (IS_ERR(clock) || clk_prepare_enable(clock)) + return -ENODEV; + + *clk_nr = IRB_MOD_XTAL3_CLK; + ir->clk_rate = clk_get_rate(clock) / 3; + + if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) { + *clk_nr = IRB_MOD_1US_CLK; + ir->clk_rate = IRB_MOD_1US_CLK_RATE; + } + + dev_info(ir->dev, "F_clk = %luHz\n", ir->clk_rate); + + return 0; +} + +static int __init meson_irtx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct meson_irtx *ir; + struct rc_dev *rc; + int irq; + unsigned int clk_nr; + int ret; + + ir = devm_kzalloc(dev, sizeof(*ir), GFP_KERNEL); + if (!ir) + return -ENOMEM; + + ir->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ir->reg_base)) + return PTR_ERR(ir->reg_base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no irq resource found\n"); + return -ENODEV; + } + + ir->dev = dev; + ir->carrier = MIRTX_DEFAULT_CARRIER; + ir->duty_cycle = MIRTX_DEFAULT_DUTY_CYCLE; + init_completion(&ir->completion); + spin_lock_init(&ir->lock); + + ret = meson_irtx_mod_clock_probe(ir, &clk_nr); + if (ret) { + dev_err(dev, "modulator clock setup failed\n"); + return ret; + } + meson_irtx_setup(ir, clk_nr); + + ret = devm_request_irq(dev, irq, + meson_irtx_irqhandler, + IRQF_TRIGGER_RISING, + DRIVER_NAME, ir); + if (ret) { + dev_err(dev, "irq request failed\n"); + return ret; + } + + rc = rc_allocate_device(RC_DRIVER_IR_RAW_TX); + if (!rc) + return -ENOMEM; + + rc->driver_name = DRIVER_NAME; + rc->device_name = DEVICE_NAME; + rc->priv = ir; + + rc->tx_ir = meson_irtx_transmit; + rc->s_tx_carrier = meson_irtx_set_carrier; + rc->s_tx_duty_cycle = meson_irtx_set_duty_cycle; + + ret = rc_register_device(rc); + if (ret < 0) { + dev_err(dev, "rc_dev registration failed\n"); + rc_free_device(rc); + return ret; + } + + platform_set_drvdata(pdev, rc); + + return 0; +} + +static int meson_irtx_remove(struct platform_device *pdev) +{ + struct rc_dev *rc = platform_get_drvdata(pdev); + + rc_unregister_device(rc); + + return 0; +} + +static const struct of_device_id meson_irtx_dt_match[] = { + { + .compatible = "amlogic,meson-g12a-ir-tx", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, meson_irtx_dt_match); + +static struct platform_driver meson_irtx_pd = { + .remove = meson_irtx_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = meson_irtx_dt_match, + }, +}; + +module_platform_driver_probe(meson_irtx_pd, meson_irtx_probe); + +MODULE_DESCRIPTION("Meson IR TX driver"); +MODULE_AUTHOR("Viktor Prutyanov "); +MODULE_LICENSE("GPL"); -- cgit