imx28 leds 平台总线驱动
成都莱得科技
led驱动从实质上来说很简单,只是在linux平台总线下,要理清这层层的关系还是要费点功夫了。
为什么是led驱动呢,柿子总是软的好吃!其实也不全是了,led代码比较少,用它来入门平台总线驱动的写法还是不错的选择滴。imx28的led驱动是使用pwm调节亮度的,驱动是挂接在linux 标准led platform总线模型上的,即leds 子系统。进入正题:
leds的代码结构:
# led core
obj-$(config_new_leds) = led-core.o
obj-$(config_leds_class) = led-class.o
obj-$(config_leds_triggers) = led-triggers.o
obj-$(config_leds_gpio) = leds-gpio.o
obj-$(config_leds_mxs) = leds-mxs-pwm.o
主要的文件就这么几个了。先来看看led-core.c 吧,这个文件无比的具有个性和让人兴奋,个性是只有变量的声明,兴奋是代码极其的少。
declare_rwsem(leds_list_lock);
export_symbol_gpl(leds_list_lock);
list_head(leds_list);
export_symbol_gpl(leds_list);
所有代码就怎么几行,而且就是声明了2个变量。貌似注定打酱油来得,不过还真不是打酱油哦。leds_list_lock: 防止竞态,并发的锁,leds_list: 链接所有注册的led的全局链表 .这2个全局数据无比的重要,后面你就会发现了。
在开始分析led-class.c之前,先来介绍下一些重要的数据结构:
/include/linux/leds.h
亮度描述:
-
enum led_brightness {
-
led_off = 0, //熄灭
-
led_half = 127,
-
led_full = 255,
-
};
led的实例
-
struct led_classdev {
-
const char *name;
-
int brightness; //当前亮度,imx28 通过控制pwm控制led端的电压
-
int max_brightness; //最大亮度 led_full
-
int flags; //标志,目前只支持 led_suspended
-
/*设置led的亮度,不可以睡眠*/
-
void (*brightness_set)(struct led_classdev *led_cdev,
-
enum led_brightness brightness);
-
/* 获取亮度 */
-
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
-
/* 激活硬件加速闪烁 */
-
int (*blink_set)(struct led_classdev *led_cdev,
-
unsigned long *delay_on,
-
unsigned long *delay_off);
-
-
struct device *dev;
-
struct list_head node; /* 串联起所有已经注册的led_classdev */
-
const char *default_trigger; /* 默认触发器 */
-
#ifdef config_leds_triggers
-
/* 这个读写信号量保护触发器数据 */
-
struct rw_semaphore trigger_lock;
-
struct led_trigger *trigger; //触发器指针
-
/*触发器使用的链表节点,用来连接同一触发器上的所有led_classdev */
-
struct list_head trig_list;
-
void *trigger_data; //触发器使用的私有数据
-
#endif
-
};
触发器的结构体
-
struct led_trigger {
-
const char *name; //触发器名字
-
/*led_classdev和触发器建立连接时会调用这个方法。*/
-
void (*activate)(struct led_classdev *led_cdev);
-
/*led_classdev和触发器取消连接时会调用这个方法 */
-
void (*deactivate)(struct led_classdev *led_cdev);
-
-
rwlock_t leddev_list_lock; //保护链表的锁
-
struct list_head led_cdevs;
-
-
/* 连接下一个已注册触发器的链表节点,所有已注册的触发器都会被加入一个全局链表*/
-
struct list_head next_trig;
-
};
平台设备相关的led数据结构
-
struct led_info {
-
const char *name; //led 的名字
-
char *default_trigger; //默认触发器
-
int flags;
-
};
-
-
struct led_platform_data {
-
int num_leds; //led 个数 imx28 有2个led
-
struct led_info *leds; //每个led 信息
-
};
在来介绍下和imx28平台相关的数据结构
arch/arm/plat-mxs/include/mach/device.h
-
struct mxs_pwm_led {
-
struct led_classdev dev; //每个led对应一个led_classdev
-
const char *name; //名字
-
unsigned int pwm; //pwm通道,最大8个通道
-
};
-
struct mxs_pwm_leds_plat_data {
-
unsigned int num;
-
struct mxs_pwm_led *leds;
-
};
-
struct mxs_pwm_leds {
-
struct clk *pwm_clk; //pwm 模块的时钟
-
unsigned int base; //寄存器基地址
-
unsigned int led_num; //led 个数,这里开发板只有2个led
-
struct mxs_pwm_led *leds; //指向2个led
-
};
继续分析 driver/leds/led-class.c ,初始化函数在sys/class 目录下产生leds子目录,按照leds模型注册的设备都会产生这个目录。
-
static int __init leds_init(void)
-
{
-
leds_class = class_create(this_module, "leds");
-
if (is_err(leds_class))
-
return ptr_err(leds_class);
-
leds_class->suspend = led_suspend;
-
leds_class->resume = led_resume;
-
return 0;
-
}
-
-
static void __exit leds_exit(void)
-
{
-
class_destroy(leds_class);
-
}
-
-
static device_attr(brightness, 0644, led_brightness_show, led_brightness_store);
-
static device_attr(max_brightness, 0444, led_max_brightness_show, null);
-
#ifdef config_leds_triggers
-
static device_attr(trigger, 0644, led_trigger_show, led_trigger_store);
-
#endif
属性文件,在用户空间可以访问。后面会有提及到.
-
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
-
{
-
int rc;
-
/*创建device 在leds_class生成的目录下*/
-
led_cdev->dev = device_create(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);
-
//在sys/class/leds/led-pwmx 下创建一个属性文件。
-
if (rc)
-
goto err_out;
-
#ifdef config_leds_triggers
-
init_rwsem(&led_cdev->trigger_lock);
-
#endif
-
/* add to the list of leds */
-
down_write(&leds_list_lock);
-
list_add_tail(&led_cdev->node, &leds_list);//将新的led加入全局链表
-
up_write(&leds_list_lock);
-
if (!led_cdev->max_brightness)
-
led_cdev->max_brightness = led_full;
-
rc = device_create_file(led_cdev->dev, &dev_attr_max_brightness);
-
if (rc)
-
goto err_out_attr_max;
-
led_update_brightness(led_cdev);//获取led的当前亮度 更新led_cdev->brightness
-
#ifdef config_leds_triggers
-
rc = device_create_file(led_cdev->dev, &dev_attr_trigger);
-
if (rc)
-
goto err_out_led_list;
-
led_trigger_set_default(led_cdev);//为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_max_brightness);
-
#endif
-
err_out_attr_max:
-
device_remove_file(led_cdev->dev, &dev_attr_brightness);
-
list_del(&led_cdev->node);
-
err_out:
-
device_unregister(led_cdev->dev);
-
return rc;
-
}
led_classdev_register注册的struct led_classdev会被加入leds_list链表.(led-core中定义了这个list)
注销led_classdev:
void led_classdev_unregister(struct led_classdev *led_cdev)
led_brightness_show 和 led_brightness_store 分别显示和设置亮度 。
led_trigger_show用于读取当前触发器的名字,led_trigger_store用于指定触发器的名字, 它会寻找所有已注册的触发器,找到同名的并设置为当前led的触发器。
-
static ssize_t led_brightness_show(struct device *dev,
-
struct device_attribute *attr, char *buf)
-
{
-
struct led_classdev *led_cdev = dev_get_drvdata(dev);
-
led_update_brightness(led_cdev);
-
return sprintf(buf, "%u\n", led_cdev->brightness); //显示当前亮度
-
}
-
-
static ssize_t led_brightness_store(struct device *dev,
-
struct device_attribute *attr, const char *buf, size_t size)
-
{
-
/*取得led_classdev ,放在device的driver_data里。*/
-
struct led_classdev *led_cdev = dev_get_drvdata(dev);
-
ssize_t ret = -einval;
-
char *after;
-
unsigned long state = simple_strtoul(buf, &after, 10); //字符串形式的亮度值转换成数字。
-
size_t count = after - buf;
-
if (*after && isspace(*after))
-
count;
-
if (count == size) {
-
ret = count;
-
if (state == led_off)
-
led_trigger_remove(led_cdev);
-
//如果亮度值为0,移除trigger led_cdev->trigger = null;
-
led_set_brightness(led_cdev, state); //设置亮度值
-
}
-
return ret;
-
}
led_trigger分析 /driver/leds/led-triggers.c
注册触发器
int led_trigger_register(struct led_trigger *trigger)
注册的trigger会加入到全局链表trigger_list中。
首选确定触发器的名字不存在,然后遍历所有注册的触发器,如果发现那个led_classdev 的 default_trigger和这个触发器名字相同,就将这个触发器设置成led_classdev 的默认触发器。
设置触发器上所有的led为某个亮度
void led_trigger_event(struct led_trigger *trigger, enum led_brightness brightness);
触发器和led的连接
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);//建立连接
//建立连接的时候会调用触发器的activate方法
void led_trigger_remove(struct led_classdev *led_cdev);//取消连接。
取消连接的时候会调用触发器的deactivate方法
void led_trigger_set_default(struct led_classdev *led_cdev);
//在所有已注册的触发器中寻找led_cdev的默认触发器并调用 led_trigger_set建立连接
led trigger --->>> led设备模型 ---->>>led硬件
trigger好比是控制led类设备的算法,这个算法决定着led什么时候亮什么时候暗。led trigger类设备可以是现实的硬件设备,比如ide硬盘,也可以是系统心跳等事件。
imx28 的led 驱动:
/driver/leds/leds-mxs-pwm.c
-
static struct platform_driver mxs_pwm_led_driver = {
-
.probe = mxs_pwm_led_probe,
-
.remove = __devexit_p(mxs_pwm_led_remove),
-
.driver = {
-
.name = "mxs-leds",
-
},
-
};
-
static int __init mxs_pwm_led_init(void)
-
{
-
return platform_driver_register(&mxs_pwm_led_driver);
-
}
-
static void __exit mxs_pwm_led_exit(void)
-
{
-
platform_driver_unregister(&mxs_pwm_led_driver);
-
}
mxs_pwm_led_init 里面会调用platform_driver_register,最终会调到mxs_pwm_led_driver的probe函数。即mxs_pwm_led_probe。进去一探究竟!
-
static int __devinit mxs_pwm_led_probe(struct platform_device *pdev)
-
{
-
struct mxs_pwm_leds_plat_data *plat_data;
-
struct resource *res;
-
struct led_classdev *led;
-
unsigned int pwmn;
-
int leds_in_use = 0, rc = 0;
-
int i;
-
plat_data = (struct mxs_pwm_leds_plat_data *)pdev->dev.platform_data;
-
//pdev->dev.platform_data 在mx28evk_init_leds()设置了
-
if (plat_data == null)
-
return -enodev;
-
res = platform_get_resource(pdev, ioresource_mem, 0);
-
//得到资源,即pwm寄存器的物理地址
-
if (res == null)
-
return -enodev;
-
leds.base = (unsigned int)io_address(res->start);
-
//物理地址转化为虚拟地址(静态映射方式)
-
mxs_reset_block((void __iomem *)leds.base, 1);
-
leds.led_num = plat_data->num;
-
if (leds.led_num <= 0 || leds.led_num > config_mxs_pwm_channels)
-
return -efault;
-
leds.leds = plat_data->leds;
-
if (leds.leds == null)
-
return -efault;
-
leds.pwm_clk = clk_get(&pdev->dev, "pwm"); //得到pwm控制器的时钟
-
if (is_err(leds.pwm_clk)) {
-
rc = ptr_err(leds.pwm_clk);
-
return rc;
-
}
-
clk_enable(leds.pwm_clk); //开启时钟
-
for (i = 0; i < leds.led_num; i) {
-
pwmn = leds.leds[i].pwm;
-
if (pwmn >= config_mxs_pwm_channels) {//检测pwm的通道
-
dev_err(&pdev->dev,
-
"[led-pwm%d]:pwm %d doesn't exist\n",
-
i, pwmn);
-
continue;
-
}
-
led = &(leds.leds[i].dev);
-
led->name = leds.leds[i].name;
-
led->brightness = led_half;
-
led->flags = 0;
-
led->brightness_set = mxs_pwm_led_brightness_set;
-
led->default_trigger = 0;
-
//led_trigger_register 中会设置default_trigger
-
rc = led_classdev_register(&pdev->dev, led);
-
if (rc < 0) {
-
dev_err(&pdev->dev,
-
"unable to register led device %d (err=%d)\n",
-
i, rc);
-
continue;
-
}
-
leds_in_use;
-
/* set default brightness */
-
mxs_pwm_led_brightness_set(led, led_half);
-
}
-
if (leds_in_use == 0) {
-
dev_info(&pdev->dev, "no pwm leds available\n");
-
clk_disable(leds.pwm_clk);
-
clk_put(leds.pwm_clk);
-
return -enodev;
-
}
-
return 0;
-
}
-
-
static struct mxs_pwm_led mx28evk_led_pwm[2] = {
-
[0] = {
-
.name = "led-pwm0",
-
.pwm = 0,//通道0
-
},
-
[1] = {
-
.name = "led-pwm1",
-
.pwm = 1,//通道1
-
},
-
};
-
-
struct mxs_pwm_leds_plat_data mx28evk_led_data = {
-
.num = array_size(mx28evk_led_pwm),
-
.leds = mx28evk_led_pwm,
-
};
-
static struct resource mx28evk_led_res = {
-
.flags = ioresource_mem,
-
.start = pwm_phys_addr,
-
.end = pwm_phys_addr 0x3fff, //pwm寄存器地址范围
-
};
-
-
static void __init mx28evk_init_leds(void) //系统启动时候调用
-
{
-
struct platform_device *pdev;
-
pdev = mxs_get_device("mxs-leds", 0);
-
if (pdev == null || is_err(pdev))
-
return;
-
pdev->resource = &mx28evk_led_res;
-
pdev->num_resources = 1;
-
pdev->dev.platform_data = &mx28evk_led_data;
-
mxs_add_device(pdev, 3);
-
}
-
#define bm_pwm_ctrl_pwm_enable ((1<<(config_mxs_pwm_channels)) - 1)
-
#define bf_pwm_ctrl_pwm_enable(n) ((1<<(n)) & bm_pwm_ctrl_pwm_enable)
-
#define bf_pwm_periodn_settings \
-
(bf_pwm_periodn_cdiv(5) | /* divide by 64 */ \
-
bf_pwm_periodn_inactive_state(3) | /* low */
-
bf_pwm_periodn_active_state(2) | /* high */ \
-
bf_pwm_periodn_period(led_full)) /* 255 cycles */
-
//24mhz 进行64分频
-
//这里代码注释好像有问题。通过查看数据手册,inactive_state(3)时 pwm output
-
//定义为1,active_state(2) 时,为0
-
//一个pwm为256个clk
-
-
static void mxs_pwm_led_brightness_set(struct led_classdev *pled,
-
enum led_brightness value)
-
{
-
struct mxs_pwm_led *pwm_led;
-
pwm_led = container_of(pled, struct mxs_pwm_led, dev);
-
if (pwm_led->pwm < config_mxs_pwm_channels) {
-
//先关闭pwm
-
__raw_writel(bf_pwm_ctrl_pwm_enable(pwm_led->pwm),
-
leds.base hw_pwm_ctrl_clr);
-
//设置无效时间和无效时间
-
__raw_writel(bf_pwm_activen_inactive(led_full) |
-
bf_pwm_activen_active(value),
-
leds.base hw_pwm_activen(pwm_led->pwm));
-
//设置周期
-
__raw_writel(bf_pwm_periodn_settings,
-
leds.base hw_pwm_periodn(pwm_led->pwm));
-
//打开pwm
-
__raw_writel(bf_pwm_ctrl_pwm_enable(pwm_led->pwm),
-
leds.base hw_pwm_ctrl_set);
-
}
-
}
看看几个主要的寄存器吧:
1:pwm control and status register (hw_pwm_ctrl):pwm0-pwm7的控制,在驱动的probe时候设置,主要是设置pwm通道的可以状态。
2:pwm channel 0 active register
31:16 inactive rw 无效时间。
15: 0 active rw 有效时间。
有效和无效只是相对的,因为在pwm channel n period register中有效时间和无效时间都会有对应的状态,在设置寄存器时只要把有效时间及有效时间的状态、无效时间和无效时间的状态对应的设置成我们想要的效果就可以了。
3:pwm channel period register
31:27 rsrvd2 保留位。
26 hsadc_out pwm output to high speed adc
25 hsadc_clk_sel hsadc clock select for pwm output
24 matt_sel 多芯片附件模式
23 matt多芯片附件模式
22-20 cdiv 时钟分频比率
0x0 div_1 — divide by 1.
0x01 div_2 — divide by 2.
0x02div_4 — divide by 4.
0x03div_8 — divide by 8.
0x04div_16 — divide by 16.
0x05div_64 — divide by 64.
0x06div_256 — divide by 256.
0x07div_1024 — divide by 1024.
19–18inactive_state 有效值的状态
0x0 表示hi_z(高阻)。
0x2 表示低电平。
0x3 表示高电平。
17:16 active_state 无效时间的状态,设置值含义同上。
15:0 period 波形周期,一个波形周期等于此值减一的时钟周期。
官方文档给出了个pwm应用的例子:
到此,leds 也分析完毕,现在在用户空间测试:
先写个简单的sh
#vi led.sh
#!bin/sh
path=/sys/class/leds/led-pwm1/brightness
led_full=255
led_off=0
led_half=127
while true
do
echo $led_full > $path
sleep 1
echo $led_off > $path
sleep 1
echo $led_half > $path
sleep 1
done
#chmod u x led.sh
# . ./led.sh (或者 source led.sh)
开发板的led1在3种状态之间闪烁.
阅读(3263) | 评论(0) | 转发(0) |