mirror of
https://github.com/coolsnowwolf/lede.git
synced 2025-05-01 10:20:31 +08:00
404 lines
11 KiB
Diff
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
|