海思官方手册上说Hi3536CV100/Hi3536DV100/Hi3559AV100/Hi3559CV100 支持内核标准 RTC 驱动,内核编译默认打开 RTC 选项就可以了。HI35XX其它的设备就需要自己设计RTC驱动了。不管什么平台,海思SDK里面都有自带一个简单的rtc驱动,SDK的/drv/rtc/ 里面有详细的说明,这里不再重复。但是,官方自带的rtc驱动是不支持linux hwclock命令操作的。所以一般还是需要自己写。
驱动程序模型分为三种:
(1)传统模型,直接固定硬件资源
(2)总线设备驱动模型,驱动不用改,不同的硬件注册不同的设备。
(3)设备树模型,最新的模型,简便高效
我有两款设备Hi3520DV300和Hi3520DV400,V400是使用设备树来管理硬件,但V300还是使用传统的方式。为了兼容,这里采用了总线设备驱动模型。
(1)修改linux-3.18.y/drivers/rtc/ 下的Kconfig ,最后面添加:
comment "HI3520DV400 RTC drivers"
config RTC_HI3520DV400
bool "HI3520DV400 RTC SET"
help
If you say Y here you will get support for the Hypervisor
based RTC on SUN4V systems.
endif # RTC_CLASS
(2)修改linux-3.18.y/drivers/rtc/ 下的Makefile ,最后面添加:
obj-$(CONFIG_RTC_HI3520DV400) += hi_rtc_drv.o
obj-$(CONFIG_RTC_HI3520DV400) += hi_rtc_dev.o
(3)配置内核,支持RTC功能,并选中我们自己的驱动,我这里是HI3520DV400
在RTC目录下创建设备文件:hi_rtc_dev.c
#include
#include
#include
#include
#include
#include
#include
#define RTC_SPI_BASE_ADDR IO_ADDRESS(0x120b0000)
#define RTC_SPI_CLK_DIV (RTC_SPI_BASE_ADDR + 0x000)
#define RTC_SPI_RW (RTC_SPI_BASE_ADDR + 0x004)
#define HI_RTC_IRQ (37)
static struct resource rtc_resource[] = {
[0] = {
.start = RTC_SPI_BASE_ADDR,
.end = RTC_SPI_RW,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = HI_RTC_IRQ,
.end = HI_RTC_IRQ,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device wisdom_device_rtc = {
.name = "biao_rtc",
.id = 0,
.resource = rtc_resource,
.num_resources = ARRAY_SIZE(rtc_resource),
};
static int biao_rtc_dev_init(void)
{
platform_device_register(&wisdom_device_rtc);
return 0;
}
static void biao_rtc_dev_exit(void)
{
platform_device_unregister(&wisdom_device_rtc);
}
module_init(biao_rtc_dev_init);
module_exit(biao_rtc_dev_exit);
MODULE_LICENSE("GPL");
在RTC目录下创建驱动程序:hi_rtc_drv.c
/*
* DS1286 Real Time Clock interface for Linux
*
* Copyright (C) 1998, 1999, 2000 Ralf Baechle
* Copyright (C) 2008 Thomas Bogendoerfer
*
* Based on code written by Paul Gortmaker.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DRV_VERSION "1.0"
/* RTC Control over SPI */
#define RTC_SPI_BASE_ADDR IO_ADDRESS(0x120b0000)
#define SPI_CLK_DIV (RTC_SPI_BASE_ADDR + 0x000)
#define SPI_RW (RTC_SPI_BASE_ADDR + 0x004)
/* Define the union SPI_RW */
typedef union {
struct {
unsigned int spi_wdata : 8; /* [7:0] */
unsigned int spi_rdata : 8; /* [15:8] */
unsigned int spi_addr : 7; /* [22:16] */
unsigned int spi_rw : 1; /* [23] */
unsigned int spi_start : 1; /* [24] */
unsigned int reserved : 6; /* [30:25] */
unsigned int spi_busy : 1; /* [31] */
} bits;
/* Define an unsigned member */
unsigned int u32;
} U_SPI_RW;
#define SPI_WRITE (0)
#define SPI_READ (1)
#define RTC_IRQ (37)
/* RTC REG */
#define RTC_10MS_COUN 0x00
#define RTC_S_COUNT 0x01
#define RTC_M_COUNT 0x02
#define RTC_H_COUNT 0x03
#define RTC_D_COUNT_L 0x04
#define RTC_D_COUNT_H 0x05
#define RTC_MR_10MS 0x06
#define RTC_MR_S 0x07
#define RTC_MR_M 0x08
#define RTC_MR_H 0x09
#define RTC_MR_D_L 0x0A
#define RTC_MR_D_H 0x0B
#define RTC_LR_10MS 0x0C
#define RTC_LR_S 0x0D
#define RTC_LR_M 0x0E
#define RTC_LR_H 0x0F
#define RTC_LR_D_L 0x10
#define RTC_LR_D_H 0x11
#define RTC_LORD 0x12
#define RTC_IMSC 0x13
#define RTC_INT_CLR 0x14
#define RTC_INT_MASK 0x15
#define RTC_INT_RAW 0x16
#define RTC_CLK 0x17
#define RTC_POR_N 0x18
#define RTC_SAR_CTRL 0x1A
#define RTC_FREQ_H 0x51
#define RTC_FREQ_L 0x52
#define RETRY_CNT 100
#define FREQ_MAX_VAL 3277000
#define FREQ_MIN_VAL 3276000
static DEFINE_MUTEX(hirtc_lock);
struct biao_rtc_priv {
struct rtc_device *rtc;
u32 __iomem *rtcregs;
spinlock_t lock;
};
static int spi_write(char reg, char val)
{
U_SPI_RW w_data, r_data;
r_data.u32 = 0;
w_data.u32 = 0;
w_data.bits.spi_wdata = val;
w_data.bits.spi_addr = reg;
w_data.bits.spi_rw = SPI_WRITE;
w_data.bits.spi_start = 0x1;
writel(w_data.u32, (volatile void __iomem *)SPI_RW);
do {
r_data.u32 = readl((volatile void __iomem *)SPI_RW);
} while (r_data.bits.spi_busy);
return 0;
}
static int spi_rtc_write(char reg, char val)
{
mutex_lock(&hirtc_lock);
spi_write(reg, val);
mutex_unlock(&hirtc_lock);
return 0;
}
static int spi_read(char reg, char *val)
{
U_SPI_RW w_data, r_data;
r_data.u32 = 0;
w_data.u32 = 0;
w_data.bits.spi_addr = reg;
w_data.bits.spi_rw = SPI_READ;
w_data.bits.spi_start = 0x1;
writel(w_data.u32, (volatile void __iomem *)SPI_RW);
do {
r_data.u32 = readl((volatile void __iomem *)SPI_RW);
} while (r_data.bits.spi_busy);
*val = r_data.bits.spi_rdata;
return 0;
}
static int spi_rtc_read(char reg, char *val)
{
mutex_lock(&hirtc_lock);
spi_read(reg, val);
mutex_unlock(&hirtc_lock);
return 0;
}
static int hirtc_get_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
unsigned char dayl, dayh;
unsigned char second, minute, hour;
unsigned long seconds = 0;
unsigned int day;
spi_rtc_read(RTC_MR_S, &second);
spi_rtc_read(RTC_MR_M, &minute);
spi_rtc_read(RTC_MR_H, &hour);
spi_rtc_read(RTC_MR_D_L, &dayl);
spi_rtc_read(RTC_MR_D_H, &dayh);
day = (unsigned int)(dayl | (dayh << 8));
seconds = second + minute*60 + hour*60*60 + day*24*60*60;
rtc_time_to_tm(seconds,&(alm->time));
return 0;
}
static int hirtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
unsigned int days;
unsigned long seconds = 0;
if(rtc_valid_tm(&alm->time))
return -1;
rtc_tm_to_time(&alm->time,&seconds);
days = seconds/(60*60*24);
spi_rtc_write(RTC_MR_10MS, 0);
spi_rtc_write(RTC_MR_S, alm->time.tm_sec);
spi_rtc_write(RTC_MR_M, alm->time.tm_min);
spi_rtc_write(RTC_MR_H, alm->time.tm_hour);
spi_rtc_write(RTC_MR_D_L, (days & 0xFF));
spi_rtc_write(RTC_MR_D_H, (days >> 8));
return 0;
}
static int hirtc_get_time(struct device *dev, struct rtc_time *tm)
{
unsigned char dayl, dayh;
unsigned char second, minute, hour;
unsigned long seconds = 0;
unsigned int day;
unsigned char raw_value;
spi_rtc_read(RTC_LORD, &raw_value);
if(raw_value & 0x4)
{
spi_rtc_write(RTC_LORD, (~(1<<2)) & raw_value);
}
spi_rtc_read(RTC_LORD, &raw_value);
spi_rtc_write(RTC_LORD, (1<<1) | raw_value);//lock the time
do
{
spi_rtc_read(RTC_LORD, &raw_value);
}while(raw_value & 0x2);
udelay(1000);
spi_rtc_read(RTC_S_COUNT, &second);
spi_rtc_read(RTC_M_COUNT, &minute);
spi_rtc_read(RTC_H_COUNT, &hour);
spi_rtc_read(RTC_D_COUNT_L, &dayl);
spi_rtc_read(RTC_D_COUNT_H, &dayh);
day = (dayl | (dayh << 8));
seconds = second + minute*60 + hour*60*60 + day*24*60*60;
rtc_time_to_tm(seconds, tm);
printk("[FJW]:hirtc_get_time [year %d] [mon %d] [mday %d] [hour %d] [min %d] [sec %d] \n"
,tm->tm_year,tm->tm_mon,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
return 0;
}
static int hirtc_set_time(struct device *dev, struct rtc_time *tm)
{
unsigned char ret;
unsigned int days;
unsigned long seconds = 0;
unsigned int cnt = 0;
if(rtc_valid_tm(tm))
return -1;
rtc_tm_to_time(tm,&seconds);
days = seconds/(60*60*24);
printk("[FJW]:hirtc_set_time [year %d] [mon %d] [mday %d] [hour %d] [min %d] [sec %d] \n"
,tm->tm_year,tm->tm_mon,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
do {
spi_rtc_read(RTC_LORD, &ret);
udelay(1000);
cnt++;
} while ( (ret&0x1) && (cnt < RETRY_CNT));
if (cnt >= RETRY_CNT) {
printk(KERN_ERR "check state error!\n");
return -1;
}
spi_rtc_write(RTC_LR_10MS, 0);
spi_rtc_write(RTC_LR_S, tm->tm_sec);
spi_rtc_write(RTC_LR_M, tm->tm_min);
spi_rtc_write(RTC_LR_H, tm->tm_hour);
spi_rtc_write(RTC_LR_D_L, (days & 0xFF));
spi_rtc_write(RTC_LR_D_H, (days >> 8));
spi_rtc_write(RTC_LORD, (ret|0x1));
return 0;
}
static const struct rtc_class_ops hi_rtc_ops = {
.read_time = hirtc_get_time,
.set_time = hirtc_set_time,
.read_alarm = hirtc_get_alarm,
.set_alarm = hirtc_set_alarm,
};
static int hi_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
struct resource *res;
struct biao_rtc_priv *priv;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
priv = devm_kzalloc(&pdev->dev, sizeof(struct biao_rtc_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->rtcregs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->rtcregs))
return PTR_ERR(priv->rtcregs);
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
rtc = devm_rtc_device_register(&pdev->dev, "biao_rtc", &hi_rtc_ops,THIS_MODULE);
if (IS_ERR(rtc))
return PTR_ERR(rtc);
priv->rtc = rtc;
return 0;
}
static int hi_rtc_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver hi_biao_rtc_platform_driver = {
.driver = {
.name = "biao_rtc",
.owner = THIS_MODULE,
},
.probe = hi_rtc_probe,
.remove = hi_rtc_remove,
};
module_platform_driver(hi_biao_rtc_platform_driver);
MODULE_AUTHOR("Caibiao Lee");
MODULE_DESCRIPTION("biao_rtc RTC driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
MODULE_ALIAS("platform:biao_rtc");
在/include/linux/ 下面创建文件:hi_rtc.h
/*
* include/hi_rtc.h for Linux .
*
* This file defines hi_rtc micro-definitions for driver developer.
*
* History:
* 10-April-2006 create this file
*/
#ifndef __HI_RTC__
#define __HI_RTC__
typedef struct {
unsigned int year;
unsigned int month;
unsigned int date;
unsigned int hour;
unsigned int minute;
unsigned int second;
unsigned int weekday;
} rtc_time_t;
typedef struct {
unsigned char reg;
unsigned char val;
} reg_data_t;
typedef enum temp_sel_mode {
TEMP_SEL_FIXMODE = 0,
TEMP_SEL_OUTSIDE,
TEMP_SEL_INTERNAL,
} temp_sel_mode;
typedef struct {
temp_sel_mode mode;
int value;
} reg_temp_mode_t;
typedef struct {
unsigned int freq_l;
} rtc_freq_t;
#define HI_RTC_AIE_ON _IO('p', 0x01)
#define HI_RTC_AIE_OFF _IO('p', 0x02)
#define HI_RTC_BM_ON _IO('p', 0x03)
#define HI_RTC_BM_OFF _IO('p', 0x04)
#define HI_RTC_GET_FREQ _IOR('P', 0x05, rtc_freq_t)
#define HI_RTC_SET_FREQ _IOW('p', 0x06, rtc_freq_t)
#define HI_RTC_ALM_SET _IOW('p', 0x07, rtc_time_t)
#define HI_RTC_ALM_READ _IOR('p', 0x08, rtc_time_t)
#define HI_RTC_RD_TIME _IOR('p', 0x09, rtc_time_t)
#define HI_RTC_SET_TIME _IOW('p', 0x0a, rtc_time_t)
#define HI_RTC_RESET _IOW('p', 0x0b, rtc_time_t)
#define HI_RTC_REG_SET _IOW('p', 0x0c, reg_data_t)
#define HI_RTC_REG_READ _IOR('p', 0x0d, reg_data_t)
#endif
如果驱动正常加载,会在/dev 下生成rtc0的设备文件。使用hwclock命令测试RTC是否正常。
~ # date
Tue Jan 29 17:32:10 CST 2019
~ # date 11062112
Wed Nov 6 21:12:00 CST 2019
~ # date
Wed Nov 6 21:12:02 CST 2019
~ # hwclock
Tue Jan 29 17:33:36 2019 0.000000 seconds
~ # hwclock -w
~ # hwclock
Wed Nov 6 21:12:20 2019 0.000000 seconds
~ #
~ # hwclock
Wed Nov 6 21:16:24 2019 0.000000 seconds
~ #
可以看到时间的设置和读取都是正常的。
代码文件下载路径:Hi3520DV400 RTC 驱动程序