设备树学习之(五)watchdog

本文介绍如何在Linux 4.4.0内核上为tiny4412开发板配置看门狗设备树。通过详细解释设备树中的中断配置及代码实现细节,帮助读者理解设备树中普通中断的表示方法。

开发板:tiny4412SDK + S702 + 4GB Flash 
要移植的内核版本:Linux-4.4.0 (支持device tree) 
u-boot版本:友善之臂自带的 U-Boot 2010.12 
busybox版本:busybox 1.25

目标: 
学习设备树中普通中断的表示方法。

设备树参考:

    watchdog: watchdog@10060000 {
        compatible = "samsung,s3c2410-wdt";
        reg = <0x10060000 0x100>;
        interrupts = <0 43 0>;
        clocks = <&clock CLK_WDT>;
        clock-names = "watchdog";
        status = "disabled";
    };
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

参考 arm,gic.txt

ARM SMP cores are often associated with a GIC, providing per processor
interrupts (PPI), shared processor interrupts (SPI) and software
generated interrupts (SGI).

Primary GIC is attached directly to the CPU and typically has PPIs and SGIs.
Secondary GICs are cascaded into the upward interrupt controller and do not
have PPIs or SGIs.

Main node required properties:

- compatible : should be one of:
    "arm,arm1176jzf-devchip-gic"
    "arm,arm11mp-gic"
    "arm,cortex-a15-gic"
    "arm,cortex-a7-gic"
    "arm,cortex-a9-gic"
    "arm,gic-400"
    "arm,pl390"
    "brcm,brahma-b15-gic"
    "qcom,msm-8660-qgic"
    "qcom,msm-qgic2"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
  interrupt source.  The type shall be a <u32> and the value shall be 3.

  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts.
  The 2nd cell contains the interrupt number for the interrupt type. SPI interrupts are in the range [0-987].  PPI interrupts are in the range [0-15].
  The 3rd cell is the flags, encoded as follows:
    bits[3:0] trigger type and level flags.
        1 = low-to-high edge triggered
        2 = high-to-low edge triggered (invalid for SPIs)
        4 = active high level-sensitive
        8 = active low level-sensitive (invalid for SPIs).
    bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
    the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
    the interrupt is wired to that CPU.  Only valid for PPI interrupts.
    Also note that the configurability of PPI interrupts is IMPLEMENTATION
    DEFINED and as such not guaranteed to be present (most SoC available
    in 2014 seem to ignore the setting of this flag and use the hardware
    default value).

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

interrupts = <0 43 0>; 
0 flags:

  • shared processor interrupts (SPI) // 共享中断
  • providing per processor interrupts (PPI) // 每个处理器拥有独立中断

43 中断号: 
这里写图片描述

0 触发方式:

  • 1 = low-to-high edge triggered
  • 2 = high-to-low edge triggered (invalid for SPIs)
  • 4 = active high level-sensitive
  • 8 = active low level-sensitive (invalid for SPIs).

这里设备树中虽然包含了中断资源,但是只是演示一下,代码中其实并没有用到,看门狗定时器时间到达时可以选择复位或者中断,这里采用的是复位。此外,在 4412 中看门狗复位还需要设置芯片手册中第八章的相关寄存器。具体,请参考代码。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>

/*
    PCLK 时钟频率 100M
    1/(100M / 100 / 128)
    128us
*/

#define  MAGIC_NUMBER    'k'
#define  WTD_ON     _IO(MAGIC_NUMBER    ,0)
#define  WTD_OFF    _IO(MAGIC_NUMBER    ,1)
#define  WTD_FEED   _IO(MAGIC_NUMBER    ,2)
#define  WTD_READ   _IO(MAGIC_NUMBER    ,3)

struct WTD_BASE
{
    unsigned int wtcon;     // 0
    unsigned int wtdat;     // 4
    unsigned int wtcnt;     // 8
    unsigned int wtclrint;  // c
};

int                         major;
struct      cdev            wtd_cdev;
struct      class           *cls;
volatile    unsigned long   *mask_wtd_reset;
struct wtddev
{
    struct      clk             *base_clk;
    volatile    struct WTD_BASE *wtd_base;
};

static struct wtddev wtd;

