内核自带的基于GPIO的LED驱动学习(二)

2)分析平台驱动的probe函数
好,既然这个LED驱动使用的是平台驱动框架,当设备和驱动匹配上之后,就会执行指定的probe函数,那接下来的工作就转移到分析对应的probe函数了。为了直观,我把probe函数也粘贴上来。

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;

	if (pdata && pdata->num_leds) {
		priv = devm_kzalloc(&pdev->dev,
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;
		for (i = 0; i < priv->num_leds; i++) {
			ret = create_gpio_led(&pdata->leds[i],
					      &priv->leds[i],
					      &pdev->dev, pdata->gpio_blink_set);
			if (ret < 0) {
				/* On failure: unwind the led creations */
				for (i = i - 1; i >= 0; i--)
					delete_gpio_led(&priv->leds[i]);
				return ret;
			}
		}
	} else {
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

拿到一个函数的时候,一般不要急于直接一行行地去分析代码细节,可以大概浏览一遍,这个函数大概是干嘛的,看看根据自己目前已有的认知,能看懂多少,看不懂的地方在哪里,这些看不懂的地方,是否会影响自己对这个函数功能的理解。总之,如果没有必要,千万不要一头扎进庞大的源码里面去。

对于这个probe函数,我们目前知道它是LED驱动的probe函数,所以里面应该要配置LED对应的gpio引脚,还应该要为每个LED分配一个私有结构体来保存必要的信息,还要做以前纯字符设备驱动里面做的一些工作,比如创建class和device,在/dev下生成一个应用可访问的设备节点。这是我目前能够想到的,可能它还会做其它的工作,但是我觉得应该八九不离十。
函数的主体是如下的一个选择结构

if (pdata && pdata->num_leds)
{
	...
} else {
	...
}

我想,我们要分析下去,必须的知道 if(pdata && pdata->num_leds) 这个判断是什么意思。pdata是一个指针,定义如下:

struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);

它什么时候为空?什么时候非空?从定义来看,pdata是通过dev_get_platdata()获取到的该平台设备的私有数据。

static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

私有数据是跟平台设备相关,不同的平台设备,私有数据是不同的,需要由驱动开发人员来定义。那内核就不可能知道该用什么类型来表示,所以是一个void *。当我们还没有给它分配内存的时候,这个指针肯定是空的(因为内核不知道具体类型,不会给它分配内存),也就是说,当一个平台设备与驱动匹配上的时候,执行probe函数

struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);

这个时候,由于还没有分配私有数据,pdata就是NULL,会走else分支。那么,会存在匹配上,pdata却不为空的情况吗?这个我暂时还想不到,所以这里就先不考虑了。

当平台设备与驱动匹配上的时候,由于私有数据为空,应该走else分支,调用gpio_leds_create()函数。一起来看看gpio_leds_create()做了些什么事情,我把函数的定义贴出来:

static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct fwnode_handle *child;
	struct gpio_leds_priv *priv;
	int count, ret;
	struct device_node *np;

	count = device_get_child_node_count(dev);
	if (!count)
		return ERR_PTR(-ENODEV);

	priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	device_for_each_child_node(dev, child) {
		struct gpio_led led = {};
		const char *state = NULL;

		led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			ret = PTR_ERR(led.gpiod);
			goto err;
		}

		np = of_node(child);

		if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state",
						 &state)) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      dev, NULL);
		if (ret < 0) {
			fwnode_handle_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(ret);
}

首先,这个函数的返回值是指向struct gpio_leds_priv结构体的指针,也就是平台设备的私有数据结构体的首地址,用来保存LED平台设备信息的,其定义如下:

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

诶,一看这个结构体,它居然有一个变量num_leds,应该是用来保存具体的LED设备的数量。然后有一个struct gpio_led_data类型的数组,应该是用来保存该平台设备下面各个LED设备的信息。进入到函数gpio_leds_create(),我们来看看函数里面做了什么。

1)获取LED设备的数量

count = device_get_child_node_count(dev);

其中,参数dev就是匹配到的平台设备。从这里可以看出来,内核自带的这个LED驱动是将LED设备进行分组匹配的,也就是说dev代表的平台设备其实是设备树里面的一个LED分组,并不是一个真正的LED设备。所以在这里, LED分组 = LED平台设备,后面的内容就会混用这两个概念了。真正的LED设备是作为LED分组下面的子节点存在的,也就是说,这个LED驱动所对应的设备树结构应该是类似下面的模型:

LED-GRP1 {
	compatible = "leds,kernel"

	led-red {
		...
	};
	led-green {
		...
	};
};

LED-GRP2 {
	compatible = "leds,kernel"

	led-yellow {
		...
	};
	led-blue {
		...
	};
};

匹配到一个LED分组时,就会执行probe函数,然后通过device_get_child_node_count(dev)获取分组下面的LED设备数量。

2)为LED分组分配一个结构体struct gpio_leds_priv,作为该平台设备的私有数据。devm_kzalloc()是内核的内存分配函数,其原型位于include/linux/device.h中(关于devm_kzalloc,请参考《深入分析linux内核的内存分配函数devm_kzalloc》)

priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);

dev表示这片私有数据内存需要关联到的平台设备。使用devm_kzalloc()申请的内存,不需要我们手动释放,因为我们将其关联到平台设备dev,当平台设备与驱动分离时,这片内存会自动释放。这里我们要申请的内存大小为sizeof_gpio_leds_priv(count)。sizeof_gpio_leds_priv是一个内联函数,其定义如下:

