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 == 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 = 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 = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds,

                            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 = 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 = 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 = pdev->dev.platform_data;

       struct gpio_led_data *leds_data;

 

       leds_data = 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值