* [PATCH v5 0/2] media: rc: add support for Amlogic Meson IR blaster
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
Hi,
this is a driver for the IR transmitter (also called IR blaster)
available in some Amlogic Meson SoCs.
Viktor Prutyanov (2):
media: rc: meson-ir-tx: document device tree bindings
media: rc: introduce Meson IR TX driver
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 ++++++++++++++++++
4 files changed, 488 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
create mode 100644 drivers/media/rc/meson-ir-tx.c
--
2.21.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v5 0/2] media: rc: add support for Amlogic Meson IR blaster
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
Hi,
this is a driver for the IR transmitter (also called IR blaster)
available in some Amlogic Meson SoCs.
Viktor Prutyanov (2):
media: rc: meson-ir-tx: document device tree bindings
media: rc: introduce Meson IR TX driver
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 ++++++++++++++++++
4 files changed, 488 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
create mode 100644 drivers/media/rc/meson-ir-tx.c
--
2.21.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v5 0/2] media: rc: add support for Amlogic Meson IR blaster
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
Hi,
this is a driver for the IR transmitter (also called IR blaster)
available in some Amlogic Meson SoCs.
Viktor Prutyanov (2):
media: rc: meson-ir-tx: document device tree bindings
media: rc: introduce Meson IR TX driver
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 ++++++++++++++++++
4 files changed, 488 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
create mode 100644 drivers/media/rc/meson-ir-tx.c
--
2.21.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v5 1/2] media: rc: meson-ir-tx: document device tree bindings
2021-07-14 21:27 ` Viktor Prutyanov
(?)
@ 2021-07-14 21:27 ` Viktor Prutyanov
-1 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
This patch adds binding documentation for the IR transmitter
available in Amlogic Meson SoCs.
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@phystech.edu>
---
changes in v2:
- compatible = "amlogic,meson-g12a-irblaster" added
- clocks, clock-names and mod-clock updated
changes in v3:
- mod-clock removed
- max-fifo-level added
changes in v4:
- irblaster -> ir-tx renaming
changes in v5:
- max-fifo-level -> amlogic,fifo-threshold (fifo-threshold
== 128 - max-fifo-level)
- amlogic,fifo-threshold becomes uint32 in range [0; 127]
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
diff --git a/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
new file mode 100644
index 000000000000..88655413495d
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/media/amlogic,meson-ir-tx.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Amlogic Meson IR transmitter
+
+maintainers:
+ - Viktor Prutyanov <viktor.prutyanov@phystech.edu>
+
+description: |
+ 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.
+
+properties:
+ compatible:
+ oneOf:
+ - const: amlogic,meson-ir-tx
+ - items:
+ - const: amlogic,meson-g12a-ir-tx
+ - const: amlogic,meson-ir-tx
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: sysclk
+ - const: xtal
+
+ amlogic,fifo-threshold:
+ description: TX FIFO threshold
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 127
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/g12a-clkc.h>
+
+ ir@ff80014c {
+ compatible = "amlogic,meson-g12a-ir-tx", "amlogic,meson-ir-tx";
+ reg = <0xff80014c 0x10>;
+ interrupts = <0 198 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_CLK81>, <&xtal>;
+ clock-names = "sysclk", "xtal";
+ amlogic,fifo-threshold = <32>;
+ };
--
2.21.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v5 1/2] media: rc: meson-ir-tx: document device tree bindings
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
This patch adds binding documentation for the IR transmitter
available in Amlogic Meson SoCs.
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@phystech.edu>
---
changes in v2:
- compatible = "amlogic,meson-g12a-irblaster" added
- clocks, clock-names and mod-clock updated
changes in v3:
- mod-clock removed
- max-fifo-level added
changes in v4:
- irblaster -> ir-tx renaming
changes in v5:
- max-fifo-level -> amlogic,fifo-threshold (fifo-threshold
== 128 - max-fifo-level)
- amlogic,fifo-threshold becomes uint32 in range [0; 127]
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
diff --git a/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
new file mode 100644
index 000000000000..88655413495d
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/media/amlogic,meson-ir-tx.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Amlogic Meson IR transmitter
+
+maintainers:
+ - Viktor Prutyanov <viktor.prutyanov@phystech.edu>
+
+description: |
+ 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.
+
+properties:
+ compatible:
+ oneOf:
+ - const: amlogic,meson-ir-tx
+ - items:
+ - const: amlogic,meson-g12a-ir-tx
+ - const: amlogic,meson-ir-tx
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: sysclk
+ - const: xtal
+
+ amlogic,fifo-threshold:
+ description: TX FIFO threshold
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 127
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/g12a-clkc.h>
+
+ ir@ff80014c {
+ compatible = "amlogic,meson-g12a-ir-tx", "amlogic,meson-ir-tx";
+ reg = <0xff80014c 0x10>;
+ interrupts = <0 198 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_CLK81>, <&xtal>;
+ clock-names = "sysclk", "xtal";
+ amlogic,fifo-threshold = <32>;
+ };
--
2.21.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v5 1/2] media: rc: meson-ir-tx: document device tree bindings
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
This patch adds binding documentation for the IR transmitter
available in Amlogic Meson SoCs.
Signed-off-by: Viktor Prutyanov <viktor.prutyanov@phystech.edu>
---
changes in v2:
- compatible = "amlogic,meson-g12a-irblaster" added
- clocks, clock-names and mod-clock updated
changes in v3:
- mod-clock removed
- max-fifo-level added
changes in v4:
- irblaster -> ir-tx renaming
changes in v5:
- max-fifo-level -> amlogic,fifo-threshold (fifo-threshold
== 128 - max-fifo-level)
- amlogic,fifo-threshold becomes uint32 in range [0; 127]
.../bindings/media/amlogic,meson-ir-tx.yaml | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
diff --git a/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
new file mode 100644
index 000000000000..88655413495d
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,meson-ir-tx.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/media/amlogic,meson-ir-tx.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Amlogic Meson IR transmitter
+
+maintainers:
+ - Viktor Prutyanov <viktor.prutyanov@phystech.edu>
+
+description: |
+ 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.
+
+properties:
+ compatible:
+ oneOf:
+ - const: amlogic,meson-ir-tx
+ - items:
+ - const: amlogic,meson-g12a-ir-tx
+ - const: amlogic,meson-ir-tx
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: sysclk
+ - const: xtal
+
+ amlogic,fifo-threshold:
+ description: TX FIFO threshold
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0
+ maximum: 127
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/g12a-clkc.h>
+
+ ir@ff80014c {
+ compatible = "amlogic,meson-g12a-ir-tx", "amlogic,meson-ir-tx";
+ reg = <0xff80014c 0x10>;
+ interrupts = <0 198 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_CLK81>, <&xtal>;
+ clock-names = "sysclk", "xtal";
+ amlogic,fifo-threshold = <32>;
+ };
--
2.21.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
2021-07-14 21:27 ` Viktor Prutyanov
(?)
@ 2021-07-14 21:27 ` Viktor Prutyanov
-1 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
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 <viktor.prutyanov@phystech.edu>
---
changes in v2:
- threaded IRQ removed, all stuff done in IRQ handler
- DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
- compatible changed to "amlogic,meson-g12a-irblaster"
- 'debug' parameter removed
- dprintk() replaced with dev_dbg()/dev_info()
- carrier frequency checked against 0
- device_name added
changes in v3:
- license header fixed
- 'max_fifo_level' parameter removed
- irq and clk_nr deleted from irblaster_dev struct
- some divisions replaced with DIV_ROUND_CLOSEST
- irb_send inlined
- fixed early completion in IRQ handler
- spin lock added before kfree
changes in v4:
- irblaster -> ir-tx renaming
- spin lock added before buffer allocation
changes in v5:
- spinlocks rework made in meson_irtx_transmit
- max_fifo_level replaced with fifo_threshold (max_fifo_level +
fifo_threshold == IRB_FIFO_LEN == 128)
- max-fifo-level -> amlogic,fifo-threshold
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 drivers/media/rc/meson-ir-tx.c
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..1730af93e43d
--- /dev/null
+++ b/drivers/media/rc/meson-ir-tx.c
@@ -0,0 +1,410 @@
+// 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 <viktor.prutyanov@phystech.edu>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <media/rc-core.h>
+
+#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_DEFAULT_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;
+ spinlock_t lock;
+ struct completion completion;
+ unsigned int fifo_threshold;
+ 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 | ir->fifo_threshold,
+ 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 - ir->fifo_threshold;
+
+ 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);
+
+ 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;
+
+ 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);
+
+ ret = wait_for_completion_interruptible(&ir->completion);
+ dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
+
+ spin_lock_irqsave(&ir->lock, flags);
+ kfree(ir->buf);
+ meson_irtx_update_buf(ir, NULL, 0, 0);
+ spin_unlock_irqrestore(&ir->lock, flags);
+
+ return len;
+}
+
+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;
+ }
+
+ if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
+ &ir->fifo_threshold))
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ else if (ir->fifo_threshold >= IRB_FIFO_LEN)
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
+
+ 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 <viktor.prutyanov@phystech.edu>");
+MODULE_LICENSE("GPL");
--
2.21.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
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 <viktor.prutyanov@phystech.edu>
---
changes in v2:
- threaded IRQ removed, all stuff done in IRQ handler
- DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
- compatible changed to "amlogic,meson-g12a-irblaster"
- 'debug' parameter removed
- dprintk() replaced with dev_dbg()/dev_info()
- carrier frequency checked against 0
- device_name added
changes in v3:
- license header fixed
- 'max_fifo_level' parameter removed
- irq and clk_nr deleted from irblaster_dev struct
- some divisions replaced with DIV_ROUND_CLOSEST
- irb_send inlined
- fixed early completion in IRQ handler
- spin lock added before kfree
changes in v4:
- irblaster -> ir-tx renaming
- spin lock added before buffer allocation
changes in v5:
- spinlocks rework made in meson_irtx_transmit
- max_fifo_level replaced with fifo_threshold (max_fifo_level +
fifo_threshold == IRB_FIFO_LEN == 128)
- max-fifo-level -> amlogic,fifo-threshold
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 drivers/media/rc/meson-ir-tx.c
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..1730af93e43d
--- /dev/null
+++ b/drivers/media/rc/meson-ir-tx.c
@@ -0,0 +1,410 @@
+// 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 <viktor.prutyanov@phystech.edu>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <media/rc-core.h>
+
+#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_DEFAULT_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;
+ spinlock_t lock;
+ struct completion completion;
+ unsigned int fifo_threshold;
+ 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 | ir->fifo_threshold,
+ 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 - ir->fifo_threshold;
+
+ 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);
+
+ 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;
+
+ 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);
+
+ ret = wait_for_completion_interruptible(&ir->completion);
+ dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
+
+ spin_lock_irqsave(&ir->lock, flags);
+ kfree(ir->buf);
+ meson_irtx_update_buf(ir, NULL, 0, 0);
+ spin_unlock_irqrestore(&ir->lock, flags);
+
+ return len;
+}
+
+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;
+ }
+
+ if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
+ &ir->fifo_threshold))
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ else if (ir->fifo_threshold >= IRB_FIFO_LEN)
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
+
+ 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 <viktor.prutyanov@phystech.edu>");
+MODULE_LICENSE("GPL");
--
2.21.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-14 21:27 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-14 21:27 UTC (permalink / raw)
To: sean, mchehab, robh+dt, khilman, narmstrong
Cc: jbrunet, martin.blumenstingl, linux-media, devicetree,
linux-kernel, linux-arm-kernel, linux-amlogic, rockosov,
Viktor Prutyanov
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 <viktor.prutyanov@phystech.edu>
---
changes in v2:
- threaded IRQ removed, all stuff done in IRQ handler
- DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
- compatible changed to "amlogic,meson-g12a-irblaster"
- 'debug' parameter removed
- dprintk() replaced with dev_dbg()/dev_info()
- carrier frequency checked against 0
- device_name added
changes in v3:
- license header fixed
- 'max_fifo_level' parameter removed
- irq and clk_nr deleted from irblaster_dev struct
- some divisions replaced with DIV_ROUND_CLOSEST
- irb_send inlined
- fixed early completion in IRQ handler
- spin lock added before kfree
changes in v4:
- irblaster -> ir-tx renaming
- spin lock added before buffer allocation
changes in v5:
- spinlocks rework made in meson_irtx_transmit
- max_fifo_level replaced with fifo_threshold (max_fifo_level +
fifo_threshold == IRB_FIFO_LEN == 128)
- max-fifo-level -> amlogic,fifo-threshold
drivers/media/rc/Kconfig | 10 +
drivers/media/rc/Makefile | 1 +
drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 drivers/media/rc/meson-ir-tx.c
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..1730af93e43d
--- /dev/null
+++ b/drivers/media/rc/meson-ir-tx.c
@@ -0,0 +1,410 @@
+// 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 <viktor.prutyanov@phystech.edu>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <media/rc-core.h>
+
+#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_DEFAULT_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;
+ spinlock_t lock;
+ struct completion completion;
+ unsigned int fifo_threshold;
+ 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 | ir->fifo_threshold,
+ 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 - ir->fifo_threshold;
+
+ 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);
+
+ 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;
+
+ 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);
+
+ ret = wait_for_completion_interruptible(&ir->completion);
+ dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
+
+ spin_lock_irqsave(&ir->lock, flags);
+ kfree(ir->buf);
+ meson_irtx_update_buf(ir, NULL, 0, 0);
+ spin_unlock_irqrestore(&ir->lock, flags);
+
+ return len;
+}
+
+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;
+ }
+
+ if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
+ &ir->fifo_threshold))
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ else if (ir->fifo_threshold >= IRB_FIFO_LEN)
+ ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
+ dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
+
+ 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 <viktor.prutyanov@phystech.edu>");
+MODULE_LICENSE("GPL");
--
2.21.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
2021-07-14 21:27 ` Viktor Prutyanov
(?)
@ 2021-07-15 21:40 ` Sean Young
-1 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-15 21:40 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> 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 <viktor.prutyanov@phystech.edu>
> ---
> changes in v2:
> - threaded IRQ removed, all stuff done in IRQ handler
> - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> - compatible changed to "amlogic,meson-g12a-irblaster"
> - 'debug' parameter removed
> - dprintk() replaced with dev_dbg()/dev_info()
> - carrier frequency checked against 0
> - device_name added
> changes in v3:
> - license header fixed
> - 'max_fifo_level' parameter removed
> - irq and clk_nr deleted from irblaster_dev struct
> - some divisions replaced with DIV_ROUND_CLOSEST
> - irb_send inlined
> - fixed early completion in IRQ handler
> - spin lock added before kfree
> changes in v4:
> - irblaster -> ir-tx renaming
> - spin lock added before buffer allocation
> changes in v5:
> - spinlocks rework made in meson_irtx_transmit
> - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> fifo_threshold == IRB_FIFO_LEN == 128)
> - max-fifo-level -> amlogic,fifo-threshold
>
> drivers/media/rc/Kconfig | 10 +
> drivers/media/rc/Makefile | 1 +
> drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
> 3 files changed, 421 insertions(+)
> create mode 100644 drivers/media/rc/meson-ir-tx.c
>
> 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..1730af93e43d
> --- /dev/null
> +++ b/drivers/media/rc/meson-ir-tx.c
> @@ -0,0 +1,410 @@
> +// 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 <viktor.prutyanov@phystech.edu>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/interrupt.h>
> +#include <linux/spinlock.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <media/rc-core.h>
> +
> +#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_DEFAULT_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;
> + spinlock_t lock;
> + struct completion completion;
> + unsigned int fifo_threshold;
> + 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 | ir->fifo_threshold,
> + 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 - ir->fifo_threshold;
> +
> + 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);
> +
> + 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;
> +
> + 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);
> +
> + ret = wait_for_completion_interruptible(&ir->completion);
> + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
Here two things can happen. One is, the process received a signal (e.g. ^C).
The other is that the hardware didn't issue any interrupts due some
problem somewhere. In the latter case, we only escape this
wait_for_completion_interruptable() when the user gets fed up and presses ^C
or something like that.
> +
> + spin_lock_irqsave(&ir->lock, flags);
> + kfree(ir->buf);
> + meson_irtx_update_buf(ir, NULL, 0, 0);
> + spin_unlock_irqrestore(&ir->lock, flags);
Now it is possible that the buffer gets cleared before that IR was sent,
if the signal was received early enough. This means not all the Tx was
completed.
> +
> + return len;
Yet, we always return success.
In case no interrupts were generated we should return an error in a timely
manner, so the wait_for_completion() needs the timeout. You can use the
fact that the IR is never longer IR_MAX_DURATION (half a second currently).
Not sure what the returned error should be, maybe -ETIMEDOUT?
The problem with the interruptable wait is that a process can receive a
signal at any time, and now when this happens your IR gets truncated. I
don't think this is what you want.
Thanks
Sean
> +}
> +
> +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;
> + }
> +
> + if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
> + &ir->fifo_threshold))
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + else if (ir->fifo_threshold >= IRB_FIFO_LEN)
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
> +
> + 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 <viktor.prutyanov@phystech.edu>");
> +MODULE_LICENSE("GPL");
> --
> 2.21.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-15 21:40 ` Sean Young
0 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-15 21:40 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> 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 <viktor.prutyanov@phystech.edu>
> ---
> changes in v2:
> - threaded IRQ removed, all stuff done in IRQ handler
> - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> - compatible changed to "amlogic,meson-g12a-irblaster"
> - 'debug' parameter removed
> - dprintk() replaced with dev_dbg()/dev_info()
> - carrier frequency checked against 0
> - device_name added
> changes in v3:
> - license header fixed
> - 'max_fifo_level' parameter removed
> - irq and clk_nr deleted from irblaster_dev struct
> - some divisions replaced with DIV_ROUND_CLOSEST
> - irb_send inlined
> - fixed early completion in IRQ handler
> - spin lock added before kfree
> changes in v4:
> - irblaster -> ir-tx renaming
> - spin lock added before buffer allocation
> changes in v5:
> - spinlocks rework made in meson_irtx_transmit
> - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> fifo_threshold == IRB_FIFO_LEN == 128)
> - max-fifo-level -> amlogic,fifo-threshold
>
> drivers/media/rc/Kconfig | 10 +
> drivers/media/rc/Makefile | 1 +
> drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
> 3 files changed, 421 insertions(+)
> create mode 100644 drivers/media/rc/meson-ir-tx.c
>
> 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..1730af93e43d
> --- /dev/null
> +++ b/drivers/media/rc/meson-ir-tx.c
> @@ -0,0 +1,410 @@
> +// 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 <viktor.prutyanov@phystech.edu>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/interrupt.h>
> +#include <linux/spinlock.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <media/rc-core.h>
> +
> +#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_DEFAULT_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;
> + spinlock_t lock;
> + struct completion completion;
> + unsigned int fifo_threshold;
> + 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 | ir->fifo_threshold,
> + 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 - ir->fifo_threshold;
> +
> + 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);
> +
> + 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;
> +
> + 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);
> +
> + ret = wait_for_completion_interruptible(&ir->completion);
> + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
Here two things can happen. One is, the process received a signal (e.g. ^C).
The other is that the hardware didn't issue any interrupts due some
problem somewhere. In the latter case, we only escape this
wait_for_completion_interruptable() when the user gets fed up and presses ^C
or something like that.
> +
> + spin_lock_irqsave(&ir->lock, flags);
> + kfree(ir->buf);
> + meson_irtx_update_buf(ir, NULL, 0, 0);
> + spin_unlock_irqrestore(&ir->lock, flags);
Now it is possible that the buffer gets cleared before that IR was sent,
if the signal was received early enough. This means not all the Tx was
completed.
> +
> + return len;
Yet, we always return success.
In case no interrupts were generated we should return an error in a timely
manner, so the wait_for_completion() needs the timeout. You can use the
fact that the IR is never longer IR_MAX_DURATION (half a second currently).
Not sure what the returned error should be, maybe -ETIMEDOUT?
The problem with the interruptable wait is that a process can receive a
signal at any time, and now when this happens your IR gets truncated. I
don't think this is what you want.
Thanks
Sean
> +}
> +
> +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;
> + }
> +
> + if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
> + &ir->fifo_threshold))
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + else if (ir->fifo_threshold >= IRB_FIFO_LEN)
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
> +
> + 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 <viktor.prutyanov@phystech.edu>");
> +MODULE_LICENSE("GPL");
> --
> 2.21.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-15 21:40 ` Sean Young
0 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-15 21:40 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> 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 <viktor.prutyanov@phystech.edu>
> ---
> changes in v2:
> - threaded IRQ removed, all stuff done in IRQ handler
> - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> - compatible changed to "amlogic,meson-g12a-irblaster"
> - 'debug' parameter removed
> - dprintk() replaced with dev_dbg()/dev_info()
> - carrier frequency checked against 0
> - device_name added
> changes in v3:
> - license header fixed
> - 'max_fifo_level' parameter removed
> - irq and clk_nr deleted from irblaster_dev struct
> - some divisions replaced with DIV_ROUND_CLOSEST
> - irb_send inlined
> - fixed early completion in IRQ handler
> - spin lock added before kfree
> changes in v4:
> - irblaster -> ir-tx renaming
> - spin lock added before buffer allocation
> changes in v5:
> - spinlocks rework made in meson_irtx_transmit
> - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> fifo_threshold == IRB_FIFO_LEN == 128)
> - max-fifo-level -> amlogic,fifo-threshold
>
> drivers/media/rc/Kconfig | 10 +
> drivers/media/rc/Makefile | 1 +
> drivers/media/rc/meson-ir-tx.c | 410 +++++++++++++++++++++++++++++++++
> 3 files changed, 421 insertions(+)
> create mode 100644 drivers/media/rc/meson-ir-tx.c
>
> 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..1730af93e43d
> --- /dev/null
> +++ b/drivers/media/rc/meson-ir-tx.c
> @@ -0,0 +1,410 @@
> +// 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 <viktor.prutyanov@phystech.edu>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/interrupt.h>
> +#include <linux/spinlock.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <media/rc-core.h>
> +
> +#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_DEFAULT_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;
> + spinlock_t lock;
> + struct completion completion;
> + unsigned int fifo_threshold;
> + 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 | ir->fifo_threshold,
> + 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 - ir->fifo_threshold;
> +
> + 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);
> +
> + 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;
> +
> + 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);
> +
> + ret = wait_for_completion_interruptible(&ir->completion);
> + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" : "completed");
Here two things can happen. One is, the process received a signal (e.g. ^C).
The other is that the hardware didn't issue any interrupts due some
problem somewhere. In the latter case, we only escape this
wait_for_completion_interruptable() when the user gets fed up and presses ^C
or something like that.
> +
> + spin_lock_irqsave(&ir->lock, flags);
> + kfree(ir->buf);
> + meson_irtx_update_buf(ir, NULL, 0, 0);
> + spin_unlock_irqrestore(&ir->lock, flags);
Now it is possible that the buffer gets cleared before that IR was sent,
if the signal was received early enough. This means not all the Tx was
completed.
> +
> + return len;
Yet, we always return success.
In case no interrupts were generated we should return an error in a timely
manner, so the wait_for_completion() needs the timeout. You can use the
fact that the IR is never longer IR_MAX_DURATION (half a second currently).
Not sure what the returned error should be, maybe -ETIMEDOUT?
The problem with the interruptable wait is that a process can receive a
signal at any time, and now when this happens your IR gets truncated. I
don't think this is what you want.
Thanks
Sean
> +}
> +
> +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;
> + }
> +
> + if (of_property_read_u32(dev->of_node, "amlogic,fifo-threshold",
> + &ir->fifo_threshold))
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + else if (ir->fifo_threshold >= IRB_FIFO_LEN)
> + ir->fifo_threshold = MIRTX_DEFAULT_FIFO_THD;
> + dev_dbg(dev, "FIFO threshold set to %u\n", ir->fifo_threshold);
> +
> + 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 <viktor.prutyanov@phystech.edu>");
> +MODULE_LICENSE("GPL");
> --
> 2.21.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
2021-07-15 21:40 ` Sean Young
(?)
@ 2021-07-15 22:36 ` Viktor Prutyanov
-1 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-15 22:36 UTC (permalink / raw)
To: Sean Young
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Sean,
On Thu, 15 Jul 2021 22:40:01 +0100
Sean Young <sean@mess.org> wrote:
> On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > 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 <viktor.prutyanov@phystech.edu>
> > ---
> > changes in v2:
> > - threaded IRQ removed, all stuff done in IRQ handler
> > - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> > - compatible changed to "amlogic,meson-g12a-irblaster"
> > - 'debug' parameter removed
> > - dprintk() replaced with dev_dbg()/dev_info()
> > - carrier frequency checked against 0
> > - device_name added
> > changes in v3:
> > - license header fixed
> > - 'max_fifo_level' parameter removed
> > - irq and clk_nr deleted from irblaster_dev struct
> > - some divisions replaced with DIV_ROUND_CLOSEST
> > - irb_send inlined
> > - fixed early completion in IRQ handler
> > - spin lock added before kfree
> > changes in v4:
> > - irblaster -> ir-tx renaming
> > - spin lock added before buffer allocation
> > changes in v5:
> > - spinlocks rework made in meson_irtx_transmit
> > - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> > fifo_threshold == IRB_FIFO_LEN == 128)
> > - max-fifo-level -> amlogic,fifo-threshold
> >
> > drivers/media/rc/Kconfig | 10 +
> > drivers/media/rc/Makefile | 1 +
> > drivers/media/rc/meson-ir-tx.c | 410
> > +++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+)
> > create mode 100644 drivers/media/rc/meson-ir-tx.c
> >
> > 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..1730af93e43d
> > --- /dev/null
> > +++ b/drivers/media/rc/meson-ir-tx.c
> > @@ -0,0 +1,410 @@
> > +// 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 <viktor.prutyanov@phystech.edu>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/sched.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/of.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <media/rc-core.h>
> > +
> > +#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_DEFAULT_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;
> > + spinlock_t lock;
> > + struct completion completion;
> > + unsigned int fifo_threshold;
> > + 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 | ir->fifo_threshold,
> > + 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 -
> > ir->fifo_threshold; +
> > + 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);
> > +
> > + 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;
> > +
> > + 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);
> > +
> > + ret = wait_for_completion_interruptible(&ir->completion);
> > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > "completed");
>
> Here two things can happen. One is, the process received a signal
> (e.g. ^C). The other is that the hardware didn't issue any interrupts
> due some problem somewhere. In the latter case, we only escape this
> wait_for_completion_interruptable() when the user gets fed up and
> presses ^C or something like that.
>
> > +
> > + spin_lock_irqsave(&ir->lock, flags);
> > + kfree(ir->buf);
> > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > + spin_unlock_irqrestore(&ir->lock, flags);
>
> Now it is possible that the buffer gets cleared before that IR was
> sent, if the signal was received early enough. This means not all the
> Tx was completed.
>
> > +
> > + return len;
>
> Yet, we always return success.
>
> In case no interrupts were generated we should return an error in a
> timely manner, so the wait_for_completion() needs the timeout. You
> can use the fact that the IR is never longer IR_MAX_DURATION (half a
> second currently). Not sure what the returned error should be, maybe
> -ETIMEDOUT?
As for me, ETIMEDOUT is OK.
>
> The problem with the interruptable wait is that a process can receive
> a signal at any time, and now when this happens your IR gets
> truncated. I don't think this is what you want.
Should I replace wait_for_completion_interruptible by
wait_for_completion_timeout in order to wait in uninterruptible way?
>
> Thanks
>
> Sean
>
Best regards,
Viktor
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-15 22:36 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-15 22:36 UTC (permalink / raw)
To: Sean Young
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Sean,
On Thu, 15 Jul 2021 22:40:01 +0100
Sean Young <sean@mess.org> wrote:
> On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > 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 <viktor.prutyanov@phystech.edu>
> > ---
> > changes in v2:
> > - threaded IRQ removed, all stuff done in IRQ handler
> > - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> > - compatible changed to "amlogic,meson-g12a-irblaster"
> > - 'debug' parameter removed
> > - dprintk() replaced with dev_dbg()/dev_info()
> > - carrier frequency checked against 0
> > - device_name added
> > changes in v3:
> > - license header fixed
> > - 'max_fifo_level' parameter removed
> > - irq and clk_nr deleted from irblaster_dev struct
> > - some divisions replaced with DIV_ROUND_CLOSEST
> > - irb_send inlined
> > - fixed early completion in IRQ handler
> > - spin lock added before kfree
> > changes in v4:
> > - irblaster -> ir-tx renaming
> > - spin lock added before buffer allocation
> > changes in v5:
> > - spinlocks rework made in meson_irtx_transmit
> > - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> > fifo_threshold == IRB_FIFO_LEN == 128)
> > - max-fifo-level -> amlogic,fifo-threshold
> >
> > drivers/media/rc/Kconfig | 10 +
> > drivers/media/rc/Makefile | 1 +
> > drivers/media/rc/meson-ir-tx.c | 410
> > +++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+)
> > create mode 100644 drivers/media/rc/meson-ir-tx.c
> >
> > 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..1730af93e43d
> > --- /dev/null
> > +++ b/drivers/media/rc/meson-ir-tx.c
> > @@ -0,0 +1,410 @@
> > +// 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 <viktor.prutyanov@phystech.edu>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/sched.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/of.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <media/rc-core.h>
> > +
> > +#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_DEFAULT_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;
> > + spinlock_t lock;
> > + struct completion completion;
> > + unsigned int fifo_threshold;
> > + 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 | ir->fifo_threshold,
> > + 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 -
> > ir->fifo_threshold; +
> > + 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);
> > +
> > + 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;
> > +
> > + 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);
> > +
> > + ret = wait_for_completion_interruptible(&ir->completion);
> > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > "completed");
>
> Here two things can happen. One is, the process received a signal
> (e.g. ^C). The other is that the hardware didn't issue any interrupts
> due some problem somewhere. In the latter case, we only escape this
> wait_for_completion_interruptable() when the user gets fed up and
> presses ^C or something like that.
>
> > +
> > + spin_lock_irqsave(&ir->lock, flags);
> > + kfree(ir->buf);
> > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > + spin_unlock_irqrestore(&ir->lock, flags);
>
> Now it is possible that the buffer gets cleared before that IR was
> sent, if the signal was received early enough. This means not all the
> Tx was completed.
>
> > +
> > + return len;
>
> Yet, we always return success.
>
> In case no interrupts were generated we should return an error in a
> timely manner, so the wait_for_completion() needs the timeout. You
> can use the fact that the IR is never longer IR_MAX_DURATION (half a
> second currently). Not sure what the returned error should be, maybe
> -ETIMEDOUT?
As for me, ETIMEDOUT is OK.
>
> The problem with the interruptable wait is that a process can receive
> a signal at any time, and now when this happens your IR gets
> truncated. I don't think this is what you want.
Should I replace wait_for_completion_interruptible by
wait_for_completion_timeout in order to wait in uninterruptible way?
>
> Thanks
>
> Sean
>
Best regards,
Viktor
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-15 22:36 ` Viktor Prutyanov
0 siblings, 0 replies; 19+ messages in thread
From: Viktor Prutyanov @ 2021-07-15 22:36 UTC (permalink / raw)
To: Sean Young
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Sean,
On Thu, 15 Jul 2021 22:40:01 +0100
Sean Young <sean@mess.org> wrote:
> On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > 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 <viktor.prutyanov@phystech.edu>
> > ---
> > changes in v2:
> > - threaded IRQ removed, all stuff done in IRQ handler
> > - DIV_ROUND_CLOSEST_ULL replaced with DIV_ROUND_CLOSEST
> > - compatible changed to "amlogic,meson-g12a-irblaster"
> > - 'debug' parameter removed
> > - dprintk() replaced with dev_dbg()/dev_info()
> > - carrier frequency checked against 0
> > - device_name added
> > changes in v3:
> > - license header fixed
> > - 'max_fifo_level' parameter removed
> > - irq and clk_nr deleted from irblaster_dev struct
> > - some divisions replaced with DIV_ROUND_CLOSEST
> > - irb_send inlined
> > - fixed early completion in IRQ handler
> > - spin lock added before kfree
> > changes in v4:
> > - irblaster -> ir-tx renaming
> > - spin lock added before buffer allocation
> > changes in v5:
> > - spinlocks rework made in meson_irtx_transmit
> > - max_fifo_level replaced with fifo_threshold (max_fifo_level +
> > fifo_threshold == IRB_FIFO_LEN == 128)
> > - max-fifo-level -> amlogic,fifo-threshold
> >
> > drivers/media/rc/Kconfig | 10 +
> > drivers/media/rc/Makefile | 1 +
> > drivers/media/rc/meson-ir-tx.c | 410
> > +++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+)
> > create mode 100644 drivers/media/rc/meson-ir-tx.c
> >
> > 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..1730af93e43d
> > --- /dev/null
> > +++ b/drivers/media/rc/meson-ir-tx.c
> > @@ -0,0 +1,410 @@
> > +// 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 <viktor.prutyanov@phystech.edu>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include <linux/sched.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/of.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <media/rc-core.h>
> > +
> > +#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_DEFAULT_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;
> > + spinlock_t lock;
> > + struct completion completion;
> > + unsigned int fifo_threshold;
> > + 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 | ir->fifo_threshold,
> > + 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 -
> > ir->fifo_threshold; +
> > + 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);
> > +
> > + 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;
> > +
> > + 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);
> > +
> > + ret = wait_for_completion_interruptible(&ir->completion);
> > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > "completed");
>
> Here two things can happen. One is, the process received a signal
> (e.g. ^C). The other is that the hardware didn't issue any interrupts
> due some problem somewhere. In the latter case, we only escape this
> wait_for_completion_interruptable() when the user gets fed up and
> presses ^C or something like that.
>
> > +
> > + spin_lock_irqsave(&ir->lock, flags);
> > + kfree(ir->buf);
> > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > + spin_unlock_irqrestore(&ir->lock, flags);
>
> Now it is possible that the buffer gets cleared before that IR was
> sent, if the signal was received early enough. This means not all the
> Tx was completed.
>
> > +
> > + return len;
>
> Yet, we always return success.
>
> In case no interrupts were generated we should return an error in a
> timely manner, so the wait_for_completion() needs the timeout. You
> can use the fact that the IR is never longer IR_MAX_DURATION (half a
> second currently). Not sure what the returned error should be, maybe
> -ETIMEDOUT?
As for me, ETIMEDOUT is OK.
>
> The problem with the interruptable wait is that a process can receive
> a signal at any time, and now when this happens your IR gets
> truncated. I don't think this is what you want.
Should I replace wait_for_completion_interruptible by
wait_for_completion_timeout in order to wait in uninterruptible way?
>
> Thanks
>
> Sean
>
Best regards,
Viktor
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
2021-07-15 22:36 ` Viktor Prutyanov
(?)
@ 2021-07-16 8:24 ` Sean Young
-1 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-16 8:24 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Viktor,
On Fri, Jul 16, 2021 at 01:36:52AM +0300, Viktor Prutyanov wrote:
> Hi Sean,
>
> On Thu, 15 Jul 2021 22:40:01 +0100
> Sean Young <sean@mess.org> wrote:
>
> > On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > + 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);
> > > +
> > > + ret = wait_for_completion_interruptible(&ir->completion);
> > > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > > "completed");
> >
> > Here two things can happen. One is, the process received a signal
> > (e.g. ^C). The other is that the hardware didn't issue any interrupts
> > due some problem somewhere. In the latter case, we only escape this
> > wait_for_completion_interruptable() when the user gets fed up and
> > presses ^C or something like that.
> >
> > > +
> > > + spin_lock_irqsave(&ir->lock, flags);
> > > + kfree(ir->buf);
> > > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > > + spin_unlock_irqrestore(&ir->lock, flags);
> >
> > Now it is possible that the buffer gets cleared before that IR was
> > sent, if the signal was received early enough. This means not all the
> > Tx was completed.
> >
> > > +
> > > + return len;
> >
> > Yet, we always return success.
> >
> > In case no interrupts were generated we should return an error in a
> > timely manner, so the wait_for_completion() needs the timeout. You
> > can use the fact that the IR is never longer IR_MAX_DURATION (half a
> > second currently). Not sure what the returned error should be, maybe
> > -ETIMEDOUT?
>
> As for me, ETIMEDOUT is OK.
> >
> > The problem with the interruptable wait is that a process can receive
> > a signal at any time, and now when this happens your IR gets
> > truncated. I don't think this is what you want.
>
> Should I replace wait_for_completion_interruptible by
> wait_for_completion_timeout in order to wait in uninterruptible way?
Yes, the process can receive a signal if the terminal is resized
(SIGWINCH) or if the process is backgrounded and then foregrounded with
^Z and fg (SIGCONT). If this happens during tx then the tx might be
incomplete.
Thanks,
Sean
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-16 8:24 ` Sean Young
0 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-16 8:24 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Viktor,
On Fri, Jul 16, 2021 at 01:36:52AM +0300, Viktor Prutyanov wrote:
> Hi Sean,
>
> On Thu, 15 Jul 2021 22:40:01 +0100
> Sean Young <sean@mess.org> wrote:
>
> > On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > + 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);
> > > +
> > > + ret = wait_for_completion_interruptible(&ir->completion);
> > > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > > "completed");
> >
> > Here two things can happen. One is, the process received a signal
> > (e.g. ^C). The other is that the hardware didn't issue any interrupts
> > due some problem somewhere. In the latter case, we only escape this
> > wait_for_completion_interruptable() when the user gets fed up and
> > presses ^C or something like that.
> >
> > > +
> > > + spin_lock_irqsave(&ir->lock, flags);
> > > + kfree(ir->buf);
> > > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > > + spin_unlock_irqrestore(&ir->lock, flags);
> >
> > Now it is possible that the buffer gets cleared before that IR was
> > sent, if the signal was received early enough. This means not all the
> > Tx was completed.
> >
> > > +
> > > + return len;
> >
> > Yet, we always return success.
> >
> > In case no interrupts were generated we should return an error in a
> > timely manner, so the wait_for_completion() needs the timeout. You
> > can use the fact that the IR is never longer IR_MAX_DURATION (half a
> > second currently). Not sure what the returned error should be, maybe
> > -ETIMEDOUT?
>
> As for me, ETIMEDOUT is OK.
> >
> > The problem with the interruptable wait is that a process can receive
> > a signal at any time, and now when this happens your IR gets
> > truncated. I don't think this is what you want.
>
> Should I replace wait_for_completion_interruptible by
> wait_for_completion_timeout in order to wait in uninterruptible way?
Yes, the process can receive a signal if the terminal is resized
(SIGWINCH) or if the process is backgrounded and then foregrounded with
^Z and fg (SIGCONT). If this happens during tx then the tx might be
incomplete.
Thanks,
Sean
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-16 8:24 ` Sean Young
0 siblings, 0 replies; 19+ messages in thread
From: Sean Young @ 2021-07-16 8:24 UTC (permalink / raw)
To: Viktor Prutyanov
Cc: mchehab, robh+dt, khilman, narmstrong, jbrunet,
martin.blumenstingl, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-amlogic, rockosov
Hi Viktor,
On Fri, Jul 16, 2021 at 01:36:52AM +0300, Viktor Prutyanov wrote:
> Hi Sean,
>
> On Thu, 15 Jul 2021 22:40:01 +0100
> Sean Young <sean@mess.org> wrote:
>
> > On Thu, Jul 15, 2021 at 12:27:06AM +0300, Viktor Prutyanov wrote:
> > + 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);
> > > +
> > > + ret = wait_for_completion_interruptible(&ir->completion);
> > > + dev_dbg(ir->dev, "TX %s\n", ret ? "interrupted" :
> > > "completed");
> >
> > Here two things can happen. One is, the process received a signal
> > (e.g. ^C). The other is that the hardware didn't issue any interrupts
> > due some problem somewhere. In the latter case, we only escape this
> > wait_for_completion_interruptable() when the user gets fed up and
> > presses ^C or something like that.
> >
> > > +
> > > + spin_lock_irqsave(&ir->lock, flags);
> > > + kfree(ir->buf);
> > > + meson_irtx_update_buf(ir, NULL, 0, 0);
> > > + spin_unlock_irqrestore(&ir->lock, flags);
> >
> > Now it is possible that the buffer gets cleared before that IR was
> > sent, if the signal was received early enough. This means not all the
> > Tx was completed.
> >
> > > +
> > > + return len;
> >
> > Yet, we always return success.
> >
> > In case no interrupts were generated we should return an error in a
> > timely manner, so the wait_for_completion() needs the timeout. You
> > can use the fact that the IR is never longer IR_MAX_DURATION (half a
> > second currently). Not sure what the returned error should be, maybe
> > -ETIMEDOUT?
>
> As for me, ETIMEDOUT is OK.
> >
> > The problem with the interruptable wait is that a process can receive
> > a signal at any time, and now when this happens your IR gets
> > truncated. I don't think this is what you want.
>
> Should I replace wait_for_completion_interruptible by
> wait_for_completion_timeout in order to wait in uninterruptible way?
Yes, the process can receive a signal if the terminal is resized
(SIGWINCH) or if the process is backgrounded and then foregrounded with
^Z and fg (SIGCONT). If this happens during tx then the tx might be
incomplete.
Thanks,
Sean
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v5 2/2] media: rc: introduce Meson IR TX driver
@ 2021-07-15 7:12 kernel test robot
0 siblings, 0 replies; 19+ messages in thread
From: kernel test robot @ 2021-07-15 7:12 UTC (permalink / raw)
To: kbuild
[-- Attachment #1: Type: text/plain, Size: 1852 bytes --]
CC: kbuild-all(a)lists.01.org
In-Reply-To: <20210714212706.24945-3-viktor.prutyanov@phystech.edu>
References: <20210714212706.24945-3-viktor.prutyanov@phystech.edu>
TO: Viktor Prutyanov <viktor.prutyanov@phystech.edu>
TO: sean(a)mess.org
TO: mchehab(a)kernel.org
TO: robh+dt(a)kernel.org
TO: khilman(a)baylibre.com
TO: narmstrong(a)baylibre.com
CC: jbrunet(a)baylibre.com
CC: martin.blumenstingl(a)googlemail.com
CC: linux-media(a)vger.kernel.org
CC: devicetree(a)vger.kernel.org
CC: linux-kernel(a)vger.kernel.org
Hi Viktor,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on linuxtv-media/master]
[also build test WARNING on v5.14-rc1 next-20210715]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]
url: https://github.com/0day-ci/linux/commits/Viktor-Prutyanov/media-rc-add-support-for-Amlogic-Meson-IR-blaster/20210715-052839
base: git://linuxtv.org/media_tree.git master
:::::: branch date: 10 hours ago
:::::: commit date: 10 hours ago
config: nds32-randconfig-c004-20210715 (attached as .config)
compiler: nds32le-linux-gcc (GCC) 10.3.0
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Julia Lawall <julia.lawall@lip6.fr>
cocci warnings: (new ones prefixed by >>)
>> drivers/media/rc/meson-ir-tx.c:323:2-9: line 323 is redundant because platform_get_irq() already prints an error
--
>> drivers/media/rc/meson-ir-tx.c:401:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org
[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 36591 bytes --]
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2021-07-16 8:26 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-14 21:27 [PATCH v5 0/2] media: rc: add support for Amlogic Meson IR blaster Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-14 21:27 ` [PATCH v5 1/2] media: rc: meson-ir-tx: document device tree bindings Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-14 21:27 ` [PATCH v5 2/2] media: rc: introduce Meson IR TX driver Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-14 21:27 ` Viktor Prutyanov
2021-07-15 21:40 ` Sean Young
2021-07-15 21:40 ` Sean Young
2021-07-15 21:40 ` Sean Young
2021-07-15 22:36 ` Viktor Prutyanov
2021-07-15 22:36 ` Viktor Prutyanov
2021-07-15 22:36 ` Viktor Prutyanov
2021-07-16 8:24 ` Sean Young
2021-07-16 8:24 ` Sean Young
2021-07-16 8:24 ` Sean Young
2021-07-15 7:12 kernel test robot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.