static inline int sizeof_gpio_leds_priv(int num_leds)
{
	return sizeof(struct gpio_leds_priv) +
		(sizeof(struct gpio_led_data) * num_leds);
}

这个函数的功能是计算当LED分组里面有num_leds个LED设备的时候,结构体struct gpio_leds_priv的大小。我们把struct gpio_leds_priv的定义重新贴出来:

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

再次看这个结构体的定义,里面leds这个结构体数组,它并没有指明大小,是不是很奇怪?不指明数组大小,编译不会出错吗?大家平时可没有这样用过吧,但是人家内核代码就这样用了。为什么它不在定义时指明数组的大小?因为它根本不知道该设置为多大,内核开发人员可不能提前预测你会在一个分组下挂多少个LED,对吧。像这样的情况,我们平时都是定义一个指针,然后根据需要来分配对应大小的内存。人家这里不是定义指针,而是直接定义了一个没有指定大小的数组(经过测试,其效果相当于定义了一个已经有指向的指针,其指向的内存位置相当于直接定义一个struct gpio_led_data的led成员时,led的地址)。实际上,它是利用了数组的存储空间在内存上是连续的特点,通过使用类似leds[i]的引用来将devm_kzalloc()分配的内存的尾部当做struct gpio_led_data来访问(关于这点的用法,参考文末的参考代码1)。所以我们使用devm_kzalloc()来分配内存的时候,应该分配sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds)大小的内存空间。

3)遍历LED分组下面的所有LED设备
好,我们通过devm_kzalloc(),为匹配到的LED分组分配了一个私有结构体(关于devm_kzalloc,请参考《深入分析linux内核的内存分配函数devm_kzalloc》),里面可以保存num_leds个LED设备的信息,接下来就应该遍历这个LED分组下面的所有LED设备,提取它们的信息,保存到各自对应的struct gpio_led_data里面去。代码里面使用了一个for循环来做这件事:

device_for_each_child_node(dev, child) {
	...
}

这个for循环里面大概做了以下几件事情:

3-1)定义一个struct gpio_led类型的变量led,来临时保存该LED设备的信息

struct gpio_led led = {};

struct gpio_led的定义如下:

struct gpio_led {
	const char *name;
	const char *default_trigger;
	unsigned 	gpio;
	unsigned	active_low : 1;
	unsigned	retain_state_suspended : 1;
	unsigned	default_state : 2;
	/* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
	struct gpio_desc *gpiod;
};

3-2)获取LED设备使用的GPIO引脚信息

led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);

3-3)获取LED设备的设备树节点

np = of_node(child);

3-4)解析LED设备的设备树节点内容,保存到led变量里面

if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state",
						 &state)) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;

3-5)使用led变量作为模板,创建一个LED字符设备

ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],  dev, NULL);

这5个步骤执行完毕后,就成功创建了一个LED字符设备。这个for循环遍历结束之后,这个LED分组下面的所有LED设备都被创建为LED字符设备。这里只是大概讲了下这个for循环里面所做的工作,下一篇将详细讲解各个步骤里面具体的知识点。

参考代码1(Ubuntu16.04下编译测试)

#include <stdio.h>

struct pair
{
	const char *name;
	int value;
};

struct container
{
	int number;
	struct pair parameter[];
};

int main(int argc, char *argv)
{
	int i;
	unsigned char buf[512];
	struct container *pcon = (struct container *)buf;

	//应保证buf的内存空间足够大,否则会引起Segmentation fault
	pcon->number = 1;
	pcon->parameter[0].name = "first";
	pcon->parameter[0].value = 1;

	pcon->number++;
	pcon->parameter[1].name = "second";
	pcon->parameter[1].value = 2;

	pcon->number++;
	pcon->parameter[2].name = "third";
	pcon->parameter[2].value = 3;

	for(i = 0; i < pcon->number; i++)
	{
		printf("%dth pair:\n", i+1);
		printf("name:%s, value:%d\n", pcon->parameter[i].name, pcon->parameter[i].value);
	}

	return 0;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PWM-GPIO驱动程序是Linux内核中的一个驱动模块,用于控制嵌入式系统中的GPIO引脚产生PWM信号。该驱动程序允许开发人员通过编程的方式来控制GPIO引脚的电平变化,从而产生不同占空比的PWM信号。 在Linux内核中,PWM-GPIO驱动程序通过向用户空间提供了相应的接口来实现PWM信号的控制。开发人员可以通过打开相应的设备节点,并使用相应的系统调用函数来设置PWM的频率、占空比等参数,从而实现对GPIO引脚的PWM信号的控制。 驱动程序的核心部分是一个PWM子系统,它与GPIO子系统紧密集成。PWM子系统负责管理PWM信号的生成和控制,而GPIO子系统负责管理GPIO引脚的配置和操作。PWM-GPIO驱动程序在这两个子系统之间起着桥梁的作用。 PWM-GPIO驱动程序的实现方式与硬件平台相关,每个平台可能有不同的具体实现。在驱动程序的初始化过程中,必须先配置GPIO引脚的功能为PWM模式,并将相应的寄存器映射到内核中,以便能够通过对寄存器的操作来控制GPIO引脚。驱动程序还需要初始化PWM子系统,为每个GPIO引脚分配相应的PWM通道,并根据需求设置PWM的频率、占空比等参数。 通过PWM-GPIO驱动程序,开发人员可以方便地利用Linux内核的功能来实现对嵌入式系统中GPIO引脚产生PWM信号的控制。这为开发PWM驱动、控制舵机、LED等应用提供了便捷的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值