RK3588 定时器驱动(Linux内核下操作寄存器)

目标

通过查阅RK3588的芯片资料了解RK3588的硬件定时器,并在linux内核下面通过硬件定时器的寄存器来操作定时器。

一. RK3588 定时器

在RK3588中,根据定时器的计数类型,有三种类型的定时器,分别是普通递减计数TIMER、普通
递增计数TIMER和特殊功能递增计数TIMER。普通定时器一共有12个通道,RK3588的linux内核只
用了1个通道,有11个通道是没有用到的。
本节只对普通定时器的寄存器编程,不对特殊定时器编程
在这里插入图片描述





二. RK3588 普通定时器基地址

通过查阅RK3588的编程手册,我们很容易得到RK3588的普通寄存器的寄存器基地址。
RK3588的普通定时器总共分2组,每组有6个通道,一共12个通道,默认的通道0已经被RK使用了,
那么我们使用通道1吧。
RK3588的普通定时器的通道0~通道5的控制寄存器映射到物理地址 0xFEAE0000 上面,如下图。
在这里插入图片描述




手册中寄存器的描述和寄存器偏移地址:
在这里插入图片描述





三. 分析RK3588的设备树源码

在这里插入图片描述



在这里插入图片描述
在这里插入图片描述
通过对RK3588的设备树文件rk3588s.dtsi 可以分析到,设备树映射了地址0xFEAE0000,不过设备树
仅仅映射了32(0x20)个字节,而手册说了每32个字节控制一个定时器的通道。
经过芯片手册分析可以知道这12个通道的定时器都是挂载APB上面,并且计数频率是24MHZ,看吧,比STM32还简单。。。。。。。。





四. 查看普通定时器通道1的中断地址

在这里插入图片描述
查阅到中断的地址是322,好了可以开始修改设备树和编写linux驱动程序了。





五. 添加设备树节点

设备树使用的基地址是定时器通道0基地址偏移32字节的位置,映射大小也是32字节。
另外为了和RK3588本身的定时器驱动冲突,修改了 compatible,同时修改 interrupts 属性。
下面的设备树节点添加到 arch/arm64/boot/dts/rockchip/rk3588s.dtsi 即可。

	timer@0xfeae0020 {
		compatible = "rk3588-timer";
		reg = <0x0 0xfeae0020 0x0 0x20>;
		interrupts = <GIC_SPI 290 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&cru PCLK_BUSTIMER0>, <&cru CLK_BUSTIMER1>;
		clock-names = "pclk", "timer";
	};





六. 驱动程序源码

drivers/clocksource/rk3588_timer.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Rockchip timer support
 *
 * Copyright (C) Daniel Lezcano <daniel.lezcano@linaro.org>
 */
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/delay.h>

#define TIMER_LOAD_COUNT0	0x00
#define TIMER_LOAD_COUNT1	0x04
#define TIMER_CURRENT_VALUE0	0x08
#define TIMER_CURRENT_VALUE1	0x0C
#define TIMER_CONTROL_REG3288	0x10
#define TIMER_CONTROL_REG3399	0x1c
#define TIMER_INT_STATUS	0x18

#define TIMER_DISABLE		0x0
#define TIMER_ENABLE		0x1
#define TIMER_IRQ_ENABLE	0x4
#define TIMER_MODE_FREE_RUNNING			(0 << 1)
#define TIMER_MODE_USER_DEFINED_COUNT		(1 << 1)
#define TIMER_INT_UNMASK			(1 << 2)

#define TIMER_CYCLES  (24000000)

struct rk3588_timer {
	void __iomem *base;
	void __iomem *ctrl;
	struct clk *clk;
	struct clk *pclk;
	u32 freq;
	int irq;
	struct work_struct timer_work;
};

/* 更新计数值 */
static void rk3588_timer_update_counter(unsigned long cycles,
				    struct rk3588_timer *timer)
{
	writel_relaxed(cycles, timer->base + TIMER_LOAD_COUNT0);
	writel_relaxed(0, timer->base + TIMER_LOAD_COUNT1);
}

