十七、(正点原子)Linux LCD驱动

一、Framebuffer设备

        在 Linux 中应用程序通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。

        先来看一下裸机 LCD 驱动如下:
        ①、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、 hspw、
hbp、 hfp、 vspw、 vbp 和 vfp 等信息。
        ②、初始化 LCD 像素时钟。
        ③、设置 RGBLCD 显存。
        ④、应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。

         在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显存是需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。

        为了解决上述问题, Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb,因此大家在以后的 Linux 学习中见到“Framebuffer”或者“fb”的话第一反应应该想到 RGBLCD或者显示设备。 fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通过访问/dev/fbX 这个设备就可以访问 LCD。
        NXP 官方的 Linux 内核默认已经开启了 LCD 驱动,因此我们是可以看到/dev/fb0 这样一个设备,如图所示:

        图中的/dev/fb0 就是 LCD 对应的设备文件, /dev/fb0 是个字符设备,因此肯定有file_operations 操作集, fb 的 file_operations 操作集,如下所示:

定义在 drivers/video/fbdev/core/fbmem.c 文件中

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,

#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif

	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,

#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif

#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif

	.llseek =	default_llseek,
};

         Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构体, fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设备都必须有一个 fb_info。换言之就是, LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info的过程。

定义在 include/linux/fb.h 文件里
struct fb_info {
	atomic_t count;
	int node;
	int flags;
	struct mutex lock;		        /* 互斥锁 */
	struct mutex mm_lock;		    /* 互斥锁,用于 fb_mmap 和 smem_*域*/
	struct fb_var_screeninfo var;	/* 当前可变参数 */
	struct fb_fix_screeninfo fix;	/* 当前固定参数 */
	struct fb_monspecs monspecs;	/* 当前显示器特性 */
	struct work_struct queue;	    /* 帧缓冲事件队列 */
	struct fb_pixmap pixmap;	    /* 图像硬件映射 */
	struct fb_pixmap sprite;	    /* 光标硬件映射 */
	struct fb_cmap cmap;		    /* 当前调色板 */
	struct list_head modelist;     /* 当前模式列表 */
	struct fb_videomode *mode;	    /* 当前视频模式 */

#ifdef CONFIG_FB_BACKLIGHT        /* 如果 LCD 支持背光的话 */
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;    /* 背光设备 */

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;       /* 帧缓冲操作函数集 */
	struct device *device;		/* 父设备 */
	struct device *dev;		    /* 当前 fb 设备 */
	int class_flag;             * 私有 sysfs 标志 */
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* 虚拟内存基地址(屏幕显存) */
	unsigned long screen_size;	/* 虚拟内存大小(屏幕显存大小) */ 
	void *pseudo_palette;		/* 伪 16 位调色板 */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

        fb_info 结构体的成员变量很多,我们重点关注 var、 fix、 fbops、 screen_base、screen_size和 pseudo_palette。

        fb_info结构体创建好初始化完成之后,使用register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。函数原型:

定义在 drivers/video/fbdev/mxsfb.c 种

int register_framebuffer(struct fb_info *fb_info)

        fb_info:需要注册的 fb_info。
        返回值: 0,成功;负值,失败。
       

        卸载 fb_info结构体使用函数unregister_framebuffer

定义在 drivers/video/fbmem.c 种

int unregister_framebuffer(struct fb_info *fb_info);

        fb_info:需要卸载的 fb_info。 

        返回值: 0,成功;负值,失败。 

二、LCD驱动简析

         LCD 直接操作寄存器驱动主要分两部分:
                ①、获取 LCD 的屏幕参数。
                ②、根据屏幕参数信息来初始化 eLCDIF 接口控制器。


        不同分辨率的 LCD 屏幕其 eLCDIF 控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们本章的主要工作就是修改设备树, NXP 官方的设备树已经添加了 LCD 设备节点,只是此节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 寸 480*272 编写的,我们需要将其改为我们所使用的屏幕参数。

         我们简单看一下 NXP 官方编写的 Linux 下的 LCD 驱动,打开 imx6ull.dtsi,然后找到 lcdif节点内容,如下所示:

lcdif: lcdif@021c8000 {
    compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
    reg = <0x021c8000 0x4000>;
    interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
             <&clks IMX6UL_CLK_LCDIF_APB>,
             <&clks IMX6UL_CLK_DUMMY>;
    clock-names = "pix", "axi", "disp_axi";
    status = "disabled";
};

