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总线和spi总线都是基于platform驱动框架编写的,由SOC厂家写好在linux内核中