1. 程式人生 > >linux設備驅動之平臺總線實踐環節(二)

linux設備驅動之平臺總線實踐環節(二)

linux設備驅動模型

1、上一節中,我們將初步的驅動代碼寫完後編譯後,放入到rootfs中進行insmod時,在/sys/bus/platform/drvier/目錄中能夠看到why_led這個目錄,但是進入後只有一些基本的東西,卻沒有能使用這個led驅動的關鍵性東西,那是因為我們沒有提供platform_device,並且驅動代碼中的probe函數remove函數的代碼內容也不對。這一節課中,做另一半,就是platform_device這一部分。


2、做platform_device這一半

參考mach-mini2440.c中添加led的platform_device定義來自己進行做platform_device

(1)首先檢查mach-x210.c中是否有led相關的platform_device

看下mach-x210.c中的platform_device結構體數組,這個結構體數組中包含的是x210開發板中所有的platform_device,代碼如下

static struct platform_device *smdkc110_devices[] __initdata = {
#ifdef CONFIG_FIQ_DEBUGGER
	&s5pv210_device_fiqdbg_uart2,
#endif
#ifdef CONFIG_MTD_ONENAND
	&s5pc110_device_onenand,
#endif
#ifdef CONFIG_MTD_NAND
	&s3c_device_nand,
#endif
	&s5p_device_rtc,
#ifdef CONFIG_SND_S3C64XX_SOC_I2S_V4
	&s5pv210_device_iis0,
	&s5pv210_device_iis1,
#endif
#ifdef CONFIG_SND_S3C_SOC_AC97
	&s5pv210_device_ac97,
#endif
#ifdef CONFIG_SND_S3C_SOC_PCM
	&s5pv210_device_pcm0,
#endif
#ifdef CONFIG_SND_SOC_SPDIF
	&s5pv210_device_spdif,
#endif
	&s3c_device_wdt,

#ifdef CONFIG_FB_S3C
	&s3c_device_fb,
#endif
#ifdef CONFIG_DM9000
	&s5p_device_dm9000,
#endif

#ifdef CONFIG_VIDEO_MFC50
	&s3c_device_mfc,
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C
	&s3c_device_ts,
#endif
	&s3c_device_keypad,
#ifdef CONFIG_S5P_ADC
	&s3c_device_adc,
#endif
#ifdef CONFIG_VIDEO_FIMC
	&s3c_device_fimc0,
	&s3c_device_fimc1,
	&s3c_device_fimc2,
#endif
#ifdef CONFIG_VIDEO_FIMC_MIPI
	&s3c_device_csis,
#endif
#ifdef CONFIG_VIDEO_JPEG_V2
	&s3c_device_jpeg,
#endif
#ifdef CONFIG_VIDEO_G2D
	&s3c_device_g2d,
#endif
#ifdef CONFIG_VIDEO_TV20
	&s5p_device_tvout,
	&s5p_device_cec,
	&s5p_device_hpd,
#endif

	&s3c_device_g3d,
	&s3c_device_lcd,

	&s3c_device_i2c0,
#ifdef CONFIG_S3C_DEV_I2C1
	&s3c_device_i2c1,
#endif
#ifdef CONFIG_S3C_DEV_I2C2
	&s3c_device_i2c2,
#endif

#ifdef CONFIG_USB_EHCI_HCD
	&s3c_device_usb_ehci,
#endif
#ifdef CONFIG_USB_OHCI_HCD
	&s3c_device_usb_ohci,
#endif

#ifdef CONFIG_USB_GADGET
	&s3c_device_usbgadget,
#endif
#ifdef CONFIG_USB_ANDROID
	&s3c_device_android_usb,
#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
	&s3c_device_usb_mass_storage,
#endif
#ifdef CONFIG_USB_ANDROID_RNDIS
	&s3c_device_rndis,
#endif
#endif
#ifdef CONFIG_BATTERY_S3C
	&sec_device_battery,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC
	&s3c_device_hsmmc0,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC1
	&s3c_device_hsmmc1,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC2
	&s3c_device_hsmmc2,
#endif
#ifdef CONFIG_S3C_DEV_HSMMC3
	&s3c_device_hsmmc3,
#endif

#ifdef CONFIG_S3C64XX_DEV_SPI
	&s5pv210_device_spi0,
	&s5pv210_device_spi1,
#endif
#ifdef CONFIG_S5PV210_POWER_DOMAIN
	&s5pv210_pd_audio,
	&s5pv210_pd_cam,
	&s5pv210_pd_tv,
	&s5pv210_pd_lcd,
	&s5pv210_pd_g3d,
	&s5pv210_pd_mfc,
#endif

#ifdef CONFIG_HAVE_PWM
	&s3c_device_timer[0],
	&s3c_device_timer[1],
	&s3c_device_timer[2],
	&s3c_device_timer[3],
#endif

//	&timed_gpio_device,

