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\n");
        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\n", 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\n", 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\n");
        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\n",
                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\n");
            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\n");
                rc = -ENXIO;
                goto fail_rtc_enable;
            }
            break;
        default:
            dev_err(&pdev->dev, "Invalid peripheral subtype\n");
            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\n");
        goto fail_rtc_enable;
    }

    if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {
        dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered\n");
        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\n");
        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!\n");
        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)\n",
                    __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)\n", 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 !!\n");

    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\n");
        return;
    }
    rtc_dd = dev_get_drvdata(&pdev->dev);
    if (!rtc_dd) {
        pr_err("qpnp-rtc: rtc driver data not found\n");
        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\n");

        /* 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\n");
            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\n");

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)\n",
            CONFIG_RTC_HCTOSYS_DEVICE);
    if (rtc == NULL) {
        pr_info("unable to open rtc device (%s)\n",
            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\n");
        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)\n",
        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\n", err); return err; } err = rtc_valid_tm(tm); if (err < 0) dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n"); } 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\n", (int) tv.tv_sec);

    ret = impl->setTime(&tv); //調用setTime

    if(ret < 0) {
        ALOGW("Unable to set rtc to %ld: %s\n", 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\n", 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\n", rtc_dev.string(), strerror(errno));
        return res;
    }

    gmtime_res = gmtime_r(&tv->tv_sec, &tm);
    if (!gmtime_res) {
        ALOGV("gmtime_r() failed: %s\n", 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\n", 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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM