LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH v6 0/2] media: rc: add support for Amlogic Meson IR blaster
@ 2021-07-16 14:45 Viktor Prutyanov
  2021-07-16 14:45 ` [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings Viktor Prutyanov
  2021-07-16 14:45 ` [PATCH v6 2/2] media: rc: introduce Meson IR TX driver Viktor Prutyanov
  0 siblings, 2 replies; 7+ messages in thread
From: Viktor Prutyanov @ 2021-07-16 14:45 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                | 415 ++++++++++++++++++
 4 files changed, 493 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] 7+ messages in thread

* [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings
  2021-07-16 14:45 [PATCH v6 0/2] media: rc: add support for Amlogic Meson IR blaster Viktor Prutyanov
@ 2021-07-16 14:45 ` Viktor Prutyanov
  2021-07-17 19:57   ` Martin Blumenstingl
  2021-07-16 14:45 ` [PATCH v6 2/2] media: rc: introduce Meson IR TX driver Viktor Prutyanov
  1 sibling, 1 reply; 7+ messages in thread
From: Viktor Prutyanov @ 2021-07-16 14:45 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]
 no changes in v6

 .../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	[flat|nested] 7+ messages in thread

* [PATCH v6 2/2] media: rc: introduce Meson IR TX driver
  2021-07-16 14:45 [PATCH v6 0/2] media: rc: add support for Amlogic Meson IR blaster Viktor Prutyanov
  2021-07-16 14:45 ` [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings Viktor Prutyanov
@ 2021-07-16 14:45 ` Viktor Prutyanov
  2021-07-16 15:34   ` Sean Young
  1 sibling, 1 reply; 7+ messages in thread
From: Viktor Prutyanov @ 2021-07-16 14:45 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
 changes in v6:
   - interruptible wait replaced with uninterruptible with timeout
   - completion done check added to IRQ handler

 drivers/media/rc/Kconfig       |  10 +
 drivers/media/rc/Makefile      |   1 +
 drivers/media/rc/meson-ir-tx.c | 415 +++++++++++++++++++++++++++++++++
 3 files changed, 426 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..77e276e3dbe8
--- /dev/null
+++ b/drivers/media/rc/meson-ir-tx.c
@@ -0,0 +1,415 @@
+// 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 MIRTX_TIMEOUT_US		(IR_MAX_DURATION / NSEC_PER_USEC)
+
+#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);
+
+	if (completion_done(&ir->completion))
+		return IRQ_HANDLED;
+
+	spin_lock_irqsave(&ir->lock, flags);
+	if (ir->buf_head < ir->buf_len)
+		meson_irtx_send_buffer(ir);
+	else
+		complete(&ir->completion);
+	spin_unlock_irqrestore(&ir->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier)
+{
+	struct meson_irtx *ir = rc->priv;
+
+	if (carrier == 0)
+		return -EINVAL;
+
+	ir->carrier = carrier;
+	meson_irtx_set_mod(ir);
+
+	return 0;
+}
+
+static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32 duty_cycle)
+{
+	struct meson_irtx *ir = rc->priv;
+
+	ir->duty_cycle = duty_cycle;
+	meson_irtx_set_mod(ir);
+
+	return 0;
+}
+
+static void meson_irtx_update_buf(struct meson_irtx *ir, u32 *buf,
+				  unsigned int len, unsigned int head)
+{
+	ir->buf = buf;
+	ir->buf_len = len;
+	ir->buf_head = head;
+}
+
+static int meson_irtx_transmit(struct rc_dev *rc, unsigned int *buf,
+			       unsigned int len)
+{
+	unsigned long flags;
+	struct meson_irtx *ir = rc->priv;
+	u32 *tx_buf;
+	int ret = len;
+
+	if (!meson_irtx_check_buf(ir, buf, len))
+		return -EINVAL;
+
+	tx_buf = kmalloc_array(len, sizeof(u32), GFP_KERNEL);
+	if (!tx_buf)
+		return -ENOMEM;
+
+	meson_irtx_fill_buf(ir, tx_buf, buf, len);
+	dev_dbg(ir->dev, "TX buffer filled, length = %u\n", len);
+
+	spin_lock_irqsave(&ir->lock, flags);
+	meson_irtx_update_buf(ir, tx_buf, len, 0);
+	reinit_completion(&ir->completion);
+	meson_irtx_send_buffer(ir);
+	spin_unlock_irqrestore(&ir->lock, flags);
+
+	if (!wait_for_completion_timeout(&ir->completion,
+					 usecs_to_jiffies(MIRTX_TIMEOUT_US)))
+		ret = -ETIMEDOUT;
+
+	spin_lock_irqsave(&ir->lock, flags);
+	kfree(ir->buf);
+	meson_irtx_update_buf(ir, NULL, 0, 0);
+	spin_unlock_irqrestore(&ir->lock, flags);
+
+	return ret;
+}
+
+static int meson_irtx_mod_clock_probe(struct meson_irtx *ir,
+				      unsigned int *clk_nr)
+{
+	struct device_node *np = ir->dev->of_node;
+	struct clk *clock;
+
+	if (!np)
+		return -ENODEV;
+
+	clock = devm_clk_get(ir->dev, "xtal");
+	if (IS_ERR(clock) || clk_prepare_enable(clock))
+		return -ENODEV;
+
+	*clk_nr = IRB_MOD_XTAL3_CLK;
+	ir->clk_rate = clk_get_rate(clock) / 3;
+
+	if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) {
+		*clk_nr = IRB_MOD_1US_CLK;
+		ir->clk_rate = IRB_MOD_1US_CLK_RATE;
+	}
+
+	dev_info(ir->dev, "F_clk = %luHz\n", ir->clk_rate);
+
+	return 0;
+}
+
+static int __init meson_irtx_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct meson_irtx *ir;
+	struct rc_dev *rc;
+	int irq;
+	unsigned int clk_nr;
+	int ret;
+
+	ir = devm_kzalloc(dev, sizeof(*ir), GFP_KERNEL);
+	if (!ir)
+		return -ENOMEM;
+
+	ir->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ir->reg_base))
+		return PTR_ERR(ir->reg_base);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "no irq resource found\n");
+		return -ENODEV;
+	}
+
+	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] 7+ messages in thread