	&headset_switch_device,
};

這個platform_device結構體數組中每個元素都是x210開發板中的platform_device,我們可以看下這個裏面有沒led的platform_device,這裏面沒有,說明九鼎做的led設備是用的另外一套機制,並不是用的platform_device可能。這個platform_device結構體數組肯定是要用platform的註冊方法將這些設備註冊到platform總線下的。註冊到平臺總線方法,九鼎用的是

platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));

函數,這個platform_add_devices函數就是將數組中的所有設備進行註冊到平臺總線下的,這個函數的詳細內容為

int platform_add_devices(struct platform_device **devs, int num)
{
	int i, ret = 0;

	for (i = 0; i < num; i++) {
		ret = platform_device_register(devs[i]);
		if (ret) {
			while (--i >= 0)
				platform_device_unregister(devs[i]);
			break;
		}
	}

	return ret;
}

platform_add_devices函數中通過platform註冊設備使用的專用函數platform_device_register進行註冊,devs形參就是platform_device的結構體數組。從代碼中可以看出九鼎的設計邏輯是將platform_device結構體數組中的所有設備進行註冊到平臺總線下,只要有一個設備註冊到平臺總線失敗了,那麽就會將已經註冊的設備全部註銷掉,可以知道這是九鼎的一個錯誤處理機制。

其中沒有確實沒有led的platform_device,也就是九鼎確使用的是另外一個機制,用的並不是platform_device,所以我們可以仿造mach-mini2440.c中led的platform_device來實現一個s5pv210的led的platform_device,下面說一下參照mach-mini2440.c中實現的led的platform_device來實現s5pv210的led的platform_device。

在mach-mini2440.c中一些其他的數據結構如

static struct s3c2410fb_display mini2440_lcd_cfg[] __initdata = {
	[0] = {	/* mini2440 + 3.5" TFT + touchscreen */
		_LCD_DECLARE(
			7,			/* The 3.5 is quite fast */
			240, 21, 38, 6, 	/* x timing */
			320, 4, 4, 2,		/* y timing */
			60),			/* refresh rate */
		.lcdcon5	= (S3C2410_LCDCON5_FRM565 |
				   S3C2410_LCDCON5_INVVLINE |
				   S3C2410_LCDCON5_INVVFRAME |
				   S3C2410_LCDCON5_INVVDEN |
				   S3C2410_LCDCON5_PWREN),
	},
	[1] = { /* mini2440 + 7" TFT + touchscreen */
		_LCD_DECLARE(
			10,			/* the 7" runs slower */
			800, 40, 40, 48, 	/* x timing */
			480, 29, 3, 3,		/* y timing */
			50),			/* refresh rate */
		.lcdcon5	= (S3C2410_LCDCON5_FRM565 |
				   S3C2410_LCDCON5_INVVLINE |
				   S3C2410_LCDCON5_INVVFRAME |
				   S3C2410_LCDCON5_PWREN),
	},
	/* The VGA shield can outout at several resolutions. All share 
	 * the same timings, however, anything smaller than 1024x768
	 * will only be displayed in the top left corner of a 1024x768
	 * XGA output unless you add optional dip switches to the shield.
	 * Therefore timings for other resolutions have been ommited here.
	 */
	[2] = {
		_LCD_DECLARE(
			10,
			1024, 1, 2, 2,		/* y timing */
			768, 200, 16, 16, 	/* x timing */
			24),	/* refresh rate, maximum stable,
				 tested with the FPGA shield */
		.lcdcon5	= (S3C2410_LCDCON5_FRM565 |
				   S3C2410_LCDCON5_HWSWP),
	},
};