        lcdif 节点信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是完整的 lcdif 节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向 imx6ullalientek-emmc.dts 中的 lcdif 节点添加其他的属性信息。
        可以看出 lcdif 节点的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c, mxsfb.c就是 I.MX6ULL 的 LCD 驱动文件,在此文件中找到如下内容:

1362 static const struct of_device_id mxsfb_dt_ids[] = {
1363     { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
1364     { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
1365     { /* sentinel */ }
1366 };
......
1625 static struct platform_driver mxsfb_driver = {
1626     .probe = mxsfb_probe,
1627     .remove = mxsfb_remove,
1628     .shutdown = mxsfb_shutdown,
1629     .id_table = mxsfb_devtype,
1630     .driver = {
1631         .name = DRIVER_NAME,
1632         .of_match_table = mxsfb_dt_ids,
1633         .pm = &mxsfb_pm_ops,
1634     },
1635 };
1636
1637 module_platform_driver(mxsfb_driver);

        从示例代码可以看出,这是一个标准的 platform 驱动,当驱动和设备匹配以后mxsfb_probe 函数就会执行。mxsfb_probe 函数的主要工作内容为:

        ①、申请 fb_info。
        ②、初始化 fb_info 结构体中的各个成员变量。
        ③、初始化 eLCDIF 控制器。
        ④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。

        函数内容:

1369 static int mxsfb_probe(struct platform_device *pdev)
1370 {
1371     const struct of_device_id *of_id =
1372     of_match_device(mxsfb_dt_ids, &pdev->dev);
1373     struct resource *res;
1374     struct mxsfb_info *host;
1375     struct fb_info *fb_info;
1376     struct pinctrl *pinctrl;
1377     int irq = platform_get_irq(pdev, 0);
1378     int gpio, ret;
1379
            ......
1394
1395     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1396     if (!res) {
1397         dev_err(&pdev->dev, "Cannot get memory IO resource\n");
1398         return -ENODEV;
1399     }
1401     host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info),
                             GFP_KERNEL);
1402     if (!host) {
1403         dev_err(&pdev->dev, "Failed to allocate IO resource\n");
1404         return -ENOMEM;
1405     }
1406
1407     fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
1408     if (!fb_info) {
1409         dev_err(&pdev->dev, "Failed to allocate fbdev\n");
1410         devm_kfree(&pdev->dev, host);
1411         return -ENOMEM;
1412     }
1413     host->fb_info = fb_info;
1414     fb_info->par = host;
1415
1416     ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
1417     dev_name(&pdev->dev), host);
1418     if (ret) {
1419         dev_err(&pdev->dev, "request_irq (%d) failed with
1420         error %d\n", irq, ret);
1421         ret = -ENODEV;
1422         goto fb_release;
1423     }
1425     host->base = devm_ioremap_resource(&pdev->dev, res);
1426     if (IS_ERR(host->base)) {
1427         dev_err(&pdev->dev, "ioremap failed\n");
1428         ret = PTR_ERR(host->base);
1429         goto fb_release;
1430     }
......
1461
1462     fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) *
1463                                             16, GFP_KERNEL);
1464     if (!fb_info->pseudo_palette) {
1465         ret = -ENOMEM;
1466         goto fb_release;
1467     }
1468
1469     INIT_LIST_HEAD(&fb_info->modelist);
1471     pm_runtime_enable(&host->pdev->dev);
1472
1473     ret = mxsfb_init_fbinfo(host);
1474     if (ret != 0)
1475         goto fb_pm_runtime_disable;
1476
1477     mxsfb_dispdrv_init(pdev, fb_info);
1478
1479     if (!host->dispdrv) {
1480         pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
1481         if (IS_ERR(pinctrl)) {
1482             ret = PTR_ERR(pinctrl);
1483             goto fb_pm_runtime_disable;
1484         }
1485     }
1486
1487     if (!host->enabled) {
1488         writel(0, host->base + LCDC_CTRL);
1489         mxsfb_set_par(fb_info);
1490         mxsfb_enable_controller(fb_info);
1491         pm_runtime_get_sync(&host->pdev->dev);
1492     }
1493
1494     ret = register_framebuffer(fb_info);
1495     if (ret != 0) {
1496         dev_err(&pdev->dev, "Failed to register framebuffer\n");
1497         goto fb_destroy;
1498     }
        ......
