为AM335x移植Linux内核主线代码(35)使用platform中的GPIO

时间:2024-03-22 08:05:25

http://www.eefocus.com/marianna/blog/15-02/310352_46e8f.html

使用GPIO,当然可以自己编写驱动,比如之前的第34节,也可以使用Kernel中的platform方法,它对资源的管理更加方便。为了理解platform工作的过程,需要先来看看mainline里面的帮助文档:

Documentation/driver-model/platform.txt
====================================
(头文件linux/platform_device.h里面包含了platform的driver model interface,比较重要的结构体是platform_device和platform_driver。platform其实是一种pseudo-bus,而并不是真正的总线,使用它能够实现connect devices on busses with minimal infrastructure。区别于PCI或者USB总线,platform更见简单。)

Platform device
~~~~~~~~~~~~~~~~
Platform devices are devices that typically apear as autonomous entities in the system. This includes legacy port-based devices and host bridges to peripheral buses, and most controllers integrated into system-on-chip platforms. What they usually have in common is directly addressing from a CPU bus. Rarely, a platform_device will be connected through a segment of some other kind of bus; but its registers will still be directly addressable.

(platform device的特点是什么?它们都可以从CPU BUS上直接寻址,比如GPIO、UART、I2C、SPI等等外设,它们的寄存器可以通过对应地址来读取。因此也可以知道,platform与硬件关系密切。)

Platform devices are given a name, used in driver binding, and a list of resources such as addresses and IRQs.

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

(来看一个platform_device的例子(arch/arm/mach-omap2/board-omap3stalker.c):)
(基本上platform_device都定义在了arch目录下,这是其中有关GPIO的一例,它一共定义了4个用作LED的GPIO,存放在gpio_leds中:)

static struct gpio_led_platform_data gpio_led_info = {
        .leds           = gpio_leds,
        .num_leds       = ARRAY_SIZE(gpio_leds),
};

static struct platform_device leds_gpio = {
        .name   = "leds-gpio",
        .id     = -1,
        .dev    = {
                .platform_data  = &gpio_led_info,
        },
};

Platform drivers
~~~~~~~~~~~~~~~~
Platform drivers follow the standard driver model conventiaon, where discovery/enumeration is handled outside the drivers, and drivers provide probe() and remove() methods. They support power management and shutdown notifications using the standard conventions.

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 (*suspend_late)(struct platform_device *, pm_message_t state);
        int (*resume_early)(struct platform_device *);
        int (*resume)(struct platform_device *);
        struct device_driver driver;
};

(再来看一个platform_driver的例子(drivers/leds/leds-gpio.c):)
(它对probe、remove,以及driver赋值:)

static struct platform_driver gpio_led_driver = {
        .probe          = gpio_led_probe,
        .remove         = gpio_led_remove,
        .driver         = {
                .name   = "leds-gpio",
                .owner  = THIS_MODULE,
                .of_match_table = of_match_ptr(of_gpio_leds_match),
        },
};

Note that probe() should in general verify that the specified device hardware actually exist; sometimes platform setup code can't be sure. The probing can use device resources, including clocks, and device platform_data.

Platform drivers register themselves the normal way:
        int platform_driver_register(struct platform_driver *drv);

(注册了platform设备之后,等待触发事件发生就好了,它会自动执行probe(),在dts文件中有触发事件的定义。至于触发事件怎么和驱动联系起来,在以后的驱动编写中再学,因此GPIO并不是热插拔设备,这里直接初始化顺带probe就行。)

Or, in common situations where the device is known not to be hot-pluggable, the probe() routine can live in an init section to reduce the driver's runtime memory footprint:
        int platform_driver_probe(struct platform_driver *drv,
                          int (*probe)(struct platform_device *))

Device Enumeration
~~~~~~~~~~~~~~~~~~
As a rule, platform specific (and often board-specific) setup code will register platform devices:
        int platform_device_register(struct platform_device *pdev);
        int platform_add_devices(struct platform_device **pdevs, int ndev);

The general rule is to register only those devices that actually exist, but in some cases extra devices might be registered. For example, a kernel might be configured to work with an external network adapter that might not be pupulated on all boards, or likewise to work with an integrated controller that some boards might not hook up to any peripherals.

In some cases, boot firmware will export tables describing the devices that are populated on a given board. Without such tables, often the only way for system setup code to set up the correct devices is to build a kernel for a specific target board. Such board-specific kernels are common with embedded and custom systems development.

In many cases, the memory and IRQ resources associated with the platform device are not enough to let the device's driver work. Board setup code will often provide additional information using the device's platform_data field to hold additional information.

(所以,虽然driver目录下删掉了各种各样乱七八糟的board,但并不是说board这个概念就在内核中消失了,因为普遍适用的外设并不能完全搞定某一特殊的board,所以在arch/arm/mach-omap2目录下仍然有各种各样的board文件,它存放了有关platform的内容。)

