RTC与PWM驱动开发指南
1. RTC驱动
1.1 驱动示例
以下是一个简单的虚拟RTC驱动示例,它会在系统中注册一个RTC设备:
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/time.h>
#include <linux/err.h>
#include <linux/rtc.h>
#include <linux/of.h>
static int fake_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
/*
* One can update "tm" with fake values and then call
*/
return rtc_valid_tm(tm);
}
static int fake_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
return 0;
}
static const struct rtc_class_ops fake_rtc_ops = {
.read_time = fake_rtc_read_time,
.set_time = fake_rtc_set_time
};
static const struct of_device_id rtc_dt_ids[] = {
{ .compatible = "packt,rtc-fake", },
{ /* sentinel */ }
};
static int fake_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
rtc = rtc_device_register(pdev->name, &pdev->dev,
&fake_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc))
return PTR_ERR(rtc);
platform_set_drvdata(pdev, rtc);
pr_info("Fake RTC module loaded\n");
return 0;
}
static int fake_rtc_remove(struct platform_device *pdev)
{
rtc_device_unregister(platform_get_drvdata(pdev));
return 0;
}
static struct platform_driver fake_rtc_drv = {
.probe = fake_rtc_probe,
.remove = fake_rtc_remove,
.driver = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(rtc_dt_ids),
},
};
module_platform_driver(fake_rtc_drv);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Fake RTC driver description");
1.2 闹钟功能
RTC闹钟是一种可编程事件,设备会在指定时间触发。RTC闹钟由
struct rtc_wkalarm
结构体表示:
struct rtc_wkalrm {
unsigned char enabled; /* 0 = alarm disabled, 1 = enabled */
unsigned char pending; /* 0 = alarm not pending, 1 = pending */
struct rtc_time time; /* time the alarm is set to */
};
驱动程序应提供
set_alarm()
和
read_alarm()
操作,用于设置和读取闹钟触发时间,以及
alarm_irq_enable()
函数,用于启用/禁用闹钟。
1.3 闹钟中断处理
在向系统报告闹钟事件之前,必须将RTC芯片连接到SoC的IRQ线。可以使用通用的IRQ API(如
request_threaded_irq()
)来注册闹钟IRQ的处理程序。在IRQ处理程序中,需要使用
rtc_update_irq()
函数通知内核RTC IRQ事件:
void rtc_update_irq(struct rtc_device *rtc,
unsigned long num, unsigned long events)
参数说明:
| 参数 | 说明 |
| ---- | ---- |
| rtc | 触发IRQ的RTC设备 |
| num | 报告的IRQ数量(通常为1) |
| events | RTC_IRQF与RTC_PF、RTC_AF、RTC_UF中的一个或多个的掩码 |
以下是一个IRQ处理程序示例:
static irqreturn_t foo_rtc_alarm_irq(int irq, void *data)
{
struct foo_rtc_struct * foo_device = data;
dev_info(foo_device ->dev, "%s:irq(%d)\n", __func__, irq);
rtc_update_irq(foo_device ->rtc_dev, 1, RTC_IRQF | RTC_AF);
return IRQ_HANDLED;
}
1.4 RTC设备作为唤醒源
具有闹钟功能的RTC设备可以用作唤醒源。可以使用
device_init_wakeup()
函数将设备声明为唤醒源,并使用
dev_pm_set_wake_irq()
函数将实际唤醒系统的IRQ注册到电源管理核心:
int device_init_wakeup(struct device *dev, bool enable)
int dev_pm_set_wake_irq(struct device *dev, int irq)
1.5 用户空间操作
1.5.1 内核选项
在Linux系统中,要从用户空间正确管理RTC,需要关注两个内核选项:
CONFIG_RTC_HCTOSYS
和
CONFIG_RTC_HCTOSYS_DEVICE
。
-
CONFIG_RTC_HCTOSYS
:在内核构建过程中包含
drivers/rtc/hctosys.c
代码文件,用于在启动和恢复时从RTC设置系统时间。
-
CONFIG_RTC_HCTOSYS_DEVICE
:指定要使用的RTC设备。
示例配置:
CONFIG_RTC_HCTOSYS=y
CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
1.5.2 sysfs接口
RTC设备注册后,会在
/sys/class/rtc
下创建一个
rtc<id>
目录,包含一些只读属性:
| 属性 | 说明 | 示例 |
| ---- | ---- | ---- |
| date | 打印RTC接口的当前日期 |
$ cat /sys/class/rtc/rtc0/date
2017-08-28
|
| time | 打印RTC的当前时间 |
$ cat /sys/class/rtc/rtc0/time
14:54:20
|
| hctosys | 指示RTC设备是否是
CONFIG_RTC_HCTOSYS_DEVICE
中指定的设备 |
$ cat /sys/class/rtc/rtc0/hctosys
1
|
| dev | 显示设备的主设备号和次设备号 |
$ cat /sys/class/rtc/rtc0/dev
251:0
|
| since_epoch | 打印自UNIX纪元(1970年1月1日)以来经过的秒数 |
$ cat /sys/class/rtc/rtc0/since_epoch
1503931738
|
1.5.3 hwclock工具
hwclock
是一个用于访问RTC设备的工具。以下是一些使用示例:
$ sudo ntpd -q # make sure system clock is set from network time
$ sudo hwclock --systohc # set rtc from the system clock
$ sudo hwclock --show # check rtc was set
Sat May 17 17:36:50 2017 -0.671045 seconds
也可以手动设置系统时间:
$ sudo date -s '2017-08-28 17:14:00' '+%s' #set system clock manually
$ sudo hwclock --systohc #synchronize rtc chip on system time
2. PWM驱动
2.1 PWM简介
脉冲宽度调制(PWM)就像一个不断开关的开关,是一种用于控制伺服电机、电压调节等的硬件特性。常见应用包括电机速度控制、灯光调光和电压调节。
2.2 PWM术语
一个完整的PWM周期涉及以下术语:
| 术语 | 说明 |
| ---- | ---- |
| Ton | 信号高电平的持续时间 |
| Toff | 信号低电平的持续时间 |
| Period | 一个完整PWM周期的持续时间,等于Ton + Toff |
| Duty cycle | 信号在一个周期内保持高电平的时间百分比 |
2.3 Linux PWM框架
Linux PWM框架有两个接口:
- 控制器接口:暴露PWM线的接口,即PWM芯片,是生产者。
- 消费者接口:消费控制器暴露的PWM线的设备。此类设备的驱动程序使用控制器通过通用PWM框架导出的辅助函数。
两个接口都依赖于以下头文件:
#include <linux/pwm.h>
2.4 PWM控制器驱动
PWM控制器在内核中由
struct pwm_chip
结构体表示:
struct pwm_chip {
struct device *dev;
const struct pwm_ops *ops;
int base;
unsigned int npwm;
struct pwm_device *pwms;
struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;
bool can_sleep;
};
结构体元素说明:
| 元素 | 说明 |
| ---- | ---- |
| dev | 与此芯片关联的设备 |
| ops | 为消费者驱动程序提供回调函数的数据结构 |
| base | 此芯片控制的第一个PWM的编号。如果chip->base < 0,则内核将动态分配一个基编号 |
| can_sleep | 如果ops字段的.config()、.enable()或.disable()操作可能会休眠,则芯片驱动程序应将其设置为true |
| npwm | 此芯片提供的PWM通道(设备)数量 |
| pwms | 此芯片的PWM设备数组,由框架分配给消费者驱动程序 |
| of_xlate | 一个可选的回调函数,用于根据DT PWM说明符请求PWM设备 |
| of_pwm_n_cells | DT中PWM说明符预期的单元数 |
PWM控制器的添加和移除依赖于
pwmchip_add()
和
pwmchip_remove()
函数:
int pwmchip_add(struct pwm_chip *chip)
int pwmchip_remove(struct pwm_chip *chip)
pwmchip_remove()
函数返回值说明:
- 0:成功
- -EBUSY:芯片有PWM线仍在使用(仍被请求)
每个PWM驱动程序必须通过
struct pwm_ops
字段实现一些钩子函数:
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity);
int (*enable)(struct pwm_chip *chip,struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state); /* since kernel v4.7 */
struct module *owner;
};
钩子函数说明:
| 钩子函数 | 说明 |
| ---- | ---- |
| request | 可选钩子,在请求PWM通道时执行 |
| free | 与request类似,在释放PWM时执行 |
| config | PWM配置钩子,用于配置占空比和周期长度 |
| set_polarity | 配置PWM极性的钩子 |
| enable | 启用PWM线,开始输出切换 |
| disable | 禁用PWM线,停止输出切换 |
| get_state | 返回当前PWM状态,仅在PWM芯片注册时为每个PWM设备调用一次 |
| owner | 拥有此芯片的模块,通常为THIS_MODULE |
2.5 驱动示例
以下是一个具有三个通道的PWM控制器的虚拟驱动示例:
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
struct fake_chip {
struct pwm_chip chip;
int foo;
int bar;
/* put the client structure here (SPI/I2C) */
};
static inline struct fake_chip *to_fake_chip(struct pwm_chip *chip)
{
return container_of(chip, struct fake_chip, chip);
}
static int fake_pwm_request(struct pwm_chip *chip,
struct pwm_device *pwm)
{
/*
* One may need to do some initialization when a PWM channel
* of the controller is requested. This should be done here.
*
* One may do something like
* prepare_pwm_device(struct pwm_chip *chip, pwm->hwpwm);
*/
return 0;
}
static int fake_pwm_config(struct pwm_chip *chip,
struct pwm_device *pwm,
int duty_ns, int period_ns)
{
/*
* In this function, one ne can do something like:
* struct fake_chip *priv = to_fake_chip(chip);
*
* return send_command_to_set_config(priv,
* duty_ns, period_ns);
*/
return 0;
}
static int fake_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
/*
* In this function, one ne can do something like:
* struct fake_chip *priv = to_fake_chip(chip);
*
* return foo_chip_set_pwm_enable(priv, pwm->hwpwm, true);
*/
pr_info("Somebody enabled PWM device number %d of this chip",
pwm->hwpwm);
return 0;
}
static void fake_pwm_disable(struct pwm_chip *chip,
struct pwm_device *pwm)
{
/*
* In this function, one ne can do something like:
* struct fake_chip *priv = to_fake_chip(chip);
*
* return foo_chip_set_pwm_enable(priv, pwm->hwpwm, false);
*/
pr_info("Somebody disabled PWM device number %d of this chip",
pwm->hwpwm);
}
static const struct pwm_ops fake_pwm_ops = {
.request = fake_pwm_request,
.config = fake_pwm_config,
.enable = fake_pwm_enable,
.disable = fake_pwm_disable,
.owner = THIS_MODULE,
};
static int fake_pwm_probe(struct platform_device *pdev)
{
struct fake_chip *priv;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->chip.ops = &fake_pwm_ops;
priv->chip.dev = &pdev->dev;
priv->chip.base = -1; /* Dynamic base */
priv->chip.npwm = 3; /* 3 channel controller */
platform_set_drvdata(pdev, priv);
return pwmchip_add(&priv->chip);
}
static int fake_pwm_remove(struct platform_device *pdev)
{
struct fake_chip *priv = platform_get_drvdata(pdev);
return pwmchip_remove(&priv->chip);
}
static const struct of_device_id fake_pwm_dt_ids[] = {
{ .compatible = "packt,fake-pwm", },
{ }
};
综上所述,RTC和PWM驱动开发涉及到内核编程的多个方面,包括设备注册、中断处理、用户空间操作等。通过掌握这些知识和示例代码,开发者可以为不同的硬件设备开发相应的驱动程序。
2.6 PWM设备树实例化
在设备树中实例化PWM设备和控制器时,需要遵循一定的规范。PWM控制器通常会在设备树中以节点的形式存在,并且可以指定其属性。以下是一个简单的设备树节点示例,展示了如何定义一个PWM控制器:
pwm-controller {
compatible = "packt,fake-pwm";
#pwm-cells = <2>;
reg = <0x1000 0x100>;
interrupts = <0 1 2>;
/* 其他属性 */
};
在这个示例中:
-
compatible
属性用于匹配驱动程序,这里指定为
"packt,fake-pwm"
。
-
#pwm-cells
属性指定了PWM说明符中预期的单元数。
-
reg
属性指定了设备的寄存器地址范围。
-
interrupts
属性指定了设备的中断信息。
消费者设备可以通过引用PWM控制器的节点来请求PWM资源。例如:
consumer-device {
compatible = "packt,consumer";
pwm = <&pwm-controller 0 500000 1000000>;
/* 其他属性 */
};
在这个消费者设备节点中:
-
pwm
属性引用了PWM控制器节点,并指定了所需的PWM通道、占空比和周期。
2.7 请求和消费PWM设备
在驱动程序中,消费者设备可以通过以下步骤请求和消费PWM设备:
1.
包含必要的头文件
:
#include <linux/pwm.h>
- 请求PWM设备 :
struct pwm_device *pwm;
pwm = pwm_get(&dev, NULL);
if (IS_ERR(pwm)) {
dev_err(&dev, "Failed to get PWM device\n");
return PTR_ERR(pwm);
}
在这个示例中,
pwm_get()
函数用于请求PWM设备。第一个参数是设备指针,第二个参数是可选的PWM名称。
3.
配置PWM设备
:
int ret;
ret = pwm_config(pwm, duty_ns, period_ns);
if (ret) {
dev_err(&dev, "Failed to configure PWM device\n");
pwm_put(pwm);
return ret;
}
pwm_config()
函数用于配置PWM设备的占空比和周期。
4.
启用PWM设备
:
ret = pwm_enable(pwm);
if (ret) {
dev_err(&dev, "Failed to enable PWM device\n");
pwm_put(pwm);
return ret;
}
pwm_enable()
函数用于启用PWM设备。
5.
使用完毕后释放PWM设备
:
pwm_disable(pwm);
pwm_put(pwm);
pwm_disable()
函数用于禁用PWM设备,
pwm_put()
函数用于释放PWM设备。
2.8 通过sysfs接口从用户空间使用PWM
用户空间可以通过sysfs接口来使用PWM设备。PWM设备在sysfs中以目录的形式存在,通常位于
/sys/class/pwm/pwmchipX
下,其中
X
是PWM芯片的编号。以下是一些常见的操作步骤:
1.
导出PWM通道
:
echo <channel> > /sys/class/pwm/pwmchipX/export
这里的
<channel>
是要导出的PWM通道编号。
2.
配置PWM周期和占空比
:
echo <period> > /sys/class/pwm/pwmchipX/pwmY/period
echo <duty_cycle> > /sys/class/pwm/pwmchipX/pwmY/duty_cycle
<period>
和
<duty_cycle>
分别是PWM的周期和占空比,单位为纳秒。
3.
启用PWM通道
:
echo 1 > /sys/class/pwm/pwmchipX/pwmY/enable
- 禁用PWM通道 :
echo 0 > /sys/class/pwm/pwmchipX/pwmY/enable
- 取消导出PWM通道 :
echo <channel> > /sys/class/pwm/pwmchipX/unexport
2.9 PWM驱动开发流程总结
为了更清晰地展示PWM驱动开发的流程,下面给出一个mermaid格式的流程图:
graph TD;
A[开始] --> B[包含必要头文件];
B --> C[定义pwm_chip和pwm_ops结构体];
C --> D[实现pwm_ops中的回调函数];
D --> E[在probe函数中初始化pwm_chip];
E --> F[调用pwmchip_add添加PWM芯片];
F --> G[设备树中实例化PWM控制器和消费者];
G --> H[消费者驱动请求和消费PWM设备];
H --> I[用户空间通过sysfs使用PWM设备];
I --> J[在remove函数中调用pwmchip_remove移除PWM芯片];
J --> K[结束];
3. 总结
RTC和PWM驱动开发是内核编程中的重要部分,它们各自有着独特的功能和应用场景。
3.1 RTC驱动总结
- RTC驱动主要用于管理实时时钟设备,包括时间读取、设置以及闹钟功能。
- 可以通过内核选项和sysfs接口从用户空间对RTC设备进行配置和操作。
- 具有闹钟功能的RTC设备还可以作为唤醒源,在系统休眠时发挥作用。
3.2 PWM驱动总结
- PWM驱动用于控制脉冲宽度调制设备,广泛应用于电机控制、灯光调光等领域。
- Linux PWM框架提供了控制器和消费者两个接口,方便驱动开发。
-
开发PWM控制器驱动需要定义
pwm_chip和pwm_ops结构体,并实现相应的回调函数。 - 消费者设备可以通过设备树请求PWM资源,并在驱动程序中进行配置和使用。
- 用户空间可以通过sysfs接口对PWM设备进行操作。
通过掌握RTC和PWM驱动开发的相关知识和技术,开发者可以为不同的硬件设备开发出高效、稳定的驱动程序,从而实现各种复杂的功能。在实际开发过程中,还需要根据具体的硬件平台和需求进行适当的调整和优化。
114

被折叠的 条评论
为什么被折叠?



