首页 > 代码库 > Platform设备之gpio-led分析

Platform设备之gpio-led分析

Platform设备之gpio-led分析

led测试

以使用的9263板子为例,首先看board-sam9263ek.c的ek_board_init函数,

static void __init ek_board_init(void)

{

       /* Serial */

       at91_add_device_serial();

       /* USB Host */

       at91_add_device_usbh(&ek_usbh_data);

       /* USB Device */

       at91_add_device_udc(&ek_udc_data);

       /* SPI */

       at91_set_gpio_output(AT91_PIN_PE20, 1);        /* select spi0 clock */

       at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));

       /* Touchscreen */

       ek_add_device_ts();

       /* MMC */

       at91_add_device_mmc(1, &ek_mmc_data);

       /* Ethernet */

       at91_add_device_eth(&ek_macb_data);

       /* NAND */

       ek_add_device_nand();

       /* I2C */

       at91_add_device_i2c(NULL, 0);

       /* LCD Controller */

       at91_add_device_lcdc(&ek_lcdc_data);

       /* Push Buttons */

       ek_add_device_buttons();

       /* AC97 */

       at91_add_device_ac97(&ek_ac97_data);

       /* LEDs */

       at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds));

       at91_pwm_leds(ek_pwm_led, ARRAY_SIZE(ek_pwm_led));

       /* shutdown controller, wakeup button (5 msec low) */

       at91_sys_write(AT91_SHDW_MR, AT91_SHDW_CPTWK0_(10) | AT91_SHDW_WKMODE0_LOW

                            | AT91_SHDW_RTTWKEN);

}

 

然后我们看看ek_leds的定义,如下

/*

 * LEDs ... these could all be PWM-driven, for variable brightness

 */

static struct gpio_led ek_leds[] = {

       {     /* "right" led, green, userled2 (could be driven by pwm2) */

              .name                    = "ds2",

              .gpio                     = AT91_PIN_PB23,

              .active_low            = 1,

              .default_trigger      = "none",

       },

       {     /* "power" led, yellow (could be driven by pwm0) */

              .name                    = "ds3",

              .gpio                     = AT91_PIN_PB24,

              .default_trigger      = "heartbeat",

       }

};

因为我使用的板子上有2个led灯接的是pb23和pb24,然后再make menuconfig里面配置,如下

     Device Driversà[*]LED Supportà[*]LED class support

                                  [*]LED support for GPIO connected LEDS

                                  [*]LED Trigger support

                                     [*]LED heartbeat trigger

然后编译,成功后,下载进入板子,启动开发板,会发现ds3不停的闪烁,就是上面的heartbeat.

测试下ds2,如下

# echo 1 > /sys/class/leds/ds2/brightness

ds2会亮。

继续测试

# echo 0 > /sys/class/leds/ds2/brightness

ds2会灭掉。

 

测试就到此为止,接下来,分析下它的实现原理。

原理跟踪  

1,相关重要结构体介绍

struct platform_device {

       const char       * name;   // 设备名

       int          id;           // 设备编号

       struct device   dev;      // device 结构

       u32         num_resources;  //设备所使用的各类资源数量

       struct resource * resource;  // 资源 主要是io内存和irq

};

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

设备的编号,如果是唯一设备,则用-1表示。

 

    /* For the leds-gpio driver */

struct gpio_led {

       const char *name;

       char *default_trigger;

       unsigned        gpio;

       u8          active_low;

};

这里的name会显示在/sys相关的子目录下,如本例中的/sys/class/leds/ds2

default_trigger是用来和匹配某个设备闪烁的名字,比如本例中的heartbeat和none,none表示默认不闪烁。

Gpio就不用多解释了,就是所用的那个pin。

active_low  可以参考下面函数的红色部分

static void gpio_led_set(struct led_classdev *led_cdev,

       enum led_brightness value)

{

       struct gpio_led_data *led_dat =

              container_of(led_cdev, struct gpio_led_data, cdev);

       int level;

 