1525     return ret;
1526 }

        第 1374 行, host 结构体指针变量,表示 I.MX6ULL 的 LCD 的主控接口, mxsfb_info 结构体是 NXP 定义的针对 I.MX 系列 SOC 的 Framebuffer 设备结构体。也就是我们前面一直说的设备结构体,此结构体包含了 I.MX 系列 SOC 的 Framebuffer 设备详细信息,比如时钟、 eLCDIF控制器寄存器基地址、 fb_info 等。
        第 1395 行,从设备树中获取 eLCDIF 接口控制器的寄存器首地址,设备树中 lcdif 节点已经设置了 eLCDIF 寄存器首地址为 0X021C8000,因此 res=0X021C8000。
        第 1401 行,给 host 申请内存, host 为 mxsfb_info 类型结构体指针。
        第 1407 行,给 fb_info 申请内存,也就是申请 fb_info。

        第 1413~1414 行,设置 host 的 fb_info 成员变量为 fb_info,设置 fb_info 的 par 成员变量为host。通过这一步就将前面申请的 host 和 fb_info 联系在了一起。
        第 1416 行,申请中断,中断服务函数为 mxsfb_irq_handler。
        第 1425 行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到 host 的 base 成员变量。因此通过访问 host 的 base 成员即可访问 I.MX6ULL 的整个 eLCDIF寄存器。其实在 mxsfb.c 中已经定义了 eLCDIF 各个寄存器相比于基地址的偏移值,如下所示:

         第1462 行,给 fb_info中的 pseudo_palette申请内存。

        第 1473 行,调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info 的 var、 fix、 fbops,screen_base 和 screen_size。其中 fbops 是 Framebuffer 设备的操作集, NXP 提供的 fbops 为mxsfb_ops,内容如下:

987 static struct fb_ops mxsfb_ops = {
988     .owner = THIS_MODULE,
989     .fb_check_var = mxsfb_check_var,
990     .fb_set_par = mxsfb_set_par,
991     .fb_setcolreg = mxsfb_setcolreg,
992     .fb_ioctl = mxsfb_ioctl,
993     .fb_blank = mxsfb_blank,
994     .fb_pan_display = mxsfb_pan_display,
995     .fb_mmap = mxsfb_mmap,
996     .fb_fillrect = cfb_fillrect,
997     .fb_copyarea = cfb_copyarea,
998     .fb_imageblit = cfb_imageblit,
999 };

        mxsfb_init_fbinfo 函数通过调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 LCD 的各个参数信息。最后, mxsfb_init_fbinfo函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)。
        第 1489~1490 行,设置 eLCDIF 控制器的相应寄存器。
        第 1494 行,最后调用 register_framebuffer 函数向 Linux 内核注册 fb_info。

三、LCD驱动程序编写

        前面已经说了, 6ULL 的 eLCDIF 接口驱动程序 NXP 已经编写好了,因此 LCD 驱动部分我们不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。重点要注意三个地方:
        ①、 LCD 所使用的 IO 配置。
        ②、 LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数。
        ③、 LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息。

        1、LCD屏幕IO配置

        打开 imx6ull-alientek-emmc.dts 文件,在 iomuxc 节点中找到如下内容:

1 pinctrl_lcdif_dat: lcdifdatgrp {
2   fsl,pins = <
3      MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
4      MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
5      MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
6      MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
7      MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
8      MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79
9      MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79
10     MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79
11     MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79
12     MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79
13     MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79
14     MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79
15     MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79
16     MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79
17     MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79
18     MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79
19     MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79
20     MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79
21     MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79
22     MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79
23     MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79
24     MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79
25     MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79
26     MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
27   >;
28 };
29
30 pinctrl_lcdif_ctrl: lcdifctrlgrp {
31     fsl,pins = <
32         MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
33         MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
34         MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
35         MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
36      >;
37     pinctrl_pwm1: pwm1grp {
38         fsl,pins = <
39             MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
40     >;
41 };

        第 2~27 行,子节点 pinctrl_lcdif_dat,为 RGB LCD 的 24 根数据线配置项。
        第 30~36 行,子节点 pinctrl_lcdif_ctrl, RGB LCD 的 4 根控制线配置项,包括 CLK、ENABLE、 VSYNC 和 HSYNC。
        第 37~40 行,子节点 pinctrl_pwm1, LCD 背光 PWM 引脚配置项。这个引脚要根据实际
情况设置,这里我们建议LCD 的背光 IO 尽量和半导体厂商的官方开发板一致。
       

        2、LCD屏幕参数节点信息修改

        继续在 imx6ull-alientek-emmc.dts 文件中找到 lcdif 节点,节点内容如下所示:

1 &lcdif {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
4                  &pinctrl_lcdif_ctrl
5                  &pinctrl_lcdif_reset>;
6     display = <&display0>;
7     status = "okay";
8 
9    display0: display { /* LCD 属性信息 */
10         bits-per-pixel = <16>; /* 一个像素占用几个 bit */
11         bus-width = <24>; /* 总线宽度 */
12
13         display-timings {
14             native-mode = <&timing0>; /* 时序信息 */
15             timing0: timing0 {
16                 clock-frequency = <9200000>; /* LCD 像素时钟,单位 Hz */
17                 hactive = <480>; /* LCD X 轴像素个数 */
18                 vactive = <272>; /* LCD Y 轴像素个数 */
19                 hfront-porch = <8>; /* LCD hfp 参数 */
20                 hback-porch = <4>; /* LCD hbp 参数 */
21                 hsync-len = <41>; /* LCD hspw 参数 */
22                 vback-porch = <2>; /* LCD vbp 参数 */
23                 vfront-porch = <4>; /* LCD vfp 参数 */
24                 vsync-len = <10>; /* LCD vspw 参数 */
25
26                 hsync-active = <0>; /* hsync 数据线极性 */
27                 vsync-active = <0>; /* vsync 数据线极性 */
28                 de-active = <1>; /* de 数据线极性 */
29                 pixelclk-active = <0>; /* clk 数据线先极性 */
30                 };
31          };
32    };
33 };

        第 3 行, pinctrl-0 属性, LCD 所使用的 IO 信息,这里用到了 pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和 pinctrl_lcdif_reset 这三个 IO 相关的节点,前两个在示例代码 59.3.1 中已经讲解了。pinctrl_lcdif_reset 是 LCD 复位 IO 信息节点,正点原子的 I.MX6U-ALPHA 开发板的 LCD 没有用到复位 IO,因此 pinctrl_lcdif_reset 可以删除掉。
        第 6 行, display 属性,指定 LCD 属性信息所在的子节点,这里为 display0,下面就是 display0子节点内容。
        第 9~32 行, display0 子节点,描述 LCD 的参数信息,第 10 行的 bits-per-pixel 属性用于指明一个像素占用的 bit 数,默认为 16bit。我们将 LCD 配置为 RGB888 模式,因此一个像素点占用 24bit, bits-per-pixel 属性要改为 24。第 11 行的 bus-width 属性用于设置数据线宽度,因为要配置为 RGB888 模式,因此 bus-width 也要设置为 24。
        第 13~30 行,这几行非常重要!因为这几行设置了 LCD 的时序参数信息, NXP 官方的 EVK开发板使用了一个 4.3 寸的 480*272 屏幕,因此这里默认是按照 NXP 官方的那个屏幕参数设置的。我们需要对应步通屏幕的信息修改值:

        3、LCD屏幕背光点信息 

        正点原子的 LCD 接口背光控制 IO 连接到了 I.MX6U 的 GPIO1_IO08 引脚上, GPIO1_IO08
复用为 PWM1_OUT,通过 PWM 信号来控制 LCD 屏幕背光的亮度,正点原子 I.MX6U-ALPHA 开发板的 LCD 背光引脚和 NXP 官方 EVK 开发板的背光引脚一样,因此背光的设备树节点是不需要修改的,如果是步同的引脚如何设置?

        首先是 GPIO1_IO08 这个 IO 的配置,在 imx6ull-alientek-emmc.dts 中找到如下内容

pinctrl_pwm1: pwm1grp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
			   >;
};

        pinctrl_pwm1 节点就是 GPIO1_IO08 的配置节点,可以看出,设置 GPIO1_IO08这个 IO 复用为 PWM1_OUT,并且设置电气属性值为 0x110b0。
        LCD 背光要用到 PWM1,因此也要设置 PWM1 节点,在 imx6ull.dtsi 文件中找到如下内容:

pwm1: pwm@02080000 {
    compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
    reg = <0x02080000 0x4000>;
    interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_PWM1>,
             <&clks IMX6UL_CLK_PWM1>;
    clock-names = "ipg", "per";
    #pwm-cells = <2>;
};

        继续在 imx6ull-alientek-emmc.dts 文件中找到向 pwm1追加的内容,如下所示:

&pwm1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm1>;
	status = "okay";
};

        设置 pwm1 所使用的 IO 为 pinctrl_pwm1,将 status 设置为 okay。根据这个设备树的模式,将引脚设置为其他就可以了。

        pwm 和相关的 IO 已经准备好了,但是 Linux 系统怎么知道PWM1_OUT 就是控制 LCD背光的呢?因此我们还需要一个节点来将 LCD 背光和 PWM1_OUT连 接 起 来 。 这 个 节 点 就 是 backlight , backlight 节 点 描 述 可 以 参 考Documentation/devicetree/indings/video/backlight/pwm-backlight.txt 这个文档:

此文档详细讲解了backlight 节点该如何去创建,这里大概总结一下:
        ①、节点名称要为“backlight”。
        ②、节点的 compatible 属性值要为“pwm-backlight”,因此可以通过在 Linux 内核中搜索 pwm-backlight ”来 查 找 PWM 背 光 控 制 驱 动 程 序 , 这 个 驱 动 程 序 文 件 为
drivers/video/backlight/pwm_bl.c
        ③、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,
pwm 频率设置为 200Hz(NXP 官方推荐设置)。
        ④、 brightness-levels 属性描述亮度级别,范围为 0~255, 0 表示 PWM 占空比为 0%,就
是亮度最低, 255 表示 100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。       

        ⑤、default-brightness-level属性为默认亮度级别。

        根据上述 5 点设置 backlight 节点, imx6ullalientekemmc.dts 文件中找到如下内容:

backlight {
    compatible = "pwm-backlight";
    pwms = <&pwm1 0 5000000>;
    brightness-levels = <0 4 8 16 32 64 128 255>;
    default-brightness-level = <6>;
    status = "okay";
};

        设置背光使用 pwm1, PWM 频率为 200Hz。设置背 8 级背光(0~7),分别为 0、 4、 8、 16、 32、 64、 128、 255,对应占空比为0%、 1.57%、 3.13%、 6.27%、 12.55%、 25.1%、 50.19%、 100%,如果嫌少的话可以自行添加一些其他的背光等级值。设置默认背光等级为 6,也就是 50.19%的亮度。


四、运行测试

        1、LCD屏幕基本测试

        首先我们先使能 Linux logo 显示,Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我们的 LCD 驱动基本就工作正常了。这个 logo 显示是要配置的,不过 Linux 内核一般都会默认开启 logo 显示,打开 Linux内核图形化配置界面,按下路径找到对应的配置项:

-> Device Drivers
    -> Graphics support
        -> Bootup logo (LOGO [=y])
            -> Standard black and white Linux logo
            -> Standard 16-color Linux logo
            -> Standard 224-color Linux logo



        图种这三个选项分别对应黑白、 16 位、 24 位色彩格式的 logo,我们把这三个都选中,都编译进 Linux 内核里面。设置好以后保存退出,重新编译 Linux 内核,编译完成以后使用新编译出来的 imx6ull-alientek-emmc.dtb 和 zImage 镜像启动系统,如果 LCD 驱动工作正常的话就会在 LCD 屏幕左上角出现一个彩色的小企鹅 logo,屏幕背景色为黑色,


        2、设置LCD作为终端控制台

        我一直使用MobaXterm作为Linux开发板终端,开发板通过串口和 MobaXterm进行通信。