static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
	.displays	 = &mini2440_lcd_cfg[0], /* not constant! see init */
	.num_displays	 = 1,
	.default_display = 0,

	/* Enable VD[2..7], VD[10..15], VD[18..23] and VCLK, syncs, VDEN
	 * and disable the pull down resistors on pins we are using for LCD
	 * data. */

	.gpcup		= (0xf << 1) | (0x3f << 10),

	.gpccon		= (S3C2410_GPC1_VCLK   | S3C2410_GPC2_VLINE |
			   S3C2410_GPC3_VFRAME | S3C2410_GPC4_VM |
			   S3C2410_GPC10_VD2   | S3C2410_GPC11_VD3 |
			   S3C2410_GPC12_VD4   | S3C2410_GPC13_VD5 |
			   S3C2410_GPC14_VD6   | S3C2410_GPC15_VD7),

	.gpccon_mask	= (S3C2410_GPCCON_MASK(1)  | S3C2410_GPCCON_MASK(2)  |
			   S3C2410_GPCCON_MASK(3)  | S3C2410_GPCCON_MASK(4)  |
			   S3C2410_GPCCON_MASK(10) | S3C2410_GPCCON_MASK(11) |
			   S3C2410_GPCCON_MASK(12) | S3C2410_GPCCON_MASK(13) |
			   S3C2410_GPCCON_MASK(14) | S3C2410_GPCCON_MASK(15)),

	.gpdup		= (0x3f << 2) | (0x3f << 10),

	.gpdcon		= (S3C2410_GPD2_VD10  | S3C2410_GPD3_VD11 |
			   S3C2410_GPD4_VD12  | S3C2410_GPD5_VD13 |
			   S3C2410_GPD6_VD14  | S3C2410_GPD7_VD15 |
			   S3C2410_GPD10_VD18 | S3C2410_GPD11_VD19 |
			   S3C2410_GPD12_VD20 | S3C2410_GPD13_VD21 |
			   S3C2410_GPD14_VD22 | S3C2410_GPD15_VD23),

	.gpdcon_mask	= (S3C2410_GPDCON_MASK(2)  | S3C2410_GPDCON_MASK(3) |
			   S3C2410_GPDCON_MASK(4)  | S3C2410_GPDCON_MASK(5) |
			   S3C2410_GPDCON_MASK(6)  | S3C2410_GPDCON_MASK(7) |
			   S3C2410_GPDCON_MASK(10) | S3C2410_GPDCON_MASK(11)|
			   S3C2410_GPDCON_MASK(12) | S3C2410_GPDCON_MASK(13)|
			   S3C2410_GPDCON_MASK(14) | S3C2410_GPDCON_MASK(15)),
};

static struct s3c24xx_mci_pdata mini2440_mmc_cfg __initdata = {
   .gpio_detect   = S3C2410_GPG(8),
   .gpio_wprotect = S3C2410_GPH(8),
   .set_power     = NULL,
   .ocr_avail     = MMC_VDD_32_33|MMC_VDD_33_34,
};

static struct mtd_partition mini2440_default_nand_part[] __initdata = {
	[0] = {
		.name	= "u-boot",
		.size	= SZ_256K,
		.offset	= 0,
	},
	[1] = {
		.name	= "u-boot-env",
		.size	= SZ_128K,
		.offset	= SZ_256K,
	},
	[2] = {
		.name	= "kernel",
		/* 5 megabytes, for a kernel with no modules
		 * or a uImage with a ramdisk attached */
		.size	= 0x00500000,
		.offset	= SZ_256K + SZ_128K,
	},
	[3] = {
		.name	= "root",
		.offset	= SZ_256K + SZ_128K + 0x00500000,
		.size	= MTDPART_SIZ_FULL,
	},
};

static struct s3c2410_nand_set mini2440_nand_sets[] __initdata = {
	[0] = {
		.name		= "nand",
		.nr_chips	= 1,
		.nr_partitions	= ARRAY_SIZE(mini2440_default_nand_part),
		.partitions	= mini2440_default_nand_part,
		.flash_bbt 	= 1, /* we use u-boot to create a BBT */
	},
};

static struct s3c2410_platform_nand mini2440_nand_info __initdata = {
	.tacls		= 0,
	.twrph0		= 25,
	.twrph1		= 15,
	.nr_sets	= ARRAY_SIZE(mini2440_nand_sets),
	.sets		= mini2440_nand_sets,
	.ignore_unset_ecc = 1,
};

