RTC-BM8563-标准Linux驱动和app测试代码

最近需要调试一颗RTC BM6563,
按照标准Linux RTC来实现,方便驱动多平台的移植或者IC替换,
也方便app代码rtc部分的移植性.
遇到的问题
1) 设置的时间格式为无效值,导致无法设置rtc时间,排查了小半天.
2) RTC框架上时间设置和bm6563不完全对应,寄存器的读写需要细微调整转换数值.

// linux 官方rtc app 测试code
linux 下的RTC可概括为三个功能
(1) watcher(当前时间): 读取时间和设置时间 RTC_RD_TIME, RTC_SET_TIME
(2) alarm(闹钟):    24小时内的闹钟功能,可能需要打开和关闭相应中断 RTC_AIE_ON, RTC_AIE_OFF, RTC_ALM_SET, RTC_ALM_READ
(3) 用于低功耗唤醒等等,不关注

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


/*
 * This expects the new RTC class driver framework, working with
 * clocks that will often not be clones of what the PC-AT had.
 * Use the command line to specify another RTC if you need one.
 */
static const char default_rtc[] = "/dev/rtc0";


int main(int argc, char **argv)
{
    int i, fd, retval, irqcount = 0;
    unsigned long tmp, data;
    struct rtc_time rtc_tm;
    const char *rtc = default_rtc;

    switch (argc) {
    case 2:
        rtc = argv[1];
        /* FALLTHROUGH */A
    case 1:
        break;
    default:
        fprintf(stderr, "usage:  rtctest [rtcdev]\n");
        return 1;
    }

    fd = open(rtc, O_RDONLY);

    if (fd ==  -1) {
        perror(rtc);
        exit(errno);
    }

    fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n");

    /* Turn on update interrupts (one per second) */
    retval = ioctl(fd, RTC_UIE_ON, 0);
    if (retval == -1) {
        if (errno == ENOTTY) {
            fprintf(stderr,
                "\n...Update IRQs not supported.\n");
            goto test_READ;
        }
        perror("RTC_UIE_ON ioctl");
        exit(errno);
    }

    fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading %s:",
            rtc);
    fflush(stderr);
    for (i=1; i<6; i++) {
        /* This read will block */
        retval = read(fd, &data, sizeof(unsigned long));
        if (retval == -1) {
            perror("read");
            exit(errno);
        }
        fprintf(stderr, " %d",i);
        fflush(stderr);
        irqcount++;
    }

    fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:");
    fflush(stderr);
    for (i=1; i<6; i++) {
        struct timeval tv = {5, 0};     /* 5 second timeout on select */
        fd_set readfds;

        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        /* The select will wait until an RTC interrupt happens. */
        retval = select(fd+1, &readfds, NULL, NULL, &tv);
        if (retval == -1) {
                perror("select");
                exit(errno);
        }
        /* This read won't block unlike the select-less case above. */
        retval = read(fd, &data, sizeof(unsigned long));
        if (retval == -1) {
                perror("read");
                exit(errno);
        }
        fprintf(stderr, " %d",i);
        fflush(stderr);
        irqcount++;
    }

    /* Turn off update interrupts */
    retval = ioctl(fd, RTC_UIE_OFF, 0);
    if (retval == -1) {
        perror("RTC_UIE_OFF ioctl");
        exit(errno);
    }

test_READ:
    /* Read the RTC time/date */
    retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
    if (retval == -1) {
        perror("RTC_RD_TIME ioctl");
        exit(errno);
    }

    fprintf(stderr, "\n\nCurrent RTC date/time is %d-%d-%d, %02d:%02d:%02d.\n",
        rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,
        rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);

    /* Set the alarm to 5 sec in the future, and check for rollover */
    rtc_tm.tm_sec += 5;
    if (rtc_tm.tm_sec >= 60) {
        rtc_tm.tm_sec %= 60;
        rtc_tm.tm_min++;
    }
    if (rtc_tm.tm_min == 60) {
        rtc_tm.tm_min = 0;
        rtc_tm.tm_hour++;
    }
    if (rtc_tm.tm_hour == 24)
        rtc_tm.tm_hour = 0;

    retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);
    if (retval == -1) {
        if (errno == ENOTTY) {
            fprintf(stderr,
                "\n...Alarm IRQs not supported.\n");
            goto test_PIE;
        }
        perror("RTC_ALM_SET ioctl");
        exit(errno);
    }

    /* Read the current alarm settings */
    retval = ioctl(fd, RTC_ALM_READ, &rtc_tm);
    if (retval == -1) {
        perror("RTC_ALM_READ ioctl");
        exit(errno);
    }

    fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n",
        rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);

    /* Enable alarm interrupts */
    retval = ioctl(fd, RTC_AIE_ON, 0);
    if (retval == -1) {
        perror("RTC_AIE_ON ioctl");
        exit(errno);
    }

    fprintf(stderr, "Waiting 5 seconds for alarm...");
    fflush(stderr);
    /* This blocks until the alarm ring causes an interrupt */
    retval = read(fd, &data, sizeof(unsigned long));
    if (retval == -1) {
        perror("read");
        exit(errno);
    }
    irqcount++;
    fprintf(stderr, " okay. Alarm rang.\n");

    /* Disable alarm interrupts */
    retval = ioctl(fd, RTC_AIE_OFF, 0);
    if (retval == -1) {
        perror("RTC_AIE_OFF ioctl");
        exit(errno);
    }

