linux driver learning notes(continuing)

1)

虚拟一个字符设备,实现简单的开关读写

实现:xxx_open xxx_read xxx_write xxx_release 赋值 struct file_operations 驱动操作函数集合,在驱动入口函数xxx_init中调用register_chrdev注册设备号、设备名、设备操作函数,在xxx_exit中调用unregister_chrdev注销设备号,相应设备号在/proc/devices中消失,module_init()和module_exit()用以向内核申明驱动入口函数和注销函数,make module生产相应驱动加载文件.ko,insmod加载驱动模块,mknod生成设备号对应的字符设备的设备文件节点,编写一个应用程序调用open read write close使用设备

static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .open = chardevbase_open,
    .read = chardevbase_read,
    .write = chardevbase_write,
    .release = chrdevbase_release
};

register_chrdev(200, "chrdev", &test_fops);

unregister_chrdev(200, "chrdev");

2)

对于实际的字符设备驱动开发,在1)基础上增加物理地址到虚拟地址的映射,ioremap()以及iounmap(),ioremap()在字符设备驱动入口函数中调用,然后对需要初始化的虚拟地址赋值,

ccm_ptr = ioremap(CCM_CONFIG, 4);
mux_ptr = ioremap(MUX_CONFIG, 4);
pad_ptr = ioremap(PAD_CONFIG, 4);
gdr_ptr = ioremap(GDR_CONFIG, 4);
gdd_ptr = ioremap(GDD_CONFIG, 4);

在设备驱动注销函数中调用iounmap()注销虚拟地址

iounmap(ccm_ptr);
iounmap(mux_ptr);
iounmap(pad_ptr);
iounmap(gdr_ptr);
iounmap(gdd_ptr);

3)

构建设备结构体

typedef struct {
    dev_t devid;
    int minor;
    int major;
    struct cdev cdev;
    struct class *class;
    struct device *device;
}newchrdev_t;

在1)基础上采用新字符设备驱动API编写设备驱动,设备号由固定改为由内核自动分配:

if(newchrdev.major){
        newchrdev.devid = MKDEV(newchrdev.major, 0);
        register_chrdev_region(newchrdev.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
}
else{
        alloc_chrdev_region(&newchrdev.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("my own alloc: major=%d,minor=%d\r\n",newchrdev.major, newchrdev.minor);	
}
unregister_chrdev_region(newchrdev.devid, NEWCHRLED_CNT);

register_chrdev() unregister_chrdev()设备注册、注销函数->cdev_init() cdev_add() cdev_del()

newchrdev.cdev.owner = THIS_MODULE;
cdev_init(&newchrdev.cdev, &newchrled_fops);

cdev_add(&newchrdev.cdev, newchrdev.devid, NEWCHRLED_CNT);
    
cdev_del(&newchrdev.cdev);

mknod手动创建设备节点->mdev的class device自动创建设备节点

newchrdev.class = class_create(THIS_MODULE, NEWCHRLED_NAME);

newchrdev.device = device_create(newchrdev.class, NULL, newchrdev.devid, NULL, NEWCHRLED_NAME);

rm手动删除设备节点->rmmod时自动删除

device_destroy(newchrdev.class, newchrdev.devid);

class_destroy(newchrdev.class);

4)设备树,为内核驱动提供设备信息

设备树语法

常用属性compatible status reg

提取设备树信息of系列函数 of_find_node_by_name()...

of_find_property()... of_property_read_u32_array()... of_property_read_string()...

of_iomap()用于直接从设备节点中提取寄存器信息然后将其映射到虚拟地址

5)基于设备树的设备驱动开发:驱动从设备树节点中提取设备信息,用于设备的初始化

设备树根节点添加

mydtsled {
                #address-cells = <1>; 
                #size-cells = <1>; 
                compatible = "terrorblade-led";
                status = "okay";
                reg = < 0X020C406C 0X04 
                        0X020E0068 0X04 
                        0X020E02F4 0X04 
                        0X0209C004 0X04 
                        0X0209C000 0X04 >;
};

