linux RTC驱动程序

        实时时钟RTC用于在非易失性存储器中记录绝对时间,它可以位于处理器内,也可以位于其外部(I2C或SPI接口)。

一、RTC框架数据结构

        内核RTC框架提供了3中数据结构,他们是struct rtc_time、struct rtc_device和struct rtc_device_ops结构,其定义如下:

struct rtc_time {
    int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year/*从1900开始*/;
    int tm_wday, tm_yday, tm_isdst/*夏令时标志*/;
};

struct rtc_device {
    struct device dev;
    struct module *owner;
    int id; // 由内核提供给rtc设备的全局索引,/dev/rtc<id>
    char name[RTC_DEVICE_NAME_SIZE];
    const struct rtc_class_ops *ops; // 一组操作,例如 设置/读取 时间/闹钟
    struct mutex ops_lock;
    struct cdev char_dev;
    ulong flags;

    ulong irq_data;
    spinlock_t irq_lock;
    wait_queue_head_t irq_queue;

    struct rtc_task *irq_task;
    spinlock_t irq_task_lock;
    int irq_freq;
    int max_user_freq;

    struct work_struct irqwork;
};

struct rtc_class_ops {
    int (*open)(struct device *dev); // 用户在设备/dev/rtc1上调用read时的callback
    int (*release)(struct device *dev);// 用户在设备/dev/rtc1上调用close时的callback
    int (*ioctl)(struct device *dev, uint cmd, ulong arg); // 用户在设备/dev/rtc1上调用ioctl时的callback
    int (*read_time)(struct device *dev, struct rtc_time *tm); // rtc内核的回调函数
    int (*set_time)(struct device *dev, struct rtc_time *tm);
    int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
    int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
    int (*read_callback)(struct device *dev, int data); // 用户在设备/dev/rtc1上调用read时的callback
    int (*alarm_irq_enable)(struct device *dev, uint enabled);
};

// rtc模块提供了宏to_rtc_device(d)把 struct device结构转变为struct rtc_device{}结构

二、RTC内核框架提供的API

        RTC设备在内核中表示为struct rtc_device{},RTC设备由内核创建,并在struct rtc_device{}注册后返回给驱动程序。使用rtc_device_register()注册rtc_device{}

struct rtc_device *rtc_device_register(const char *name, struct device *dev,
            const struct rtc_class_ops *ops, struct module *owner);
/* 其中,name是rtc设备名称;
    dev:父设备,为设备模型所用,例如spi_device.dev, i2c_client.dev;
    ops:RTC操作,根据RTC具有的特性或驱动程序可以支持的功能填写;
    owner: THIS_MODULE;
*/
void rtc_device_unregister(struct rtc_device *rtc);
// devres版本的函数
struct rtc_device *devm_rtc_device_register(struct device *dev,
            const char *name, struct device *dev,
            const struct rtc_class_ops *ops, struct module *owner);

1. 读取和设置时间

        驱动程序负责提供读取和设置设备时间的函数。RTC总是以二进制编码的格式存储/恢复时间,其中每个4位表示0~9而非0~F,内核提供bcd2bin()和bin2bcd()两个转换的宏。提供辅助函数rtc_valid_tm(struct rtm_time *)用于确定struct rtc_time{}表示合法的时间。

函数使用示例:

static int foo_rtc_read_time(struct device *dev, struct rtc_time *tm) {
    struct foo_regs regs;
    int error = foo_device_read(dev, &regs, 0, sizeof(regs));
    if (error) return error;
    tm->tm_sec = bcd2bin(regs.seconds);
    tm->tm_min = bcd2bin(regs.minutes);
    tm->tm_hour = bcd2bin(regs.cent_hours);
    tm->tm_mday = bcd2bin(regs.date);
    tm->tm_wday = bcd2bin(regs.day) - 1;
    tm->tm_mon = bcd2bin(regs.month) - 1;
    tm->tm_year = bcd2bin(regs.years) + 100; // 设备的epoch是2000,加回到1900
    return rtc_valid_tm(tm);
}

