linux驱动之platform总线学习

platform总线通常使用基于两个部分,一个部分是device,一个部分是driver.


我以一个简单的led的例子为例总结platform总线的使用。

我使用的是s5pv210处理器,所以我以该处理器为例总结,其实linux驱动,对任何处理器都一样。


首先以实现device部分。

device主要是数据。

首先我在自定义一个结构体,最好是自己创建一个头文件,方便驱动部分好包含。

我定义的放在对应处理器的mach目录下面,因为这个目录下都是放的和处理器相关的头文件。

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

很简单,主要就是led的一些信息

接下来就是填充这个结构体了。

/***************run add leds*******************/
static struct s5pv210_led_platdata s5pv210_led0_pdata = {
    .name       = "led0",
    .gpio       = S5PV210_GPJ0(3),
    .flags      = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger    = "",
};


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

static struct s5pv210_led_platdata s5pv210_led2_pdata = {
    .name       = "led2",
    .gpio       = S5PV210_GPJ0(5),
    .flags      = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger    = "",
};
static struct platform_device s5pv210_led0 =
{
    .name = "s5pv210_led",
    .id = 0, 
    .dev = 
    {
        .platform_data = &s5pv210_led0_pdata,
    },
};


static struct platform_device s5pv210_led1 =
{
    .name = "s5pv210_led",
    .id = 1,
    .dev =
    {
        .platform_data = &s5pv210_led1_pdata,
    },
};

static struct platform_device s5pv210_led2 =
{
    .name = "s5pv210_led",
    .id = 2,
    .dev =
    {
        .platform_data = &s5pv210_led2_pdata,
    },
};
   

我的板子上有三个led,我就定义了三个相对应的结构体。

struct s5pv210_led_platdata这个结构体里面的gpio我使用了gpiolib库的定义。

name自己定义,flags可以自定义其作用,比如输入输出,上下拉等。

def_trigger可以表示该gpio可以表示某个设备运行等,这里我没有使用。


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

这个结构体比较重要,name非常重要,必须和驱动名一样,是匹配驱动的唯一途径。

id是表示区分同类设备。

我们这里关注struct device dev;

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 */
	struct dev_pm_info	power;

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
	/* arch specific additions */
	struct dev_archdata	archdata;
#ifdef CONFIG_OF
	struct device_node	*of_node;
#endif

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
};

struct device这个结构体里面数据很多,我们主要关注void *platform_data;

因为这个平台数据指针,就是设计为了给驱动传参用的(看注释)。void *可以串任何类型的指针就更方便了。

而我这里就是把每个led自定义的结构体struct s5pv210_led_platdata的地址放了进去。


自己做一个结构体,把自己创建的平台设备数据放进去。

static struct platform_device *smdkv210_devices[] __initdata = {
    &s5pv210_led0,
    &s5pv210_led1,
    &s5pv210_led2,

};
利用下面这个函数注册进入,device这边就已经结束了。
platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));
当然,比较简单的方式是把自己的设备数据,添加到原来的设备后面,就可以不用自己添加了。



接下来看驱动部分。

大的部分看就是平台数据的注册和卸载。

/* 平台总线模型驱动部分 */
static struct platform_driver s5pv210_led_driver =
{
    .probe = s5pv210_led_probe,
    .remove = s5pv210_led_remove,
    .driver =
    {
        .name = "s5pv210_led",
        .owner = THIS_MODULE,
    },
};

/* 平台总线驱动注册 */
static int __init s5pv210_led_init(void)
{
    return platform_driver_register(&s5pv210_led_driver);
}

/* 平台总线驱动取消 */
static void __exit s5pv210_led_exit(void)
{
    platform_driver_unregister(&s5pv210_led_driver);
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("run");
MODULE_DESCRIPTION("leds-s5pv210");

驱动的注册和卸载是固定的比较简单。但里面填充的驱动模型,这个最主要的。

下面看一下平台驱动模型的原型。

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

上面这个驱动模型比较全面。而我的led的需要很简单,只需要填充probe和remove函数即可。

还有一个比较中要的是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;
};

这里我们主要关注char *naem和 driver_private *p。

其中name主要是这个驱动的名字。为了和对应设备匹配,驱动名字必须和设备名字一致。

driver_private *p的使用可以看我的上一个博客。

接下来我们看最主要的连个函数probe和remove。

其中probe是负责设备初始化的。remove是负责卸载设备的时候做收尾工作的。


因为我们这里是led,所以使用了led的设备驱动模型。


所以初始化部分用的是led的设备模型初始化的。