static void wtd_on(unsigned long arg)
{
    int ret;
    unsigned int buf;
    unsigned int wtcon;
    printk("%s\n", __func__);
    ret = copy_from_user(&buf, (const void __user *)arg, 4);

    if (ret < 0)
    {
        printk("%s copy_from_user error\n", __func__);
    }

    wtcon = wtd.wtd_base->wtcon;
    wtcon |= (0x63 << 8) | (0x03 << 3) | (0x01 << 5);
    wtcon &= ~(0x01 << 1);
    wtcon |= (0x01 << 0);
    printk("wtcon %x\n", wtcon);
    wtd.wtd_base->wtcnt = buf;
    wtd.wtd_base->wtdat = buf;
    wtd.wtd_base->wtcon = wtcon;
}

static void wtd_off(void)
{
    printk("%s\n", __func__);
    wtd.wtd_base->wtcon &= ~(0x01 << 5);
}

static void wtd_feed(unsigned long arg)
{
    int ret;
    unsigned int buf;
    printk("%s\n", __func__);
    ret = copy_from_user(&buf, (const void __user *)arg, 4);

    if (ret < 0)
    {
        printk("%s copy_from_user error\n", __func__);
    }

    wtd.wtd_base->wtcnt = buf;
}

static void wtd_read(unsigned long arg)
{
    int ret;
    unsigned int buf = wtd.wtd_base->wtcnt;
    printk("wtcnt %x\n", wtd.wtd_base->wtcnt);
    ret = copy_to_user((void __user *)arg, &buf, 4);

    if (ret < 0)
    {
        printk("%s copy_to_user error\n", __func__);
    }
}

static long wtd_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    switch (cmd)
    {
        case WTD_ON:
            wtd_on(arg);
            break;
        case WTD_OFF:
            wtd_off();
            break;
        case WTD_FEED:
            wtd_feed(arg);
            break;
        case WTD_READ:
            wtd_read(arg);
        default :
            return -EINVAL;
    };

    return 0;
}

static int wtd_open(struct inode *inode, struct file *file)
{
    printk("wtd_open\n");
    return 0;
}

static int wtd_release(struct inode *inode, struct file *file)
{
    printk("wtd_exit\n");
    return 0;
}

static struct file_operations wtd_fops =
{
    .owner              = THIS_MODULE,
    .open               = wtd_open,
    .release            = wtd_release,
    .unlocked_ioctl     = wtd_ioctl,
};

static int wtd_probe(struct platform_device *pdev)
{
    dev_t       devid;
    struct      device      *dev    = &pdev->dev;
    struct      resource    *res    = NULL;
    struct      resource    *res1   = NULL;
    int ret;
    printk("enter %s\n", __func__);
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);//reset mask /unit 8 pmu

    if (res == NULL || res1 == NULL)
    {
        printk("platform_get_resource error\n");
        return -EINVAL;
    }

    printk("res: %x\n", (unsigned int)res->start);
    printk("res1: %x\n", (unsigned int)res1->start);
    wtd.base_clk = devm_clk_get(&pdev->dev, "watchdog");

    if (IS_ERR(wtd.base_clk))
    {
        dev_err(dev, "failed to get timer base clk\n");
        return PTR_ERR(wtd.base_clk);
    }

    ret = clk_prepare_enable(wtd.base_clk);

    if (ret != 0)
    {
        dev_err(dev, "failed to enable base clock\n");
        return ret;
    }

    wtd.wtd_base = devm_ioremap_resource(&pdev->dev, res);
    mask_wtd_reset = ioremap(res1->start, 0x04);
    writel(0x00, mask_wtd_reset);

    if (wtd.wtd_base == NULL)
    {
        printk("devm_ioremap_resource error\n");
        goto err_clk;
    }

    if (alloc_chrdev_region(&devid, 0, 1, "wtd") < 0)
    {
        printk("%s ERROR\n", __func__);
        goto err_clk;
    }

    major = MAJOR(devid);
    cdev_init(&wtd_cdev, &wtd_fops);
    cdev_add(&wtd_cdev, devid, 1);
    cls = class_create(THIS_MODULE, "mywtd");
    device_create(cls, NULL, MKDEV(major, 0), NULL, "wtd");
    return 0;
err_clk:
    clk_disable(wtd.base_clk);
    clk_unprepare(wtd.base_clk);
    return -EINVAL;
}

static int wtd_remove(struct platform_device *pdev)
{
    printk("enter %s\n", __func__);
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    cdev_del(&wtd_cdev);
    unregister_chrdev_region(MKDEV(major, 0), 1);
    clk_disable(wtd.base_clk);
    clk_unprepare(wtd.base_clk);
    iounmap(mask_wtd_reset);
    printk("%s enter.\n", __func__);
    return 0;
}

