天天看點

第七章 驅動程式開發-LED驅動-7.10.3.代碼分析

    在.dts中定義一個led_gpio(client device)節點,子節點包含pinctrl系統,整個節點會在核心中注冊成一個platform_device。對應的要注冊一個platform_driver,當insmod drv.ko時,device和driver比對之後,就執行probe()獲得引腳,注冊file_operations,設定方向、讀值/寫值。???在file_opretions中進行讀寫???

不同之處,之前用board_A_led.c注冊的platform_device,用chip_demo_gpio.c注冊platform_driver。現在隻剩下leddrv.c檔案了,其他檔案都沒有了啊!看來pinctrl+gpio可以省好多代碼!

第七章 驅動程式開發-LED驅動-7.10.3.代碼分析

7.10.3.1.ledtest.c

    沒有改變。

7.10.3.2.leddrv.c

    用代碼對比看了下改動挺大的,逐一分析。

1.增加了2個.h檔案,

#include <linux/gpio/consumer.h>   //包含GPIO擷取和使用的函數
#include <linux/platform_device.h>  
           

2.新增一個結構體指針變量,static struct gpio_desc *led_gpio,看下原定義:

struct gpio_desc {
	struct gpio_device	*gdev;
	unsigned long		flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED	0
……

	/* Connection label */
	const char		*label;
	/* Name of the GPIO */
	const char		*name;
};
           

搜尋了一下,有一個部落格有一個很好的說明,借用下:https://blog.csdn.net/u010388659/article/details/81286821,

第七章 驅動程式開發-LED驅動-7.10.3.代碼分析

    “如上圖所示,右上方部分為GPIO驅動對其它驅動提供的GPIO操作接口,其對應的右下方部分為GPIO硬體操作接口,也就是說對外提供的接口最終會一一對應的對硬體GPIO進行操作。再來看左邊部分,左上方部分為一全局數組,記錄各個GPIO的描述符,即對應左下方的gpio_desc結構體,其中gpio_chip指向硬體層的GPIO,flags為一标志位,用來訓示目前GPIO是否已經占用,當用gpio_request申請GPIO資源時,flags位就會置位,當調用gpio_free釋放GPIO資源時,flags就會清零。label是一個字元串指針,用來作說明。”(暫時不了解)

3.led_init()/led_exit(),看驅動程式先看入口函數和出口函數,入口函數用來向核心注冊platform_driver,以前的函數是向核心注冊一個chrdev然後建立dev class,出口函數也對應隻是解除安裝platform_driver即可。另外,定義了platform_driver結構體,之前是在chip_demo_gpio.c中定義的,結構體内容不多說了之前都說過。

134: /* 4.定義比對platform_device的compatible變量 */
135: static const struct of_device_id ask100_leds[] = {
136: 	{ .compatible = "100ask,leddrv" },
137: 	{ },
138: }; 

126: /* 1. 定義platform_driver */
127: static struct platform_driver chip_demo_gpio_driver = {
128: 	.probe = chip_demo_gpio_probe,
129: 	.remove = chip_demo_gpio_remove,
130: 	.driver = {
131: 		.name = "100ask_led",
132: 		.of_match_table = ask100_leds,
133: 	},
134: };
135:
136: /* 2. 注冊platform_driver */
137: static int __init led_init(void)
138: {
139: 	int err;
141: 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
142:
143: 	/* 2.1 注冊platform_driver */
144: 	err = platform_driver_register(&chip_demo_gpio_driver);
145:
146: 	return err;
147: }
148:
149: /* 3. 解除安裝platform_driver */
150: static void __exit led_exit(void)
151: {
152: 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
153:
154: 	/* 3.1 解除安裝platform_driver */
155: 	platform_driver_unregister(&chip_demo_gpio_driver);
156: } 
           

    看下platform_driver_register(),可以依次追蹤一下driver是如何和device macthing的以及如何調用probe函數的。

    如果probe和remove沒有内容的話,那最簡單的leddrv是不是可以就這樣了,編譯,insmod之後platform_driver和經裝置樹轉化的platform_driver比對之後就可以什麼都不做了!不行,比如執行測試程式,./ledtest /dev/led0 on,需要先open這個/dev/led0次裝置,問題是此時核心中沒都沒這個次裝置呢!是以還是得需要file_operations注冊一個裝置,之前都是在入口函數中注冊,現在轉移到probe中注冊了。

4.probe(),主要有4點功能:

24: /* 6.0定義gpio_dsec結構體 */
25: static struct gpio_desc *led_gpio; 