/* 禁用定时器 */
static inline void rk3588_timer_disable(struct rk3588_timer *timer)
{
	writel_relaxed(TIMER_DISABLE, timer->ctrl);
}

/* 使能定时器 */
static inline void rk3588_timer_enable(struct rk3588_timer *timer, u32 flags)
{
	writel_relaxed(TIMER_ENABLE | flags, timer->ctrl);
}

/* 清除中断状态 */
static void rk3588_timer_interrupt_clear(struct rk3588_timer *timer)
{
	writel_relaxed(1, timer->base + TIMER_INT_STATUS);
}

static int __init
rk3588_timer_hal_init(struct rk3588_timer *timer, struct device_node *np)
{
	struct clk *timer_clk;
	struct clk *pclk;
	int ret = -EINVAL, irq;
	u32 ctrl_reg = 0x10;

	timer->base = of_iomap(np, 0);
	if (!timer->base) {
		pr_err("Failed to get base address for '%s'\n", np->name);
		return -ENXIO;
	}

	timer->ctrl = timer->base + ctrl_reg;

	pclk = of_clk_get_by_name(np, "pclk");
	if (IS_ERR(pclk)) {
		ret = PTR_ERR(pclk);
		pr_err("Failed to get pclk for '%s'\n", np->name);
		goto out_unmap;
	}

	ret = clk_prepare_enable(pclk);
	if (ret) {
		pr_err("Failed to enable pclk for '%s'\n", np->name);
		goto out_unmap;
	}
	timer->pclk = pclk;

	timer_clk = of_clk_get_by_name(np, "timer");
	if (IS_ERR(timer_clk)) {
		ret = PTR_ERR(timer_clk);
		pr_err("Failed to get timer clock for '%s'\n", np->name);
		goto out_timer_clk;
	}

	ret = clk_prepare_enable(timer_clk);
	if (ret) {
		pr_err("Failed to enable timer clock\n");
		goto out_timer_clk;
	}
	timer->clk = timer_clk;

	timer->freq = clk_get_rate(timer_clk);
	/* 解析设备树的中断并映射 IRQ号 */
	irq = irq_of_parse_and_map(np, 0);
	if (!irq) {
		ret = -EINVAL;
		pr_err("Failed to map interrupts for '%s'\n", np->name);
		goto out_irq;
	}
	timer->irq = irq;

	/* 清除中断状态 */
	writel_relaxed(1, timer->base + TIMER_INT_STATUS);
	/* 禁用定时器 */
	writel_relaxed(TIMER_DISABLE, timer->ctrl);

	return 0;

out_irq:
	clk_disable_unprepare(timer_clk);
out_timer_clk:
	clk_disable_unprepare(pclk);
out_unmap:
	iounmap(timer->base);

	return ret;
}

static int rk3588_timer_hal_exit(struct rk3588_timer *timer)
{
	clk_disable_unprepare(timer->clk);
	clk_disable_unprepare(timer->pclk);
	iounmap(timer->base);

	return 0;
}

/* 中断处理函数 */
static irqreturn_t rk3588_timer_interrupt(int irq, void *dev_id)
{
	struct rk3588_timer *timer = dev_id;

	rk3588_timer_interrupt_clear(timer);
	rk3588_timer_disable(timer);
	/* 触发任务的执行 */
	schedule_work(&timer->timer_work);
	
	return IRQ_HANDLED;
}

/* 执行任务 */
void rk3588_timer_work_func(struct work_struct *work) 
{
	struct rk3588_timer *timer;

	timer = container_of(work, struct rk3588_timer, timer_work);
	/* 再次重启定时器 */
	#if 0
	rk3588_timer_disable(timer);
	rk3588_timer_update_counter(TIMER_CYCLES, timer);
	rk3588_timer_enable(timer, TIMER_MODE_USER_DEFINED_COUNT |
			       TIMER_INT_UNMASK);
	#endif
	printk("The timer task is being processed. \n");
}