static int foo_rtc_set_time(struct device *dev, struct rtc_time *tm) {
    struct foo_regs regs;
    regs.seconds = bin2bcd(tm->tm_sec);
    regs.minutes = bin2bcd(tm->tm_min);
    regs.cent_hours = bin2bcd(tm->tm_hour);

    regs.day = bin2bcd(tm->tm_wday + 1);
    regs.date = bin2bcd(tm->tm_mday);

    regs.month = bin2bcd(tm->tm_mon + 1);

    regs.cent_hours |= BQ32K_CENT;
    regs.years = bin2bcd(tm->tm_year % 100);
    return write_into_device(dev, &regs, 0, sizeof(regs));
}

驱动程序示例:

static int fake_rtc_read_time(struct device *dev, struct rtc_time *tm) {
    [...]
    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",},{},
};

static int fake_rtc_probe(struct platform_device *dev) {
    struct rtc_device *rtc;
    rtc = rtc_device_register(pdev->name, &pdev->dev, &fake_rtc_ops, THIS_MODULE);
    if (IS_ERR(rtc)) return PTR(rtc);
    platform_set_drvdata(dev, rtc);
    return 0;
}

static int fake_rtc_remove(struct platform_device *dev) {
    rtc_device_unregister(platform_get_drvdata(dev));
}

static struct platform_driver fake_rtc_driver = {
    .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_patform_driver(fake_rtc_driver);

2. 读取和设置闹钟

        RTC闹钟是设备在给定时间触发的可编程事件。RTC闹钟表示为struct rtc_wkalarm{}

struct rtc_wkalarm {
    uchar enabled; // 0禁用,1启用
    uchar pending; // 0报警未挂起,1挂起
    struct rtc_time time; //闹钟的时间
};

        在向系统报告闹钟事件之前,必须将RTC芯片连接到SoC的IRQ线。例如request_thread_irq()来注册闹钟的IRQ处理程序,在内核IRQ处理程序中,要使用rtc_updata_irq()函数向内核通知RTC IRQ事件:

// 用于向rtc内核汇报产生了alarm事件
void rtc_update_irq(struct rtc_device *rtc, ulong num, ulong events);
/* 其中rtc是引发IRQ的RTC设备;
    num表示报告了多少个IRQ(通常是一个);
    events:表示RTC_IRQF掩码,其中包含若干个RTC_PF, RTC_AF, RTC_UF
    #define RTC_IRQF 0x80 //下面的三项都是活动的
    #define RTC_PF 0x40 //周期性中断
    #define RTC_AF 0x20 // 报警中断
    #define RTC_UF 0x10 // 更新为1HZ的RTC
*/

// 下面是RTC芯片的示例中断函数实现
static irqreturn_t foo_rtc_alarm_irq(int irq, void *data) {
    struct foo_rtc_struct *foo_device = data;
    rtc_update_irq(foo_device->rtc_dev, 1, RTC_IRQF | RTC_AF);
    return IRQ_HANDLED;
}

        具有闹钟功能的RTC设备可以用作唤醒源。只要闹钟触发,系统就可以从挂起模式唤醒。调用device_init_wakeup()可以把设备声明为唤醒源,同时还必须使用devm_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);

// 示例:把spi RTC设备声明为唤醒源的方式如下,以下代码在probe函数中:
device_init_wakeup(&spi->dev, true);
dev_pm_set_wake_irq(&spi->dev, spi->irq);

三、RTC和用户空间

        用户空间使用RTC,主要涉及两个内核选项CONFIG_RTC_HCTOSYS和CONFIG_RTC_HCTOSYS_DEVICE,它会导致在系统启动时从RTC设置系统时间。

CONFIG_RTC_HCTOSYS=y
CONFIG_RTC_HCTOSYS_DEVICE="rtc0"

RTC设备的sysfs目录是/sys/class/rtc/rtc<id>/,在其目录中有文件 date, time。

使用hwclock可以设置RTC设备的时间

#hwclok --systohc

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值