点亮屏幕是由mcu控制,但是arm要在开机初始化framebuffer后通过一个lcd_ready脚来通知mcu点亮屏幕
关机/休眠的灭屏与掉电要等mcu发出关机/休眠信号,通知hmi保存重要数据后,消息中心才执行关机命令,关机后通过lcd_ready脚点平通知mcu允许掉电
开机亮屏部分,这部分比较容易定义一个io引脚,放到framebuffer初始化完成的的驱动代码中,代码如下,基于linux3.14.28
引入一个io引脚定义
power-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;
mxcfb1: fb@0 {
compatible = "fsl,mxc_sdc_fb";
disp_dev = "ldb";
interface_pix_fmt = "RGB24";
default_bpp = <24>;
int_clk = <0>;
late_init = <0>;
power-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;
status = "disabled";
};
在显示驱动里调用io引脚函数,开机显示就绪后拉低io,mcu检测到引脚拉低后点亮屏幕背光
struct power_gpios {
int power_gpios_gpio;
unsigned long gpio_level; // 0: on 1: off
int g_first_ldb;
};
static struct power_gpios g_power_gpios = {0,0,1};
g_power_gpios.power_gpios_gpio = of_get_named_gpio_flags(np, "power-gpios", 0, &flags);
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
printk("[power_gpios] mxcfb_probe gpio_is_valid, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
if(!gpio_request(g_power_gpios.power_gpios_gpio, "power_gpios"))
{
printk("[power_gpios] mxcfb_probe gpio_request \n");
gpio_direction_output(g_power_gpios.power_gpios_gpio, 1);
gpio_set_value(g_power_gpios.power_gpios_gpio, 0);
g_power_gpios.gpio_level = 0;
}
}
通知关机的部分相对坑多,在开部分已经被坑了,只是不影响功能所以看不出来
关机逻辑是为了应对在没有只读文件系统的前提下频繁掉电引起的emmc程序丢失,数据损坏的问题
mcu发出关机命令后,系统进行关机,关机完成后对lcd_ready引脚拉高电平,mcu检测到高电平后方可给arm断电
这里采用了增加属性节点的方式来进行io引脚的控制
加入了class属性的节点,这部分没有保留就拿了一个类似的写在文章里
#include <linux/miscdevice.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/device.h>
struct shutdown_io {
int shutdown_io_gpio;
unsigned long gpio_level; // 0:PA mute on 1:PA mute off
};
static struct shutdown_io g_shutdown_io;
static struct class *shutdown_io_class;
static ssize_t enable_store(struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int rc;
unsigned long gpio_level = 0;
rc = kstrtoul(buf, 0, &gpio_level);
if (rc)
return rc;
if (gpio_is_valid(g_shutdown_io.shutdown_io_gpio))
{
gpio_direction_output(g_shutdown_io.shutdown_io_gpio, 1);
gpio_set_value(g_shutdown_io.shutdown_io_gpio, gpio_level);
g_shutdown_io.gpio_level = gpio_level;
//printk("[bgk] 43 shutdown_io enable_store, gpio_level = %ld \n", gpio_level);
}
else
{
printk("[bgk] 46 shutdown_io gpio_is_valid failed \n");
}
return count;
}
static ssize_t enable_show(struct class *class, struct class_attribute *attr, char *buf)
{
if (gpio_is_valid(g_shutdown_io.shutdown_io_gpio))
{
//printk("[bgk] 34 shutdown_io enable_show, gpio_level = %ld \n", g_shutdown_io.gpio_level);
}
else
{
printk("[bgk] 35 shutdown_io gpio_is_valid failed \n");
return -1;
}
return sprintf(buf, "%ld\n", g_shutdown_io.gpio_level);
}
static CLASS_ATTR(enable, 0755, enable_show, enable_store);
static int shutdown_io_probe(struct platform_device *pdev)
{
int ret = -1;
enum of_gpio_flags flag;
struct device_node *shutdown_io_node = pdev->dev.of_node;
g_shutdown_io.shutdown_io_gpio = of_get_named_gpio_flags(shutdown_io_node, "enable-gpios", 0, &flag);
//printk("[bgk] 8001 shutdown_io_probe, get gpio id successful \n");
if(!gpio_is_valid(g_shutdown_io.shutdown_io_gpio))
{
printk("[bgk] 8002 invalid shutdown_io-gpios: %d\n", g_shutdown_io.shutdown_io_gpio);
return -1;
}
if(gpio_request(g_shutdown_io.shutdown_io_gpio, "shutdown_io_gpio"))
{
printk("[bgk] 8003 shutdown_io gpio request failed! \n");
return ret;
}
gpio_direction_output(g_shutdown_io.shutdown_io_gpio, 1);
gpio_set_value(g_shutdown_io.shutdown_io_gpio, 1);
g_shutdown_io.gpio_level = 1;
shutdown_io_class = class_create(THIS_MODULE, "shutdown_io");
if (IS_ERR(shutdown_io_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(shutdown_io_class));
return PTR_ERR(shutdown_io_class);
}
ret = class_create_file(shutdown_io_class, &class_attr_enable);
if(ret < 0)
{
printk("[bgk] 8004 shutdown_io class_create_file failed! \n");
return -ENOMEM;
}
return 0;
}
static int shutdown_io_remove(struct platform_device *pdev)
{
if(!gpio_is_valid(g_shutdown_io.shutdown_io_gpio))
{
printk("[bgk] 8005 invalid shutdown_io-gpios: %d\n", g_shutdown_io.shutdown_io_gpio);
return -1;
}
gpio_set_value(g_shutdown_io.shutdown_io_gpio, 0);
gpio_free(g_shutdown_io.shutdown_io_gpio);
class_remove_file(shutdown_io_class, &class_attr_enable);
class_destroy(shutdown_io_class);
return 0;
}
static struct of_device_id shutdown_io_of_match[] = {
{ .compatible = "shutdown_io" },
{ }
};
MODULE_DEVICE_TABLE(of, shutdown_io_of_match);
static struct platform_driver shutdown_io_driver = {
.driver = {
.name = "shutdown_io",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(shutdown_io_of_match),
},
.probe = shutdown_io_probe,
.remove = shutdown_io_remove,
};
module_platform_driver(shutdown_io_driver);
MODULE_DESCRIPTION("shutdown_io Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:shutdown_io");
这部分代码加在了drivers/video/mxc/mxc_ipuv3_fb.c的mxcfb_probe函数中最末尾
信心满满去验证属性节点,结果当头一棒,运行提示权限不足运行失败
怀疑是属性节点的使用条件问题,多次检测属性节点的使用条件,在平台驱动里调用,打印返回值也都是正确的,就是在应用层访问属性节点的时候提示权限问题,但这个提示显然不是因为创建属性节点时指定的权限问题
static CLASS_ATTR(enable, 0755, enable_show, enable_store);
很是迷茫,百度这个函数,竟然没有任何收获,倒是看源码得出正确时返回0,这是也确实返回0
class_create_file
在mxc_ipuv3_fb.c里看到有引用了device_create_file,设备属性节点的创建,而且能正确运转,于是将class属性节点更换为device属性节点
struct power_gpios {
int power_gpios_gpio;
unsigned long gpio_level; // 0: on 1: off
int g_first_ldb;
};
static struct power_gpios g_power_gpios = {0,0,1};
static struct class *power_gpios_class;
static ssize_t enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
printk("[power_gpios] enable_show, power_gpios_gpio = %d \n", g_power_gpios.power_gpios_gpio);
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
printk("[bgk] 34 power_gpios enable_show, gpio_level = %ld \n", g_power_gpios.gpio_level);
}
else
{
printk("[bgk] 35 power_gpios gpio_is_valid failed, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
return -1;
}
return sprintf(buf, "%ld\n", g_power_gpios.gpio_level);
}
static ssize_t enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int rc;
unsigned long gpio_level = 0;
printk("[power_gpios] enable_store, power_gpios_gpio = %d \n", g_power_gpios.power_gpios_gpio);
rc = kstrtoul(buf, 0, &gpio_level);
if (rc)
return rc;
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
gpio_direction_output(g_power_gpios.power_gpios_gpio, 1);
gpio_set_value(g_power_gpios.power_gpios_gpio, gpio_level);
g_power_gpios.gpio_level = gpio_level;
printk("[bgk] 43 power_gpios enable_store, gpio_level = %ld \n", gpio_level);
}
else
{
printk("[bgk] 35 power_gpios gpio_is_valid failed, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
}
return count;
}
static DEVICE_ATTR(lcd_power, S_IWUSR | S_IRUGO, enable_show, enable_store);
ret = device_create_file(fbi->dev, &dev_attr_lcd_power);
printk("[bgk] device_create_file ret = %d \n", ret);
if (ret)
dev_err(&pdev->dev, "Error %d on creating fdev_attr_enable "
" device propety\n", ret);
更换完之后出了属性节点的路径不同,无法读取节点的现象是一样的,故排除了class属性节点或者device属性节点的原因,问题定位到enbale_show和enable_store这两个函数
static DEVICE_ATTR(lcd_power, S_IWUSR | S_IRUGO, enable_show, enable_store);
将这两个函数留空后,正确运行,定位到操作io的函数,通过打印发现drivers/video/mxc/mxc_ipuv3_fb.c的mxcfb_probe函数被执行了3次,说明有3个设备被找到并进行了注册,这个是导致IO引脚运行一场的根本原因
mxcfb1: fb@0 {
compatible = "fsl,mxc_sdc_fb";
disp_dev = "ldb";
interface_pix_fmt = "RGB24";
default_bpp = <24>;
int_clk = <0>;
late_init = <0>;
power-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;
status = "disabled";
};
// modified-end by xiongqin.bi for 720p
mxcfb2: fb@1 {
compatible = "fsl,mxc_sdc_fb";
disp_dev = "hdmi";
interface_pix_fmt = "RGB24";
mode_str ="1920x1080M@60";
default_bpp = <24>;
int_clk = <0>;
late_init = <0>;
status = "disabled";
};
mxcfb3: fb@2 {
compatible = "fsl,mxc_sdc_fb";
disp_dev = "lcd";
interface_pix_fmt = "RGB565";
mode_str ="CLAA-WVGA";
default_bpp = <16>;
int_clk = <0>;
late_init = <0>;
status = "disabled";
};
mxcfb4: fb@3 {
compatible = "fsl,mxc_sdc_fb";
disp_dev = "ldb";
interface_pix_fmt = "RGB666";
default_bpp = <16>;
int_clk = <0>;
late_init = <0>;
status = "disabled";
};
将dts中的显示设备遍历到驱动里在mxcfb_dispdrv_init函数进行,disp_dev数组则是存放着每次注册的显示设备
static int mxcfb_dispdrv_init(struct platform_device *pdev,
struct fb_info *fbi)
{
struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
struct mxc_dispdrv_setting setting;
char disp_dev[32], *default_dev = "lcd";
int ret = 0;
setting.if_fmt = plat_data->interface_pix_fmt;
setting.dft_mode_str = plat_data->mode_str;
setting.default_bpp = plat_data->default_bpp;
if (!setting.default_bpp)
setting.default_bpp = 16;
setting.fbi = fbi;
if (!strlen(plat_data->disp_dev)) {
memcpy(disp_dev, default_dev, strlen(default_dev));
disp_dev[strlen(default_dev)] = '\0';
} else {
memcpy(disp_dev, plat_data->disp_dev,
strlen(plat_data->disp_dev));
disp_dev[strlen(plat_data->disp_dev)] = '\0';
}
mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
if (IS_ERR(mxcfbi->dispdrv)) {
ret = PTR_ERR(mxcfbi->dispdrv);
dev_err(&pdev->dev, "NO mxc display driver found!\n");
return ret;
} else {
/* fix-up */
mxcfbi->ipu_di_pix_fmt = setting.if_fmt;
mxcfbi->default_bpp = setting.default_bpp;
ret = mxcfb_get_crtc(&pdev->dev, mxcfbi, setting.crtc);
if (ret)
return ret;
dev_dbg(&pdev->dev, "di_pixfmt:0x%x, bpp:0x%x, di:%d, ipu:%d\n",
setting.if_fmt, setting.default_bpp,
mxcfbi->ipu_di, mxcfbi->ipu_id);
}
dev_info(&pdev->dev, "registered mxc display driver %s\n", disp_dev);
return ret;
}
定位到原因之后就做过滤,只在注册ldb驱动的时候创建和调用属性节点
struct power_gpios {
int power_gpios_gpio;
unsigned long gpio_level; // 0: on 1: off
int g_first_ldb;
};
static struct power_gpios g_power_gpios = {0,0,1};
static struct class *power_gpios_class;
static ssize_t enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
printk("[power_gpios] enable_show, power_gpios_gpio = %d \n", g_power_gpios.power_gpios_gpio);
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
printk("[bgk] 34 power_gpios enable_show, gpio_level = %ld \n", g_power_gpios.gpio_level);
}
else
{
printk("[bgk] 35 power_gpios gpio_is_valid failed, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
return -1;
}
return sprintf(buf, "%ld\n", g_power_gpios.gpio_level);
}
static ssize_t enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int rc;
unsigned long gpio_level = 0;
printk("[power_gpios] enable_store, power_gpios_gpio = %d \n", g_power_gpios.power_gpios_gpio);
rc = kstrtoul(buf, 0, &gpio_level);
if (rc)
return rc;
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
gpio_direction_output(g_power_gpios.power_gpios_gpio, 1);
gpio_set_value(g_power_gpios.power_gpios_gpio, gpio_level);
g_power_gpios.gpio_level = gpio_level;
printk("[bgk] 43 power_gpios enable_store, gpio_level = %ld \n", gpio_level);
}
else
{
printk("[bgk] 35 power_gpios gpio_is_valid failed, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
}
return count;
}
static DEVICE_ATTR(lcd_power, S_IWUSR | S_IRUGO, enable_show, enable_store);
static int mxcfb_probe(struct platform_device *pdev)
{
// int lcd_ready;
enum of_gpio_flags flags;
struct device_node *np = pdev->dev.of_node;
struct ipuv3_fb_platform_data *plat_data;
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
struct resource *res;
int ret = 0;
dev_dbg(&pdev->dev, "%s enter\n", __func__);
pdev->id = of_alias_get_id(pdev->dev.of_node, "mxcfb");
if (pdev->id < 0) {
dev_err(&pdev->dev, "can not get alias id\n");
return pdev->id;
}
plat_data = devm_kzalloc(&pdev->dev, sizeof(struct
ipuv3_fb_platform_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
pdev->dev.platform_data = plat_data;
ret = mxcfb_get_of_property(pdev, plat_data);
if (ret < 0) {
dev_err(&pdev->dev, "get mxcfb of property fail\n");
return ret;
}
/* Initialize FB structures */
fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
if (!fbi) {
ret = -ENOMEM;
goto init_fbinfo_failed;
}
ret = mxcfb_option_setup(pdev, fbi);
if (ret)
goto get_fb_option_failed;
mxcfbi = (struct mxcfb_info *)fbi->par;
mxcfbi->ipu_int_clk = plat_data->int_clk;
mxcfbi->late_init = plat_data->late_init;
mxcfbi->first_set_par = true;
ret = mxcfb_dispdrv_init(pdev, fbi);
if (ret < 0)
goto init_dispdrv_failed;
ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
if (ret < 0) {
dev_err(&pdev->dev, "ipu%d-di%d already in use\n",
mxcfbi->ipu_id, mxcfbi->ipu_di);
goto ipu_in_busy;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res && res->start && res->end) {
fbi->fix.smem_len = res->end - res->start + 1;
fbi->fix.smem_start = res->start;
fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len);
/* Do not clear the fb content drawn in bootloader. */
if (!mxcfbi->late_init)
memset(fbi->screen_base, 0, fbi->fix.smem_len);
}
mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id);
if (IS_ERR(mxcfbi->ipu)) {
ret = -ENODEV;
goto get_ipu_failed;
}
/* first user uses DP with alpha feature */
if (!g_dp_in_use[mxcfbi->ipu_id]) {
mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF;
mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK;
mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
mxcfbi->ipu_ch = MEM_BG_SYNC;
/* Unblank the primary fb only by default */
if (pdev->id == 0)
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK;
else
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
ret = mxcfb_register(fbi);
if (ret < 0)
goto mxcfb_register_failed;
ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch,
true, 0x80);
ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
ret = mxcfb_setup_overlay(pdev, fbi, res);
if (ret < 0) {
mxcfb_unregister(fbi);
goto mxcfb_setupoverlay_failed;
}
g_dp_in_use[mxcfbi->ipu_id] = true;
ret = device_create_file(mxcfbi->ovfbi->dev,
&dev_attr_fsl_disp_property);
if (ret)
dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
"file for disp property\n",
ret);
ret = device_create_file(mxcfbi->ovfbi->dev,
&dev_attr_fsl_disp_dev_property);
if (ret)
dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
"file for disp device "
"propety\n", ret);
} else {
mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK;
mxcfbi->ipu_alp_ch_irq = -1;
mxcfbi->ipu_ch = MEM_DC_SYNC;
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
ret = mxcfb_register(fbi);
if (ret < 0)
goto mxcfb_register_failed;
}
platform_set_drvdata(pdev, fbi);
ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property);
if (ret)
dev_err(&pdev->dev, "Error %d on creating file for disp "
"property\n", ret);
ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
if (ret)
dev_err(&pdev->dev, "Error %d on creating file for disp "
" device propety\n", ret);
// add bi.xiongqin
printk("[bgk] pdev->dev.platform_data->disp_dev = %s \n ", plat_data->disp_dev);
if (!strcmp(plat_data->disp_dev, "ldb"))
{
printk("[bgk] g_first_ldb = %d \n ", g_power_gpios.g_first_ldb);
if (g_power_gpios.g_first_ldb == true)
{
g_power_gpios.g_first_ldb = false;
ret = device_create_file(fbi->dev, &dev_attr_lcd_power);
printk("[bgk] device_create_file ret = %d \n", ret);
if (ret)
dev_err(&pdev->dev, "Error %d on creating fdev_attr_enable "
" device propety\n", ret);
g_power_gpios.power_gpios_gpio = of_get_named_gpio_flags(np, "power-gpios", 0, &flags);
printk("[power_gpios] gpio_is_valid, power_gpios_gpio = %d \n", g_power_gpios.power_gpios_gpio);
if (gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
printk("[power_gpios] mxcfb_probe gpio_is_valid, ret = %d \n", gpio_is_valid(g_power_gpios.power_gpios_gpio));
if(!gpio_request(g_power_gpios.power_gpios_gpio, "power_gpios"))
{
printk("[power_gpios] mxcfb_probe gpio_request \n");
gpio_direction_output(g_power_gpios.power_gpios_gpio, 1);
gpio_set_value(g_power_gpios.power_gpios_gpio, 0);
g_power_gpios.gpio_level = 0;
}
}
}
}
// end bi.xiongqin
return 0;
mxcfb_setupoverlay_failed:
mxcfb_register_failed:
get_ipu_failed:
ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
ipu_in_busy:
init_dispdrv_failed:
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
get_fb_option_failed:
init_fbinfo_failed:
return ret;
}
static int mxcfb_remove(struct platform_device *pdev)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
struct mxcfb_info *mxc_fbi = fbi->par;
if (!fbi)
return 0;
device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
device_remove_file(fbi->dev, &dev_attr_fsl_disp_property);
mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
mxcfb_unregister(fbi);
mxcfb_unmap_video_memory(fbi);
if (mxc_fbi->ovfbi) {
device_remove_file(mxc_fbi->ovfbi->dev,
&dev_attr_fsl_disp_dev_property);
device_remove_file(mxc_fbi->ovfbi->dev,
&dev_attr_fsl_disp_property);
mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
mxcfb_unsetup_overlay(fbi);
mxcfb_unmap_video_memory(mxc_fbi->ovfbi);
}
ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di);
if (&fbi->cmap)
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
// add bi.xiongqin
if(!gpio_is_valid(g_power_gpios.power_gpios_gpio))
{
printk("[bgk] 8005 invalid power_gpios-gpios: %d\n", g_power_gpios.power_gpios_gpio);
return -1;
}
gpio_set_value(g_power_gpios.power_gpios_gpio, 0);
gpio_free(g_power_gpios.power_gpios_gpio);
device_remove_file(fbi->dev, &dev_attr_lcd_power);
// add bi.xiongqin
return 0;
}
最后还要在关机的时候调用该属性节点,增加/etc/rc0.d下在S40umountfs之前脚本S34power_gpios.sh
#!/bin/bash
echo 1 > /sys/class/graphics/fb0/lcd_power