static int rk3588_timer_probe(struct platform_device *pdev)
{
	struct rk3588_timer *timer;
	struct device_node	*np;
	int ret = -EINVAL;
	u32 cur_value1, cur_value2;
	unsigned long cycles;
	
	timer = kzalloc(sizeof(struct rk3588_timer), GFP_KERNEL);
	if (!timer) {
		ret = -ENOMEM;
		goto out;
	}

	np = pdev->dev.of_node;
	/* 初始化硬件和中断 */
	ret = rk3588_timer_hal_init(timer, np);
	if (ret)
		goto out_init;

	cycles = timer->freq;
	INIT_WORK(&timer->timer_work, rk3588_timer_work_func);

	ret = request_irq(timer->irq, rk3588_timer_interrupt, IRQF_TIMER,
			  "rk3588_timer", timer);
	if (ret) {
		pr_err("Failed to initialize '%s': %d\n",
			"rk3588_timer", ret);
		goto out_irq;
	}

	/* (装载计数值 == 计数频率) -- 1S后中断 */
	rk3588_timer_disable(timer);
	rk3588_timer_update_counter(cycles, timer);
	rk3588_timer_enable(timer, TIMER_MODE_USER_DEFINED_COUNT | TIMER_INT_UNMASK);		
	/* 延时确保定时器已经启动 */
	msleep(1);
	cur_value1 = readl_relaxed(timer->base + TIMER_CURRENT_VALUE0);
	msleep(100);
	cur_value2 = readl_relaxed(timer->base + TIMER_CURRENT_VALUE0);
	/* 观察100ms计数值的变化 -向下计数 */
	printk("cur_value1 = %u cur_value2 = %u diff = %u\n", cur_value1, cur_value2, cur_value1 - cur_value2);
	platform_set_drvdata(pdev, timer);

    return 0;

out_irq:
	rk3588_timer_hal_exit(timer);
out_init:
	kfree(timer);
out:
	timer = ERR_PTR(ret);
	return ret;
}

static int rk3588_timer_remove(struct platform_device *pdev)
{
	struct rk3588_timer *timer;
	
	timer = platform_get_drvdata(pdev);
	rk3588_timer_hal_exit(timer);
	kfree(timer);
	timer = NULL;
    return 0;
}

static const struct of_device_id rk3588_timer_of_table[] __maybe_unused = {
	{ .compatible = "rk3588-timer",},
};

static struct platform_driver rk3588_timer_driver = {
	.probe		= rk3588_timer_probe,
	.remove		= rk3588_timer_remove,
	.driver		= {
		.name	= "rk3588_timer",
		.of_match_table = of_match_ptr(rk3588_timer_of_table),
	},
};

static int __init rk3588_timer_init(void)
{
	return platform_driver_register(&rk3588_timer_driver);
}

static void __exit rk3588_timer_exit(void)
{
	platform_driver_unregister(&rk3588_timer_driver);
}

subsys_initcall(rk3588_timer_init);
module_exit(rk3588_timer_exit);

MODULE_AUTHOR("Magnus Damm");
MODULE_DESCRIPTION("SuperH CMT Timer Driver");
MODULE_LICENSE("GPL v2");




最后在 drivers/clocksource/Makefile 文件最下面增加一行:

obj-y							+=	rk3588_timer.o





7. 运行效果

由于定时器的计算频率是24MHZ,那么一秒钟可以计数24000000次。这样的话我们装载到定时器的
计数值就取 24000000 吧,因为刚好1秒钟。
另外计数寄存器是64位的,分为高32位和低32位。本实验我们仅仅用低32位就够了。
注意这个定时器只能是向下计数的。

定时计数到0,会产生中断,中断再进入work 并产生下面的打印。

root@linaro-alip:/# dmesg | grep "The timer task"
[    4.604885] The timer task is being processed. 

通过下面命令查看运行打印,可知定时器计数100ms,计数值大概减少 2400000,满载的10分之1。

root@linaro-alip:/# dmesg | grep "cur_value1"
[    3.725108] cur_value1 = 23719321 cur_value2 = 21158595 diff = 2560726
  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值