       if (value =http://www.mamicode.com/= LED_OFF)

              level = 0;

       else

              level = 1;

 

       if (led_dat->active_low)

              level = !level;

 

       /* Setting GPIOs with I2C/etc requires a task context, and we don‘t

        * seem to have a reliable way to know if we‘re already in one; so

        * let‘s just assume the worst.

        */

       if (led_dat->can_sleep) {

              led_dat->new_level = level;

              schedule_work(&led_dat->work);

       } else

              gpio_set_value(led_dat->gpio, level);

}    

也就是说,当active_low为1时,取值刚好相反,特意测试了下,把ds2的active_low取值为0后,一开机,ds2就亮了

# echo 1 > /sys/class/leds/ds2/brightness

ds2会灭。

继续测试

# echo 0 > /sys/class/leds/ds2/brightness

ds2会亮。

struct gpio_led_platform_data {

       int          num_leds;   // led灯的个数

       struct gpio_led *leds;  // leds指针,参考struct gpio_led

       int          (*gpio_blink_set)(unsigned gpio,

                                   unsigned long *delay_on,

                                   unsigned long *delay_off);

};

gpio_blink_set如下

static int gpio_blink_set(struct led_classdev *led_cdev,

       unsigned long *delay_on, unsigned long *delay_off)

{

       struct gpio_led_data *led_dat =

              container_of(led_cdev, struct gpio_led_data, cdev);

 

       return led_dat->platform_gpio_blink_set(led_dat->gpio, delay_on, delay_off);

}

简单的分析下,

struct gpio_led_data *led_dat =

              container_of(led_cdev, struct gpio_led_data, cdev);

这里调用的container_of是通过结构体成员的指针找到对应结构体的指针,这个技巧在linux内核编程使用常用。本例中container_of的第一个参数是结构体成员的指针,第2个参数为整个结构体的类型,第3个参数为传入的第一个参数即结构体成员的类型,container_of()返回的是整个结构体的指针。

得到led_dat后,然后返回led_dat->platform_gpio_blink_set(led_dat->gpio, delay_on, delay_off);这个现在还没有用到过,如果用到了,再补充。

上面又有个新的结构体,如下

struct gpio_led_data {

  struct led_classdev cdev;  //

  unsigned gpio;        // IO pin

  struct work_struct work; 

  u8 new_level;

  u8 can_sleep;

  u8 active_low;

  int (*platform_gpio_blink_set)(unsigned gpio,

                unsigned long *delay_on, unsigned long *delay_off);

};

这个结构题里面就是struct led_classdev cdev;相对复杂一点,放到后面具体的实例讲解,先说简单的。

struct work_struct work;

u8 new_level;

u8 can_sleep;

这3个主要是为了led的睡眠,可暂不考虑。

u8 active_low;

int (*platform_gpio_blink_set)(unsigned gpio,

                     unsigned long *delay_on, unsigned long *delay_off);

这2个可以参考struct gpio_led和struct gpio_led_platform_data

 

2,流程跟踪

(1)at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds));

进入到函数里面如下

/* ------------------------------------------------------------------------- */

 

#if defined(CONFIG_NEW_LEDS)

 

/*

 * New cross-platform LED support.

 */

 

static struct gpio_led_platform_data led_data;

 

static struct platform_device at91_gpio_leds_device = {

       .name                    = "leds-gpio",

       .id                 = -1,

       .dev.platform_data  = &led_data,

};

 

void __init at91_gpio_leds(struct gpio_led *leds, int nr)

{

       int i;

 

       if (!nr)

              return;

 

       for (i = 0; i < nr; i++)

              at91_set_gpio_output(leds[i].gpio, leds[i].active_low);  // 设置为输出,低

 

       led_data.leds = leds;         // led指针

       led_data.num_leds = nr;      // led 数量

       platform_device_register(&at91_gpio_leds_device);  // 平台设备注册

}

 

#else

void __init at91_gpio_leds(struct gpio_led *leds, int nr) {}

#endif

 

进入platform_device_register(&at91_gpio_leds_device);  // 平台设备注册

