Linux驱动基础:platform设备驱动

初始化

以高通平台为例,会在kernel/arch/arm/mach-msm下的相应的board-xxx.c文件里边用
DT_MACHINE_START()这个宏定义一系列的芯片。以高通8916为例:
在kernel/arch/arm/mach-msm/board-8916.c文件里定义了

//当然下面使用哪个要看一下。
DT_MACHINE_START(MSM8916_DT,
        "Qualcomm Technologies, Inc. MSM 8916 (Flattened Device Tree)")
    .map_io = msm8916_map_io,
    .init_machine = msm8916_init,
    .dt_compat = msm8916_dt_match,
    .reserve = msm8916_dt_reserve,
    .smp = &msm8916_smp_ops,
MACHINE_END
//下面还定了很多其他名字的,比如msm8939。但最终会根据dt_compat,也就是msm8916_dt_match的名字找到相应的dts文件。比如
static const char *msm8916_dt_match[] __initconst = {
    "qcom,msm8916",
    "qcom,apq8016",
    NULL
};
这个会找到dts文件里边的对应名字的compatible。比如:
/ {
    ...
    compatible = "qcom,msm8916";
    ...
};
....

在.init_machine对应的msm8916_init()函数里,会查找到设备所有的platform设备进行初始化。

static void __init msm8916_init(void){
    ... 
    of_platform_populate(NULL, of_default_bus_match_table, adata, NULL);
    ...
}
const struct of_device_id of_default_bus_match_table[] = {
    { .compatible = "simple-bus", },//搜一下simple-bus
#ifdef CONFIG_ARM_AMBA
    { .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
    {} /* Empty terminated list */
};
//搜一下上面的comatible,比如“simple-bus”可以在类似msm8916.dtsi文件里边找到
&soc { //包含在&soc大括号里边的设备应该是都会被初始化为platform_device??
       //只有platform设备才需要device tree初始化!
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0 0 0 0xffffffff>;
    compatible = "simple-bus";
    //下面定义了一堆platform设备对应的。
    //of_platform_populate函数会根据下面定义的内容,生成相应的platform_device。然后在注册platform_driver的时候,如果名字和定义的一样,就可以进行初始化platform设备了。
    //......
    tsens: tsens@4a8000 {
        compatible = "qcom,msm8916-tsens";
        reg = <0x4a8000 0x2000>,
              <0x5c000  0x1000>;
        reg-names = "tsens_physical", "tsens_eeprom_physical";
        interrupts = <0 184 0>;
        interrupt-names = "tsens-upper-lower";
        qcom,sensors = <5>;
        qcom,slope = <3200 3200 3200 3200 3200>;
        qcom,sensor-id = <0 1 2 4 5>;
    };
    //....

在找到simple-bus并初始化&soc下面所有的platform device之后,就可以在
sys/devices/soc.0下面找到所有的device节点。比如拿上面tsens为例会根据device tree设定的名字platform device id等给设备取名字。

/sys/devices/soc.0/4a8000.tsens

而且platform device创建的时候在platform_device_add函数中,dev.bus都会默认设置成platform_bus_type
所以上面4a8000.tsens也会在/sys/bus/platform/devices下面生成一样的节点,并被symlink。

/sys/bus/platform/devices # ls -l
lrwxrwxrwx root root 2015-01-13 18:30 4a8000.tsens -> ../../../devices/soc.0/4a8000.tsens

platform设备的名字,通常是@后面的名字.加上:后面的名字。如果什么都没有,就名字加上platform id。
但也有例外,确切的device tree节点名字读一下uevent节点就可以知道。

cpubw: qcom,cpubw@0 {}就会生成/sys/devices/soc.0/0.qcom,cpubw

qcom,armbw-pm {}就会生成/sys/devices/soc.0/qcom,armbw-pm.32

当然&soc{}这个有效果的前提也是需要包含在整个device tree的根目录下。

/ {
    model = "Qualcomm Technologies, Inc. MSM8916";
    compatible = "qcom,msm8916";
    qcom,msm-id =   <206 0>,
            <248 0>,
            <249 0>,
            <250 0>;

    interrupt-parent = <&intc>;

    chosen {
        bootargs = "sched_enable_hmp=1";
    };

    aliases {
        sdhc1 = &sdhc_1; /* SDC1 eMMC slot */
        sdhc2 = &sdhc_2; /* SDC2 SD card slot */

        /* smdtty devices */
        smd0 = &smdtty_ds;
        smd1 = &smdtty_apps_fm;
        smd2 = &smdtty_apps_riva_bt_acl;
        smd3 = &smdtty_apps_riva_bt_cmd;
        smd4 = &smdtty_mbalbridge;
        smd5 = &smdtty_apps_riva_ant_cmd;
        smd6 = &smdtty_apps_riva_ant_data;
        smd7 = &smdtty_data1;
        smd8 = &smdtty_data4;
        smd11 = &smdtty_data11;
        smd21 = &smdtty_data21;
        smd36 = &smdtty_loopback;

        //spi0 = &spi_0; /* SPI0 controller device */
        i2c0 = &i2c_0; /* I2C0 controller device */
        i2c1 = &i2c_1; /* I2C1 controller device */
        i2c5 = &i2c_5; /* I2C5 controller device */
        i2c6 = &i2c_6; /* I2C6 NFC qup6 device */
        i2c4 = &i2c_4; /* I2C4 controller device */
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        CPU0: cpu@0 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x0>;
            enable-method = "qcom,arm-cortex-acc";
            qcom,acc = <&acc0>;
            next-level-cache = <&L2_0>;
            L2_0: l2-cache {
                  compatible = "arm,arch-cache";
                  cache-level = <2>;
                  power-domain = <&l2ccc_0>;
            };
        };

        CPU1: cpu@1 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x1>;
            enable-method = "qcom,arm-cortex-acc";
            qcom,acc = <&acc1>;
            next-level-cache = <&L2_0>;
        };

        CPU2: cpu@2 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x2>;
            enable-method = "qcom,arm-cortex-acc";
            qcom,acc = <&acc2>;
            next-level-cache = <&L2_0>;
        };

        CPU3: cpu@3 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x3>;
            enable-method = "qcom,arm-cortex-acc";
            qcom,acc = <&acc3>;
            next-level-cache = <&L2_0>;
        };
    };

    soc: soc { };//没有这个,platform设备相关的&soc{}包含的都是不会被初始化的!!!
};