上面這些等等代碼,都是mini2440這個開發板中的platform_device設備的數據部分,按照之前學的platform總線邏輯,這些platform_device的數據部分都會被綁定到platform_device結構體中的device結構體的platform_data成員中。

下面說正式的話題,參考mini-2440.c中led的platform_device移植s5pv210的led的platform_device

首先找到mach-mini2440.c中led的platform_device,代碼如下

static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &mini2440_led1_pdata,
	},
};

找到mach-mini2440.c中led的platform_device後,我們將這個led的platform_device的變量名改為x210名字,數據部分的platform_data改為我們x210_led_pdata,後面我們還要提供x210_led_pdata這個數據結構的實現,id為1來區分這個led為第一個led,如果id為-1,則是讓內核來給我們板子上的led設備分配編號,其中.name成員要和我們驅動中實現的那個led的驅動代碼中的數據結構體也就是platform_driver中的name一樣,因為我的led的platform_driver結構體中name為why_led,所以這裏led的platform_device數據結構體中的name也要為why_led,不然將設備和驅動進行匹配的時候,platform總線的match函數無法將總線下的設備和驅動進行匹配上,因為設備和驅動的匹配用的是名字來進行匹配的。

最後我們仿造mini2440的led的platform_device,將我們x210的led的platform_device,x210是核心板,s5pv210是SoC,改完led的platform_device結構體為

static struct platform_device x210_led1 = {
	.name		= "why_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &x210_led1_pdata,
	},
};

接下來我們要提供x210_led1_pdata這個綁定到platform_device結構體中的device成員結構體中的platform_data的數據結構。我們可以仿造mini2440中為led設備提供的數據部分platform_data的數據結構mini2440_led1_pdata,來實現我們x210的led的設備數據部分platform_data的數據結構x210_led1_pdata,我們看下mini2440的led設備的數據部分platform_data的數據結構

static struct s3c24xx_led_platdata mini2440_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

我們仿造上面的,將變量名字改為x210_led1_pdata,成員中的gpio、flags、def_trigger都需要根據我們x210板子的硬件情況來進行更改,比如這個led1的gpio對應的是哪個,flags屬性有沒有,def_trigger這個led1作為什麽用。我們先不管這個led1的gpio對應的是哪,也不管flags屬性,也不管def_trigger,先把框架改完,其中涉及到的硬件後續在改,仿造mini2440的led的platform_device的platform_data,我們初步將x210的led設備platform_device的platform_data數據部分的數據結構改為

static struct s3c24xx_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

這個時候看下我們有了什麽東西,這個時候我們有了x210的led的platform_device數據結構,並且也有了x210的led的platform_devoce數據結構中需要的led設備的數據部分platform_data,如下

static struct s3c24xx_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

static struct platform_device x210_led1 = {
	.name		= "why_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &x210_led1_pdata,
	},
};

x210_led1為x210的led設備的platform_device結構體變量,成員.platform_data綁定的x210_led1_pdata數據結構為x210_led1這個led設備的數據部分,表示這個led的gpio引腳是哪個,屬性是什麽,這個led用來做什麽,當然我們這裏目前關於led設備的數據部分x210_led1_pdata填寫的還不對.


在mach-mini2440.c中的struct s3c24xx_led_platdata這個結構體類型是被聲明在內核源碼目錄下的/arch/arm/mach-s3c2410/include/mach/leds-gpio.h中的,因為我們是為x210的led設備加入設備的數據部分platform_data,所以我們不能要將platform_data要綁定的結構體類型定義在mach-s5pv210/include/mach/目錄下,我們可以自己在mach-s5pv210/include/mach/目錄下創建一個頭文件leds-gpio.h,先將mach-mini2440/include/mach/leds-gpio.h中的內容拷貝到我們的mach-s5pv210/include/mach/目錄下的led-gpio.h中,然後在做修改,mach-mini2440/include/mach/leds-gpio.h中的內容為

*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/

#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"

#define S3C24XX_LEDF_ACTLOW	(1<<0)		/* LED is on when GPIO low */
#define S3C24XX_LEDF_TRISTATE	(1<<1)		/* tristate to turn off */

struct s3c24xx_led_platdata {
	unsigned int		 gpio;
	unsigned int		 flags;

	char			*name;
	char			*def_trigger;
};