/**

 * platform_device_register - add a platform-level device

 * @pdev: platform device we‘re adding

 */

int platform_device_register(struct platform_device *pdev)

{

       device_initialize(&pdev->dev);

       return platform_device_add(pdev);

}

这里知道是怎么回事就行了,暂不深究。

调用platform_device_register后,若没问题,则注册平台设备成功。

然后我们进入到下一步platform_driver

(2) gpio-led的platform_driver

static struct platform_driver gpio_led_driver = {

       .probe            = gpio_led_probe,

       .remove          = __devexit_p(gpio_led_remove),

       .suspend  = gpio_led_suspend,

       .resume          = gpio_led_resume,

       .driver            = {

              .name      = "leds-gpio",

              .owner    = THIS_MODULE,

       },

};

 

static int __init gpio_led_init(void)

{

       return platform_driver_register(&gpio_led_driver);

}

 

static void __exit gpio_led_exit(void)

{

       platform_driver_unregister(&gpio_led_driver);

}

 

这里先简单介绍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 (*suspend_late)(struct platform_device *, pm_message_t state); //

       int (*resume_early)(struct platform_device *);

       int (*resume)(struct platform_device *);  // 恢复

       struct pm_ext_ops *pm;

       struct device_driver driver;

};

然后看看platform_driver_register干了些什么?

/**

 * platform_driver_register

 * @drv: platform driver structure

 */

int platform_driver_register(struct platform_driver *drv)

{

       drv->driver.bus = &platform_bus_type;

       if (drv->probe)

              drv->driver.probe = platform_drv_probe;

       if (drv->remove)

              drv->driver.remove = platform_drv_remove;

       if (drv->shutdown)

              drv->driver.shutdown = platform_drv_shutdown;

       if (drv->suspend)

              drv->driver.suspend = platform_drv_suspend;

       if (drv->resume)

              drv->driver.resume = platform_drv_resume;

       if (drv->pm)

              drv->driver.pm = &drv->pm->base;

       return driver_register(&drv->driver);

}

然后看下gpio_led_driver就比较好理解了,该platform_driver只有probe,remove,suspend,resume 4个函数指针。

.driver            = {

              .name      = "leds-gpio",

              .owner    = THIS_MODULE,

       },

这里的name用来表示匹配platform_device里的name,如果匹配了,则开始下一步的探测。

测试如下:

# cat /sys/class/leds/ds2/uevent

PHYSDEVPATH=/devices/platform/leds-gpio

PHYSDEVBUS=platform

PHYSDEVDRIVER=leds-gpio

 

  然后开始下一步的探测----probe

(3) gpio-led的gpio_led_probe

static int gpio_led_probe(struct platform_device *pdev

{

       struct gpio_led_platform_data *pdata = http://www.mamicode.com/pdev->dev.platform_data;

       struct gpio_led *cur_led;

       struct gpio_led_data *leds_data, *led_dat;

       int i, ret = 0;

 

       if (!pdata)

              return -EBUSY;

 

       leds_data = http://www.mamicode.com/kzalloc(sizeof(struct >

                            GFP_KERNEL);

       if (!leds_data)

              return -ENOMEM;

 

       for (i = 0; i < pdata->num_leds; i++) {

              cur_led = &pdata->leds[i];

              led_dat = &leds_data[i];

 

              ret = gpio_request(cur_led->gpio, cur_led->name);

              if (ret < 0)

                     goto err;

 

              led_dat->cdev.name = cur_led->name;

              led_dat->cdev.default_trigger = cur_led->default_trigger;

              led_dat->gpio = cur_led->gpio;

              led_dat->can_sleep = gpio_cansleep(cur_led->gpio);

              led_dat->active_low = cur_led->active_low;

              if (pdata->gpio_blink_set) {

                     led_dat->platform_gpio_blink_set = pdata->gpio_blink_set;

                     led_dat->cdev.blink_set = gpio_blink_set;

              }

              led_dat->cdev.brightness_set = gpio_led_set;

              led_dat->cdev.brightness = LED_OFF;

 

              gpio_direction_output(led_dat->gpio, led_dat->active_low);

 

              INIT_WORK(&led_dat->work, gpio_led_work);

 

              ret = led_classdev_register(&pdev->dev, &led_dat->cdev);

              if (ret < 0) {

                     gpio_free(led_dat->gpio);

                     goto err;

              }

       }

 

       platform_set_drvdata(pdev, leds_data);

 

       return 0;

 

err:

       if (i > 0) {

              for (i = i - 1; i >= 0; i--) {

                     led_classdev_unregister(&leds_data[i].cdev);

                     cancel_work_sync(&leds_data[i].work);

                     gpio_free(leds_data[i].gpio);

              }

       }

 

       kfree(leds_data);

 

       return ret;

}

 