static const struct of_device_id wtd_dt_ids[] =
{
    { .compatible = "tiny4412,wtd_demo", },
    {},
};

MODULE_DEVICE_TABLE(of, wtd_dt_ids);

static struct platform_driver wtd_driver =
{
    .driver        = {
        .name      = "wtd_demo",
        .of_match_table    = of_match_ptr(wtd_dt_ids),
    },
    .probe         = wtd_probe,
    .remove        = wtd_remove,
};

static int wtd_init(void)
{
    int ret;
    printk("enter %s\n", __func__);
    ret = platform_driver_register(&wtd_driver);

    if (ret)
    {
        printk(KERN_ERR "wtd demo: probe faiwtd: %d\n", ret);
    }

    return ret;
}

static void wtd_exit(void)
{
    printk("enter %s\n", __func__);
    platform_driver_unregister(&wtd_driver);
}

module_init(wtd_init);
module_exit(wtd_exit);
MODULE_LICENSE("GPL");
<think>好的,我现在需要分析用户提供的代码`static int bq25790_parse_dt(struct bq25790_device *bq)`。这个函数看起来像是用于解析设备树(Device Tree)配置的。首先,我应该先理解整个函数的结构和各个部分的作用。 首先,函数开头定义了一个整型变量`ret`用于存储返回值。然后,使用`device_property_read_u32`函数读取设备树中的属性值。第一个读取的是"watchdog-timer"属性,存储到`bq->watchdog_timer`。如果读取失败(ret非零),则设置默认值为`BQ25790_WATCHDOG_DIS`,可能表示禁用看门狗。接着检查这个值是否在允许的范围内,超出范围则返回错误码`-EINVAL`。 接下来是类似的流程,读取"input-voltage-limit-microvolt"属性到`bq->init_data.vlim`,如果失败则使用默认值`BQ25790_VINDPM_DEF_uV`,然后检查范围是否有效。同样地,读取"input-current-limit-microamp"到`bq->init_data.ilim`,失败则用默认值`BQ25790_IINDPM_DEF_uA`,并检查范围。 用户可能希望逐行了解每个函数调用和逻辑判断的作用,以及可能的错误处理。需要注意的是,用户之前已经询问过中断处理代码的分析,现在转向设备树解析,可能是在学习Linux设备驱动开发,特别是电源管理芯片的驱动实现。因此,解释时要结合设备树的使用背景,说明这些属性在驱动中的作用,比如看门狗定时器、输入电压和电流的限制参数。 需要确认每个步骤的正确性,比如函数`device_property_read_u32`的作用是从设备属性中读取32位无符号整数值,这可能来源于设备树中的定义。同时,检查每个参数的合法范围,确保配置值在硬件支持的范围内,否则返回错误,避免错误配置导致硬件问题。 此外,用户可能对错误码`-EINVAL`的含义感兴趣,需要解释这是内核中表示无效参数的错误码。同时,默认值的设定是为了在设备树未提供相应属性时,驱动仍能正常工作,使用预设的安全值。 在逐行分析时,要注意代码中的每个条件判断和错误处理,解释其背后的逻辑,比如为什么在读取失败时设置默认值,以及范围检查的必要性。还要提到这些配置参数如何影响设备的行为,例如看门狗定时器的作用是防止系统死锁,输入电压和电流限制是为了保护电路不过载。 最后,总结整个函数的作用,即从设备树中读取配置参数并进行验证,确保其有效性,为后续的硬件初始化提供正确的配置数据。</think>以下是对 `bq25790_parse_dt` 函数的逐行分析: --- ### **函数作用** 此函数用于从设备树(Device Tree)或固件配置中解析与 BQ25790 电源管理芯片相关的参数(如看门狗定时器、输入电压/电流限制),并设置默认值或验证参数有效性。若参数非法,返回错误码 `-EINVAL`。 --- ### **逐行解析** #### **1. 解析看门狗定时器参数** ```c ret = device_property_read_u32(bq->dev, "watchdog-timer", &bq->watchdog_timer); if (ret) bq->watchdog_timer = BQ25790_WATCHDOG_DIS; // 读取失败时设置默认值(禁用看门狗) if (bq->watchdog_timer > BQ25790_WATCHDOG_MAX || bq->watchdog_timer < BQ25790_WATCHDOG_DIS) return -EINVAL; // 参数超出范围,返回无效参数错误 ``` - **`device_property_read_u32`**:从设备属性中读取名为 `"watchdog-timer"` 的 32 位无符号整数值。 - 成功时:值存入 `bq->watchdog_timer`。 - 失败时(如属性未定义):设置默认值 `BQ25790_WATCHDOG_DIS`(通常表示禁用看门狗)。 - **范围检查**:确保看门狗定时器值在允许范围内(`BQ25790_WATCHDOG_DIS` 到 `BQ25790_WATCHDOG_MAX`)。若非法,返回 `-EINVAL`。 --- #### **2. 解析输入电压限制** ```c ret = device_property_read_u32(bq->dev, "input-voltage-limit-microvolt", &bq->init_data.vlim); if (ret) bq->init_data.vlim = BQ25790_VINDPM_DEF_uV; // 默认输入电压限制 if (bq->init_data.vlim > BQ25790_VINDPM_V_MAX_uV || bq->init_data.vlim < BQ25790_VINDPM_V_MIN_uV) return -EINVAL; // 电压值非法 ``` - **`input-voltage-limit-microvolt`**:从设备树读取输入电压限制(单位:微伏)。 - 成功时:值存入 `bq->init_data.vlim`。 - 失败时:使用默认值 `BQ25790_VINDPM_DEF_uV`。 - **范围检查**:确保电压值在芯片支持的 `[BQ25790_VINDPM_V_MIN_uV, BQ25790_VINDPM_V_MAX_uV]` 之间。 --- #### **3. 解析输入电流限制** ```c ret = device_property_read_u32(bq->dev, "input-current-limit-microamp", &bq->init_data.ilim); if (ret) bq->init_data.ilim = BQ25790_IINDPM_DEF_uA; // 默认输入电流限制 if (bq->init_data.ilim > BQ25790_IINDPM_I_MAX_uA || bq->init_data.ilim < BQ25790_IINDPM_I_MIN_uA) return -EINVAL; // 电流值非法 ``` - **`input-current-limit-microamp`**:从设备树读取输入电流限制(单位:微安)。 - 成功时:值存入 `bq->init_data.ilim`。 - 失败时:使用默认值 `BQ25790_IINDPM_DEF_uA`。 - **范围检查**:确保电流值在 `[BQ25790_IINDPM_I_MIN_uA, BQ25790_IINDPM_I_MAX_uA]` 之间。 --- #### **4. 返回成功** ```c return 0; // 所有参数解析成功 ``` --- ### **关键设计点** 1. **设备树参数优先级** - 优先从设备树读取配置,若未定义则使用驱动默认值。这允许硬件定制化配置(如不同产品的电压/电流需求)。 2. **参数验证** - 所有参数均需符合芯片规格(如电压/电流范围)。非法值直接返回错误,避免硬件损坏或异常。 3. **默认值设计** - 看门狗默认禁用(`BQ25790_WATCHDOG_DIS`),避免未明确配置时意外触发复位。 - 电压和电流默认值(`BQ25790_VINDPM_DEF_uV`、`BQ25790_IINDPM_DEF_uA`)是芯片的安全工作值。 --- ### **典型设备树配置示例** ```dts bq25790: charger@6b { compatible = "ti,bq25790"; reg = <0x6b>; watchdog-timer = <60>; // 看门狗超时时间(秒) input-voltage-limit-microvolt = <20000000>; // 20V输入电压限制 input-current-limit-microamp = <3000000>; // 3A输入电流限制 }; ``` --- ### **潜在问题与调试** 1. **设备树属性未生效** - 检查设备树节点是否匹配 `compatible` 字符串。 - 确认属性名称拼写正确(如 `microvolt` vs `microvolts`)。 2. **参数范围错误** - 若驱动返回 `-EINVAL`,需检查设备树中配置的值是否超出芯片规格(如电压是否超过 `BQ25790_VINDPM_V_MAX_uV`)。 3. **默认值不符合需求** - 若需修改默认值(如默认启用看门狗),需调整驱动代码中的 `BQ25790_WATCHDOG_DIS` 等宏定义。 --- ### **总结** 此函数是 BQ25790 驱动中设备树配置的核心解析逻辑,通过读取并验证关键参数(看门狗、电压、电流),确保芯片按用户或硬件设计需求安全运行。其设计体现了 Linux 驱动中“配置与代码分离”的原则,增强了驱动的灵活性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值