一、驱动分类
linux驱动一般分类方式为字符设备驱动、块设备驱动、网络设备驱动。
字符设备驱动采用字节流访问方式,如按键、串口、触摸屏、spi外设等。
块驱动如U盘、SD卡、nanflash等。
块设备驱动需要mount挂载然后才能访问。
二、驱动加载及设备访问
linux驱动加载分为动态加载和静态加载。
动态加载即是驱动编译成.ko,通过insmod加载驱动。
静态加载时编译到内核中。静态加载的顺序,首先根据优先级如下:
#define pure_initcall(fn) __define_initcall("0",fn,1)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7) //晚的初始化调用
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define module_init(x)__initcall(x);
#define__initcall(fn)device_initcall(fn)
不同层次的驱动采用不同的优先级,一般外设驱动采用的是module_init,板级文件的初始化采用arch_initcall。
若要调整设备驱动的加载顺序,一般不会调整这个优先级,因为会影响到整个系统,而是调整makefile的编译顺序,驱动加载的顺序跟.o的编译顺序也有关。
设备访问:
linux一切皆文件,因此对设备的访问也是文件的访问方式,采用open、read、write、ioctl等api访问设备。因此设备首先要有对应的设备节点。执行 ls /dev可以看到当前设备的所有设备节点。嵌入式linux驱动编译到内核中需要创建设备节点。
设备节点的创建方式分手动和自动:
手动创建设备通过mknod来创建,一般会添加到开机脚本中,根据设备名、设备类型、主设备号、次设备号创建设备节点。
自动创建则是通过busybox的udev自动创建的,这个需要系统移植好udev,然后在驱动代码中要通过class_create和device_create(有的版本为class_device_create)创建类和设备信息,这两个函数会再/sys/class目录下生成设备信息,然后udev会根据这些信息自动创建设备节点。
三、设备驱动注册
linux驱动分为总线、设备、驱动的概念。
linux驱动中存在platform总线,这个总线是一个虚拟的总线,所有的设备都挂载到这个虚拟总线上。
设备的注册:
设备的注册在板级文件中,代码例程如下:
/****************************************************************************
*
* Platform devices
*
****************************************************************************/
static struct platform_device *devices[] __initdata = {
&maximasp_uart0_device,
&maximasp_uart1_device,
&maximasp_uart2_device,
&maximasp_nand_device,
&maximasp_i2c_device,
&maximasp_macb_device,
&maximasp_sdhci_device,
&maximasp_spi0_device,
&maximasp_spi1_device,
&maximasp_spi2_device,
&maximasp_spi3_device,
&maximasp_usb0_device,
&maximasp_usb1_device,
&maximasp_kbd_device,
&maximasp_clcd_device,
&maximasp_sci_device,
&maximasp_adc_device,
&m50_adc_dev,
&maximasp_pwm_device,
&maximasp_sci_afe,
&maximasp_sci_no_afe,//virtual afe
&maximasp_thp_device,
&maximasp_buzzer_device,
&maximasp_backlight_device,
&maximasp_security_device,
&maximasp_power_device,
};
static void maximasp_poweroff(void)
{
}
static int __init maximasp_board_init(void)
{
int err;
/* Set up shutdown handler */
pm_power_off = maximasp_poweroff;
/* Initialize NAND ready line */
gpio_request(PIN_NAND_READY, "nand.0.ready");
maximasp_gpio_mode(PIN_NAND_READY, GPIO_MODE_GPIO | GPIO_MODE_IN);
/* Register external SPI devices with SPI subsystem */
err = spi_register_board_info(spi_info, ARRAY_SIZE(spi_info));
if (unlikely(err))
printk(KERN_ERR "Registering SPI devices failed (%d)\n", err);
/* Register external I2C devices with I2C subsystem */
err = i2c_register_board_info(0, i2c_info, ARRAY_SIZE(i2c_info));
if (unlikely(err))
printk(KERN_ERR "Registering I2C devices failed (%d)\n", err);
err = platform_add_devices(devices, ARRAY_SIZE(devices));
if (unlikely(err))
printk(KERN_ALERT "Registering SOC devices failed (%d)\n", err);
return err;
}
arch_initcall(maximasp_board_init);
通过platform_add_devices将设备注册到内核中,这个函数最终调用platform_device_register,platform_device 包含了设备的硬件信息和设备名等。maximasp_spi0_device、maximasp_spi1_device等属于spi的控制器,spi的外设信息通过spi_register_board_info注册到内核中。
驱动注册:
驱动的注册首先通过platform_driver_register将驱动注册到内核中(spi外设驱动是通过spi_driver_register),然后会调用prob函数实现驱动的初始化。
prob函数主要实现三步:
1、次设备号的申请,通过register_chrdev_region或alloc_chrdev_region实现设备号的静态和动态申请。(旧版本中采用register_chrdev可能会产生多个设备节点,已不建议使用),这两个函数为字符设备类型,一般驱动开发中涉及到需要自己申请设备号的就是字符设备。
关于主设备号一般在驱动源码目录的/Documentation目录下有一个devices.txt文件说明了占用的主设备号。
2、设备注册,通过cdev_init和cdev_add实现设备的注册。
3、创建设备,通过class_create和device_create(有的版本为class_device_create)创建类和设备信息,这两个函数会再/sys/class目录下生成设备信息,然后udev会根据这些信息自动创建设备节点。若是手动创建设备节点则通过mknod来创建。
注:linux中有一种杂项设备,占用主设备号为10,一般一些无法归类的会选择建立杂项设备。杂项设备属于一种特殊的字符设备。杂项设备不需要额外占用主设备号,注册也简单,只需要misc_register注册即可。
四、驱动子系统
spi子系统
spi控制器的驱动一般在arch/.../mach-*/board-*.c 声明,注册一个平台设备,然后在driver/spi下面建立一个平台驱动。
spi_master注册过程中会扫描arch/.../mach-*/board-*.c 中调用spi_register_board_info注册的信息,为每一个与本总线编号相同的信息建立一个spi_device。
根据Linux内核的驱动模型,注册在同一总线下的驱动和设备会进行匹配。spi_bus_type总线匹配的依据是名字。这样当自己编写的spi_driver和spi_device同名的时候,
spi_driver的probe方法就会被调用。spi_driver就能看到与自己匹配的spi_device了。
例程:
static struct spi_driver test_driver = {
.driver = {
.name = "test",
.owner = THIS_MODULE,
},
.probe = test_probe,
.remove = __devexit_p(test_remove),
};
static __init int test_init(void)
{
return spi_register_driver(&test_driver);
}
static __exit void test_exit(void)
{
spi_unregister_driver(&test_driver);
}
板级文件中:
static struct spi_board_info spi_info[] =
{
{ //guoyiyan, add spi device for lcd
.modalias = "spi_maximaspfb",
.max_speed_hz = 5000000,
.bus_num = 2,
.chip_select = 0,
.mode = SPI_MODE_0,
.platform_data = NULL,
},
{ //add spi0 for RFID
.modalias = "rfid_as3911",
.max_speed_hz = 10000000,
.bus_num = 0,
//.chip_select = MAXIMASP_GPIO(0, 23),//RFID_SPI_SS
.chip_select =0,
.mode = SPI_MODE_1,
.platform_data = NULL,
},
{
.modalias = "MAXQ1743",
.max_speed_hz = 100000,
.bus_num = 3,
.chip_select =PIN_MAG_SS,
.mode = SPI_MODE_0,
.irq = MAXIMASP_GPIONUM2IRQ(PIN_MAG_RED),
},
{
.modalias = "test",
.max_speed_hz = 100000,
.bus_num = 3,
.chip_select =0,
.mode = SPI_MODE_0,
.platform_data =NULL,
},
};
通过spi_register_board_info(spi_info, ARRAY_SIZE(spi_info));注册信息到内核。
i2c子系统
input子系统
输入子系统,如键盘、鼠标等。
以键盘驱动为例讲解:
1、设备访问:
首先查找设备,可通过cat /proc/bus/input/devices查看按键设备对应的是event几(驱动里面会配置设备名等,根据设备名可区分到要找的设备)。
其次可以通过文件操作函数open、lseek、read访问数据,读出的数据为input_event 类型,可根据数据确认按键事件和建码。
2、驱动移植开发
这里以max32590芯片为例,这个芯片自带keyboard控制器,若不带keyboard的芯片用io行列扫描模拟即可。
板级配置文件中配置行列、键值数等。
static struct maximasp_kbd_platform_data maximasp_kbd_data =
{
.output_number = 5,
.input_number = 4,
.scanned_output_number = 5,
.debouncing_range = 6, //25ms
.cfg_gpio = maximasp_kbd_port_setup,
.kbd_keycodes_len = 25,
.kbd_keycodes = maximasp_kbd_keymap,
};
static struct platform_device maximasp_kbd_device =
{
.name = "maximasp-kbd",
.id = 0,
.resource
= maximasp_kbd_resource,
.num_resources
= ARRAY_SIZE(maximasp_kbd_resource),
.dev =
{
.platform_data
= &maximasp_kbd_data,
},
};
驱动文件注册platform设备,prob文件中初始化注册input设备:
input_set_drvdata(kbd_dev, mxkbd);
kbd_dev->name = "kbd";
kbd_dev->phys = "kbd/input";
kbd_dev->uniq = "Maxim MAX32590 pinpad";
kbd_dev->id.bustype = BUS_HOST;
kbd_dev->id.vendor = -1;
/* not assigned */
kbd_dev->id.product = -1;
kbd_dev->id.version = DRIVER_VERSION;
kbd_dev->evbit[0] = BIT(EV_KEY);
kbd_dev->keycode = data->kbd_keycodes;
kbd_dev->keycodesize = sizeof(unsigned char);
if(data->kbd_keycodes_len > KBD_MAX_KEYS)
{
goto err4;
}
kbd_dev->keycodemax = data->kbd_keycodes_len;
for(i = 0; i < data->kbd_keycodes_len; i++)
{
if (data->kbd_keycodes[i])
{
set_bit(data->kbd_keycodes[i], kbd_dev->keybit);
}
}
kbd_dev->open = kbd_open;
kbd_dev->close = kbd_close;
rv = input_register_device(kbd_dev);
input的设备注册时会包含一些设备名信息,建码等。注册时只实现了open、close函数,实际的键盘行列扫描功能是在prob里启动中断实现的,中断处理函数里面扫描建码然后上报input事件。
Lcd子系统
linux显示采用的framebuffer驱动,对应平台的驱动在Video目录下都可以找到。
驱动移植时只需要在板级文件配置好对应panel的参数即可,对于需要通过spi配置的panel,需要修改驱动代码根据panel手册通过spi配置panel。
板级文件需要根据panel手册配置分辨率、行同步、厂同步信号等,例程如下:
static struct maximaspfb_display displays[] = {
/*//modified according to HX8347 panel*/
{
.disp_type = TFT,
.width = 240,
.height = 320,
.bpp = 16,
.num_buffers = 1,
.left_margin = 2,
.right_margin = 2,
.upper_margin = 2,
.lower_margin = 2,
.hsync_len = 2,
.vsync_len = 2,
.pixel_clk_typ = 9000000,
.pixel_clk_max = 9260000,
.pixel_clk_min = 7830000,
.pas_clk = 0,
.edge = 0,
.horz_pol = 1,
.vert_pol = 1,
.data_out_pol = 0,
.ac_bias = 0,
.lend_pol = 0,
.width_mm = 57,
.height_mm = 43,
},
};
static struct maximaspfb_ctrl fb_ctrl = {
.vert_scan_dir = 1,
.horz_scan_dir = 1,
};
static struct maximaspfb_platform_data maximaspfb_data = {
.displays = displays,
.num_displays = ARRAY_SIZE(displays),
.default_display = 0,
.ctrl = &fb_ctrl,
.cfg_gpio = maximaspfb_port_setup,
.cfg_mode = maximaspfb_mode_setup,
};
static struct platform_device maximasp_clcd_device = {
.name = "maximasp-clcd",
.id = 0,
.resource = maximaspfb_resource,
.num_resources = ARRAY_SIZE(maximaspfb_resource),
.dev = {
.platform_data = &maximaspfb_data,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
};
从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics
framebuffer的使用:
打开framebuffer设备文件: /dev/fb0
获取framebuffer设备信息 #include
mmap做映射
填充framebuffer
FB驱动框架相关代码:drivers\video 这个目录中
Backlight子系统
GPIO子系统
网卡驱动
声卡驱动
flash驱动