linux驱动学习(六) 应用中的linux驱动 platform_device

一个现实的linux设备和驱动通常要挂接在一种总线上,像pci,usb,iic,spi等都是总线结构,这当然不是问题,但是嵌入式系统中,Soc系统集成的独立外设控制器,挂接在soc内存空间的外设等却不依附于此类总线。


基于这个背景,linux发明了一种虚拟总线:platform总线,相应的设备称为platform_device,而驱动成为platform_driver。
注意,platform_device并不是与自负设备,块设备等平行的概念,而是linux提供的一种附加手段,例如s3c2440处理器中,把内部集成的iic,rtc,spi,lcd,watchdog,等控制器归纳为platform_device,但是他们本身就是字符设备。


platform_device

struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;

	const struct platform_device_id	*id_entry;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

platform_device成员变量

1、struct device(部分),include<linux/device.h>
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	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 */



2、struct resource

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

platform_device对应的platform_driver

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;
};

支持电源管理时,需要实现 shutdown,suspend,resume这三个函数,若不支持,将他们设为null。

platform_driver结构体中的重要成员变量 device_driver

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 */

#if defined(CONFIG_OF)
	const struct of_device_id	*of_match_table;
#endif

	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;
};


用于向内核注册platform_driver的函数platform_driver_register(platform_driver *)

反之,注销platform_driver的函数platform_drvier_unregister(platform*)


一般实现platform_driver时,除了实现file_operations中的read、write等函数外,还要实现platform_driver中的probe与remove等函数,其余均按正常的linux设备驱动的编写方法编写驱动程序。

例如将mychar移植成platform_driver,简略的如下形式

static int __devinit mychar_probe(struct platform_device *pdev)
{
	//申请设备号
	//申请设备结构体的内存
	//注册cdev
	//其实probe    函数里就是实现之前在mychar_init中实现的功能
}
static int __devexit mychar_remove(struct platform_device *pdev)
{
	//实现之前在mychar_exit()中的释放内存的功能
}
static struct platform_driver mychar_device_driver = {
	.probe = mychar_probe,
	.remove = __devexit_p(mychar_remove),
	.driver = {
		.name = "mychar",
		.owner = THIS_MODULE,
		}
};
//在mychar_init中注册platform_driver
static int __init mychar_init(void)
{
	return platform_driver_register(&mychar_device_driver);
}
//在mychar_exit 中注销platform
static void __exit mychar_exit(void)
{
	platform_driver_unregister(mychar_device_driver);
}
//驱动余下部分与之前实现的mychar相同
module_init(mychar_init);
module_exit(mychar_exit);
注意,如果要让这个驱动在开发板上能工作,需要在板文件中添加相应的代码,在板文件例如 arch/arm/mach-s3c2440/mach-mini2440.c,代码如下

static struct platform_device mychar_device = { 
    .name = "mychar",
    .id = -1,
};
这样就表示,开发板上有一个devie,名字叫mychar,因为mychar是内存中虚拟出来的,所以这里并不需要设置别的,只要设置一下与driver相匹配的name:mychar就可以了

通常开发板不会只有这一个设备,所以在platform_device数组中,将上面的mychar_device添加进来,如下:

static struct platform_device *mini2440_devices[] __initdata = {
    &mychar_device,
    &s3c_rtc,
    &s3c_device_fb,
    ...
}


platform_devece的资源与数据(resource 与platform_data)

还记的在platform_device 中的struct resource *resource吗,

	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
通常只关心struct resource中的以上四个成员变量
start 与end两个字段的值随着flags的改变而改变,当flags 为 IORESOURCE_MEM 时,start,end分别表示该platform_device 占据的内存的开始地址和结束地址,若flags为IORESOURCE_IRQ 时,start end 则表示该platform_device 使用的中断号的开始值和结束值,假如只使用了1个中断号,那么start与end相同。
例如dm9000的resource部分:
/* DM9000AEP 10/100 ethernet controller */