现在已经驱动起来 LCD 了,所以可以设置 LCD 作为终端,也就是开发板使用自己的显示
设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将 LCD 设置为终端控制
台的方法如下:
        ①、设置bootargs环境变量添加上console=tty1 这一句代码

        ②、修改/etc/inittab文件加入:tty1::askfirst:-bin/sh

        3、LCD背光调节

        背光设备树节点设置了 8 个等级的背光调节,可以设置为 0~7,我们可以通过设置背光等级来实现 LCD 背光亮度的调节,进入如下目录

/sys/devices/platform/backlight/backlight/backlight

 

        图中的 brightness 表示当前亮度等级, max_bgigntness 表示最大亮度等级。

        使用如下命令改变亮度:

echo 7 > brightness

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/843415.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

go中map

文章目录 Map简介哈希表与Map的概念Go语言内建的Map类型Map的声明Map的初始化Map的访问Map的添加和修改Map的删除Map的遍历 Map的基本使用Map的声明与初始化Map的访问与操作Map的删除Map的遍历Map的并发问题实现线程安全的Map 3. Map的访问与操作3.1 访问Map元素代码示例&#…

微信小程序 button样式设置为图片的方法

微信小程序 button样式设置为图片的方法 background-image background-size与background-repeat与border:none;是button必须的 <view style" position: relative;"><button class"customer-service-btn" style"background-image: url(./st…

新华三H3CNE网络工程师认证—VLAN使用场景与原理

通过华三的技术原理与VLAN配置来学习&#xff0c;首先介绍VLAN&#xff0c;然后介绍VLAN的基本原理&#xff0c;最后介绍VLAN的基本配置。 文章目录 一、传统以太网问题1、广播域范围过大2、分割广播域 二、如何实现VLAN1、VLAN技术达到的效果2、VLAN数值的编号范围3、仿真&am…

这届打工人,快把单休卷成职场用工标配了……

最近&#xff0c;小柴有个朋友跟小柴吐槽&#xff0c;自己被「灵活就业」大半年了&#xff0c;有点扛不住&#xff0c;就准备去送外卖&#xff0c;结果第一天赚了38&#xff0c;反手电瓶车罚了50…… 心灰意冷的他&#xff0c;最终还是在某BOSS上充个值&#xff0c;没日没夜的投…

手持式气象站:便携科技,掌握微观气象的利器

手持式气象站&#xff0c;顾名思义&#xff0c;是一种可以随身携带的气象监测设备。它小巧轻便&#xff0c;通常配备有温度、湿度、风速、风向、气压等多种传感器&#xff0c;能够实时测量并显示各种气象参数。不仅如此&#xff0c;它还具有数据存储、数据传输、远程控制等多种…

搭建博客系统#Golang

WANLI 博客系统 项目介绍 基于vue3和gin框架开发的前后端分离个人博客系统&#xff0c;包含md格式的文本编辑展示&#xff0c;点赞评论收藏&#xff0c;新闻热点&#xff0c;匿名聊天室&#xff0c;文章搜索等功能。 点击跳转&#xff1a;Github 项目开源地址 功能展示 B 站…

实战篇(十):使用Processing创建可爱花朵:实现随机位置、大小和颜色的花朵

使用Processing创建可爱花朵 0.效果预览1. 引言2. 设置Processing环境3. 创建花朵类4. 实现花瓣绘制5. 绘制可爱的笑脸6. 鼠标点击生成花朵7. 完整代码8. 总结与扩展0.效果预览 在本教程中,我们将使用Processing编程语言来创建一个可爱的花朵生成器。通过封装花朵为一个类,并…

使用shedlock实现分布式互斥执行

前言 前序章节&#xff1a;springboot基础(82):分布式定时任务解决方案shedlock 如果你不清楚shedlock&#xff0c;建议先阅读前序章节&#xff0c;再来查看本文。 如果我们不在spring环境下&#xff0c;如何使用shedlock实现分布式互斥执行&#xff1f; 我们可以使用shedl…

Elasticsearch 7.x入门学习-Java API操作

1 创建项目 在idea开发工具中创建Maven项目 修改 pom 文件&#xff0c;增加 Maven 依赖关系 <dependencies><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.8.0</versi…

ubuntu2204配置anacondacuda4090nvidia驱动

