lede/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch

404 lines
11 KiB
Diff

Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
MAINTAINERS | 1 +
drivers/counter/Kconfig | 13 ++
drivers/counter/Makefile | 1 +
drivers/counter/rockchip-pwm-capture.c | 341 +++++++++++++++++++++++++++++++++
4 files changed, 356 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 3ddd245fd4ad8d9ed2e762910a7a1f6436f93e34..e5d26256d05a04a9642371cf3dbb4dd0c1c34e68 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20891,6 +20891,7 @@ L: linux-rockchip@lists.infradead.org
L: linux-pwm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
+F: drivers/counter/rockchip-pwm-capture.c
F: drivers/pwm/pwm-rockchip-v4.c
F: drivers/soc/rockchip/mfpwm.c
F: include/soc/rockchip/mfpwm.h
diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
index d30d22dfe57741b145a45632b6325d5f9680590e..01b4f5c326478c73b518041830ee0d65b37f6833 100644
--- a/drivers/counter/Kconfig
+++ b/drivers/counter/Kconfig
@@ -90,6 +90,19 @@ config MICROCHIP_TCB_CAPTURE
To compile this driver as a module, choose M here: the
module will be called microchip-tcb-capture.
+config ROCKCHIP_PWM_CAPTURE
+ tristate "Rockchip PWM Counter Capture driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on ROCKCHIP_MFPWM
+ depends on HAS_IOMEM
+ help
+ Generic counter framework driver for the multi-function PWM on
+ Rockchip SoCs such as the RK3576.
+
+ Uses the Rockchip Multi-function PWM controller driver infrastructure
+ to guarantee exclusive operation with other functions of the same
+ device implemented by drivers in other subsystems.
+
config RZ_MTU3_CNT
tristate "Renesas RZ/G2L MTU3a counter driver"
depends on RZ_MTU3
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index fa3c1d08f7068835aa912aa13bc92bcfd44d16fb..2bfcfc2c584bd174a9885064746a98f15b204aec 100644
--- a/drivers/counter/Makefile
+++ b/drivers/counter/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o
obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o
obj-$(CONFIG_INTEL_QEP) += intel-qep.o
obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o
+obj-$(CONFIG_ROCKCHIP_PWM_CAPTURE) += rockchip-pwm-capture.o
diff --git a/drivers/counter/rockchip-pwm-capture.c b/drivers/counter/rockchip-pwm-capture.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2bfa2c6e04dfa0410fa0d7ef1c395217e4a9db2
--- /dev/null
+++ b/drivers/counter/rockchip-pwm-capture.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Collabora Ltd.
+ *
+ * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on
+ * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It
+ * allows for measuring the period and duty cycle in nanoseconds through the
+ * generic counter framework, while guaranteeing exclusive use over the MFPWM
+ * device while the counter is enabled.
+ *
+ * Authors:
+ * Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/counter.h>
+#include <linux/devm-helpers.h>
+#include <linux/interrupt.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <soc/rockchip/mfpwm.h>
+
+#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC)
+/*
+ * amount of jiffies between no PWM signal change and us deciding PWM is off.
+ * PWM signals with a period longer than this won't be detected by us, which is
+ * the trade-off we make to have a faster response time when a signal is turned
+ * off.
+ */
+#define RKPWMC_CLEAR_DELAY (max(msecs_to_jiffies(100), 1))
+
+struct rockchip_pwm_capture {
+ struct rockchip_mfpwm_func *pwmf;
+ bool is_enabled;
+ spinlock_t enable_lock;
+ atomic_t lpc;
+ atomic_t hpc;
+ atomic_t captures_left;
+ struct delayed_work clear_capture;
+};
+
+static struct counter_signal rkpwmc_signals[] = {
+ {
+ .id = 0,
+ .name = "Channel 1"
+ },
+};
+
+static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = {
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+
+};
+
+static struct counter_synapse rkpwmc_pwm_synapses[] = {
+ {
+ .actions_list = rkpwmc_hpc_lpc_actions,
+ .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions),
+ .signal = &rkpwmc_signals[0]
+ },
+};
+
+static const enum counter_function rkpwmc_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+};
+
+static int rkpwmc_enable_read(struct counter_device *counter,
+ struct counter_count *count,
+ u8 *enable)
+{
+ struct rockchip_pwm_capture *pc = counter_priv(counter);
+
+ guard(spinlock)(&pc->enable_lock);
+
+ *enable = pc->is_enabled;
+
+ return 0;
+}
+
+static int rkpwmc_enable_write(struct counter_device *counter,
+ struct counter_count *count,
+ u8 enable)
+{
+ struct rockchip_pwm_capture *pc = counter_priv(counter);
+ int ret;
+
+ guard(spinlock)(&pc->enable_lock);
+
+ if (!!enable != pc->is_enabled) {
+ ret = mfpwm_acquire(pc->pwmf);
+ if (ret)
+ return ret;
+
+ if (enable) {
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
+ PWMV4_EN(false));
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL,
+ PWMV4_CTRL_CAP_FLAGS);
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN,
+ PWMV4_INT_LPC_W(true) |
+ PWMV4_INT_HPC_W(true));
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
+ PWMV4_EN(true) | PWMV4_CLK_EN(true));
+
+ ret = mfpwm_pwmclk_enable(pc->pwmf);
+ if (ret)
+ goto err_release;
+
+ ret = mfpwm_acquire(pc->pwmf);
+ if (ret)
+ goto err_disable_pwm_clk;
+
+ atomic_set(&pc->captures_left, 4);
+ pc->is_enabled = true;
+ } else {
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN,
+ PWMV4_INT_LPC_W(false) |
+ PWMV4_INT_HPC_W(false));
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
+ PWMV4_EN(false) | PWMV4_CLK_EN(false));
+ /*
+ * Do not use cancel_delayed_work here unless you want
+ * to cause the interrupt handler, which may still be
+ * running at this point, to stall. Similarly, don't do
+ * flush_delayed_work since it may sleep.
+ */
+ mod_delayed_work(system_bh_wq, &pc->clear_capture, 0);
+ mfpwm_pwmclk_disable(pc->pwmf);
+ pc->is_enabled = false;
+ mfpwm_release(pc->pwmf);
+ }
+
+ mfpwm_release(pc->pwmf);
+ }
+
+ return 0;
+
+err_disable_pwm_clk:
+ mfpwm_pwmclk_disable(pc->pwmf);
+err_release:
+ mfpwm_release(pc->pwmf);
+
+ return ret;
+}
+
+static struct counter_comp rkpwmc_ext[] = {
+ COUNTER_COMP_ENABLE(rkpwmc_enable_read, rkpwmc_enable_write),
+};
+
+enum rkpwmc_count_id {
+ COUNT_PERIOD = 0,
+ COUNT_DUTY = 1,
+};
+
+static struct counter_count rkpwmc_counts[] = {
+ {
+ .id = COUNT_PERIOD,
+ .name = "Period in nanoseconds",
+ .functions_list = rkpwmc_functions,
+ .num_functions = ARRAY_SIZE(rkpwmc_functions),
+ .synapses = rkpwmc_pwm_synapses,
+ .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses),
+ .ext = rkpwmc_ext,
+ .num_ext = ARRAY_SIZE(rkpwmc_ext),
+ },
+ {
+ .id = COUNT_DUTY,
+ .name = "Duty cycle in nanoseconds",
+ .functions_list = rkpwmc_functions,
+ .num_functions = ARRAY_SIZE(rkpwmc_functions),
+ .synapses = rkpwmc_pwm_synapses,
+ .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses),
+ .ext = rkpwmc_ext,
+ .num_ext = ARRAY_SIZE(rkpwmc_ext),
+ },
+};
+
+static int rkpwmc_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *value)
+{
+ struct rockchip_pwm_capture *pc = counter_priv(counter);
+ unsigned long rate;
+ u64 period;
+ u64 lpc;
+ u64 hpc;
+ int ret = 0;
+
+ if (count->id != COUNT_PERIOD && count->id != COUNT_DUTY)
+ return -EINVAL;
+
+ ret = mfpwm_acquire(pc->pwmf);
+ if (ret)
+ return ret;
+
+ rate = mfpwm_clk_get_rate(pc->pwmf->parent);
+ if (!rate) {
+ ret = -EINVAL;
+ goto out_release;
+ }
+
+ hpc = (u32) atomic_read(&pc->hpc);
+
+ if (count->id == COUNT_PERIOD) {
+ lpc = (u32) atomic_read(&pc->lpc);
+ period = mult_frac((hpc + lpc), NSEC_PER_SEC, rate);
+ *value = period;
+ } else {
+ *value = mult_frac(hpc, NSEC_PER_SEC, rate);
+ }
+
+out_release:
+ mfpwm_release(pc->pwmf);
+
+ return ret;
+}
+
+static const struct counter_ops rkpwmc_ops = {
+ .count_read = rkpwmc_count_read,
+};
+
+static irqreturn_t rkpwmc_irq_handler(int irq, void *data)
+{
+ struct rockchip_pwm_capture *pc = data;
+ u32 intsts;
+ u32 clr = 0;
+ u32 lpc;
+ u32 hpc;
+
+ intsts = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_INTSTS);
+
+ if (!(intsts & RKPWMC_INT_MASK))
+ return IRQ_NONE;
+
+ if (intsts & PWMV4_INT_LPC) {
+ clr |= PWMV4_INT_LPC;
+ atomic_dec_if_positive(&pc->captures_left);
+ }
+
+ if (intsts & PWMV4_INT_HPC) {
+ clr |= PWMV4_INT_HPC;
+ atomic_dec_if_positive(&pc->captures_left);
+ }
+
+ /* After 4 interrupts, reset to 4 captures left and read the regs */
+ if (!atomic_cmpxchg(&pc->captures_left, 0, 4)) {
+ lpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC);
+ hpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC);
+
+ atomic_set(&pc->lpc, lpc);
+ atomic_set(&pc->hpc, hpc);
+ mod_delayed_work(system_bh_wq, &pc->clear_capture,
+ RKPWMC_CLEAR_DELAY);
+ }
+
+ if (clr)
+ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INTSTS, clr);
+
+ if (intsts ^ clr)
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static void rkpwmc_clear_capture_worker(struct work_struct *work)
+{
+ struct rockchip_pwm_capture *pc = container_of(
+ work, struct rockchip_pwm_capture, clear_capture.work);
+
+ atomic_set(&pc->hpc, 0);
+ atomic_set(&pc->lpc, 0);
+}
+
+static int rockchip_pwm_capture_probe(struct platform_device *pdev)
+{
+ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);
+ struct rockchip_pwm_capture *pc;
+ struct counter_device *counter;
+ int ret;
+
+ counter = devm_counter_alloc(&pdev->dev, sizeof(*pc));
+ if (IS_ERR(counter))
+ return PTR_ERR(counter);
+
+ pc = counter_priv(counter);
+ pc->pwmf = pwmf;
+ spin_lock_init(&pc->enable_lock);
+
+ platform_set_drvdata(pdev, pc);
+
+ ret = devm_request_irq(&pdev->dev, pwmf->irq, rkpwmc_irq_handler,
+ IRQF_SHARED, pdev->name, pc);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed requesting IRQ\n");
+
+ ret = devm_delayed_work_autocancel(&pdev->dev, &pc->clear_capture,
+ rkpwmc_clear_capture_worker);
+
+ counter->name = pdev->name;
+ counter->signals = rkpwmc_signals;
+ counter->num_signals = ARRAY_SIZE(rkpwmc_signals);
+ counter->ops = &rkpwmc_ops;
+ counter->counts = rkpwmc_counts;
+ counter->num_counts = ARRAY_SIZE(rkpwmc_counts);
+
+ ret = devm_counter_add(&pdev->dev, counter);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
+
+ return 0;
+}
+
+static void rockchip_pwm_capture_remove(struct platform_device *pdev)
+{
+ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);
+
+ mfpwm_remove_func(pwmf);
+}
+
+static const struct platform_device_id rockchip_pwm_capture_id_table[] = {
+ { .name = "rockchip-pwm-capture", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, rockchip_pwm_capture_id_table);
+
+static struct platform_driver rockchip_pwm_capture_driver = {
+ .probe = rockchip_pwm_capture_probe,
+ .remove = rockchip_pwm_capture_remove,
+ .id_table = rockchip_pwm_capture_id_table,
+ .driver = {
+ .name = "rockchip-pwm-capture",
+ },
+};
+module_platform_driver(rockchip_pwm_capture_driver);
+
+MODULE_AUTHOR("Nicolas Frattaroli <nicolas.frattaroli@collabora.com>");
+MODULE_DESCRIPTION("Rockchip PWM Counter Capture Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ROCKCHIP_MFPWM");
+MODULE_IMPORT_NS("COUNTER");
--
2.49.0