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
作者:一分生一分熟