背景 某个机房的几台机器前段时间通过dnat暴露至公网后被入侵挖矿&#xff0c;为避免一些安全隐患将这几台机器执行重装系统操作&#xff1b; 这里主要记录配置nvidia驱动及cuda&anaconda。 步骤 大概分为几个步骤 禁用nouveau配置grub显示菜单install nvidia-driveri…

Linux云计算 |【第一阶段】ENGINEER-DAY3

主要内容&#xff1a; LVM逻辑卷管理、VDO、RAID磁盘阵列、进程管理 一、新建逻辑卷 1、什么是逻辑卷 逻辑卷&#xff08;Logical Volume&#xff09;是逻辑卷管理&#xff08;Logical Volume Management&#xff0c;LVM&#xff09;系统中的一个概念。LVM是一种用于磁盘管理…

SpringBoot集成MQTT实现交互服务通信

引言 本文是springboot集成mqtt的一个实战案例。 gitee代码库地址&#xff1a;源码地址 一、什么是MQTT MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&…

[Armbian] 部署Docker版Home Assistent,安装HACS并连接米家设备

title: [Armbian] 部署Docker版Home Assistent&#xff0c;安装HACS并连接米家设备 date: 2024-07-21T10:51:23Z lastmod: 2024-07-21T11:40:39Z [Armbian] 部署Docker版Home Assistent&#xff0c;安装HACS并连接米家设备 官网&#xff1a;Home Assistant (home-assistant.i…

sql常见50道查询练习题

sql常见50道查询练习题 1. 表创建1.1 表创建1.2 数据插入 2. 简单查询例题(3题&#xff09;2.1 查询"李"姓老师的数量2.2 查询男生、女生人数2.3 查询名字中含有"风"字的学生信息 3. 日期相关例题(6题&#xff09;3.1 查询各学生的年龄3.2 查询本周过生日的…

Yolo-World网络模型结构及原理分析(一)——YOLO检测器

文章目录 概要一、整体架构分析二、详细结构分析YOLO检测器1. Backbone2. Head3.各模块的过程和作用Conv卷积模块C2F模块BottleNeck模块SPPF模块Upsampling模块Concat模块 概要 尽管YOLO&#xff08;You Only Look Once&#xff09;系列的对象检测器在效率和实用性方面表现出色…

【GraphRAG】微软 graphrag 效果实测

GraphRAG 本文将基于以下来源&#xff0c;对Microsoft GraphRAG分析优缺点、以及示例实测分析。 1. Source 代码仓库&#xff1a; Welcome to GraphRAGhttps://microsoft.github.io/graphrag/ 微软文章1&#xff08;2024.2.13&#xff09;&#xff1a;GraphRAG: Unlocking…

通过albumentation对目标检测进行数据增强(简单直接)

albumentation官方文档看不懂&#xff1f;xml文件不知道如何操作&#xff1f;下面只需要修改部分代码即可上手使用 要使用这个方法之前需要按照albumentation这个库还有一些辅助库,自己看着来安装就行 pip install albumentation pip install opencv-python pip install json…

阿尔泰科技利用485模块搭建自动灌溉系统实现远程控制

自动灌溉系统又叫土壤墒情监控系统&#xff0c;土壤墒情监控系统主要实现固定站无人值守情况下的土壤墒情数据的自动采集和无线传输&#xff0c;数据在监控中心自动接收入库&#xff1b;可以实现24小时连续在线监控并将监控数据通过有线、无线等传输方式实时传输到监控中心生成…

破解反爬虫策略 /_guard/auto.js(二)实战

这次我们用上篇文章讲到的方法来真正破解一下反爬虫策略&#xff0c;这两个案例是两个不同的网站&#xff0c;一个用的是 /_guard/auto.js&#xff0c;另一个用的是/_guard/delay_jump.js。经过解析发现这两个网站用的反爬虫策略基本是一模一样&#xff0c;只不过在js混淆和生成…

FOG Project 文件名命令注入漏洞复现(CVE-2024-39914)

0x01 产品简介 FOG是一个开源的计算机镜像解决方案,旨在帮助管理员轻松地部署、维护和克隆大量计算机。FOG Project 提供了一套功能强大的工具,使用户能够快速部署操作系统、软件和配置设置到多台计算机上,从而节省时间和精力。该项目支持基于网络的 PXE 启动、镜像创建和还…