• Qcom平台RTC驱动分析


    相关文件list:

    pm8998.dtsi ---RTC dts配置
    qpnp-rtc.c  ---qcom RTC驱动
    class.c    ---RTC相关class
    interface.c  ---相关RTC功能的接口定义
    hctosys.c    ---一开机通过RTC设置系统时间
    rtc-dev.c   ---RTC device fops接口:open、close、ioctl、poll等

    简述:

    所谓RTC(Real Time Clock),用于关机时继续计算系统日期和时间。是基于硬件的功能。也可以RTC做Alarm来设置power on/off。

    驱动分析:

    首先在dts的Document中看到两个配置项:

    - qcom,qpnp-rtc-write: This property enables/disables rtc write
                           0 = Disable rtc writes.
                           1 = Enable rtc writes.
    - qcom,qpnp-rtc-alarm-pwrup:	This property enables/disables 
                                    feature of powering up phone (from 
                                    power down state) through alarm 
                                    interrupt.
                                    If not mentioned rtc driver will 
                                    disable feature of powring-up     
                                    phone through alarm.
                                    0 = Disable powering up of phone 
                                        through alarm interrupt.
                                    1 = Enable powering up of phone 
                                        through alarm interrupt.            

    一个是是否使能写RTC时间的功能。另一个是是否支持RTC alarm开机的功能。

    接下来,再看RTC的驱动部分,从qpnp-rtc.c:

    其中根据.compatible = "qcom,qpnp-rtc"匹配成功后走到probe,probe中获取了dts中上面两条配置参数,从而进行初始化。获取RTC/Alarm相关的寄存器和资源,并通过操作寄存器enable RTC相关功能。注册了RTC中断并配置了RTC相关操作的api。

    通过dts的配置,使用不同的ops,从而实现支持write RTC与否。

    //这个是不支持write RTC的ops
    static const struct rtc_class_ops qpnp_rtc_ro_ops = {
        .read_time = qpnp_rtc_read_time,
        .set_alarm = qpnp_rtc_set_alarm,
        .read_alarm = qpnp_rtc_read_alarm,
        .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
    };
    
    //这个是支持读写 RTC的ops,就是多了个write的接口
    static const struct rtc_class_ops qpnp_rtc_rw_ops = {
        .read_time = qpnp_rtc_read_time,
        .set_alarm = qpnp_rtc_set_alarm,
        .read_alarm = qpnp_rtc_read_alarm,
        .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
        .set_time = qpnp_rtc_set_time,
    };
    static int qpnp_rtc_probe(struct platform_device *pdev)
    {
        const struct rtc_class_ops *rtc_ops = &qpnp_rtc_ro_ops; //默认使用不支持RTC write的ops
        int rc;
        u8 subtype;
        struct qpnp_rtc *rtc_dd;
        unsigned int base;
        struct device_node *child;
    
        rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL);
        if (rtc_dd == NULL)
            return -ENOMEM;
    
        rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);
        if (!rtc_dd->regmap) {
            dev_err(&pdev->dev, "Couldn't get parent's regmap
    ");
            return -EINVAL;
        }
    
        /* Get the rtc write property */
        rc = of_property_read_u32(pdev->dev.of_node, "qcom,qpnp-rtc-write",
                            &rtc_dd->rtc_write_enable); //读取dts中的RTC write配置
        if (rc && rc != -EINVAL) {
            dev_err(&pdev->dev,
                "Error reading rtc_write_enable property %d
    ", rc);
            return rc;
        }
    
        rc = of_property_read_u32(pdev->dev.of_node,
                            "qcom,qpnp-rtc-alarm-pwrup",
                            &rtc_dd->rtc_alarm_powerup); //读取dts中RTC alarm powerup的配置
        if (rc && rc != -EINVAL) {
            dev_err(&pdev->dev,
                "Error reading rtc_alarm_powerup property %d
    ", rc);
            return rc;
        }
    
        /* Initialise spinlock to protect RTC control register */
        spin_lock_init(&rtc_dd->alarm_ctrl_lock);
    
        rtc_dd->rtc_dev = &(pdev->dev);
        rtc_dd->pdev = pdev;
    
    
        if (of_get_available_child_count(pdev->dev.of_node) == 0) {
            pr_err("no child nodes
    ");
            rc = -ENXIO;
            goto fail_rtc_enable;
        }
    
        /* Get RTC/ALARM resources */
        for_each_available_child_of_node(pdev->dev.of_node, child) {
            rc = of_property_read_u32(child, "reg", &base);
            if (rc < 0) {
                dev_err(&pdev->dev,
                    "Couldn't find reg in node = %s rc = %d
    ",
                    child->full_name, rc);
                goto fail_rtc_enable;
            }
    
            rc = qpnp_read_wrapper(rtc_dd, &subtype,
                    base + REG_OFFSET_PERP_SUBTYPE, 1);
            if (rc) {
                dev_err(&pdev->dev,
                    "Peripheral subtype read failed
    ");
                goto fail_rtc_enable;
            }
    
            switch (subtype) {
            case RTC_PERPH_SUBTYPE:
                rtc_dd->rtc_base = base;
                break;
            case ALARM_PERPH_SUBTYPE:
                rtc_dd->alarm_base = base;
                rtc_dd->rtc_alarm_irq = of_irq_get(child, 0);
                if (rtc_dd->rtc_alarm_irq < 0) {
                    dev_err(&pdev->dev, "ALARM IRQ absent
    ");
                    rc = -ENXIO;
                    goto fail_rtc_enable;
                }
                break;
            default:
                dev_err(&pdev->dev, "Invalid peripheral subtype
    ");
                rc = -EINVAL;
                goto fail_rtc_enable;
            }
        }
    
        rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->rtc_ctrl_reg,
                    rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
        if (rc) {
            dev_err(&pdev->dev, "Read from RTC control reg failed
    ");
            goto fail_rtc_enable;
        }
    
        if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {
            dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered
    ");
            goto fail_rtc_enable;
        }
    
        rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
                    rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
        if (rc) {
            dev_err(&pdev->dev, "Read from  Alarm control reg failed
    ");
            goto fail_rtc_enable;
        }
        /* Enable abort enable feature */
        rtc_dd->alarm_ctrl_reg1 |= BIT_RTC_ABORT_ENABLE;
        rc = qpnp_write_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
                rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
        if (rc) {
            dev_err(&pdev->dev, "SPMI write failed!
    ");
            goto fail_rtc_enable;
        }
    
        if (rtc_dd->rtc_write_enable == true) //判断dts中配置如果要支持RTC write功能,那就重新赋值,使用将RTC RW都支持的ops接口
            rtc_ops = &qpnp_rtc_rw_ops;
    
        dev_set_drvdata(&pdev->dev, rtc_dd);
    
        /* Register the RTC device */
        rtc_dd->rtc = rtc_device_register("qpnp_rtc", &pdev->dev,
                          rtc_ops, THIS_MODULE);
        if (IS_ERR(rtc_dd->rtc)) {
            dev_err(&pdev->dev, "%s: RTC registration failed (%ld)
    ",
                        __func__, PTR_ERR(rtc_dd->rtc));
            rc = PTR_ERR(rtc_dd->rtc);
            goto fail_rtc_enable;
        }
    
        /* Request the alarm IRQ */
        rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
                     qpnp_alarm_trigger, IRQF_TRIGGER_RISING,
                     "qpnp_rtc_alarm", rtc_dd);
        if (rc) {
            dev_err(&pdev->dev, "Request IRQ failed (%d)
    ", rc);
            goto fail_req_irq;
        }
    
        device_init_wakeup(&pdev->dev, 1);
        enable_irq_wake(rtc_dd->rtc_alarm_irq);
    
        dev_dbg(&pdev->dev, "Probe success !!
    ");
    
        return 0;
    
    fail_req_irq:
        rtc_device_unregister(rtc_dd->rtc);
    fail_rtc_enable:
        dev_set_drvdata(&pdev->dev, NULL);
    
        return rc;
    }

    而在RTC shutdown时,会根据是否要支持RTC alarm开机,进行中断和寄存器的配置。

    static void qpnp_rtc_shutdown(struct platform_device *pdev)
    {
        u8 value[4] = {0};
        u8 reg;
        int rc;
        unsigned long irq_flags;
        struct qpnp_rtc *rtc_dd;
        bool rtc_alarm_powerup;
    
        if (!pdev) {
            pr_err("qpnp-rtc: spmi device not found
    ");
            return;
        }
        rtc_dd = dev_get_drvdata(&pdev->dev);
        if (!rtc_dd) {
            pr_err("qpnp-rtc: rtc driver data not found
    ");
            return;
        }
        rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup;
        if (!rtc_alarm_powerup && !poweron_alarm) { //根据flag设置是否disbale RTC alarm的中断,以及通过设置reg,控制是否disable RTC alarm
            spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
            dev_dbg(&pdev->dev, "Disabling alarm interrupts
    ");
    
            /* Disable RTC alarms */
            reg = rtc_dd->alarm_ctrl_reg1;
            reg &= ~BIT_RTC_ALARM_ENABLE;
            rc = qpnp_write_wrapper(rtc_dd, &reg,
                rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
            if (rc) {
                dev_err(rtc_dd->rtc_dev, "SPMI write failed
    ");
                goto fail_alarm_disable;
            }
    
            /* Clear Alarm register */
            rc = qpnp_write_wrapper(rtc_dd, value,
                    rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
                    NUM_8_BIT_RTC_REGS);
            if (rc)
                dev_err(rtc_dd->rtc_dev, "SPMI write failed
    ");
    
    fail_alarm_disable:
            spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
        }
    }

    内核的开机时间首先是从RTC读取的时间作为基准,之后会通过QCOM time daemon进行corection。QCOM time daemon的代码非open source,所以暂不分析。这里我们仅分析RTC的部分。

    而RTC的时间,我们知道第一次开机,这里指的是RTC断电后的第一次开机,RTC时间是1970-01-01 00:00:00(初始时间),这个值就是从RTC中读取出来的。而在使用一段时间之后,再重启手机,这时RTC的时间为=初始时间+使用的时间。也就是说,这个时间会随着使用而不断累计,除非RTC掉电,重置为初始时间。RTC也支持将同步后的时间再次写入RTC,用于校准当前的正确日期和时间。

    Case 1 开机系统内核时间设置 from RTC

    这里主要分析RTC驱动部分的RTC时间读取,并设置内核系统时间。

    log:

    [    0.897142] qcom,qpnp-rtc c440000.qcom,spmi:qcom,pm8998@0:qcom,pm8998_rtc: rtc core: registered qpnp_rtc as rtc0
    [    1.808755] hctosys: [ljj]open rtc device (rtc0)
    [    1.808819] qcom,qpnp-rtc c440000.qcom,spmi:qcom,pm8998@0:qcom,pm8998_rtc: setting system clock to 1970-01-05 00:01:06 UTC (345666)

    上面的代码,可以看到kernel log的最前面是距离开机的时间累计(CLOCK_MONOTONIC),后面的则是RTC读取的UTC时间(CLOCK_REALTIME)。可以看到一开机,就会从RTC中读取UTC时间。

    对应代码在hcttosys.c中:

    static int __init rtc_hctosys(void)
    {
        int err = -ENODEV;
        struct rtc_time tm;
        struct timespec64 tv64 = {
            .tv_nsec = NSEC_PER_SEC >> 1,
        };
        struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
            pr_info("[ljj]open rtc device (%s)
    ",
                CONFIG_RTC_HCTOSYS_DEVICE);
        if (rtc == NULL) {
            pr_info("unable to open rtc device (%s)
    ",
                CONFIG_RTC_HCTOSYS_DEVICE);
            goto err_open;
        }
        //api 调用
        err = rtc_read_time(rtc, &tm);
        if (err) {
            dev_err(rtc->dev.parent,
                "hctosys: unable to read the hardware clock
    ");
            goto err_read;
    
        }
    
        tv64.tv_sec = rtc_tm_to_time64(&tm);
    
    #if BITS_PER_LONG == 32
        if (tv64.tv_sec > INT_MAX)
            goto err_read;
    #endif
        //设置内核系统时间
        err = do_settimeofday64(&tv64);
    
        dev_info(rtc->dev.parent,
            "setting system clock to "
            "%d-%02d-%02d %02d:%02d:%02d UTC (%lld)
    ",
            tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
            tm.tm_hour, tm.tm_min, tm.tm_sec,
            (long long) tv64.tv_sec);
    
    err_read:
        rtc_class_close(rtc);
    
    err_open:
        rtc_hctosys_ret = err;
    
        return err;
    }
    
    late_initcall(rtc_hctosys);
    int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
    {
        int err;
    
        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
            return err;
    
        err = __rtc_read_time(rtc, tm); //调用内部__rtc_read_time
        mutex_unlock(&rtc->ops_lock);
        return err;
    }
    EXPORT_SYMBOL_GPL(rtc_read_time);
    static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm) { int err; if (!rtc->ops) err = -ENODEV; else if (!rtc->ops->read_time) err = -EINVAL; else { memset(tm, 0, sizeof(struct rtc_time)); err = rtc->ops->read_time(rtc->dev.parent, tm); //调用了qpnp-rtc.c中的read_time ops,最后就是通过读取寄存器,来获取RTC硬件计算的时间,具体代码就不贴了 if (err < 0) { dev_dbg(&rtc->dev, "read_time: fail to read: %d ", err); return err; } err = rtc_valid_tm(tm); if (err < 0) dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid "); } return err; }

    Case 2 kernel RTC时间与system时间进行同步

    实际是因为RTC中的时间并不一定准,所以会用android system中的时间与RTC时间进行对比同步和更新。也就是上面可能会牵扯到QCOM time Daemon的部分,所以避开这块闭源代码。可以参考如下链接,了解开源代码中内核时间与system时间同步。

    kernel log中时间同步的流程,参考自:http://blog.chinaunix.net/uid-23141914-id-5715368.html

    Case 3 更新并设置RTC时间

    当时间与网络、或者setting同步后,可能需要将当前正确的时间再写入RTC中,以保证RTC中的时间准确。这块我们往上看多一点,从jni开始分析。如下:

    路径:frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp

    static const JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
        {"init", "()J", (void*)android_server_AlarmManagerService_init},
        {"close", "(J)V", (void*)android_server_AlarmManagerService_close},
        {"set", "(JIJJ)I", (void*)android_server_AlarmManagerService_set},
        {"waitForAlarm", "(J)I", (void*)android_server_AlarmManagerService_waitForAlarm},
        {"setKernelTime", "(JJ)I", (void*)android_server_AlarmManagerService_setKernelTime}, //设置时间的JNI
        {"setKernelTimezone", "(JI)I", (void*)android_server_AlarmManagerService_setKernelTimezone},
    };
    
    static jint android_server_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis)
    {
        AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
        struct timeval tv;
        int ret;
    
        if (millis <= 0 || millis / 1000LL >= INT_MAX) {
            return -1;
        }
    
        tv.tv_sec = (time_t) (millis / 1000LL);
        tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL);
    
        ALOGD("Setting time of day to sec=%d
    ", (int) tv.tv_sec);
    
        ret = impl->setTime(&tv); //调用setTime
    
        if(ret < 0) {
            ALOGW("Unable to set rtc to %ld: %s
    ", tv.tv_sec, strerror(errno));
            ret = -1;
        }
        return ret;
    }
    
    int AlarmImpl::setTime(struct timeval *tv)
    {
        struct rtc_time rtc;
        struct tm tm, *gmtime_res;
        int fd;
        int res;
    
        res = settimeofday(tv, NULL);
        if (res < 0) {
            ALOGV("settimeofday() failed: %s
    ", strerror(errno));
            return -1;
        }
    
        if (rtc_id < 0) {
            ALOGV("Not setting RTC because wall clock RTC was not found");
            errno = ENODEV;
            return -1;
        }
    
        android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id);
        fd = open(rtc_dev.string(), O_RDWR); //打开了RTC设备
        if (fd < 0) {
            ALOGV("Unable to open %s: %s
    ", rtc_dev.string(), strerror(errno));
            return res;
        }
    
        gmtime_res = gmtime_r(&tv->tv_sec, &tm);
        if (!gmtime_res) {
            ALOGV("gmtime_r() failed: %s
    ", strerror(errno));
            res = -1;
            goto done;
        }
    
        memset(&rtc, 0, sizeof(rtc));
        rtc.tm_sec = tm.tm_sec;
        rtc.tm_min = tm.tm_min;
        rtc.tm_hour = tm.tm_hour;
        rtc.tm_mday = tm.tm_mday;
        rtc.tm_mon = tm.tm_mon;
        rtc.tm_year = tm.tm_year;
        rtc.tm_wday = tm.tm_wday;
        rtc.tm_yday = tm.tm_yday;
        rtc.tm_isdst = tm.tm_isdst;
        res = ioctl(fd, RTC_SET_TIME, &rtc); //通过ioctl设置RTC时间
        if (res < 0)
            ALOGV("RTC_SET_TIME ioctl failed: %s
    ", strerror(errno));
    done:
        close(fd);
        return res;
    }

    接下来就是驱动部分,rtc-dev.c文件中的ioctl接口,最后会调用至qpnp-rtc.c中的set接口,从而将准确的时间写入对应寄存器:

    static long rtc_dev_ioctl(struct file *file,
            unsigned int cmd, unsigned long arg)
    {
        int err = 0;
        struct rtc_device *rtc = file->private_data;
        const struct rtc_class_ops *ops = rtc->ops;
        struct rtc_time tm;
        struct rtc_wkalrm alarm;
        void __user *uarg = (void __user *) arg;
    
        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
            return err;
    
        /* check that the calling task has appropriate permissions
         * for certain ioctls. doing this check here is useful
         * to avoid duplicate code in each driver.
         */
        switch (cmd) {
        case RTC_EPOCH_SET:
        case RTC_SET_TIME:
            if (!capable(CAP_SYS_TIME)) //如上面注释所说,设置时间的api必须要有CAP_SYS_TIME的权限
                err = -EACCES;
            break;
    
        case RTC_IRQP_SET:
            if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
                err = -EACCES;
            break;
    
        case RTC_PIE_ON:
            if (rtc->irq_freq > rtc->max_user_freq &&
                    !capable(CAP_SYS_RESOURCE))
                err = -EACCES;
            break;
        }
    
        if (err)
            goto done;
    
        /*
         * Drivers *SHOULD NOT* provide ioctl implementations
         * for these requests.  Instead, provide methods to
         * support the following code, so that the RTC's main
         * features are accessible without using ioctls.
         *
         * RTC and alarm times will be in UTC, by preference,
         * but dual-booting with MS-Windows implies RTCs must
         * use the local wall clock time.
         */
    
        switch (cmd) {
        case RTC_ALM_READ:
            mutex_unlock(&rtc->ops_lock);
    
            err = rtc_read_alarm(rtc, &alarm);
            if (err < 0)
                return err;
    
            if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
                err = -EFAULT;
            return err;
    
        case RTC_ALM_SET:
            mutex_unlock(&rtc->ops_lock);
    
            if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
                return -EFAULT;
    
            alarm.enabled = 0;
            alarm.pending = 0;
            alarm.time.tm_wday = -1;
            alarm.time.tm_yday = -1;
            alarm.time.tm_isdst = -1;
    
            /* RTC_ALM_SET alarms may be up to 24 hours in the future.
             * Rather than expecting every RTC to implement "don't care"
             * for day/month/year fields, just force the alarm to have
             * the right values for those fields.
             *
             * RTC_WKALM_SET should be used instead.  Not only does it
             * eliminate the need for a separate RTC_AIE_ON call, it
             * doesn't have the "alarm 23:59:59 in the future" race.
             *
             * NOTE:  some legacy code may have used invalid fields as
             * wildcards, exposing hardware "periodic alarm" capabilities.
             * Not supported here.
             */
            {
                time64_t now, then;
    
                err = rtc_read_time(rtc, &tm);
                if (err < 0)
                    return err;
                now = rtc_tm_to_time64(&tm);
    
                alarm.time.tm_mday = tm.tm_mday;
                alarm.time.tm_mon = tm.tm_mon;
                alarm.time.tm_year = tm.tm_year;
                err  = rtc_valid_tm(&alarm.time);
                if (err < 0)
                    return err;
                then = rtc_tm_to_time64(&alarm.time);
    
                /* alarm may need to wrap into tomorrow */
                if (then < now) {
                    rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
                    alarm.time.tm_mday = tm.tm_mday;
                    alarm.time.tm_mon = tm.tm_mon;
                    alarm.time.tm_year = tm.tm_year;
                }
            }
    
            return rtc_set_alarm(rtc, &alarm);
    
        case RTC_RD_TIME:
            mutex_unlock(&rtc->ops_lock);
    
            err = rtc_read_time(rtc, &tm);
            if (err < 0)
                return err;
    
            if (copy_to_user(uarg, &tm, sizeof(tm)))
                err = -EFAULT;
            return err;
    
        case RTC_SET_TIME:
            mutex_unlock(&rtc->ops_lock);
    
            if (copy_from_user(&tm, uarg, sizeof(tm))) //获取上层传下来的tm结构
                return -EFAULT;
    
            return rtc_set_time(rtc, &tm); //通过interface.c的接口,从而调用qpnp-rtc.c的设置RTC寄存器函数,完成RTC时间写入。
    
        case RTC_PIE_ON:
            err = rtc_irq_set_state(rtc, NULL, 1);
            break;
    
        case RTC_PIE_OFF:
            err = rtc_irq_set_state(rtc, NULL, 0);
            break;
    
        case RTC_AIE_ON:
            mutex_unlock(&rtc->ops_lock);
            return rtc_alarm_irq_enable(rtc, 1);
    
        case RTC_AIE_OFF:
            mutex_unlock(&rtc->ops_lock);
            return rtc_alarm_irq_enable(rtc, 0);
    
        case RTC_UIE_ON:
            mutex_unlock(&rtc->ops_lock);
            return rtc_update_irq_enable(rtc, 1);
    
        case RTC_UIE_OFF:
            mutex_unlock(&rtc->ops_lock);
            return rtc_update_irq_enable(rtc, 0);
    
        case RTC_IRQP_SET:
            err = rtc_irq_set_freq(rtc, NULL, arg);
            break;
    
        case RTC_IRQP_READ:
            err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
            break;
    
        case RTC_WKALM_SET:
            mutex_unlock(&rtc->ops_lock);
            if (copy_from_user(&alarm, uarg, sizeof(alarm)))
                return -EFAULT;
    
            return rtc_set_alarm(rtc, &alarm);
    
        case RTC_WKALM_RD:
            mutex_unlock(&rtc->ops_lock);
            err = rtc_read_alarm(rtc, &alarm);
            if (err < 0)
                return err;
    
            if (copy_to_user(uarg, &alarm, sizeof(alarm)))
                err = -EFAULT;
            return err;
    
        default:
            /* Finally try the driver's ioctl interface */
            if (ops->ioctl) {
                err = ops->ioctl(rtc->dev.parent, cmd, arg);
                if (err == -ENOIOCTLCMD)
                    err = -ENOTTY;
            } else
                err = -ENOTTY;
            break;
        }
    
    done:
        mutex_unlock(&rtc->ops_lock);
        return err;
    }
  • 相关阅读:
    warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    Windows10+CLion+OpenCV4.5.2开发环境搭建
    Android解决部分机型WebView播放视频全屏按钮灰色无法点击、点击全屏白屏无法播放等问题
    MediaCodec.configure Picture Width(1080) or Height(2163) invalid, should N*2
    tesseract
    Caer -- a friendly API wrapper for OpenCV
    Integrating OpenCV python tool into one SKlearn MNIST example for supporting prediction
    Integrating Hub with one sklearn mnist example
    What is WSGI (Web Server Gateway Interface)?
    Hub --- 机器学习燃料(数据)的仓库
  • 原文地址:https://www.cnblogs.com/lingjiajun/p/11104368.html
Copyright © 2020-2023  润新知