83: /* 6. 定義probe函數
84: * 功能:
85: * a.從platform_device獲得GPIO節點;
86: * b.注冊file_operations結構體,建立主裝置;
87: * c.建立裝置類;
88: * d.建立次裝置;
89: */
90: static int chip_demo_gpio_probe(struct platform_device *pdev)
91: {
92: //int err;
93:
94: 		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
95:
96: 		/* 6.1擷取裝置節點led-gpios=<...>; */
97: 		led_gpio = gpiod_get(&pdev->dev, "led", 0);
98: 		if (IS_ERR(led_gpio)) {
99: 			dev_err(&pdev->dev, "Failed to get GPIO for led\n");
100: 		return PTR_ERR(led_gpio);
101: 	}
102:
103: 	/* 6.2注冊file_operations結構體,建立100ask_led裝置 */
104: 	major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/100ask_led */
105:
106: 	/* 6.3建立100ask_led_class */
107: 	led_class = class_create(THIS_MODULE, "100ask_led_class");
108: 	if (IS_ERR(led_class)) {
109: 		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
110: 		unregister_chrdev(major, "led");
111: 		gpiod_put(led_gpio);
112: 		return PTR_ERR(led_class);
113: 	}
114:
115: 	/* 6.4建立100ask_ledx次裝置 */
116: 	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0);
116: 	/* /dev/100ask_led0 */
117:
118: 	return 0;
120: } « end chip_demo_gpio_probe » 

122: /* 8實作remove函數 */
123: static int chip_demo_gpio_remove(struct platform_device *pdev)
124: {
125: 	/* 依次解除安裝次裝置、裝置類、主裝置和釋放gpio節點 */
126: 	device_destroy(led_class, MKDEV(major, 0));
127: 	class_destroy(led_class);
128: 	unregister_chrdev(major, "100ask_led");
129: 	gpiod_put(led_gpio);
130:
131: 	return 0;
132: } 
           

看下gpiod_get()

/**
 * gpiod_get - obtain a GPIO for a given GPIO function
 * @dev:	GPIO consumer, can be NULL for system-global GPIOs
 * @con_id:	function within the GPIO consumer
 * @flags:	optional GPIO initialization flags
 *
 * Return the GPIO descriptor corresponding to the function con_id of device
 * dev, -ENOENT if no GPIO has been assigned to the requested function, or
 * another IS_ERR() code if an error occurred while trying to acquire the GPIO.
 */
struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,
					 enum gpiod_flags flags)
{
	return gpiod_get_index(dev, con_id, 0, flags);
}
           

然後gpiod_get->gpiod_get_index->of_find_gpio->

of_get_named_gpiod_flags依次找到dts led-gpio node,那它又是怎麼去比對的pinctrl呢?自己找了下:

platform_driver_register

    __platform_driver_register

        driver_register

            bus_add_driver

                driver_attach

                   bus_for_each_dev

                       __driver_attach

                         driver_probe_device

                            really_probe

                               pinctrl_bind_pins

                                  pinctrl_lookup_state

                          ret = drv->probe(dev);

5.定義file_operations結構體,結構體成員沒有變化,open函數和write有所改變,可以直接對gpio子系統操作。

28: /* 7.實作open/read/write/close等函數 */
29:
30: /* 7.1實作open函數 */
31: static int led_drv_open (struct inode *node, struct file *file)
32: {
33: 	//int minor = iminor(node);
34:
35: 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
36: 	/* 根據次裝置号初始化LED */
37: 	gpiod_direction_output(led_gpio, 0);
38:
39: 	return 0;
40: }
41:

48:
49: /* 7.3實作write函數 */
50: /* write(fd, &val, 1); */
51: static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t
51: *offset)
52: {
53: 	int err;
54: 	char status;

58: 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
59: 	err = copy_from_user(&status, buf, 1);
60:
61: 	/* 根據次裝置号和status控制LED */
62: 	gpiod_set_value(led_gpio, status);
63:
64: 	return 1;
65: }
           
/**
 * gpiod_direction_output - set the GPIO direction to output
 * @desc:	GPIO to set to output
 * @value:	initial output value of the GPIO
 *
 * Set the direction of the passed GPIO to output, such as gpiod_set_value() can
 * be called safely on it. The initial value of the output must be specified
 * as the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account.
 *
 * Return 0 in case of success, else an error code.
 */
int gpiod_direction_output(struct gpio_desc *desc, int value)
{
	VALIDATE_DESC(desc);
	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;
	return _gpiod_direction_output_raw(desc, value);
}
_gpiod_direction_output_raw
   gpiod_direction_input
    gpio_chip_hwgpio
*********************************************************************
/**
 * gpiod_set_value() - assign a gpio's value
 * @desc: gpio whose value will be assigned
 * @value: value to assign
 *
 * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
 * account
 *
 * This function should be called from contexts where we cannot sleep, and will
 * complain if the GPIO chip functions potentially sleep.
 */
void gpiod_set_value(struct gpio_desc *desc, int value)
{
	VALIDATE_DESC_VOID(desc);
	/* Should be using gpiod_set_value_cansleep() */
	WARN_ON(desc->gdev->chip->can_sleep);
	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;
	_gpiod_set_raw_value(desc, value);
}
_gpiod_set_raw_value
   _gpio_set_open_drain_value
   _gpio_set_open_source_value
           

    小結:核心通過gpio_get()獲知led-gpios,進而擷取到IO的複用配置,通過gpiod_direction_output設定IO為output,通過gpiod_set_value設定IO邏輯電平,進而控制LED。

繼續閱讀