驱动中先根据path找到设备节点,据从设备节点中提取设备信息进行初始化

    led.dn = of_find_node_by_path("/mydtsled");
    if(led.dn == NULL){
        printk("find node error\n");
        return -1;
    }
    else
        printk("/mydtsled node found\n");

    property = of_find_property(led.dn, "compatible", NULL);
    printk("compatible: %s\n", (char *)property->value);

    of_property_read_string(led.dn, "status", &str);
    printk("status: %s\n", str);

    of_property_read_u32_array(led.dn, "reg", reg_buf, 10);
    for(i=0; i<10; ++i)
        printk("reg_buf[%d]: 0x%x", i, reg_buf[i]);
    printk("\n");

    ccm_ptr = of_iomap(led.dn, 0);
    mux_ptr = of_iomap(led.dn, 1);
    pad_ptr = of_iomap(led.dn, 2);
    gdi_ptr = of_iomap(led.dn, 3);
    gdd_ptr = of_iomap(led.dn, 4);

其中of_find_property of_property_read_string of_property_read_u32_array用于验证,可删去

6)

1.pinctrl子系统:在iomuxc节点下添加引脚信息,iomuxc设备节点的 compatible = "fsl,imx6ul-iomuxc" ,对应platform_driver是 drivers/pinctrl/freescale/pinctrl-imx6ul.c 里的 imx6ul_pinctrl_driver 此驱动会初始化iomuxc设备节点下所有的pinctrl信息

example

        /* terrorblade LED */
        pinctrl_myled: ledgrp {
            fsl,pins = <
                MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
            >;
        };

2.gpio子系统:gpio1控制器节点 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; 对应platform_driver是drivers/gpio/gpio-mxc.c 里的mxc_gpio_driver 此驱动是处理gpio相关操作

example

        platformled {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "terrorblade-led";
                pinctrl-names = "default";
                pinctrl-0 = <&pinctrl_myled>;
                platformled = <&gpio1 3 GPIO_ACTIVE_LOW>;
                status = "okay";
        };

3.驱动中调用gpio子系统API可以对gpio子系统初始化后的io进行操作,这些函数有:

of_find_node_by_path of_get_named_gpio gpio_direction_output gpio_set_value

在5)基础上加上6)中3点可以利用linux内核提供的pinctrl子系统和gpio子系统很方便的操作IO

7)linux并发竞争中的原子操作,定义设备

typedef struct{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *dn;
    int gpio;
    atomic_t lock;
}led_t;

调用原子操作API

    if(!atomic_dec_and_test(&led.lock)){
        atomic_inc(&led.lock);
        return -1;
    }

    atomic_inc(&tmp->lock);

    atomic_set(&led.lock, 1);

8)按键输入of_find_node_by_path of_get_named_gpio gpio_direction_input

9)按键输入中加入中断和延迟消抖

...

10)platform驱动框架是linux驱动分离思想下的产物,linux驱动分离为bus driver device,总线由linux内核处理,对于没有实际总线的设备,linux内核虚拟出drivers/base/platform.c中的platform_bus_type

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};

相应的有 include/linux/platform_device.h 中的结构体platform_driver platform_device,他们是include/linux/device.h中device_driver device结构体的派生

struct device {
        struct device           *parent;

        struct device_private   *p;

        struct kobject kobj;
        const char              *init_name; /* initial name of the device */
        const struct device_type *type;

        struct mutex            mutex;  /* mutex to synchronize calls to
                                         * its driver.
                                         */

        struct bus_type *bus;           /* type of bus device is on */
        struct device_driver *driver;   /* which driver has allocated this
                                           device */
        void            *platform_data; /* Platform specific data, device
                                           core doesn't touch it */
        void            *driver_data;   /* Driver data, set and get with
                                           dev_set/get_drvdata */
        struct dev_pm_info      power;
        struct dev_pm_domain    *pm_domain;
...
struct device_driver {
        const char              *name;
        struct bus_type         *bus;

        struct module           *owner;
        const char              *mod_name;      /* used for built-in modules */

        bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */

