Enable capture driver support for OMAP DM timer hardware.
DM timer hardware supports capture feature.It can be used
to timestamp events (falling/rising edges) detected on input signal.
The timer IO pin can also be configured in PWM mode but this
driver supports simple independent capture functionality.
It is assumed that the capture signal is active else both duty cycle
and period returned by driver will not be valid.
Signed-off-by: Gokul Praveen <g-praveen@ti.com>
---
BOOT LOGS:
https://gist.github.com/GokulPraveen2001/581fa07b2e93f30dea9f6188a97b944b
TIMER CAPTURE DTSO:
&{/} {
main_cap10: dmtimer-main-cap-10 {
compatible = "ti,omap-dmtimer-cap";
ti,timers = <&main_timer10>;
pinctrl-0 = <&maindmtimer1_pins_default>,<&main_timer10_ctrl_config>;
pinctrl-names = "default";
};
};
&main_timer10{
status = "okay";
};
/*MAIN_TIMERIO1*/
&main_pmx0 {
maindmtimer1_pins_default: maindmtimer1-default-pins {
pinctrl-single,pins = <
J784S4_IOPAD(0x0ec, PIN_INPUT, 0) /* (AN37) TIMER_IO1:Input mode for Capture*/
>;
};
};
/*Sets in CAP mode using MAIN TIMER IOX(X=1 here) CTRL MMR REGISTERS*/
&main_timerio_input {
main_timer10_ctrl_config: main-timer10-ctrl-config {
pinctrl-single,pins = <
J784S4_IOPAD(0x28,0,0x1)>; /* MAIN_TIMER_10 will use MAIN_TIMERIO1 pin(maindmtimer1-default-pins) for capture*/
};
};
---
drivers/counter/Kconfig | 13 +
drivers/counter/Makefile | 1 +
drivers/counter/ti-dmtimer-cap.c | 455 +++++++++++++++++++++++++++++++
3 files changed, 469 insertions(+)
create mode 100644 drivers/counter/ti-dmtimer-cap.c
diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
index d30d22dfe577..bec07cf15779 100644
--- a/drivers/counter/Kconfig
+++ b/drivers/counter/Kconfig
@@ -121,6 +121,19 @@ config STM32_TIMER_CNT
To compile this driver as a module, choose M here: the
module will be called stm32-timer-cnt.
+config TI_DMTIMER_CAPTURE
+ tristate "OMAP Dual-Mode Timer Capture support"
+ depends on OMAP_DM_TIMER || COMPILE_TEST
+ help
+ Select this option to use the Texas Instruments OMAP DM Timer
+ driver in capture mode.
+
+ It can be used to timestamp events (falling/rising edges) detected
+ on Timer input signal.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ti-dmtimer-cap.
+
config TI_ECAP_CAPTURE
tristate "TI eCAP capture driver"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index fa3c1d08f706..7c2d226fe984 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_TI_DMTIMER_CAPTURE) +=ti-dmtimer-cap.o
diff --git a/drivers/counter/ti-dmtimer-cap.c b/drivers/counter/ti-dmtimer-cap.c
new file mode 100644
index 000000000000..dedfb15faa10
--- /dev/null
+++ b/drivers/counter/ti-dmtimer-cap.c
@@ -0,0 +1,455 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DM timer Capture Driver
+ */
+
+#include <clocksource/timer-ti-dm.h>
+#include <linux/atomic.h>
+#include <linux/counter.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/dmtimer-omap.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+
+#define TIMER_DRV_NAME "CAP_OMAP_DMTIMER"
+/* Timer signals */
+#define TIMER_CLOCK_SIG 0
+#define TIMER_INPUT_SIG 1
+
+/**
+ * struct cap_omap_dmtimer_counter - Structure representing a cap counter
+ * corresponding to omap dm timer.
+ * @counter: Capture counter
+ * @enabled: Tracks the enable status of omap dm timer.
+ * @mutex: Mutex to protect cap apply state
+ * @dm_timer: Pointer to omap dm timer.
+ * @pdata: Pointer to omap dm timer ops.
+ * @dm_timer_pdev: Pointer to omap dm timer platform device
+ */
+struct cap_omap_dmtimer_counter {
+ struct counter_device counter;
+ bool enabled;
+ /* Mutex to protect cap apply state */
+ struct mutex mutex;
+ struct omap_dm_timer *dm_timer;
+ const struct omap_dm_timer_ops *pdata;
+ struct platform_device *dm_timer_pdev;
+};
+/**
+ * cap_omap_dmtimer_start() - Start the cap omap dm timer in capture mode
+ * @omap: Pointer to cap omap dm timer counter
+ */
+static void cap_omap_dmtimer_start(struct cap_omap_dmtimer_counter *omap)
+{
+ u32 ret;
+ struct device *dev = &omap->dm_timer_pdev->dev;
+
+ ret = omap->pdata->start(omap->dm_timer);
+ if (ret)
+ dev_err(dev, "%d: Failed to start timer.\n", ret);
+}
+
+/**
+ * cap_omap_dmtimer_is_enabled() - Detect if the timer capture is enabled.
+ * @omap: Pointer to cap omap dm timer counter
+ *
+ * Return true if capture is enabled else false.
+ */
+static bool cap_omap_dmtimer_is_enabled(struct cap_omap_dmtimer_counter *omap)
+{
+ u32 status;
+
+ status = omap->pdata->get_cap_status(omap->dm_timer);
+
+ return !!(status & OMAP_TIMER_CTRL_ST);
+}
+
+static int cap_omap_dmtimer_clk_get_freq(struct counter_device *counter,
+ struct counter_signal *signal, u64 *freq)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+ struct clk *fclk;
+
+ fclk = omap->pdata->get_fclk(omap->dm_timer);
+ if (!fclk) {
+ dev_err(counter->parent, "invalid dmtimer fclk\n");
+ return -EINVAL;
+ }
+
+ *freq = clk_get_rate(fclk);
+ if (!(*freq)) {
+ dev_err(counter->parent, "invalid dmtimer fclk rate\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+/**
+ * cap_omap_dmtimer_apply() - Changes the state of the cap omap dm timer counter.
+ * @counter:Pointer to capture counter.
+ *
+ * Return 0 if successfully changed the state else appropriate error.
+ */
+static int cap_omap_dmtimer_apply(struct counter_device *counter)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+ struct device *dev = &omap->dm_timer_pdev->dev;
+ int ret = 0;
+
+ /* Ensure that the timer is in stop mode so that the configs can be changed. */
+ if (cap_omap_dmtimer_is_enabled(omap)) {
+ ret = omap->pdata->stop(omap->dm_timer);
+ if (ret)
+ dev_err(dev, "%d: Failed to stop timer.\n", ret);
+ }
+
+ ret = omap->pdata->set_cap(omap->dm_timer, true, true);
+ if (ret) {
+ dev_err(dev, "%d: Failed to set timer capture configuration.\n", ret);
+ return ret;
+ }
+
+ cap_omap_dmtimer_start(omap);
+
+ return ret;
+}
+
+static int cap_omap_dmtimer_capture(struct counter_device *counter,
+ struct counter_count *count, u64 *duty_cycle)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+ *duty_cycle = 0;
+
+ if (!omap->enabled) {
+ dev_err(counter->parent, "Timer is disabled.\n");
+ omap->pdata->stop(omap->dm_timer);
+ return 0;
+ }
+
+ *duty_cycle = omap->pdata->read_cap(omap->dm_timer, false);
+
+ *duty_cycle = *duty_cycle > 0 ? *duty_cycle : 0;
+
+ return *duty_cycle;
+}
+
+static int cap_omap_dmtimer_period(struct counter_device *counter,
+ struct counter_signal *signal, u64 *freq)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+ u64 clk_freq = 0;
+ u64 period = 0;
+ *freq = 0;
+
+ if (!omap->enabled) {
+ dev_err(counter->parent, "Timer is disabled.\n");
+ omap->pdata->stop(omap->dm_timer);
+ return 0;
+ }
+
+ period = omap->pdata->read_cap(omap->dm_timer, true);
+ cap_omap_dmtimer_clk_get_freq(counter, signal, &clk_freq);
+
+ if (period > 0)
+ *freq = clk_freq/period;
+
+ return *freq+1;
+}
+static int cap_omap_dmtimer_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+
+ *enable = omap->enabled;
+
+ return 0;
+}
+
+static int cap_omap_dmtimer_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+
+ *val = omap->pdata->read_counter(omap->dm_timer);
+
+ return 0;
+}
+
+static int cap_omap_dmtimer_count_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+
+ if (val > U32_MAX)
+ return -ERANGE;
+
+ omap->pdata->write_counter(omap->dm_timer, val);
+
+ return 0;
+}
+
+static int cap_omap_dmtimer_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+
+ if (enable == omap->enabled)
+ goto out;
+
+ if (enable)
+ cap_omap_dmtimer_apply(counter);
+ else
+ omap->pdata->stop(omap->dm_timer);
+
+ omap->enabled = enable;
+out:
+ return 0;
+}
+
+static int cap_omap_dmtimer_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ *function = COUNTER_FUNCTION_INCREASE;
+
+ return 0;
+}
+
+static int cap_omap_dmtimer_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ *action = (synapse->signal->id == TIMER_CLOCK_SIG) ?
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE :
+ COUNTER_SYNAPSE_ACTION_NONE;
+
+ return 0;
+}
+
+static const struct counter_ops cap_omap_dmtimer_ops = {
+ .count_read = cap_omap_dmtimer_count_read,
+ .count_write = cap_omap_dmtimer_count_write,
+ .function_read = cap_omap_dmtimer_function_read,
+ .action_read = cap_omap_dmtimer_action_read,
+};
+
+static const enum counter_function cap_omap_dmtimer_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+};
+
+static struct counter_comp cap_omap_dmtimer_clock_ext[] = {
+ COUNTER_COMP_SIGNAL_U64("frequency", cap_omap_dmtimer_clk_get_freq, NULL),
+};
+
+static struct counter_signal cap_omap_dmtimer_signals[] = {
+ {
+ .id = TIMER_CLOCK_SIG,
+ .name = "Clock Signal",
+ .ext = cap_omap_dmtimer_clock_ext,
+ .num_ext = ARRAY_SIZE(cap_omap_dmtimer_clock_ext),
+ },
+ {
+ .id = TIMER_INPUT_SIG,
+ .name = "Input Signal",
+ },
+};
+
+/* Counter will increase at rising edges of a clock */
+static const enum counter_synapse_action cap_omap_dmtimer_clock_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+/* No trigger here */
+static const enum counter_synapse_action cap_omap_dmtimer_input_actions[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+};
+
+static struct counter_synapse cap_omap_dmtimer_synapses[] = {
+ {
+ .actions_list = cap_omap_dmtimer_clock_actions,
+ .num_actions = ARRAY_SIZE(cap_omap_dmtimer_clock_actions),
+ .signal = &cap_omap_dmtimer_signals[TIMER_CLOCK_SIG],
+ },
+ {
+ .actions_list = cap_omap_dmtimer_input_actions,
+ .num_actions = ARRAY_SIZE(cap_omap_dmtimer_input_actions),
+ .signal = &cap_omap_dmtimer_signals[TIMER_INPUT_SIG],
+ },
+};
+
+static struct counter_comp cap_omap_dmtimer_count_ext[] = {
+ COUNTER_COMP_CAPTURE(cap_omap_dmtimer_capture, NULL),
+ COUNTER_COMP_ENABLE(cap_omap_dmtimer_enable_read, cap_omap_dmtimer_enable_write),
+ COUNTER_COMP_FREQUENCY(cap_omap_dmtimer_period),
+};
+
+static struct counter_count cap_omap_dmtimer_counts[] = {
+ {
+ .name = "Timestamp Counter",
+ .functions_list = cap_omap_dmtimer_functions,
+ .num_functions = ARRAY_SIZE(cap_omap_dmtimer_functions),
+ .synapses = cap_omap_dmtimer_synapses,
+ .num_synapses = ARRAY_SIZE(cap_omap_dmtimer_synapses),
+ .ext = cap_omap_dmtimer_count_ext,
+ .num_ext = ARRAY_SIZE(cap_omap_dmtimer_count_ext),
+ },
+};
+
+static int cap_omap_dmtimer_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct dmtimer_platform_data *timer_pdata;
+ const struct omap_dm_timer_ops *pdata;
+ struct platform_device *timer_pdev;
+ struct omap_dm_timer *dm_timer;
+ struct device_node *timer;
+ struct cap_omap_dmtimer_counter *omap;
+ struct counter_device *counter_dev;
+ int ret = 0;
+
+ timer = of_parse_phandle(np, "ti,timers", 0);
+ if (!timer) {
+ dev_err(&pdev->dev, "Unable to find Timer node\n");
+ return -ENODEV;
+ }
+
+ timer_pdev = of_find_device_by_node(timer);
+ if (!timer_pdev) {
+ dev_err(&pdev->dev, "Unable to find Timer pdev\n");
+ ret = -ENODEV;
+ goto err_find_timer_pdev;
+ }
+ timer_pdata = dev_get_platdata(&timer_pdev->dev);
+ if (!timer_pdata) {
+ dev_dbg(&pdev->dev,
+ "dmtimer pdata structure NULL, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ dev_err_probe(&pdev->dev, ret, "Probe deferred\n");
+ goto err_platdata;
+ }
+
+ pdata = timer_pdata->timer_ops;
+
+ if (!pdata || !pdata->request_by_node ||
+ !pdata->free ||
+ !pdata->enable ||
+ !pdata->disable ||
+ !pdata->get_fclk ||
+ !pdata->start ||
+ !pdata->stop ||
+ !pdata->set_load ||
+ !pdata->set_match ||
+ !pdata->set_cap ||
+ !pdata->get_cap_status ||
+ !pdata->read_cap ||
+ !pdata->set_prescaler ||
+ !pdata->write_counter) {
+ dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n");
+ ret = -EINVAL;
+ goto err_platdata;
+ }
+
+ dm_timer = pdata->request_by_node(timer);
+ if (!dm_timer) {
+ ret = -EPROBE_DEFER;
+ goto err_request_timer;
+ }
+
+ /* struct cap_omap_dmtimer_counter *omap */
+ counter_dev = devm_counter_alloc(dev, sizeof(*omap));
+ if (!counter_dev) {
+ dev_err(&pdev->dev, "Unable to allocate dmtimercounter\n");
+ ret = -ENOMEM;
+ goto err_alloc_omap;
+ }
+ omap = counter_priv(counter_dev);
+
+ counter_dev->name = TIMER_DRV_NAME;
+ counter_dev->parent = dev;
+ counter_dev->ops = &cap_omap_dmtimer_ops;
+ counter_dev->signals = cap_omap_dmtimer_signals;
+ counter_dev->num_signals = ARRAY_SIZE(cap_omap_dmtimer_signals);
+ counter_dev->counts = cap_omap_dmtimer_counts;
+ counter_dev->num_counts = ARRAY_SIZE(cap_omap_dmtimer_counts);
+ mutex_init(&omap->mutex);
+ omap->pdata = pdata;
+ omap->dm_timer = dm_timer;
+ omap->dm_timer_pdev = timer_pdev;
+
+ if (pm_runtime_active(&omap->dm_timer_pdev->dev))
+ omap->pdata->stop(omap->dm_timer);
+
+ of_node_put(timer);
+
+ platform_set_drvdata(pdev, counter_dev);
+
+ ret = devm_counter_add(dev, counter_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add counter\n");
+
+ return 0;
+
+err_alloc_omap:
+ pdata->free(dm_timer);
+err_request_timer:
+
+err_platdata:
+ put_device(&timer_pdev->dev);
+err_find_timer_pdev:
+
+ of_node_put(timer);
+
+ return ret;
+}
+
+static void cap_omap_dmtimer_remove(struct platform_device *pdev)
+{
+ struct counter_device *counter = platform_get_drvdata(pdev);
+ struct cap_omap_dmtimer_counter *omap = counter_priv(counter);
+
+ counter_unregister(counter);
+
+ if (pm_runtime_active(&omap->dm_timer_pdev->dev))
+ omap->pdata->stop(omap->dm_timer);
+
+ omap->pdata->free(omap->dm_timer);
+
+ put_device(&omap->dm_timer_pdev->dev);
+
+ mutex_destroy(&omap->mutex);
+
+}
+
+static const struct of_device_id cap_omap_dmtimer_of_match[] = {
+ {.compatible = "ti,omap-dmtimer-cap"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, cap_omap_dmtimer_of_match);
+
+static struct platform_driver cap_omap_dmtimer_driver = {
+ .driver = {
+ .name = "omap-dmtimer-cap",
+ .of_match_table = cap_omap_dmtimer_of_match,
+ },
+ .probe = cap_omap_dmtimer_probe,
+ .remove = cap_omap_dmtimer_remove,
+};
+module_platform_driver(cap_omap_dmtimer_driver);
+
+MODULE_AUTHOR("Gokul Praveen <g-praveen@ti.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("OMAP CAP Driver using Dual-mode Timers");
+MODULE_IMPORT_NS("COUNTER");
--
2.34.1
Hi Gokul, kernel test robot noticed the following build errors: [auto build test ERROR on robh/for-next] [also build test ERROR on linus/master v6.17-rc5 next-20250909] [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#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Gokul-Praveen/dt-bindings-counter-Add-new-ti-omap-dmtimer-cap-compatible/20250909-160651 base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next patch link: https://lore.kernel.org/r/20250909080042.36127-3-g-praveen%40ti.com patch subject: [PATCH 2/2] counter: ti-dmtimer-cap : capture driver support for OMAP DM timer config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20250910/202509101412.ze6xyOUu-lkp@intel.com/config) compiler: m68k-linux-gcc (GCC) 15.1.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250910/202509101412.ze6xyOUu-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202509101412.ze6xyOUu-lkp@intel.com/ All errors (new ones prefixed by >>): drivers/counter/ti-dmtimer-cap.c: In function 'cap_omap_dmtimer_is_enabled': >> drivers/counter/ti-dmtimer-cap.c:73:31: error: 'const struct omap_dm_timer_ops' has no member named 'get_cap_status'; did you mean 'get_pwm_status'? 73 | status = omap->pdata->get_cap_status(omap->dm_timer); | ^~~~~~~~~~~~~~ | get_pwm_status drivers/counter/ti-dmtimer-cap.c: In function 'cap_omap_dmtimer_apply': >> drivers/counter/ti-dmtimer-cap.c:117:26: error: 'const struct omap_dm_timer_ops' has no member named 'set_cap' 117 | ret = omap->pdata->set_cap(omap->dm_timer, true, true); | ^~ drivers/counter/ti-dmtimer-cap.c: In function 'cap_omap_dmtimer_capture': >> drivers/counter/ti-dmtimer-cap.c:140:34: error: 'const struct omap_dm_timer_ops' has no member named 'read_cap' 140 | *duty_cycle = omap->pdata->read_cap(omap->dm_timer, false); | ^~ drivers/counter/ti-dmtimer-cap.c: In function 'cap_omap_dmtimer_period': drivers/counter/ti-dmtimer-cap.c:161:29: error: 'const struct omap_dm_timer_ops' has no member named 'read_cap' 161 | period = omap->pdata->read_cap(omap->dm_timer, true); | ^~ drivers/counter/ti-dmtimer-cap.c: In function 'cap_omap_dmtimer_probe': drivers/counter/ti-dmtimer-cap.c:355:19: error: 'const struct omap_dm_timer_ops' has no member named 'set_cap' 355 | !pdata->set_cap || | ^~ drivers/counter/ti-dmtimer-cap.c:356:21: error: 'const struct omap_dm_timer_ops' has no member named 'get_cap_status'; did you mean 'get_pwm_status'? 356 | !pdata->get_cap_status || | ^~~~~~~~~~~~~~ | get_pwm_status drivers/counter/ti-dmtimer-cap.c:357:23: error: 'const struct omap_dm_timer_ops' has no member named 'read_cap' 357 | !pdata->read_cap || | ^~ vim +73 drivers/counter/ti-dmtimer-cap.c 62 63 /** 64 * cap_omap_dmtimer_is_enabled() - Detect if the timer capture is enabled. 65 * @omap: Pointer to cap omap dm timer counter 66 * 67 * Return true if capture is enabled else false. 68 */ 69 static bool cap_omap_dmtimer_is_enabled(struct cap_omap_dmtimer_counter *omap) 70 { 71 u32 status; 72 > 73 status = omap->pdata->get_cap_status(omap->dm_timer); 74 75 return !!(status & OMAP_TIMER_CTRL_ST); 76 } 77 78 static int cap_omap_dmtimer_clk_get_freq(struct counter_device *counter, 79 struct counter_signal *signal, u64 *freq) 80 { 81 struct cap_omap_dmtimer_counter *omap = counter_priv(counter); 82 struct clk *fclk; 83 84 fclk = omap->pdata->get_fclk(omap->dm_timer); 85 if (!fclk) { 86 dev_err(counter->parent, "invalid dmtimer fclk\n"); 87 return -EINVAL; 88 } 89 90 *freq = clk_get_rate(fclk); 91 if (!(*freq)) { 92 dev_err(counter->parent, "invalid dmtimer fclk rate\n"); 93 return -EINVAL; 94 } 95 96 return 0; 97 } 98 /** 99 * cap_omap_dmtimer_apply() - Changes the state of the cap omap dm timer counter. 100 * @counter:Pointer to capture counter. 101 * 102 * Return 0 if successfully changed the state else appropriate error. 103 */ 104 static int cap_omap_dmtimer_apply(struct counter_device *counter) 105 { 106 struct cap_omap_dmtimer_counter *omap = counter_priv(counter); 107 struct device *dev = &omap->dm_timer_pdev->dev; 108 int ret = 0; 109 110 /* Ensure that the timer is in stop mode so that the configs can be changed. */ 111 if (cap_omap_dmtimer_is_enabled(omap)) { 112 ret = omap->pdata->stop(omap->dm_timer); 113 if (ret) 114 dev_err(dev, "%d: Failed to stop timer.\n", ret); 115 } 116 > 117 ret = omap->pdata->set_cap(omap->dm_timer, true, true); 118 if (ret) { 119 dev_err(dev, "%d: Failed to set timer capture configuration.\n", ret); 120 return ret; 121 } 122 123 cap_omap_dmtimer_start(omap); 124 125 return ret; 126 } 127 128 static int cap_omap_dmtimer_capture(struct counter_device *counter, 129 struct counter_count *count, u64 *duty_cycle) 130 { 131 struct cap_omap_dmtimer_counter *omap = counter_priv(counter); 132 *duty_cycle = 0; 133 134 if (!omap->enabled) { 135 dev_err(counter->parent, "Timer is disabled.\n"); 136 omap->pdata->stop(omap->dm_timer); 137 return 0; 138 } 139 > 140 *duty_cycle = omap->pdata->read_cap(omap->dm_timer, false); 141 142 *duty_cycle = *duty_cycle > 0 ? *duty_cycle : 0; 143 144 return *duty_cycle; 145 } 146 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
On 9/9/25 3:00 AM, Gokul Praveen wrote: > Enable capture driver support for OMAP DM timer hardware. > > DM timer hardware supports capture feature.It can be used > to timestamp events (falling/rising edges) detected on input signal. > > The timer IO pin can also be configured in PWM mode but this > driver supports simple independent capture functionality. > > It is assumed that the capture signal is active else both duty cycle > and period returned by driver will not be valid. > > Signed-off-by: Gokul Praveen <g-praveen@ti.com> > --- > BOOT LOGS: > https://gist.github.com/GokulPraveen2001/581fa07b2e93f30dea9f6188a97b944b > > TIMER CAPTURE DTSO: > > &{/} { > > main_cap10: dmtimer-main-cap-10 { > compatible = "ti,omap-dmtimer-cap"; > ti,timers = <&main_timer10>; > pinctrl-0 = <&maindmtimer1_pins_default>,<&main_timer10_ctrl_config>; > pinctrl-names = "default"; > }; > > }; > > &main_timer10{ > status = "okay"; > }; > > /*MAIN_TIMERIO1*/ > &main_pmx0 { > maindmtimer1_pins_default: maindmtimer1-default-pins { > pinctrl-single,pins = < > J784S4_IOPAD(0x0ec, PIN_INPUT, 0) /* (AN37) TIMER_IO1:Input mode for Capture*/ > >; > }; > > }; > > /*Sets in CAP mode using MAIN TIMER IOX(X=1 here) CTRL MMR REGISTERS*/ > &main_timerio_input { > main_timer10_ctrl_config: main-timer10-ctrl-config { > pinctrl-single,pins = < > J784S4_IOPAD(0x28,0,0x1)>; /* MAIN_TIMER_10 will use MAIN_TIMERIO1 pin(maindmtimer1-default-pins) for capture*/ > }; > }; > --- > drivers/counter/Kconfig | 13 + > drivers/counter/Makefile | 1 + > drivers/counter/ti-dmtimer-cap.c | 455 +++++++++++++++++++++++++++++++ > 3 files changed, 469 insertions(+) > create mode 100644 drivers/counter/ti-dmtimer-cap.c > > diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig > index d30d22dfe577..bec07cf15779 100644 > --- a/drivers/counter/Kconfig > +++ b/drivers/counter/Kconfig > @@ -121,6 +121,19 @@ config STM32_TIMER_CNT > To compile this driver as a module, choose M here: the > module will be called stm32-timer-cnt. > > +config TI_DMTIMER_CAPTURE > + tristate "OMAP Dual-Mode Timer Capture support" > + depends on OMAP_DM_TIMER || COMPILE_TEST > + help > + Select this option to use the Texas Instruments OMAP DM Timer > + driver in capture mode. > + > + It can be used to timestamp events (falling/rising edges) detected > + on Timer input signal. > + > + To compile this driver as a module, choose M here: the module > + will be called ti-dmtimer-cap. > + > config TI_ECAP_CAPTURE > tristate "TI eCAP capture driver" > depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST > diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile > index fa3c1d08f706..7c2d226fe984 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_TI_DMTIMER_CAPTURE) +=ti-dmtimer-cap.o > diff --git a/drivers/counter/ti-dmtimer-cap.c b/drivers/counter/ti-dmtimer-cap.c > new file mode 100644 > index 000000000000..dedfb15faa10 > --- /dev/null > +++ b/drivers/counter/ti-dmtimer-cap.c > @@ -0,0 +1,455 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * DM timer Capture Driver Add proper copyright. > + */ > + > +#include <clocksource/timer-ti-dm.h> > +#include <linux/atomic.h> > +#include <linux/counter.h> > +#include <linux/clk.h> > +#include <linux/err.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/platform_data/dmtimer-omap.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/slab.h> > +#include <linux/time.h> > + > +#define TIMER_DRV_NAME "CAP_OMAP_DMTIMER" > +/* Timer signals */ > +#define TIMER_CLOCK_SIG 0 > +#define TIMER_INPUT_SIG 1 > + > +/** > + * struct cap_omap_dmtimer_counter - Structure representing a cap counter > + * corresponding to omap dm timer. > + * @counter: Capture counter > + * @enabled: Tracks the enable status of omap dm timer. > + * @mutex: Mutex to protect cap apply state > + * @dm_timer: Pointer to omap dm timer. > + * @pdata: Pointer to omap dm timer ops. > + * @dm_timer_pdev: Pointer to omap dm timer platform device > + */ > +struct cap_omap_dmtimer_counter { > + struct counter_device counter; > + bool enabled; > + /* Mutex to protect cap apply state */ > + struct mutex mutex; > + struct omap_dm_timer *dm_timer; > + const struct omap_dm_timer_ops *pdata; > + struct platform_device *dm_timer_pdev; > +}; Random missing newlines everywhere. > +/** > + * cap_omap_dmtimer_start() - Start the cap omap dm timer in capture mode > + * @omap: Pointer to cap omap dm timer counter > + */ > +static void cap_omap_dmtimer_start(struct cap_omap_dmtimer_counter *omap) > +{ > + u32 ret; > + struct device *dev = &omap->dm_timer_pdev->dev; > + > + ret = omap->pdata->start(omap->dm_timer); > + if (ret) > + dev_err(dev, "%d: Failed to start timer.\n", ret); > +} > + > +/** > + * cap_omap_dmtimer_is_enabled() - Detect if the timer capture is enabled. > + * @omap: Pointer to cap omap dm timer counter > + * > + * Return true if capture is enabled else false. > + */ > +static bool cap_omap_dmtimer_is_enabled(struct cap_omap_dmtimer_counter *omap) > +{ > + u32 status; > + > + status = omap->pdata->get_cap_status(omap->dm_timer); > + > + return !!(status & OMAP_TIMER_CTRL_ST); > +} > + > +static int cap_omap_dmtimer_clk_get_freq(struct counter_device *counter, > + struct counter_signal *signal, u64 *freq) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + struct clk *fclk; > + > + fclk = omap->pdata->get_fclk(omap->dm_timer); > + if (!fclk) { > + dev_err(counter->parent, "invalid dmtimer fclk\n"); > + return -EINVAL; > + } > + > + *freq = clk_get_rate(fclk); > + if (!(*freq)) { > + dev_err(counter->parent, "invalid dmtimer fclk rate\n"); > + return -EINVAL; > + } > + > + return 0; > +} > +/** > + * cap_omap_dmtimer_apply() - Changes the state of the cap omap dm timer counter. > + * @counter:Pointer to capture counter. > + * > + * Return 0 if successfully changed the state else appropriate error. > + */ > +static int cap_omap_dmtimer_apply(struct counter_device *counter) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + struct device *dev = &omap->dm_timer_pdev->dev; > + int ret = 0; > + > + /* Ensure that the timer is in stop mode so that the configs can be changed. */ > + if (cap_omap_dmtimer_is_enabled(omap)) { > + ret = omap->pdata->stop(omap->dm_timer); > + if (ret) > + dev_err(dev, "%d: Failed to stop timer.\n", ret); > + } > + > + ret = omap->pdata->set_cap(omap->dm_timer, true, true); > + if (ret) { > + dev_err(dev, "%d: Failed to set timer capture configuration.\n", ret); > + return ret; > + } > + > + cap_omap_dmtimer_start(omap); > + > + return ret; > +} > + > +static int cap_omap_dmtimer_capture(struct counter_device *counter, > + struct counter_count *count, u64 *duty_cycle) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + *duty_cycle = 0; > + > + if (!omap->enabled) { > + dev_err(counter->parent, "Timer is disabled.\n"); > + omap->pdata->stop(omap->dm_timer); > + return 0; > + } > + > + *duty_cycle = omap->pdata->read_cap(omap->dm_timer, false); > + > + *duty_cycle = *duty_cycle > 0 ? *duty_cycle : 0; > + > + return *duty_cycle; > +} > + > +static int cap_omap_dmtimer_period(struct counter_device *counter, > + struct counter_signal *signal, u64 *freq) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + u64 clk_freq = 0; > + u64 period = 0; > + *freq = 0; > + > + if (!omap->enabled) { > + dev_err(counter->parent, "Timer is disabled.\n"); > + omap->pdata->stop(omap->dm_timer); > + return 0; > + } > + > + period = omap->pdata->read_cap(omap->dm_timer, true); > + cap_omap_dmtimer_clk_get_freq(counter, signal, &clk_freq); > + > + if (period > 0) > + *freq = clk_freq/period; > + > + return *freq+1; > +} > +static int cap_omap_dmtimer_enable_read(struct counter_device *counter, > + struct counter_count *count, u8 *enable) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + > + *enable = omap->enabled; > + > + return 0; > +} > + > +static int cap_omap_dmtimer_count_read(struct counter_device *counter, > + struct counter_count *count, u64 *val) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + > + *val = omap->pdata->read_counter(omap->dm_timer); > + > + return 0; > +} > + > +static int cap_omap_dmtimer_count_write(struct counter_device *counter, > + struct counter_count *count, u64 val) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + > + if (val > U32_MAX) > + return -ERANGE; > + > + omap->pdata->write_counter(omap->dm_timer, val); > + > + return 0; > +} > + > +static int cap_omap_dmtimer_enable_write(struct counter_device *counter, > + struct counter_count *count, u8 enable) > +{ > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + > + if (enable == omap->enabled) > + goto out; Return 0. Seems very odd code style quirks like this and well everywhere in this patch.. > + > + if (enable) > + cap_omap_dmtimer_apply(counter); > + else > + omap->pdata->stop(omap->dm_timer); > + > + omap->enabled = enable; > +out: > + return 0; > +} > + > +static int cap_omap_dmtimer_function_read(struct counter_device *counter, > + struct counter_count *count, > + enum counter_function *function) > +{ > + *function = COUNTER_FUNCTION_INCREASE; > + > + return 0; > +} > + > +static int cap_omap_dmtimer_action_read(struct counter_device *counter, > + struct counter_count *count, > + struct counter_synapse *synapse, > + enum counter_synapse_action *action) > +{ > + *action = (synapse->signal->id == TIMER_CLOCK_SIG) ? > + COUNTER_SYNAPSE_ACTION_RISING_EDGE : > + COUNTER_SYNAPSE_ACTION_NONE; > + > + return 0; > +} > + > +static const struct counter_ops cap_omap_dmtimer_ops = { > + .count_read = cap_omap_dmtimer_count_read, > + .count_write = cap_omap_dmtimer_count_write, > + .function_read = cap_omap_dmtimer_function_read, > + .action_read = cap_omap_dmtimer_action_read, > +}; > + > +static const enum counter_function cap_omap_dmtimer_functions[] = { > + COUNTER_FUNCTION_INCREASE, > +}; > + > +static struct counter_comp cap_omap_dmtimer_clock_ext[] = { > + COUNTER_COMP_SIGNAL_U64("frequency", cap_omap_dmtimer_clk_get_freq, NULL), > +}; > + > +static struct counter_signal cap_omap_dmtimer_signals[] = { > + { > + .id = TIMER_CLOCK_SIG, > + .name = "Clock Signal", > + .ext = cap_omap_dmtimer_clock_ext, > + .num_ext = ARRAY_SIZE(cap_omap_dmtimer_clock_ext), > + }, > + { > + .id = TIMER_INPUT_SIG, > + .name = "Input Signal", > + }, > +}; > + > +/* Counter will increase at rising edges of a clock */ > +static const enum counter_synapse_action cap_omap_dmtimer_clock_actions[] = { > + COUNTER_SYNAPSE_ACTION_RISING_EDGE, > +}; > + > +/* No trigger here */ > +static const enum counter_synapse_action cap_omap_dmtimer_input_actions[] = { > + COUNTER_SYNAPSE_ACTION_NONE, > +}; > + > +static struct counter_synapse cap_omap_dmtimer_synapses[] = { > + { > + .actions_list = cap_omap_dmtimer_clock_actions, > + .num_actions = ARRAY_SIZE(cap_omap_dmtimer_clock_actions), > + .signal = &cap_omap_dmtimer_signals[TIMER_CLOCK_SIG], > + }, > + { > + .actions_list = cap_omap_dmtimer_input_actions, > + .num_actions = ARRAY_SIZE(cap_omap_dmtimer_input_actions), > + .signal = &cap_omap_dmtimer_signals[TIMER_INPUT_SIG], > + }, > +}; > + > +static struct counter_comp cap_omap_dmtimer_count_ext[] = { > + COUNTER_COMP_CAPTURE(cap_omap_dmtimer_capture, NULL), > + COUNTER_COMP_ENABLE(cap_omap_dmtimer_enable_read, cap_omap_dmtimer_enable_write), > + COUNTER_COMP_FREQUENCY(cap_omap_dmtimer_period), > +}; > + > +static struct counter_count cap_omap_dmtimer_counts[] = { > + { > + .name = "Timestamp Counter", > + .functions_list = cap_omap_dmtimer_functions, > + .num_functions = ARRAY_SIZE(cap_omap_dmtimer_functions), > + .synapses = cap_omap_dmtimer_synapses, > + .num_synapses = ARRAY_SIZE(cap_omap_dmtimer_synapses), > + .ext = cap_omap_dmtimer_count_ext, > + .num_ext = ARRAY_SIZE(cap_omap_dmtimer_count_ext), > + }, > +}; > + > +static int cap_omap_dmtimer_probe(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + struct device *dev = &pdev->dev; > + struct dmtimer_platform_data *timer_pdata; > + const struct omap_dm_timer_ops *pdata; > + struct platform_device *timer_pdev; > + struct omap_dm_timer *dm_timer; > + struct device_node *timer; > + struct cap_omap_dmtimer_counter *omap; > + struct counter_device *counter_dev; > + int ret = 0; > + > + timer = of_parse_phandle(np, "ti,timers", 0); > + if (!timer) { > + dev_err(&pdev->dev, "Unable to find Timer node\n"); > + return -ENODEV; > + } > + > + timer_pdev = of_find_device_by_node(timer); > + if (!timer_pdev) { > + dev_err(&pdev->dev, "Unable to find Timer pdev\n"); > + ret = -ENODEV; > + goto err_find_timer_pdev; > + } > + timer_pdata = dev_get_platdata(&timer_pdev->dev); > + if (!timer_pdata) { > + dev_dbg(&pdev->dev, > + "dmtimer pdata structure NULL, deferring probe\n"); > + ret = -EPROBE_DEFER; > + dev_err_probe(&pdev->dev, ret, "Probe deferred\n"); > + goto err_platdata; > + } > + > + pdata = timer_pdata->timer_ops; > + > + if (!pdata || !pdata->request_by_node || > + !pdata->free || > + !pdata->enable || > + !pdata->disable || > + !pdata->get_fclk || > + !pdata->start || > + !pdata->stop || > + !pdata->set_load || > + !pdata->set_match || > + !pdata->set_cap || > + !pdata->get_cap_status || > + !pdata->read_cap || tab vs space issues in several spots, fix your editor. > + !pdata->set_prescaler || > + !pdata->write_counter) { > + dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n"); > + ret = -EINVAL; > + goto err_platdata; > + } > + > + dm_timer = pdata->request_by_node(timer); > + if (!dm_timer) { > + ret = -EPROBE_DEFER; > + goto err_request_timer; > + } > + > + /* struct cap_omap_dmtimer_counter *omap */ > + counter_dev = devm_counter_alloc(dev, sizeof(*omap)); > + if (!counter_dev) { > + dev_err(&pdev->dev, "Unable to allocate dmtimercounter\n"); > + ret = -ENOMEM; > + goto err_alloc_omap; > + } > + omap = counter_priv(counter_dev); > + > + counter_dev->name = TIMER_DRV_NAME; > + counter_dev->parent = dev; > + counter_dev->ops = &cap_omap_dmtimer_ops; > + counter_dev->signals = cap_omap_dmtimer_signals; > + counter_dev->num_signals = ARRAY_SIZE(cap_omap_dmtimer_signals); > + counter_dev->counts = cap_omap_dmtimer_counts; > + counter_dev->num_counts = ARRAY_SIZE(cap_omap_dmtimer_counts); > + mutex_init(&omap->mutex); > + omap->pdata = pdata; > + omap->dm_timer = dm_timer; > + omap->dm_timer_pdev = timer_pdev; > + > + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) > + omap->pdata->stop(omap->dm_timer); > + > + of_node_put(timer); Why free down here, move this up right after its last use. > + > + platform_set_drvdata(pdev, counter_dev); > + > + ret = devm_counter_add(dev, counter_dev); > + if (ret) > + return dev_err_probe(dev, ret, "failed to add counter\n"); > + > + return 0; > + > +err_alloc_omap: > + pdata->free(dm_timer); > +err_request_timer: > + Unused label? Andrew > +err_platdata: > + put_device(&timer_pdev->dev); > +err_find_timer_pdev: > + > + of_node_put(timer); > + > + return ret; > +} > + > +static void cap_omap_dmtimer_remove(struct platform_device *pdev) > +{ > + struct counter_device *counter = platform_get_drvdata(pdev); > + struct cap_omap_dmtimer_counter *omap = counter_priv(counter); > + > + counter_unregister(counter); > + > + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) > + omap->pdata->stop(omap->dm_timer); > + > + omap->pdata->free(omap->dm_timer); > + > + put_device(&omap->dm_timer_pdev->dev); > + > + mutex_destroy(&omap->mutex); > + > +} > + > +static const struct of_device_id cap_omap_dmtimer_of_match[] = { > + {.compatible = "ti,omap-dmtimer-cap"}, > + {} > +}; > +MODULE_DEVICE_TABLE(of, cap_omap_dmtimer_of_match); > + > +static struct platform_driver cap_omap_dmtimer_driver = { > + .driver = { > + .name = "omap-dmtimer-cap", > + .of_match_table = cap_omap_dmtimer_of_match, > + }, > + .probe = cap_omap_dmtimer_probe, > + .remove = cap_omap_dmtimer_remove, > +}; > +module_platform_driver(cap_omap_dmtimer_driver); > + > +MODULE_AUTHOR("Gokul Praveen <g-praveen@ti.com>"); > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("OMAP CAP Driver using Dual-mode Timers"); > +MODULE_IMPORT_NS("COUNTER");
© 2016 - 2025 Red Hat, Inc.