struct gpio_led_platform_data *pdata = http://www.mamicode.com/pdev->dev.platform_data;

得到platform_device ------pdev结构体里面dev的私有平台指针platform_data

struct gpio_led *cur_led;  // 用于指向当前gpio_led_platform_data里面的gpio_led

struct gpio_led_data *leds_data, *led_dat;

leds_data用于申请内存空间的指针

led_dat用于指向当前gpio_led_data内存地址

leds_data = http://www.mamicode.com/kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds,

                            GFP_KERNEL);

       if (!leds_data)

              return -ENOMEM;

申请内存,若申请不到返回错误

 

然后看看循环里面的

cur_led = &pdata->leds[i];

led_dat = &leds_data[i];

用于指向当前gpio_led_platform_data里面的gpio_led

led_dat用于指向当前gpio_led_data内存地址

ret = gpio_request(cur_led->gpio, cur_led->name);

              if (ret < 0)

                     goto err;

申请gpio口,若失败返回错误。实际上这里总会成功,代码如下

static inline int gpio_request(unsigned gpio, const char *label)

{

       return 0;

}

 

led_dat->cdev.name = cur_led->name;

led_dat->cdev.default_trigger = cur_led->default_trigger;

led_dat->gpio = cur_led->gpio;

led_dat->can_sleep = gpio_cansleep(cur_led->gpio);

led_dat->active_low = cur_led->active_low;

就是把cur_led里面相应的数据赋给led_dat

 

if (pdata->gpio_blink_set) {

                     led_dat->platform_gpio_blink_set = pdata->gpio_blink_set;

                     led_dat->cdev.blink_set = gpio_blink_set;

              }

如果有设置gpio_blink_set,则把相应的指针都指向它,显然我们是没有设置的。

led_dat->cdev.brightness_set = gpio_led_set;

led_dat->cdev.brightness = LED_OFF;

把gpio_led_set指向类结构体led_classdev里的brightness_set。

gpio_direction_output(led_dat->gpio, led_dat->active_low);

设置相应io口为输出,并指定值

INIT_WORK(&led_dat->work, gpio_led_work);

准备好gpio_led的原子操作,可以查看workqueue.h

 

ret = led_classdev_register(&pdev->dev, &led_dat->cdev);

if (ret < 0) {

                     gpio_free(led_dat->gpio);

                     goto err;

              }

终于到注册设备了,可以松口气了。

进入到led_classdev_register

/**

 * led_classdev_register - register a new object of led_classdev class.

 * @dev: The device to register.

 * @led_cdev: the led_classdev structure for this device.

 */

int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

{

       int rc;

 

       led_cdev->dev = device_create_drvdata(leds_class, parent, 0, led_cdev,

                                         "%s", led_cdev->name);

       if (IS_ERR(led_cdev->dev))

              return PTR_ERR(led_cdev->dev);

 

       /* register the attributes */

       rc = device_create_file(led_cdev->dev, &dev_attr_brightness);

       if (rc)

              goto err_out;

 

       /* add to the list of leds */

       down_write(&leds_list_lock);

       list_add_tail(&led_cdev->node, &leds_list);

       up_write(&leds_list_lock);

 

       led_update_brightness(led_cdev);

 

#ifdef CONFIG_LEDS_TRIGGERS

       init_rwsem(&led_cdev->trigger_lock);

 

       rc = device_create_file(led_cdev->dev, &dev_attr_trigger);

       if (rc)

              goto err_out_led_list;

 

       led_trigger_set_default(led_cdev);

#endif

 

       printk(KERN_INFO "Registered led device: %s\n",

                     led_cdev->name);

 

       return 0;

 

#ifdef CONFIG_LEDS_TRIGGERS

err_out_led_list:

       device_remove_file(led_cdev->dev, &dev_attr_brightness);

       list_del(&led_cdev->node);

#endif

err_out:

       device_unregister(led_cdev->dev);

       return rc;

}