* Re: [PATCH v6 2/2] media: rc: introduce Meson IR TX driver
  2021-07-16 14:45 ` [PATCH v6 2/2] media: rc: introduce Meson IR TX driver Viktor Prutyanov
@ 2021-07-16 15:34   ` Sean Young
  2021-07-16 16:01     ` Viktor Prutyanov
  0 siblings, 1 reply; 7+ messages in thread
From: Sean Young @ 2021-07-16 15:34 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 Fri, Jul 16, 2021 at 05:45:08PM +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
>  changes in v6:
>    - interruptible wait replaced with uninterruptible with timeout
>    - completion done check added to IRQ handler
> 
>  drivers/media/rc/Kconfig       |  10 +
>  drivers/media/rc/Makefile      |   1 +
>  drivers/media/rc/meson-ir-tx.c | 415 +++++++++++++++++++++++++++++++++
>  3 files changed, 426 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..77e276e3dbe8
> --- /dev/null
> +++ b/drivers/media/rc/meson-ir-tx.c
> @@ -0,0 +1,415 @@
> +// 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 MIRTX_TIMEOUT_US		(IR_MAX_DURATION / NSEC_PER_USEC)

IR_MAX_DURATION is already in microseconds, see commit 528222d853f9. Now
the wait_for_completion_timeout() gets 500us as the timeout. Has this
been tested?

Looks good otherwise

Thanks
Sean

> +
> +#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);
> +
> +	if (completion_done(&ir->completion))
> +		return IRQ_HANDLED;
> +
> +	spin_lock_irqsave(&ir->lock, flags);
> +	if (ir->buf_head < ir->buf_len)
> +		meson_irtx_send_buffer(ir);
> +	else
> +		complete(&ir->completion);
> +	spin_unlock_irqrestore(&ir->lock, flags);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier)
> +{
> +	struct meson_irtx *ir = rc->priv;
> +
> +	if (carrier == 0)
> +		return -EINVAL;
> +
> +	ir->carrier = carrier;
> +	meson_irtx_set_mod(ir);
> +
> +	return 0;
> +}
> +
> +static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32 duty_cycle)
> +{
> +	struct meson_irtx *ir = rc->priv;
> +
> +	ir->duty_cycle = duty_cycle;
> +	meson_irtx_set_mod(ir);
> +
> +	return 0;
> +}
> +
> +static void meson_irtx_update_buf(struct meson_irtx *ir, u32 *buf,
> +				  unsigned int len, unsigned int head)
> +{
> +	ir->buf = buf;
> +	ir->buf_len = len;
> +	ir->buf_head = head;
> +}
> +
> +static int meson_irtx_transmit(struct rc_dev *rc, unsigned int *buf,
> +			       unsigned int len)
> +{
> +	unsigned long flags;
> +	struct meson_irtx *ir = rc->priv;
> +	u32 *tx_buf;
> +	int ret = len;
> +
> +	if (!meson_irtx_check_buf(ir, buf, len))
> +		return -EINVAL;
> +
> +	tx_buf = kmalloc_array(len, sizeof(u32), GFP_KERNEL);
> +	if (!tx_buf)
> +		return -ENOMEM;
> +
> +	meson_irtx_fill_buf(ir, tx_buf, buf, len);
> +	dev_dbg(ir->dev, "TX buffer filled, length = %u\n", len);
> +
> +	spin_lock_irqsave(&ir->lock, flags);
> +	meson_irtx_update_buf(ir, tx_buf, len, 0);
> +	reinit_completion(&ir->completion);
> +	meson_irtx_send_buffer(ir);
> +	spin_unlock_irqrestore(&ir->lock, flags);
> +
> +	if (!wait_for_completion_timeout(&ir->completion,
> +					 usecs_to_jiffies(MIRTX_TIMEOUT_US)))
> +		ret = -ETIMEDOUT;
> +
> +	spin_lock_irqsave(&ir->lock, flags);
> +	kfree(ir->buf);
> +	meson_irtx_update_buf(ir, NULL, 0, 0);
> +	spin_unlock_irqrestore(&ir->lock, flags);
> +
> +	return ret;
> +}
> +
> +static int meson_irtx_mod_clock_probe(struct meson_irtx *ir,
> +				      unsigned int *clk_nr)
> +{
> +	struct device_node *np = ir->dev->of_node;
> +	struct clk *clock;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	clock = devm_clk_get(ir->dev, "xtal");
> +	if (IS_ERR(clock) || clk_prepare_enable(clock))
> +		return -ENODEV;
> +
> +	*clk_nr = IRB_MOD_XTAL3_CLK;
> +	ir->clk_rate = clk_get_rate(clock) / 3;
> +
> +	if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) {
> +		*clk_nr = IRB_MOD_1US_CLK;
> +		ir->clk_rate = IRB_MOD_1US_CLK_RATE;
> +	}
> +
> +	dev_info(ir->dev, "F_clk = %luHz\n", ir->clk_rate);
> +
> +	return 0;
> +}
> +
> +static int __init meson_irtx_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct meson_irtx *ir;
> +	struct rc_dev *rc;
> +	int irq;
> +	unsigned int clk_nr;
> +	int ret;
> +
> +	ir = devm_kzalloc(dev, sizeof(*ir), GFP_KERNEL);
> +	if (!ir)
> +		return -ENOMEM;
> +
> +	ir->reg_base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(ir->reg_base))
> +		return PTR_ERR(ir->reg_base);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(dev, "no irq resource found\n");
> +		return -ENODEV;
> +	}
> +
> +	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] 7+ messages in thread

* Re: [PATCH v6 2/2] media: rc: introduce Meson IR TX driver
  2021-07-16 15:34   ` Sean Young