#endif /* __ASM_ARCH_LEDSGPIO_H */

我們先將這個內容拷貝到我們的/arch/arm/mach-s5v210/include/mach/leds-gpio.h中,然後在做更改,我們將這個將來綁定到platform_data中的結構體名字改為x210名字,內容改完後如下

*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/

#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"

#define S5PV210_LEDF_ACTLOW	(1<<0)		/* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE	(1<<1)		/* tristate to turn off */

struct s5pv210_led_platdata {
	unsigned int		 gpio;
	unsigned int		 flags;

	char			*name;
	char			*def_trigger;
};

#endif /* __ASM_ARCH_LEDSGPIO_H */

這個時候因為我們將將來綁定到platform_data的數據結構的類型名變成s5pv210_led_platdata了,所以我們前面定義填充的x210的led的platform_data也要改成s5pv210_led_platdata,改動方法,將

static struct s3c24xx_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

改為

static struct s5pv210_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

這時我們的x210的led的platform_device和platform_data為

static struct s5pv210_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

static struct platform_device x210_led1 = {
	.name		= "why_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &x210_led1_pdata,
	},
};

同時我們要在上面代碼所在的文件中包含我們剛才創建的頭文件

#include<mach/leds-gpio.h>

好啦,接下來我們將x210的led的設備數據部分platform_data也就是x210_led_pdata這個結構體中的內容按照x210的硬件實際情況進行修改,沒有改動之前的x210的led的platform_data為

static struct s5pv210_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

我們將gpio進行更改為x210中這個led實際對應的引腳,引腳為S5PV210_GPJ0(3),flags是一種擴展屬性,我們可以加上,也可以不加上,def_tigger是來表示我們這個led用來做什麽的,我們可以先不考慮這個,將def_trigger賦值為空字符"",最後改完的x210的led的platform_device和platform_data為

static struct s5pv210_led_platdata x210_led1_pdata = {
	.name		= "led1",
	.gpio		= S5PV210_GPJ0(5),
	.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
	.def_trigger	= "",
};

static struct platform_device x210_led1 = {
	.name		= "why_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &x210_led1_pdata,
	},
};

之後,我們要將這個platform_device定義的x210_led1變量添加到最開始說的那個含有x210所有設備的platform_device結構體數據中,按照九鼎的註冊platform的邏輯,會將這個platform_device結構體數組中的所有設備進行註冊。加入後代碼為,這裏只貼一部分

static struct platform_device *smdkc110_devices[] __initdata = {
#ifdef CONFIG_FIQ_DEBUGGER
	&s5pv210_device_fiqdbg_uart2,
	&x210_led1,
	.
	.
	.
	.
	.
	.
	.
}

好了,這個時候我們在mach-s5pv210.c中加入了x210的led的platform_device,並且也提供了led相應的platform_data設備的數據部分x210_led1_pdata,並且在mach-s5pv210/include/mach/目錄中的leds-gpio.h中也定義了我們這個led設備數據部分x210_led1_pdata結構體的類型,我們也在mach-s5pv210.c中將led的platform_device的變量x210_led1添加到了platform_device結構體數組中,在平臺總線設備進行註冊的時候,就會將我們添加的這個x210_led1設備進行註冊。

這個時候我們編譯內核,可以使用uboot中的tftp下載內核,運行內核,內核nfs掛載根文件系統,內核運行起來後,這個時候的內核情況是我們只添加了led的platform_device,但是我們並沒有添加led的platform_driver,因為我們沒有insmod上一節課中的led的platform_drvier,並且上一節課中的led的platform_driver在probe函數和remove函數中的代碼還沒有徹底修改。此時先觀察內核只有led的platform_deivce的情況。

此時我們在根文件系統的/sys/bus/platform/device/目錄下會看到有一個why_led.1這麽一個文件目錄,後面的1就是我們當時添加這個設備數據部分時的id的值,也就是代表led的編號。

進到這個/sys/bus/platform/device/why_led.1目錄中,我們ls看到也沒有什麽東西,都是一些常規的東西,此時說明在我們的platform中的device中已經能夠找到文件節點,但是缺陷是只有一些常規的東西。這個時候是我們只有led的platform_device,沒有led的platform_driver的情況。


本文出自 “whylinux” 博客,謝絕轉載!

linux設備驅動之平臺總線實踐環節(二)