简单分析

led_cdev->dev = device_create_drvdata(leds_class, parent, 0, led_cdev,

                                         "%s", led_cdev->name);

查看device_create_drvdata,其实也就是device_create,如下

#define device_create_drvdata device_create

看下device_create功能,下面的注释说的很清楚了,主要就是创建一个设备并在sysfs下注册。

/**

 * device_create - creates a device and registers it with sysfs

 * @class: pointer to the struct class that this device should be registered to

 * @parent: pointer to the parent struct device of this new device, if any

 * @devt: the dev_t for the char device to be added

 * @drvdata: the data to be added to the device for callbacks

 * @fmt: string for the device‘s name

 *

 * This function can be used by char device classes.  A struct device

 * will be created in sysfs, registered to the specified class.

 *

 * A "dev" file will be created, showing the dev_t for the device, if

 * the dev_t is not 0,0.

 * If a pointer to a parent struct device is passed in, the newly created

 * struct device will be a child of that device in sysfs.

 * The pointer to the struct device will be returned from the call.

 * Any further sysfs files that might be required can be created using this

 * pointer.

 *

 * Note: the struct class passed to this function must have previously

 * been created with a call to class_create().

 */

struct device *device_create(struct class *class, struct device *parent,

                          dev_t devt, void *drvdata, const char *fmt, ...)

{

       va_list vargs;

       struct device *dev;

 

       va_start(vargs, fmt);

       dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);

       va_end(vargs);

       return dev;

}

本例中这步就把设备注册上了,设备号之类的也分配了。

 

接下来继续看led_classdev_register

/* register the attributes */

       rc = device_create_file(led_cdev->dev, &dev_attr_brightness);

       if (rc)

              goto err_out;

增加属性

 

/* add to the list of leds */

       down_write(&leds_list_lock);

       list_add_tail(&led_cdev->node, &leds_list);

       up_write(&leds_list_lock);

放到led队列中去

led_update_brightness(led_cdev);

更新led_cdev的属性

#ifdef CONFIG_LEDS_TRIGGERS

       init_rwsem(&led_cdev->trigger_lock);

 

       rc = device_create_file(led_cdev->dev, &dev_attr_trigger);

       if (rc)

              goto err_out_led_list;

 

       led_trigger_set_default(led_cdev);

#endif

主要是增加led_cdev的dev_attr_trigger属性。

 

如果一切顺利,led_classdev_register就注册成功了。

 

然后回到gpio_led_probe函数

最后还有一句

platform_set_drvdata(pdev, leds_data);

设为平台设备私有数据。

 

(4) gpio-led的其他

static int __devexit gpio_led_remove(struct platform_device *pdev)

{

       int i;

       struct gpio_led_platform_data *pdata = http://www.mamicode.com/pdev->dev.platform_data;

       struct gpio_led_data *leds_data;

 

       leds_data = http://www.mamicode.com/platform_get_drvdata(pdev);

 

       for (i = 0; i < pdata->num_leds; i++) {

              led_classdev_unregister(&leds_data[i].cdev);

              cancel_work_sync(&leds_data[i].work);

              gpio_free(leds_data[i].gpio);

       }

 

       kfree(leds_data);

 

       return 0;

}

移除时先通过platform_get_drvdata得到指针地址,先释放掉相关资源,如释放掉注册的class,工作队列,申请的IO资源,最后是否掉申请的指针leds_data。