test_PIE:
    /* Read periodic IRQ rate */
    retval = ioctl(fd, RTC_IRQP_READ, &tmp);
    if (retval == -1) {
        /* not all RTCs support periodic IRQs */
        if (errno == ENOTTY) {
            fprintf(stderr, "\nNo periodic IRQ support\n");
            goto done;
        }
        perror("RTC_IRQP_READ ioctl");
        exit(errno);
    }
    fprintf(stderr, "\nPeriodic IRQ rate is %ldHz.\n", tmp);

    fprintf(stderr, "Counting 20 interrupts at:");
    fflush(stderr);

    /* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */
    for (tmp=2; tmp<=64; tmp*=2) {

        retval = ioctl(fd, RTC_IRQP_SET, tmp);
        if (retval == -1) {
            /* not all RTCs can change their periodic IRQ rate */
            if (errno == ENOTTY) {
                fprintf(stderr,
                    "\n...Periodic IRQ rate is fixed\n");
                goto done;
            }
            perror("RTC_IRQP_SET ioctl");
            exit(errno);
        }

        fprintf(stderr, "\n%ldHz:\t", tmp);
        fflush(stderr);

        /* Enable periodic interrupts */
        retval = ioctl(fd, RTC_PIE_ON, 0);
        if (retval == -1) {
            perror("RTC_PIE_ON ioctl");
            exit(errno);
        }

        for (i=1; i<21; i++) {
            /* This blocks */
            retval = read(fd, &data, sizeof(unsigned long));
            if (retval == -1) {
                perror("read");
                exit(errno);
            }
            fprintf(stderr, " %d",i);
            fflush(stderr);
            irqcount++;
        }

        /* Disable periodic interrupts */
        retval = ioctl(fd, RTC_PIE_OFF, 0);
        if (retval == -1) {
            perror("RTC_PIE_OFF ioctl");
            exit(errno);
        }
    }

done:
    fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n");

    close(fd);

    return 0;
}


然后是驱动部分,先参考下内核几个自带的驱动.
思路需要构造RTC结构体,然后填充RD_TIME,和SET_TIME.

rtc = rtc_device_register(em3027_driver.driver.name, &client->dev,
                  &em3027_rtc_ops, THIS_MODULE);

//驱动代码
/*
 * An rtc/i2c driver for the BM8563
 * Copyright 2018 by hecong
 *
 * Author: Echo 
 *
 * Based on rtc-bm8563.c by Alessandro Zummo 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* Registers */
#define REG_CTRL_1  0x00
#define REG_CTRL_2  0x01
#define REG_WATCH_SEC   0x02
#define REG_WATCH_MIN   0x03
#define REG_WATCH_HOUR  0x04
#define REG_WATCH_DATE  0x05
#define REG_WATCH_DAY   0x06
#define REG_WATCH_MON   0x07
#define REG_WATCH_YEAR  0x08
#define RTC_I2C_NUM     1
#define RTC_ADDR   0xa2/2

static struct i2c_board_info bm8563_info = {
    I2C_BOARD_INFO("bm8563", RTC_ADDR),        
};

static struct i2c_client *bm8563_client;

static int bm8563_get_time(struct device *dev, struct rtc_time *tm)
{
    struct i2c_client *client = to_i2c_client(dev);

    unsigned char addr = REG_WATCH_SEC;
    unsigned char buf[7];

    struct i2c_msg msgs[] = {
        {client->addr, 0, 1, &addr},        /* setup read addr */
        {client->addr, I2C_M_RD, 7, buf},   /* read time/date */
    };

    /* read time/date registers */
    if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
        dev_err(&client->dev, "%s: read error\n", __func__);
        return -EIO;
    }

    //printk("kernel get time\n");
    tm->tm_sec  = bcd2bin(buf[0]&0x7f) ;
    tm->tm_min  = bcd2bin(buf[1]);
    tm->tm_hour = bcd2bin(buf[2]);
    tm->tm_mday = bcd2bin(buf[3]);
    tm->tm_wday = bcd2bin(buf[4]);
    tm->tm_mon  = bcd2bin(buf[5])-1;
    tm->tm_year = bcd2bin(buf[6]) + 100;
    if((tm->tm_sec == 0) && (tm->tm_min == 0) && (tm->tm_hour == 0) )
        rtc_time_to_tm(0, tm);
    return 0;
}

