35、RTC与PWM驱动开发指南

AI助手已提取文章相关产品:

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>
  1. 请求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
  1. 禁用PWM通道
echo 0 > /sys/class/pwm/pwmchipX/pwmY/enable
  1. 取消导出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驱动开发的相关知识和技术,开发者可以为不同的硬件设备开发出高效、稳定的驱动程序,从而实现各种复杂的功能。在实际开发过程中,还需要根据具体的硬件平台和需求进行适当的调整和优化。

您可能感兴趣的与本文相关内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值