        const struct of_device_id       *of_match_table;
        const struct acpi_device_id     *acpi_match_table;

        int (*probe) (struct device *dev);
        int (*remove) (struct device *dev);
        void (*shutdown) (struct device *dev);
        int (*suspend) (struct device *dev, pm_message_t state);
        int (*resume) (struct device *dev);
        const struct attribute_group **groups;

        const struct dev_pm_ops *pm;

        struct driver_private *p;
};
struct platform_device {
        const char      *name;
        int             id;
        bool            id_auto;
        struct device   dev;
        u32             num_resources;
        struct resource *resource;

        const struct platform_device_id *id_entry;
        char *driver_override; /* Driver name to force a match */

        /* MFD cell pointer */
        struct mfd_cell *mfd_cell;

        /* arch specific additions */
        struct pdev_archdata    archdata;
};
struct platform_driver {
        int (*probe)(struct platform_device *);
        int (*remove)(struct platform_device *);
        void (*shutdown)(struct platform_device *);
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
        struct device_driver driver;
        const struct platform_device_id *id_table;
        bool prevent_deferred_probe;
};

实现platform_device 和 platform_driver ,调用platform_device_register和platform_driver_register向内核注册,当platform_device成员name和platform_driver成员driver成员name相同时,匹配成功,probe运行

11)

用设备树编写platform设备驱动,主要是实现platform_driver结构体

static int dtspaltformled_probe(struct platform_device *dev){
    if(led.major){
        led.devid = MKDEV(led.major, 0);
        register_chrdev_region(led.devid, DTSPLATFORMLED_CNT, DTSPLATFORMLED_NAME);
    }
    else{
        alloc_chrdev_region(&led.devid, 0, DTSPLATFORMLED_CNT, DTSPLATFORMLED_NAME);
        led.major = MAJOR(led.devid);
        led.minor = MINOR(led.minor);
    }

    led.cdev.owner = THIS_MODULE;
    cdev_init(&led.cdev, &led_fops);

    cdev_add(&led.cdev, led.devid, DTSPLATFORMLED_CNT);

    led.class = class_create(THIS_MODULE, DTSPLATFORMLED_NAME);

    led.device = device_create(led.class, NULL, led.devid, NULL, DTSPLATFORMLED_NAME);

    led.dn = of_find_node_by_path("/platformled");
    if(led.dn == NULL){
        printk("find node error\n");
        return -1;
    }

    led.led_num = of_get_named_gpio(led.dn, "platformled", 0);
    if(led.led_num < 0){
        printk("get gpio error\n");
        return -1;
    }

    gpio_request(led.led_num, "mydtsplatformled");

    gpio_direction_output(led.led_num, 1);

    return 0;
}

static int dtspaltformled_remove(struct platform_device *dev){
    gpio_set_value(led.led_num, 1);

    cdev_del(&led.cdev);

    unregister_chrdev_region(led.devid, DTSPLATFORMLED_CNT);

    device_destroy(led.class, led.devid);

    class_destroy(led.class);

    return 0;
}

static const struct of_device_id dtsplatformled_of_match[] = {
    {.compatible = "terrorblade-led"},
    {}
};

static struct platform_driver dtsplatformled_driver = {
    .probe = dtspaltformled_probe,
    .remove = dtspaltformled_remove,
    .driver = {
        .of_match_table = dtsplatformled_of_match,
        .name = "terrorblade-led",
    },
};

static int __init dtspaltformled_init(void){
    platform_driver_register(&dtsplatformled_driver);

    return 0;
}

static void __exit dtsplatformled_exit(void){
    platform_driver_unregister(&dtsplatformled_driver);
}

12)misc的misc_register()和misc_deregister()

input子系统中linux驱动设计的分层思想, 理解input_event()函数和input_event结构体

13)linux中 i2c驱动框架 和 spi驱动框架 的对比

i2c_adapter spi_master

i2c_alogrithm.master_xfer transfer

i2c_driver spi_driver

i2c_register_driver spi_register_driver

i2c总线和spi总线都是基于platform驱动框架编写的,由SOC厂家写好在linux内核中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值