@@ -39,6 +39,16 @@ config INTERRUPT_CNT
To compile this driver as a module, choose M here: the
module will be called interrupt-cnt.
+config RZV2M_TIM_CNT
+ tristate "Renesas RZ/V2M counter driver"
+ depends on ARCH_R9A09G011 || COMPILE_TEST
+ help
+ Enable support for RZ/V2M counter driver found on Renesas RZ/V2M alike
+ SoCs. This IP supports 32 bit counter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rzv2m-tim-cnt.
+
config STM32_TIMER_CNT
tristate "STM32 Timer encoder counter driver"
depends on MFD_STM32_TIMERS || COMPILE_TEST
@@ -8,6 +8,7 @@ counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
obj-$(CONFIG_INTERRUPT_CNT) += interrupt-cnt.o
+obj-$(CONFIG_RZV2M_TIM_CNT) += rzv2m-tim-cnt.o
obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o
obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o
obj-$(CONFIG_TI_EQEP) += ti-eqep.o
new file mode 100644
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/V2M Compare Match Timer (TIM) driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/counter.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+/* TIM REGISTERS */
+#define RZV2M_TIM_TMD 0x0
+#define RZV2M_TIM_CMD 0x4
+#define RZV2M_TIM_TMCD 0x8
+#define RZV2M_TIM_INTCLR 0xc
+
+#define RZV2M_TIM_TMCD_CS GENMASK(6, 4)
+#define RZV2M_TIM_TMCD_INTMODE BIT(2)
+#define RZV2M_TIM_TMCD_CE BIT(1)
+#define RZV2M_TIM_TMCD_CAE BIT(0)
+
+#define RZV2M_TIM_INTCLR_INTCLR BIT(0)
+
+#define RZV2M_TIM_TMCD_CH_EN (RZV2M_TIM_TMCD_CE | RZV2M_TIM_TMCD_CAE)
+#define RZV2M_TIM_TMCD_START (RZV2M_TIM_TMCD_CH_EN | RZV2M_TIM_TMCD_INTMODE)
+
+#define F2CYCLE_NSEC(f) (1000000000 / (f))
+
+/**
+ * struct rzv2m_tim_channel - TIM counter private data
+ *
+ * @dev: Channel device
+ * @base: Base address of the chanel
+ * @rstc: Reset handle
+ * @delay: write intervel for register writes
+ * @ceiling: Cache for ceiling value
+ */
+
+struct rzv2m_tim_channel {
+ struct device *dev;
+ void __iomem *base;
+ struct reset_control *rstc;
+ unsigned long delay;
+ u32 ceiling;
+};
+
+static void rzv2m_tim_cnt_wait_delay(struct rzv2m_tim_channel *ch)
+{
+ /* delay required when changing the register settings */
+ ndelay(ch->delay);
+}
+
+static bool rzv2m_tim_cnt_is_counter_enabled(struct counter_device *counter)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+ unsigned long tmcd;
+
+ tmcd = readl(ch->base + RZV2M_TIM_TMCD);
+ return !!FIELD_GET(RZV2M_TIM_TMCD_CH_EN, tmcd);
+}
+
+static void rzv2m_tim_cnt_stop(struct rzv2m_tim_channel *ch)
+{
+ unsigned long tmcd;
+
+ tmcd = readl(ch->base + RZV2M_TIM_TMCD);
+ if (FIELD_GET(RZV2M_TIM_TMCD_CH_EN, tmcd)) {
+ writel(FIELD_GET(RZV2M_TIM_TMCD_CS, tmcd),
+ ch->base + RZV2M_TIM_TMCD);
+ rzv2m_tim_cnt_wait_delay(ch);
+ }
+}
+
+static void rzv2m_tim_cnt_start(struct rzv2m_tim_channel *ch)
+{
+ unsigned long tmcd;
+
+ tmcd = readl(ch->base + RZV2M_TIM_TMCD);
+ writel(tmcd | RZV2M_TIM_TMCD_START, ch->base + RZV2M_TIM_TMCD);
+ rzv2m_tim_cnt_wait_delay(ch);
+}
+
+static irqreturn_t rzv2m_tim_cnt_interrupt(int irq, void *dev_id)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(dev_id);
+
+ writel(RZV2M_TIM_INTCLR_INTCLR, ch->base + RZV2M_TIM_INTCLR);
+ counter_push_event(dev_id, COUNTER_EVENT_OVERFLOW, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int rzv2m_tim_cnt_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+
+ *val = readl(ch->base + RZV2M_TIM_TMD);
+
+ return 0;
+}
+
+static int rzv2m_tim_cnt_ceiling_read(struct counter_device *counter,
+ struct counter_count *count,
+ u64 *ceiling)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+
+ *ceiling = ch->ceiling;
+
+ return 0;
+}
+
+static int rzv2m_tim_cnt_ceiling_write(struct counter_device *counter,
+ struct counter_count *count,
+ u64 ceiling)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+ bool enable = rzv2m_tim_cnt_is_counter_enabled(counter);
+
+ if (ceiling > U32_MAX)
+ return -ERANGE;
+
+ if (enable)
+ rzv2m_tim_cnt_stop(ch);
+ ch->ceiling = ceiling;
+ writel(ceiling, ch->base + RZV2M_TIM_CMD);
+ if (enable)
+ rzv2m_tim_cnt_start(ch);
+
+ return 0;
+}
+
+static int rzv2m_tim_cnt_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+
+ pm_runtime_get_sync(ch->dev);
+ *enable = rzv2m_tim_cnt_is_counter_enabled(counter);
+ pm_runtime_put(ch->dev);
+
+ return 0;
+}
+
+static int rzv2m_tim_cnt_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct rzv2m_tim_channel *const ch = counter_priv(counter);
+
+ if (enable) {
+ pm_runtime_get_sync(ch->dev);
+ rzv2m_tim_cnt_start(ch);
+ } else {
+ rzv2m_tim_cnt_stop(ch);
+ pm_runtime_put(ch->dev);
+ }
+
+ return 0;
+}
+
+static const struct counter_ops rzv2m_tim_cnt_ops = {
+ .count_read = rzv2m_tim_cnt_read,
+};
+
+static struct counter_comp rzv2m_tim_cnt_ext[] = {
+ COUNTER_COMP_ENABLE(rzv2m_tim_cnt_enable_read,
+ rzv2m_tim_cnt_enable_write),
+ COUNTER_COMP_CEILING(rzv2m_tim_cnt_ceiling_read,
+ rzv2m_tim_cnt_ceiling_write),
+};
+
+static struct counter_count rzv2m_tim_counts[] = {
+ {
+ .id = 0,
+ .name = "Channel Count",
+ .ext = rzv2m_tim_cnt_ext,
+ .num_ext = ARRAY_SIZE(rzv2m_tim_cnt_ext),
+ },
+};
+
+static void rzv2m_tim_cnt_reset_assert_pm_disable(void *data)
+{
+ struct rzv2m_tim_channel *ch = data;
+
+ reset_control_assert(ch->rstc);
+ pm_runtime_disable(ch->dev);
+}
+
+static int rzv2m_tim_cnt_probe(struct platform_device *pdev)
+{
+ struct counter_device *counter;
+ struct rzv2m_tim_channel *ch;
+ unsigned long apb_rate, rate;
+ struct clk *clk;
+ int irq;
+ int ret;
+
+ counter = devm_counter_alloc(&pdev->dev, sizeof(*ch));
+ if (!counter)
+ return -ENOMEM;
+
+ ch = counter_priv(counter);
+ ch->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ch->base))
+ return PTR_ERR(ch->base);
+
+ ch->rstc = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(ch->rstc))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ch->rstc),
+ "get reset failed\n");
+
+ ch->dev = &pdev->dev;
+ clk = clk_get(&pdev->dev, "tim");
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "cannot get tim clock\n");
+ rate = clk_get_rate(clk);
+ clk_put(clk);
+ if (!rate)
+ return dev_err_probe(&pdev->dev, -EINVAL, "tim clk rate is 0");
+
+ clk = clk_get(&pdev->dev, "apb");
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "cannot get apb clock\n");
+ apb_rate = clk_get_rate(clk);
+ clk_put(clk);
+ if (!apb_rate)
+ return dev_err_probe(&pdev->dev, -EINVAL, "apb clk rate is 0");
+
+ /* delay = 5 * PCLK + 5 * INCLK */
+ ch->delay = F2CYCLE_NSEC(apb_rate) * 5 + F2CYCLE_NSEC(rate) * 5;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0) {
+ dev_err_probe(&pdev->dev, ret,
+ "pm_runtime_resume_and_get failed");
+ goto err_pm_disable;
+ }
+
+ reset_control_deassert(ch->rstc);
+ writel(U32_MAX, ch->base + RZV2M_TIM_CMD);
+ writel(FIELD_PREP(RZV2M_TIM_TMCD_CS, 0), ch->base + RZV2M_TIM_TMCD);
+ pm_runtime_put(&pdev->dev);
+ platform_set_drvdata(pdev, ch);
+
+ ret = devm_add_action_or_reset(&pdev->dev,
+ rzv2m_tim_cnt_reset_assert_pm_disable,
+ ch);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(&pdev->dev, irq,
+ rzv2m_tim_cnt_interrupt,
+ 0,
+ dev_name(&pdev->dev), counter);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to request irq%d\n", irq);
+
+ counter->name = dev_name(&pdev->dev);
+ counter->parent = &pdev->dev;
+ counter->ops = &rzv2m_tim_cnt_ops;
+ counter->counts = rzv2m_tim_counts;
+ counter->num_counts = ARRAY_SIZE(rzv2m_tim_counts);
+
+ /* Register Counter device */
+ ret = devm_counter_add(&pdev->dev, counter);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static const struct of_device_id rzv2m_tim_of_table[] = {
+ { .compatible = "renesas,rzv2m-tim-cnt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzv2m_tim_of_table);
+
+static struct platform_driver rzv2m_tim_cnt_device_driver = {
+ .driver = {
+ .name = "rzv2m_tim_cnt",
+ .of_match_table = of_match_ptr(rzv2m_tim_of_table),
+ },
+ .probe = rzv2m_tim_cnt_probe,
+};
+module_platform_driver(rzv2m_tim_cnt_device_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/V2M Compare Match Timer Driver");
+MODULE_ALIAS("platform:rzv2m-tim-counter");
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(COUNTER);
Add Renesas RZ/V2M TIM(Compare Match Timer) counter driver. This IP supports 32 bit counter and it generates an interrupt request signal every cycle set in the TIM counter. Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com> --- drivers/counter/Kconfig | 10 + drivers/counter/Makefile | 1 + drivers/counter/rzv2m-tim-cnt.c | 312 ++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 drivers/counter/rzv2m-tim-cnt.c