@ 2021-07-16 16:01     ` Viktor Prutyanov
  0 siblings, 0 replies; 7+ messages in thread
From: Viktor Prutyanov @ 2021-07-16 16:01 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 Fri, 16 Jul 2021 16:34:26 +0100
Sean Young <sean@mess.org> wrote:

> On Fri, Jul 16, 2021 at 05:45:08PM +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
> >  changes in v6:
> >    - interruptible wait replaced with uninterruptible with timeout
> >    - completion done check added to IRQ handler
> > 
> >  drivers/media/rc/Kconfig       |  10 +
> >  drivers/media/rc/Makefile      |   1 +
> >  drivers/media/rc/meson-ir-tx.c | 415
> > +++++++++++++++++++++++++++++++++ 3 files changed, 426 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..77e276e3dbe8
> > --- /dev/null
> > +++ b/drivers/media/rc/meson-ir-tx.c
> > @@ -0,0 +1,415 @@
> > +// 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 MIRTX_TIMEOUT_US		(IR_MAX_DURATION /
> > NSEC_PER_USEC)  
> 
> IR_MAX_DURATION is already in microseconds, see commit 528222d853f9.
> Now the wait_for_completion_timeout() gets 500us as the timeout. Has
> this been tested?
>
Thanks for noticing this issue. I usually test each patch version on
two versions of the kernel, but now I forgot to check on a new one, I
will fix it.
 
> Looks good otherwise
> 
> Thanks
> Sean
> 
> > +
> > +#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);
> > +
> > +	if (completion_done(&ir->completion))
> > +		return IRQ_HANDLED;
> > +
> > +	spin_lock_irqsave(&ir->lock, flags);
> > +	if (ir->buf_head < ir->buf_len)
> > +		meson_irtx_send_buffer(ir);
> > +	else
> > +		complete(&ir->completion);
> > +	spin_unlock_irqrestore(&ir->lock, flags);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier)
> > +{
> > +	struct meson_irtx *ir = rc->priv;
> > +
> > +	if (carrier == 0)
> > +		return -EINVAL;
> > +
> > +	ir->carrier = carrier;
> > +	meson_irtx_set_mod(ir);
> > +
> > +	return 0;
> > +}
> > +
> > +static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32
> > duty_cycle) +{
> > +	struct meson_irtx *ir = rc->priv;
> > +
> > +	ir->duty_cycle = duty_cycle;
> > +	meson_irtx_set_mod(ir);
> > +
> > +	return 0;
> > +}
> > +
> > +static void meson_irtx_update_buf(struct meson_irtx *ir, u32 *buf,
> > +				  unsigned int len, unsigned int
> > head) +{
> > +	ir->buf = buf;
> > +	ir->buf_len = len;
> > +	ir->buf_head = head;
> > +}
> > +
> > +static int meson_irtx_transmit(struct rc_dev *rc, unsigned int
> > *buf,
> > +			       unsigned int len)
> > +{
> > +	unsigned long flags;
> > +	struct meson_irtx *ir = rc->priv;
> > +	u32 *tx_buf;
> > +	int ret = len;
> > +
> > +	if (!meson_irtx_check_buf(ir, buf, len))
> > +		return -EINVAL;
> > +
> > +	tx_buf = kmalloc_array(len, sizeof(u32), GFP_KERNEL);
> > +	if (!tx_buf)
> > +		return -ENOMEM;
> > +
> > +	meson_irtx_fill_buf(ir, tx_buf, buf, len);
> > +	dev_dbg(ir->dev, "TX buffer filled, length = %u\n", len);
> > +
> > +	spin_lock_irqsave(&ir->lock, flags);
> > +	meson_irtx_update_buf(ir, tx_buf, len, 0);
> > +	reinit_completion(&ir->completion);
> > +	meson_irtx_send_buffer(ir);
> > +	spin_unlock_irqrestore(&ir->lock, flags);
> > +
> > +	if (!wait_for_completion_timeout(&ir->completion,
> > +
> > usecs_to_jiffies(MIRTX_TIMEOUT_US)))
> > +		ret = -ETIMEDOUT;
> > +
> > +	spin_lock_irqsave(&ir->lock, flags);
> > +	kfree(ir->buf);
> > +	meson_irtx_update_buf(ir, NULL, 0, 0);
> > +	spin_unlock_irqrestore(&ir->lock, flags);
> > +
> > +	return ret;
> > +}
> > +
> > +static int meson_irtx_mod_clock_probe(struct meson_irtx *ir,
> > +				      unsigned int *clk_nr)
> > +{
> > +	struct device_node *np = ir->dev->of_node;
> > +	struct clk *clock;
> > +
> > +	if (!np)
> > +		return -ENODEV;
> > +
> > +	clock = devm_clk_get(ir->dev, "xtal");
> > +	if (IS_ERR(clock) || clk_prepare_enable(clock))
> > +		return -ENODEV;
> > +
> > +	*clk_nr = IRB_MOD_XTAL3_CLK;
> > +	ir->clk_rate = clk_get_rate(clock) / 3;
> > +
> > +	if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) {
> > +		*clk_nr = IRB_MOD_1US_CLK;
> > +		ir->clk_rate = IRB_MOD_1US_CLK_RATE;
> > +	}
> > +
> > +	dev_info(ir->dev, "F_clk = %luHz\n", ir->clk_rate);
> > +
> > +	return 0;
> > +}
> > +
> > +static int __init meson_irtx_probe(struct platform_device *pdev)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct meson_irtx *ir;
> > +	struct rc_dev *rc;
> > +	int irq;
> > +	unsigned int clk_nr;
> > +	int ret;
> > +
> > +	ir = devm_kzalloc(dev, sizeof(*ir), GFP_KERNEL);
> > +	if (!ir)
> > +		return -ENOMEM;
> > +
> > +	ir->reg_base = devm_platform_ioremap_resource(pdev, 0);
> > +	if (IS_ERR(ir->reg_base))
> > +		return PTR_ERR(ir->reg_base);
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0) {
> > +		dev_err(dev, "no irq resource found\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	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  

Best regards,
Viktor

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings
  2021-07-16 14:45 ` [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings Viktor Prutyanov
@ 2021-07-17 19:57   ` Martin Blumenstingl
  2021-07-19 10:40     ` Robin Murphy
  0 siblings, 1 reply; 7+ messages in thread
From: Martin Blumenstingl @ 2021-07-17 19:57 UTC (permalink / raw)
  To: Viktor Prutyanov
  Cc: sean, mchehab, robh+dt, khilman, Neil Armstrong, jbrunet,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, rockosov

Hi Viktor,

On Fri, Jul 16, 2021 at 4:45 PM Viktor Prutyanov
<viktor.prutyanov@phystech.edu> wrote:
[...]
> +  amlogic,fifo-threshold:
> +    description: TX FIFO threshold
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    minimum: 0
> +    maximum: 127
I tried to make sense of this property but I don't understand it yet
(even after reading the second patch in this series).
A "FIFO" (in my own words) is some physical property of the IR
transmitter in these Amlogic SoCs.
So for one specific SoC there can only be one FIFO size, not a range (0..127).

What about a value of 0: my understanding is that this means that
there's no FIFO. Will this hardware still work in that case?

From reading the driver code it seems to me that the FIFO size is 128.
The driver can use fewer FIFO entries if it wants, but this must not
affect the dt-bindings (as these describe the hardware - they don't
"configure" the driver).
If you look at arch/arm64/boot/dts/amlogic/meson-g12.dtsi you'll find
toddr_a, toddr_b and toddr_c there:
All three of them use identical circuitry internally, except that
toddr_a has a bigger FIFO size than the other two. Using a FIFO size
of 256 for toddr_a is not correct as it's an improper description of
the toddr_a hardware (that said, the driver can still decide that it
only wants to use 256 FIFO entries).


Best regards,
Martin

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings
  2021-07-17 19:57   ` Martin Blumenstingl
@ 2021-07-19 10:40     ` Robin Murphy
  0 siblings, 0 replies; 7+ messages in thread
From: Robin Murphy @ 2021-07-19 10:40 UTC (permalink / raw)
  To: Martin Blumenstingl, Viktor Prutyanov
  Cc: sean, mchehab, robh+dt, khilman, Neil Armstrong, jbrunet,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-amlogic, rockosov

Hi Martin,

(just reading this patch out of passing curiosity...)

On 2021-07-17 20:57, Martin Blumenstingl wrote:
> Hi Viktor,
> 
> On Fri, Jul 16, 2021 at 4:45 PM Viktor Prutyanov
> <viktor.prutyanov@phystech.edu> wrote:
> [...]
>> +  amlogic,fifo-threshold:
>> +    description: TX FIFO threshold
>> +    $ref: /schemas/types.yaml#/definitions/uint32
>> +    minimum: 0
>> +    maximum: 127
> I tried to make sense of this property but I don't understand it yet
> (even after reading the second patch in this series).
> A "FIFO" (in my own words) is some physical property of the IR
> transmitter in these Amlogic SoCs.
> So for one specific SoC there can only be one FIFO size, not a range (0..127).
> 
> What about a value of 0: my understanding is that this means that
> there's no FIFO. Will this hardware still work in that case?
> 
>  From reading the driver code it seems to me that the FIFO size is 128.
> The driver can use fewer FIFO entries if it wants, but this must not
> affect the dt-bindings (as these describe the hardware - they don't
> "configure" the driver).
> If you look at arch/arm64/boot/dts/amlogic/meson-g12.dtsi you'll find
> toddr_a, toddr_b and toddr_c there:
> All three of them use identical circuitry internally, except that
> toddr_a has a bigger FIFO size than the other two. Using a FIFO size
> of 256 for toddr_a is not correct as it's an improper description of
> the toddr_a hardware (that said, the driver can still decide that it
> only wants to use 256 FIFO entries).

In general, a FIFO threshold is not about how much of the FIFO you use 
overall, but how often and/or urgently you tend to it. If the only thing 
that matters is minimising CPU overhead then the optimum choice is to 
wait until the FIFO is entirely full/empty before taking action to 
drain/refill it. However, that necessarily creates a pause in 
reception/transmission for the time it takes to respond to the 
empty/full interrupt, hence why many FIFOs also implement a threshold 
interrupt for cases when uninterrupted communication is more desirable 
than absolutely minimising interrupts. Typically those are set to fire 
at some point shortly *before* the FIFO becomes completely full/empty, 
to leave enough remaining buffer for communication to continue during 
that time window until the ISR actually gets to respond.

That said, I'm also doubtful about this particular case. If the physical 
FIFO depth does actually vary between SoCs, that should be known by the 
driver and implicit in the compatible string, definitely not hidden in a 
tangential property. Otherwise, it's not apparent how this makes sense 
to configure statically on a per-SoC or per-board basis. If anything it 
would depend on the transmission rate of whatever IR protocol the user 
wishes to use at any given time AFAICS. If it's not sufficient for the 
driver to simply assume, say, an 80% threshold as "good enough", then 
presumably it has enough information about the clock rate and/or the 
parameters of the given Tx request to implement a slightly cleverer 
heuristic. If it's desirable to tweak the specific driver behaviour in 
cases where the user does know better, then by all means make that a 
module parameter, but it's not something which belongs in DT.

Cheers,
Robin.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2021-07-19 10:41 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-16 14:45 [PATCH v6 0/2] media: rc: add support for Amlogic Meson IR blaster Viktor Prutyanov
2021-07-16 14:45 ` [PATCH v6 1/2] media: rc: meson-ir-tx: document device tree bindings Viktor Prutyanov
2021-07-17 19:57   ` Martin Blumenstingl
2021-07-19 10:40     ` Robin Murphy
2021-07-16 14:45 ` [PATCH v6 2/2] media: rc: introduce Meson IR TX driver Viktor Prutyanov
2021-07-16 15:34   ` Sean Young
2021-07-16 16:01     ` Viktor Prutyanov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).