static int bm8563_set_time(struct device *dev, struct rtc_time *tm)
{
    struct i2c_client *client = to_i2c_client(dev);
    unsigned char buf[8];

    struct i2c_msg msg = {
        client->addr, 0, 8, buf,    /* write time/date */
    };

    //printk("kernel set time\n");
    buf[0] = REG_WATCH_SEC;
    buf[1] = bin2bcd(tm->tm_sec);
    buf[2] = bin2bcd(tm->tm_min);
    buf[3] = bin2bcd(tm->tm_hour);
    buf[4] = bin2bcd(tm->tm_mday);
    buf[5] = bin2bcd(tm->tm_wday);
    buf[6] = bin2bcd(tm->tm_mon)+1;
    buf[7] = bin2bcd(tm->tm_year % 100);

    /* write time/date registers */
    if ((i2c_transfer(client->adapter, &msg, 1)) != 1) {
        dev_err(&client->dev, "%s: write error\n", __func__);
        return -EIO;
    }

    return 0;
}

static const struct rtc_class_ops bm8563_rtc_ops = {
    .read_time = bm8563_get_time,
    .set_time = bm8563_set_time,
};

static int bm8563_dev_init(void)
{
    struct rtc_device *rtc;
    struct i2c_adapter *i2c_adap;       
    struct i2c_client *client;
    unsigned char buf[2];
    struct i2c_msg msg = {
        RTC_ADDR, 0, 2, buf,    /* write time/date */
    };

    i2c_adap = i2c_get_adapter(RTC_I2C_NUM);      
    bm8563_client = i2c_new_device(i2c_adap, &bm8563_info);   
    i2c_put_adapter(i2c_adap);
    client = bm8563_client;


    buf[0] = REG_CTRL_1;
    buf[1] = 0;
    if ((i2c_transfer(client->adapter, &msg, 1)) != 1) {
        dev_err(&client->dev, "%s: write error\n", __func__);
        return -EIO;
    }
    rtc = rtc_device_register("bm8563", &client->dev,
                  &bm8563_rtc_ops, THIS_MODULE);
    if (IS_ERR(rtc))
        return PTR_ERR(rtc);

    i2c_set_clientdata(client, rtc);

    return 0;
}
static void bm8563_dev_exit(void)
{
    struct rtc_device *rtc = i2c_get_clientdata(bm8563_client);

    if (rtc)
        rtc_device_unregister(rtc); 
    i2c_unregister_device(bm8563_client); 
}
module_init(bm8563_dev_init);
module_exit(bm8563_dev_exit);

MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION(" qualvision rtc driver");
MODULE_LICENSE("GPL");

测试代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * This expects the new RTC class driver framework, working with
 * clocks that will often not be clones of what the PC-AT had.
 * Use the command line to specify another RTC if you need one.
 */
static const char default_rtc[] = "/dev/rtc1";

int main(int argc, char **argv)
{
    int i, fd, fd1, retval, irqcount = 0;
    unsigned long tmp, data;
    struct rtc_time rtc_tm;
    const char *rtc = default_rtc;

    switch (argc) {
    case 2:
        rtc = argv[1];
        /* FALLTHROUGH */
    case 1:
        break;
    default:
        fprintf(stderr, "usage:  rtctest [rtcdev]\n");
        return 1;
    }

    fd = open(rtc, O_RDWR);
    if (fd == -1) {
        perror(rtc);
        exit(errno);
    }

    /* Read the RTC time/date */
    retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
    if (retval == -1) {
        perror("RTC_RD_TIME ioctl");
        exit(errno);
    }

    printf("\n\nCurrent RTC date/time is %d-%d-%d, %2d:%02d:%02d.\n\n\n",
           rtc_tm.tm_mday, rtc_tm.tm_mon, rtc_tm.tm_year + 1900,
           rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);

    /* set time to test */
    rtc_tm.tm_mday = 30;
    rtc_tm.tm_mon = 11;
    rtc_tm.tm_year = 105;       //应该大于100,注意转换,实际为1900+105
    rtc_tm.tm_hour = 10;
    rtc_tm.tm_min = 12;
    rtc_tm.tm_sec = 13;
    retval = ioctl(fd, RTC_SET_TIME, &rtc_tm);
    if (retval == -1) {
        printf("\n... not supported.\n");
    } else {
        printf("set ok rtc0\n");
    }
    for (i = 0; i < 20; i++) {
        retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);

        printf("\n\nCurrent:%d-%d-%d, %2d:%02d:%02d.\n\n\n",
               rtc_tm.tm_mday, rtc_tm.tm_mon, rtc_tm.tm_year + 1900,
               //Linux标准RTC时间都要加1900
               rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
        sleep(1);

    }
    close(fd);

    return 0;
}

你可能感兴趣的:(Linux-内核驱动,Linux驱动随笔)