linux系统介绍及驱动子系统介绍

linux系统介绍及驱动子系统介绍

一、驱动分类

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驱动

相关创作

第三节 血
365bet足球网址

第三节 血

08-28 👁 7827
魅族手机如何恢复出厂设置
beat365网站地址

魅族手机如何恢复出厂设置

08-17 👁 8508