具体的platform驱动在找到对应的compatible的名字之后,就可以添加设备驱动。
这个过程中会在/sys/bus/platform/driver目录下生成与驱动的名字对应名字的节点。
比如下面定义的platform driver在被找到对应的platform device之后会生成
相应的节点。

static struct platform_driver mdm_driver = {
    .probe      = mdm_probe,
    .driver = {
        .name   = "ext-mdm",
        .owner  = THIS_MODULE,
        .of_match_table = of_match_ptr(mdm_dt_match),
    },
};
生成的节点是:
 /sys/bus/platform/drivers/ext-mdm

以i2c-versatile驱动为例,说明platform设备名字匹配,初始化和生成节点等

现在platform device都是从device tree里边读出并进行platform device的初始化。
以前是对应platform device和platform driver的名字的话,现在是对比device相关的compatible的内容了。
像下面i2c-versatile驱动中,在i2c_versatile_match.compatible的字符串,如果与某个&soc{}里边的compatible内容一致,就回去调用相应的probe函数去初始化platform。

drivers/i2c/busses/i2c-versatile.c文件里边可以看到像下面的基本的platform设备的定义以及函数调用

static const struct of_device_id i2c_versatile_match[] = {
    { .compatible = "arm,versatile-i2c", },
    {},
};
MODULE_DEVICE_TABLE(of, i2c_versatile_match);


static struct platform_driver i2c_versatile_driver = {
    .probe      = i2c_versatile_probe,
    .remove     = i2c_versatile_remove,
    .driver     = {
        .name   = "versatile-i2c",
        .owner  = THIS_MODULE,
        .of_match_table = i2c_versatile_match,
    },
};
static int __init i2c_versatile_init(void)
{
    return platform_driver_register(&i2c_versatile_driver);
}
static void __exit i2c_versatile_exit(void)
{
    platform_driver_unregister(&i2c_versatile_driver);
}

subsys_initcall(i2c_versatile_init);
module_exit(i2c_versatile_exit);

MODULE_DESCRIPTION("ARM Versatile I2C bus driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:versatile-i2c");

/sys/bus/platform/devices/versatile-i2c.0/
modalias ,uevent这几个文件,power,subsystem文件夹

/sys/devices/platform/versatile-i2c.0/
modalias ,uevent这几个文件,power,subsystem文件夹

#######################################################

int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
    return driver_register(&drv->driver);
}

struct bus_type platform_bus_type= {
    .name       = "platform",
    .dev_attrs  =platform_dev_attrs,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
static struct device_attribute platform_dev_attrs[] = {
    __ATTR_RO(modalias),
    __ATTR_NULL,
};

这里的platform_bus_type会用bus_register()先注册一下。

int bus_register(struct bus_type *bus)
{
    int retval;
    struct subsys_private *priv;
    struct lock_class_key *key = &bus->lock_key;
    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    priv->bus = bus;
    bus->p = priv;
    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //kobj名字设定为platform
    if (retval)
        goto out;
    priv->subsys.kobj.kset = bus_kset;
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;
    retval = kset_register(&priv->subsys);
    if (retval)
        goto out;
    retval = bus_create_file(bus, &bus_attr_uevent);
    if (retval)
        goto bus_uevent_fail;
    priv->devices_kset = kset_create_and_add("devices", NULL,
                         &priv->subsys.kobj);
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }
    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                         &priv->subsys.kobj);
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }
    INIT_LIST_HEAD(&priv->interfaces);
    __mutex_init(&priv->mutex, "subsys mutex", key);
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
    klist_init(&priv->klist_drivers, NULL, NULL);
    retval = add_probe_files(bus);
    if (retval)
        goto bus_probe_files_fail;
    retval = bus_add_attrs(bus);
    if (retval)
        goto bus_attrs_fail;
    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;
bus_attrs_fail:
    remove_probe_files(bus);
bus_probe_files_fail:
    kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
    kset_unregister(bus->p->devices_kset);
bus_devices_fail:
    bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
    kset_unregister(&bus->p->subsys);
out:
    kfree(bus->p);
    bus->p = NULL;
    return retval;
}

待续….

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值