Embedded systems frequently need one or more clocks for platform devices, which are normally kept off until they'are actively needed (to save power). System setup also associates those clocks with the device, so that that calls to clk_get(&pdev->dev, clock_name) return them as needed.

(嵌入式设备为了降低功率,通常会关掉不使用设备的时钟,时钟也是作为设备来管理的,因此可以使用clk_get(&pdev->dev, clock_name)来获得其它设备所需要的时钟。)

Legacy Drivers: Device Probing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device Naming and Driver Binding
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The platform_device.dev.bus_id is the canonical name for the devices.
It's built from two components:
 * platform_device.name ... which is also used to for driver matching.
 * platform_device.id ... the device instance number, or else "-1" to indicate there's only one.

There are concatenated, so name/id "serial/0" indacates bus_id "serial.0", and "serial"/3 indicates bus_id "serial.3"; both would use the platform_driver named "serial". While "my_rtc"/-1 would be bus_id "my_rtc" (no instance is) and use the platform_driver called "my_rtc".

(这就是为什么platform_device和platform_driver的名字是一样的,它们不是为了便于用户理解,而是必须设置成一样的:“leds-gpio”,否则platform设备是找不到对应的驱动的。)

Early Platform Devices and Drivers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For further informatin, see linux/platform/platform_device.h.

========================================================================
前面螺里啰嗦的写了那么多,主要是了解platform的概念,现在再来看看Kernel里面的代码:
====================================
首先是arch/arm/boot/dts/maria-am335x-common.dtsi文件,之前在阅读booting-without-of.txt时了解到,dtb和driver产生联系的关键在于compatibal参数。maria-am335x-common.dtsi文件中有关led的部分,关键的compatible参数是这么设置的:

leds {
                pinctrl-names = "default";
                pinctrl-0 = <&core_led>;

compatible = "gpio-leds";

led@1 {
                        label = "maria-am335x:core";
                        gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>;
                        default-state = "on";
                };
        };

所以第二步是找到gpio-leds的驱动文件,使用grep命令就行:
grep -rn gpio-leds drivers/
此时就会发现包含这个compatible参数的驱动文件为:
drivers/leds/leds-gpio.c

在理解drivers/leds/leds-gpio.c之前,先来看看Makefile和Kconfig文件。
在Makefile中:
obj-$(CONFIG_LEDS_GPIO)                 += leds-gpio.o
在Kconfig中:
config LEDS_GPIO
        tristate "LED Support for GPIO connected LEDs"
        depends on LEDS_CLASS
        depends on GPIOLIB
        help
          This option enables support for the LEDs connected to GPIO
          outputs. To be useful the particular board must have LEDs
          and they must be connected to the GPIO lines.  The LEDs must be
          defined as platform devices and/or OpenFirmware platform devices.
          The code to use these bindings can be selected below.

因此在make menuconfig时可以找到对应的选项:
Device Drivers -> LED Support -> LED Support for GPIO connected LEDS
可以看到它默认是*号,意为已经编译进了内核。

来看看下面的代码:
static const struct of_device_id of_gpio_leds_match[] = {
        { .compatible = "gpio-leds", },
        {},
};

static struct platform_driver gpio_led_driver = {
        .probe          = gpio_led_probe,
        .remove         = gpio_led_remove,
        .driver         = {
                .name   = "leds-gpio",
                .owner  = THIS_MODULE,
                .of_match_table = of_match_ptr(of_gpio_leds_match),
        },
};

module_platform_driver(gpio_led_driver);

of_match_ptr函数将设备of_gpio_leds_match与驱动gpio_led_driver联系起来了。
module_platform_driver用来替代module_init()和module_exit()功能,为了理解它到底执行了什么功能,这里在free-electrons.com上根据identifier search来一步一步观察:

http://lxr.free-electrons.com/source/include/linux/platform_device.h#L219
==================================
219 #define module_platform_driver(__platform_driver) \
220         module_driver(__platform_driver, platform_driver_register, \
221                         platform_driver_unregister)

从这个宏可以知道,module_platform_driver被定义为module_driver,它以struct platform_drive为参数,去执行platform_driver_register和platform_driver_unregister这两个函数。