/* 想当于初始化函数 */
static int s5pv210_led_probe(struct platform_device *pdev)
{
    /* 得到设备传过来来的数据 */
    struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
    struct s5pv210_gpio_led *led = NULL;
    int ret = -1;

    printk(KERN_INFO"-----------s5pv210_led_probe-------------\n");

    led = kzalloc(sizeof(struct s5pv210_gpio_led),GFP_KERNEL);
    if(NULL == led)
    {
        dev_err(&pdev->dev, "no memoey for device \n");
        return -ENOMEM;
    }
    /* 使用gpiolib库,申请gpio */
    if(gpio_request(pdata->gpio,pdata->name))
    {
        printk(KERN_ERR"gpio_request fail\n");
        kfree(led);
        return -EINVAL;
    }
    else
    {
        /* 初始化gpio为输出 */
        gpio_direction_output(pdata->gpio,1);
    }

    /* 把动态申请到的driver data绑定到相应的设备的私有数据中 */
    platform_set_drvdata(pdev, led);

    led->cdev.name = pdata->name;
    led->cdev.brightness_set = s5pv210_led_brightness_set;
    led->cdev.brightness = 0;
//  led->cdev.flags = pdata->flags;     //这个标志必须用内核定义的,自己定义的可能会和内核的重复
    led->pdata = pdata;

    /* 利用led设备类模型注册该设备 */
    ret = led_classdev_register(&pdev->dev,&led->cdev );
    if(ret)
    {
        dev_err(&pdev->dev, "led classdev_register fail\n");
        kfree(led);
        gpio_free(pdata->gpio);
        return ret;
    }

    return 0;
}
这个函数里注释比较明确,唯一需要说明的是flags,因为pdata->flags是我们子定义的功能,前面在设备部分说明已的是做输入输出标志或输出的默认电平标志。这里我们led是做输出的,默认给的高电平。所以没用这个标志,但千万不能赋值和led->cdev.flags.因为这个cdev是led的设备模型定义的标志,其作用已经固定了。我就是刚在开始赋值的,导致led被挂起。不能操纵。

可以看到下面led类里面就用到

static int led_suspend(struct device *dev, pm_message_t state)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_suspend(led_cdev);

	return 0;
}

static int led_resume(struct device *dev)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
		led_classdev_resume(led_cdev);

	return 0;
}

它们的定义如下。

struct led_classdev {
	const char		*name;
	int			 brightness;
	int			 max_brightness;
	int			 flags;

	/* Lower 16 bits reflect status */
#define LED_SUSPENDED		(1 << 0)
	/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME	(1 << 16)

	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	void		(*brightness_set)(struct led_classdev *led_cdev,
					  enum led_brightness brightness);
	/* Get LED brightness level */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

	/* Activate hardware accelerated blink, delays are in
	 * miliseconds and if none is provided then a sensible default
	 * should be chosen. The call can adjust the timings if it can't
	 * match the values specified exactly. */
	int		(*blink_set)(struct led_classdev *led_cdev,
				     unsigned long *delay_on,
				     unsigned long *delay_off);

	struct device		*dev;
	struct list_head	 node;			/* LED Device list */
	const char		*default_trigger;	/* Trigger to use */

#ifdef CONFIG_LEDS_TRIGGERS
	/* Protects the trigger data below */
	struct rw_semaphore	 trigger_lock;

	struct led_trigger	*trigger;
	struct list_head	 trig_list;
	void			*trigger_data;
#endif
};

probe函数里注册led的驱动模型的时候需要用到一个brightness_set函数,这个函数就是led的设备模型中干活的函数,用来设置led的量灭的。

/* 设置led灯的亮灭 */
static void s5pv210_led_brightness_set(struct led_classdev *led_cdev,
                      enum led_brightness brightness)
{
    struct s5pv210_gpio_led * led = to_gpio(led_cdev);
    printk(KERN_INFO"led brightness set\n");

    if(LED_OFF == brightness)
    {
        gpio_set_value(led->pdata->gpio, 1);
    }
    else
    {
        gpio_set_value(led->pdata->gpio, 0);
    }

}

下面几个就是上面用到的一些的数据类型和工具函数。

/* 自定义结构体,主要是想包含平台数据 */
struct s5pv210_gpio_led
{
    struct led_classdev cdev;
    struct s5pv210_led_platdata *pdata;
};


/* 得到platform_device里面的platform_data */
static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
    return platform_get_drvdata(dev);
}

/* 通过container_of宏,利用s5pv210_gpio_led 结构体里面的led_classdev结构体得到s5pv210_gpio_led */
static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
    return container_of(led_cdev,struct s5pv210_gpio_led,cdev);
}

总结:上面设备部分用到了palaform_device的一些知识。

下面驱动部分用到了platform_driver和led设备模型,以及gpiolib库的简单应用。

几个知识点算是对上一周学习的总结。





猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/80850526
今日推荐