static struct resource mini2440_dm9k_resource[] = {
	[0] = {
		.start = MACH_MINI2440_DM9K_BASE,
		.end   = MACH_MINI2440_DM9K_BASE + 3,
		.flags = IORESOURCE_MEM
	},
	[1] = {
		.start = MACH_MINI2440_DM9K_BASE + 4,
		.end   = MACH_MINI2440_DM9K_BASE + 7,
		.flags = IORESOURCE_MEM
	},
	[2] = {
		.start = IRQ_EINT7,
		.end   = IRQ_EINT7,
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
	}
};
所谓的resource,具体来时是与板级硬件密切相关的,比如控制器映射到soc内存的地址范围,外部中断引脚等,
当然,要把定义的这个resources[]赋值给platform_device的.resource 字段,同时要设置.num_resources资源个数。
设备除了可以再bsp中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断,内存,DMA通道以外,可能还会有一些配置信息,而这些配置信息也依赖于板,不宜直接放置在设备驱动本身,因此platform也提供了platform_data的支持,platform_data的形式是自定义的,比如对于dm9000网卡来说,platform_data中可以存放mac地址,总线宽度,板上有误eeprom等信息。
 * The DM9000 has no eeprom, and it's MAC address is set by
 * the bootloader before starting the kernel.
 */
static struct dm9000_plat_data mini2440_dm9k_pdata = {
	.flags		= (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};
然后将这个data赋值给platform_device中.dev的.platform_data数据项,如下:
static struct platform_device mini2440_device_eth = {
	.name		= "dm9000",
	.id		= -1,
	.num_resources	= ARRAY_SIZE(mini2440_dm9k_resource),
	.resource	= mini2440_dm9k_resource,
	.dev		= {
		.platform_data	= &mini2440_dm9k_pdata,
	},
};

所以在抑制linux到具体的开发板时,基本都是这么移植的是不是?回答是肯定的,这里注意了,以上与板级硬件密切相关的代码部分,均在bsp板级支持文件中,例如mach-s3c2440.c中,但是你看到了真正的驱动了吗比如字符设备的read write等函数的实现了吗。
真正的驱动代码在内核的driver文件夹下,比如dm9000的驱动在 drviver/net/文件夹下的dm9000.c中,而且这部分的代码是与具体的板级硬件无关的,再比如nandflash的驱动,配置也是在mach-s3c2440.c中,但关键的驱动源码在 drvier/mtd/nand/文件夹下
这样的结构就是linux驱动的分层思想,设备驱动的核心层与例化。




  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MLX90614红外温度传感器_linux驱动源码,已经在产品使用过。android 6.0 ,内核版本为3.4.39,可以做为你的学习设计参考。 #include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/moduleparam.h> #include <linux/delay.h> #include <linux/fs.h> #include <linux/timer.h> #include <linux/ioctl.h> #include <linux/gpio.h>//__gpio_set_value #include <mach/sys_config.h>//script_item_u #include <linux/pinctrl/consumer.h>//pin_config_set #include <linux/pinctrl/pinconf-sunxi.h>//SUNXI_PINCFG_TYPE_* #include <linux/io.h> #include <mach/sys_config.h> #include <mach/platform.h> #include <linux/slab.h> #define ACK 0 #define NACK 1 #define SA 0x5a //Slave address 单个MLX90614时地址为0x00,多个时地址默认为0x5a #define RAM_ACCESS 0x00 //RAM access command #define EEPROM_ACCESS 0x20 //EEPROM access command #define RAM_TOBJ1 0x07 //To1 address in the eeprom #define RAM_TOBJ2 0x08 #define RAM_TA 0x06 #define DEVICE_NAME "mlx90614" //struct gpio_config { // u32 gpio; /* gpio global index, must be unique */ // u32 mul_sel; /* multi sel val: 0 - input, 1 - output... */ // u32 pull; /* pull val: 0 - pull up/down disable, 1 - pull up... */ // u32 drv_level; /* driver level val: 0 - level 0, 1 - level 1... */ // u32 data; /* data val: 0 - low, 1 - high, only vaild when mul_sel is input/output */ //}; #define SCL_NAME "sensor_sck" #define SDA_NAME "sensor_sda" struct gpio_func_desc { unsigned short pin; char *name; }; struct gpio_func_desc SCLK ={0,SCL_NAME};//蓝色 struct gpio_func_desc SDIN ={0,SDA_NAME};//白色 struct gpio_config *sclk_gpio_p = NULL; struct gpio_config *sdin_gpio_p = NULL; void SMBus_StartBit(void); void SMBus_StopBit(void);

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值