http://lxr.free-electrons.com/source/include/linux/device.h#L1219
==================================
1219 #define module_driver(__driver, __register, __unregister, ...) \
1220 static int __init __driver##_init(void) \
1221 { \
1222         return __register(&(__driver) , ##__VA_ARGS__); \
1223 } \
1224 module_init(__driver##_init); \
1225 static void __exit __driver##_exit(void) \
1226 { \
1227         __unregister(&(__driver) , ##__VA_ARGS__); \
1228 } \
1229 module_exit(__driver##_exit);

观察module_driver宏,是不是发现它非常眼熟呢?它定义了模块装载函数和模块移除函数。##用来作字符串连接,__VA_ARGS_则用来替代变参。通过在drivers/leds/leds-gpio.c中的函数:
static int gpio_led_probe(struct platform_device *pdev)
添加打印信息,就会发现它已经被执行了:

[    2.213132] 48060000.mmc supply vmmc not found, using dummy regulator
[    2.221323] omap_hsmmc 48060000.mmc: could not set regulator OCR (-22)
[    2.231593] omap_hsmmc 48060000.mmc: could not set regulator OCR (-22)
[    2.275880] Maria: ====================================
[    2.284377] ledtrig-cpu: registered to indicate activity on CPUs
[    2.292102] usbcore: registered new interface driver usbhid
[    2.298125] usbhid: USB HID core driver

帮助文件也看了,代码也观察了,platform的概念和示例都有了了解,那它和普通的driver有什么区别呢?
========================================================================
http://*.com/questions/15610570/what-is-the-difference-between-platform-driver-and-normal-device-driver
1. Platform devices are inherently not discoverable, i.e. the hardware cannot say "Hey! I'm present!" to the software.
2. Platform devices are bound to drivers by matching names.
3. Platform devices should be registered very early during system boot. Because they are often critical to the rest of the system (platform) and its drivers.

So basically, the question "is it a platform device or a standard device?" is more a question of which bus it uses. To work with a particular platform device, you have to:
1. register a platform driver that will manage this device. It should define a unique name.
2. register your platform device, defining the same name as driver.

终于到了它的应用阶段:
========================================================================
Step 1:
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 menuconfig
System Type -> TI OMAP/AM/DM/DRA Family -> TI AM33XX
原来是所有的CPU类型都选择了,现在只保留AM33XX,重新编译就会发现,arch/arm/mach-omap2这个目录下会少很多.o文件。观察board-*文件,发现此时只有board-generic.c被编译出来。

Step 2:
去掉board-generic.c有关其他CPU架构的内容,保留CONFIG_SOC_AM33XX架构的内容,此时会发现这个文件剩下的内容非常少。

Step 3:
还记得dts的内容吗?实际上board-generic.c里面是没有platform_device这个结构体的,因为它不需要,有下面的这个dts就好了,leds-gpio.c会根据dts的内容创建platform_device。

compatible = "gpio-leds";
led@1 {
        label = "maria-am335x:core";
        gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>;
        default-state = "on";
};

Step 4:
在下面这个函数里面添加调试代码:
static int gpio_led_probe(struct platform_device *pdev)

添加调试代码的工作就不一一细述了,总之就是各种printk,这里添来那里添,每个函数每个步骤都来添,直到大概明白这个函数里面发生了什么事情。膜拜下万能的prink~~

[    2.288198] Maria: ====================================
[    2.293747] ====> gpio_leds_create_of enter
[    2.298299] ====> gpio_leds_create_of start
[    2.302743] ====> count = 1
[    2.305838] ====> for_each_available_child_of_node
[    2.311158] ====> state = on
[    2.314210] ====> label = maria-am335x:core
[    2.318698] ====> gpio = 104
[    2.321751] ====> trigger = (null)
[    2.325356] ====> create_gpio_led enter
[    2.329469] ====> create_gpio_led
[    2.332980] ====> devm_gpio_request
[    2.336880] ====> name = maria-am335x:core
[    2.341229] ====> gpio_direction_output
[    2.345374] ====> INIT_WORK
[    2.348407] ====> led_classdev_register enter maria-am335x:core
[    2.356411] ====> Registered led device maria-am335x:core ****
[    2.362688] ====> platform_set_drvdata enter
[    2.367275] <<<<<<==========================

gpio_led_set函数用来执行GPIO操作:
led_dat->cdev.brightness_set = gpio_led_set;

那schedule_work什么时候会被执行呢?当下面这个变量被设置的时候:
led_dat->cdev.brightness_set;

所以将Kernel重新编译后启动:
root@ok335x:~# cd /sys/devices/leds/leds/maria-am335x\:core/
root@ok335x:/sys/devices/leds/leds/maria-am335x:core# echo 1 >> brightness
root@ok335x:/sys/devices/leds/leds/maria-am335x:core# echo 0 >> brightness

核心板上面的LED灯就会随着echo进去的值亮灭啦!
好吧,其实这里对platform设备的理解还不够深刻,除了echo之外也不知道其他的使用方式。但是这些内容还是留到以后正式学习platform的时候再看吧,这里只需要实现基本的操作,以及搞明白为什么打开第(34)节的模块时GPIO会busy,因此系统已经把这个GPIO作为platform设备啦~

下一节的内容是Ethernet。