为方便讲解,我将linux原来的源码进行了裁剪,确保了重要的功能,并使其正常工作,各位可以先将其复制到vscode,对照阅读
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/fb.h>
#include <linux/interrupt.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/delay.h> /* [修复] 添加 delay.h 以支持 udelay */
#include <video/of_display_timing.h>
#include <video/videomode.h>
#define DRIVER_NAME "minimal_mxsfb"
/* ================= 寄存器定义 ================= */
#define LCDC_CTRL 0x00
#define LCDC_CTRL1 0x10
#define LCDC_V4_CTRL2 0x20
#define LCDC_V4_TRANSFER_COUNT 0x30
#define LCDC_V4_CUR_BUF 0x40
#define LCDC_V4_NEXT_BUF 0x50
#define LCDC_VDCTRL0 0x70
#define LCDC_VDCTRL1 0x80
#define LCDC_VDCTRL2 0x90
#define LCDC_VDCTRL3 0xa0
#define LCDC_VDCTRL4 0xb0
#define REG_SET 4
#define REG_CLR 8
/* CTRL 位定义 */
#define CTRL_SFTRST (1 << 31)
#define CTRL_CLKGATE (1 << 30)
#define CTRL_BYPASS_COUNT (1 << 19)
#define CTRL_DOTCLK_MODE (1 << 17)
#define CTRL_SET_BUS_WIDTH(x) (((x) & 0x3) << 10)
#define CTRL_SET_WORD_LENGTH(x) (((x) & 0x3) << 8)
#define CTRL_MASTER (1 << 5)
#define CTRL_RUN (1 << 0)
/* CTRL1 位定义 */
#define CTRL1_FIFO_CLEAR (1 << 21)
#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16)
#define CTRL1_OVERFLOW_IRQ_EN (1 << 15)
#define CTRL1_UNDERFLOW_IRQ_EN (1 << 14)
#define CTRL1_CUR_FRAME_DONE_IRQ_EN (1 << 13)
#define CTRL1_VSYNC_EDGE_IRQ_EN (1 << 12)
#define CTRL1_OVERFLOW_IRQ (1 << 11)
#define CTRL1_UNDERFLOW_IRQ (1 << 10)
#define CTRL1_CUR_FRAME_DONE_IRQ (1 << 9)
#define CTRL1_VSYNC_EDGE_IRQ (1 << 8)
/* VDCTRL0 位定义 */
#define VDCTRL0_ENABLE_PRESENT (1 << 28)
#define VDCTRL0_VSYNC_ACT_HIGH (1 << 27)
#define VDCTRL0_HSYNC_ACT_HIGH (1 << 26)
#define VDCTRL0_DOTCLK_ACT_FALLING (1 << 25)
#define VDCTRL0_ENABLE_ACT_HIGH (1 << 24)
#define VDCTRL0_VSYNC_PERIOD_UNIT (1 << 21)
#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT (1 << 20)
#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
/* 其他宏 */
#define STMLCDIF_16BIT 0
#define STMLCDIF_24BIT 3
#define VDCTRL4_SYNC_SIGNALS_ON (1 << 18)
/* [修复] 手动定义 FB_SYNC_CLK_LAT_FALL,因为它不是标准内核宏,而是特定于 i.MX 的习惯用法 */
#define FB_SYNC_CLK_LAT_FALL 0x40000000
struct mxsfb_info {
struct fb_info *fb_info;
struct platform_device *pdev;
struct clk *clk_pix;
struct clk *clk_axi;
void __iomem *base;
unsigned ld_intf_width;
struct completion vsync_complete;
struct completion flip_complete;
int enabled;
};
/* ================= 核心功能实现 ================= */
static irqreturn_t mxsfb_irq_handler(int irq, void *dev_id)
{
struct mxsfb_info *host = dev_id;
u32 status;
status = readl(host->base + LCDC_CTRL1) & 0xF00;
if (status & CTRL1_VSYNC_EDGE_IRQ) {
writel(CTRL1_VSYNC_EDGE_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
complete(&host->vsync_complete);
}
if (status & CTRL1_CUR_FRAME_DONE_IRQ) {
writel(CTRL1_CUR_FRAME_DONE_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
complete(&host->flip_complete);
}
if (status & CTRL1_UNDERFLOW_IRQ) {
writel(CTRL1_UNDERFLOW_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL_RUN, host->base + LCDC_CTRL + REG_SET);
}
if (status & CTRL1_OVERFLOW_IRQ) {
writel(CTRL1_OVERFLOW_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
}
return IRQ_HANDLED;
}
static void mxsfb_enable_controller(struct fb_info *fb_info)
{
struct mxsfb_info *host = fb_info->par;
u32 reg;
if (host->enabled) return;
clk_disable_unprepare(host->clk_pix);
clk_set_rate(host->clk_pix, PICOS2KHZ(fb_info->var.pixclock) * 1000U);
clk_prepare_enable(host->clk_pix);
writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_SET);
reg = readl(host->base + LCDC_VDCTRL4);
reg |= VDCTRL4_SYNC_SIGNALS_ON;
writel(reg, host->base + LCDC_VDCTRL4);
writel(CTRL_MASTER, host->base + LCDC_CTRL + REG_SET);
writel(CTRL_RUN, host->base + LCDC_CTRL + REG_SET);
host->enabled = 1;
}
static void mxsfb_disable_controller(struct fb_info *fb_info)
{
struct mxsfb_info *host = fb_info->par;
writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
writel(CTRL_MASTER, host->base + LCDC_CTRL + REG_CLR);
host->enabled = 0;
}
static int mxsfb_set_par(struct fb_info *fb_info)
{
struct mxsfb_info *host = fb_info->par;
u32 ctrl, vdctrl0, vdctrl4;
int reenable = 0;
u32 hsync_len, h_period;
if (host->enabled) {
reenable = 1;
mxsfb_disable_controller(fb_info);
}
writel(CTRL1_FIFO_CLEAR, host->base + LCDC_CTRL1 + REG_SET);
ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER |
CTRL_SET_BUS_WIDTH(host->ld_intf_width);
switch (fb_info->var.bits_per_pixel) {
case 16:
ctrl |= CTRL_SET_WORD_LENGTH(0);
writel(CTRL1_SET_BYTE_PACKAGING(0xf), host->base + LCDC_CTRL1);
break;
case 32:
ctrl |= CTRL_SET_WORD_LENGTH(3);
writel(CTRL1_SET_BYTE_PACKAGING(0x7), host->base + LCDC_CTRL1);
break;
default:
return -EINVAL;
}
writel(ctrl, host->base + LCDC_CTRL);
writel((fb_info->var.yres << 16) | fb_info->var.xres,
host->base + LCDC_V4_TRANSFER_COUNT);
vdctrl0 = VDCTRL0_ENABLE_PRESENT |
VDCTRL0_VSYNC_PERIOD_UNIT |
VDCTRL0_VSYNC_PULSE_WIDTH_UNIT |
VDCTRL0_SET_VSYNC_PULSE_WIDTH(fb_info->var.vsync_len);
if (fb_info->var.sync & FB_SYNC_HOR_HIGH_ACT) vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
if (fb_info->var.sync & FB_SYNC_VERT_HIGH_ACT) vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
if (!(fb_info->var.sync & FB_SYNC_COMP_HIGH_ACT)) vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
if (fb_info->var.sync & FB_SYNC_CLK_LAT_FALL) vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING;
writel(vdctrl0, host->base + LCDC_VDCTRL0);
writel(fb_info->var.upper_margin + fb_info->var.vsync_len +
fb_info->var.lower_margin + fb_info->var.yres,
host->base + LCDC_VDCTRL1);
hsync_len = fb_info->var.hsync_len;
h_period = fb_info->var.left_margin + hsync_len +
fb_info->var.right_margin + fb_info->var.xres;
writel((hsync_len << 18) | h_period, host->base + LCDC_VDCTRL2);
writel(((fb_info->var.left_margin + hsync_len) << 16) |
(fb_info->var.upper_margin + fb_info->var.vsync_len),
host->base + LCDC_VDCTRL3);
vdctrl4 = fb_info->var.xres;
writel(vdctrl4, host->base + LCDC_VDCTRL4);
writel(fb_info->fix.smem_start, host->base + LCDC_V4_CUR_BUF);
writel(fb_info->fix.smem_start, host->base + LCDC_V4_NEXT_BUF);
if (reenable)
mxsfb_enable_controller(fb_info);
return 0;
}
static int mxsfb_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info)
{
/* [修复] 变量定义移到函数最开头 */
u32 *pal;
if (regno >= 16) return 1;
pal = info->pseudo_palette;
red >>= (16 - info->var.red.length);
green >>= (16 - info->var.green.length);
blue >>= (16 - info->var.blue.length);
pal[regno] = (red << info->var.red.offset) |
(green << info->var.green.offset) |
(blue << info->var.blue.offset);
return 0;
}
static int mxsfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
return dma_mmap_writecombine(info->device, vma, info->screen_base,
info->fix.smem_start, info->fix.smem_len);
}
static struct fb_ops mxsfb_ops = {
.owner = THIS_MODULE,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg,
.fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int mxsfb_probe(struct platform_device *pdev)
{
struct mxsfb_info *host;
struct fb_info *fb_info;
struct resource *res;
int ret, irq;
struct device_node *display_np;
struct display_timings *timings;
struct display_timing *dt;
struct videomode vm;
u32 bus_width;
printk("----eclipse lcd start ----
");
fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev);
if (!fb_info) return -ENOMEM;
host = fb_info->par;
host->fb_info = fb_info;
host->pdev = pdev;
init_completion(&host->vsync_complete);
init_completion(&host->flip_complete);
platform_set_drvdata(pdev, host);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
ret = PTR_ERR(host->base);
goto err_fb;
}
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
dev_name(&pdev->dev), host);
if (ret) goto err_fb;
host->clk_pix = devm_clk_get(&pdev->dev, "pix");
host->clk_axi = devm_clk_get(&pdev->dev, "axi");
if (IS_ERR(host->clk_pix) || IS_ERR(host->clk_axi)) {
ret = -EIO;
goto err_fb;
}
clk_prepare_enable(host->clk_axi);
writel(CTRL_SFTRST, host->base + LCDC_CTRL + REG_SET);
udelay(10);
writel(CTRL_SFTRST, host->base + LCDC_CTRL + REG_CLR);
udelay(10);
writel(CTRL_CLKGATE, host->base + LCDC_CTRL + REG_CLR);
display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
if (!display_np) {
dev_err(&pdev->dev, "No display node
");
ret = -ENODEV;
goto err_clk;
}
if (of_property_read_u32(display_np, "bus-width", &bus_width)) bus_width = 24;
switch(bus_width) {
case 16: host->ld_intf_width = STMLCDIF_16BIT; break;
case 24: host->ld_intf_width = STMLCDIF_24BIT; break;
default: host->ld_intf_width = STMLCDIF_24BIT; break;
}
timings = of_get_display_timings(display_np);
if (!timings) { ret = -EINVAL; goto err_clk; }
dt = display_timings_get(timings, timings->native_mode);
videomode_from_timing(dt, &vm);
fb_info->var.xres = vm.hactive;
fb_info->var.yres = vm.vactive;
fb_info->var.xres_virtual = vm.hactive;
fb_info->var.yres_virtual = vm.vactive;
fb_info->var.bits_per_pixel = 16;
fb_info->var.pixclock = KHZ2PICOS(vm.pixelclock / 1000);
fb_info->var.left_margin = vm.hback_porch;
fb_info->var.right_margin = vm.hfront_porch;
fb_info->var.upper_margin = vm.vback_porch;
fb_info->var.lower_margin = vm.vfront_porch;
fb_info->var.hsync_len = vm.hsync_len;
fb_info->var.vsync_len = vm.vsync_len;
fb_info->fix.line_length = fb_info->var.xres * (fb_info->var.bits_per_pixel / 8);
fb_info->fix.smem_len = fb_info->fix.line_length * fb_info->var.yres;
fb_info->screen_base = dma_alloc_writecombine(&pdev->dev, fb_info->fix.smem_len,
(dma_addr_t *)&fb_info->fix.smem_start,
GFP_KERNEL);
if (!fb_info->screen_base) {
ret = -ENOMEM;
goto err_clk;
}
fb_info->fbops = &mxsfb_ops;
fb_info->fix.visual = FB_VISUAL_TRUECOLOR;
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
writel(CTRL1_UNDERFLOW_IRQ_EN | CTRL1_VSYNC_EDGE_IRQ_EN,
host->base + LCDC_CTRL1 + REG_SET);
ret = register_framebuffer(fb_info);
if (ret) goto err_dma;
return 0;
err_dma:
dma_free_writecombine(&pdev->dev, fb_info->fix.smem_len,
fb_info->screen_base, fb_info->fix.smem_start);
err_clk:
clk_disable_unprepare(host->clk_axi);
err_fb:
framebuffer_release(fb_info);
return ret;
}
static int mxsfb_remove(struct platform_device *pdev)
{
struct mxsfb_info *host = platform_get_drvdata(pdev);
unregister_framebuffer(host->fb_info);
mxsfb_disable_controller(host->fb_info);
dma_free_writecombine(&pdev->dev, host->fb_info->fix.smem_len,
host->fb_info->screen_base, host->fb_info->fix.smem_start);
clk_disable_unprepare(host->clk_axi);
clk_disable_unprepare(host->clk_pix);
framebuffer_release(host->fb_info);
return 0;
}
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "eclipse_lcd" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxsfb_dt_ids);
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
},
};
module_platform_driver(mxsfb_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal MXSFB Driver Corrected");
probe函数
host和fb_info之间的关系
host 是特定的linux开发板私有的属性,他的存放一下寄存器的基地址,时钟,锁,是底层的硬件驱动代码fb_info 是linux应用层对外暴露的接口,用户可以通过给/dev/fb0写入数据才能操作具体的硬件将fb_info->par指向host的目的就是,方便应用层通过/dev/fb0找到内核中的host,从而操作硬件
fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev);看代码为什么只给fb_info分配内存空间
其实源码是固定给fb_info分配空间,size是额外分配的空间,也就是说size额外分配的空间是给host这也就是后面为什么是host = fb_info->par的原因了
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
int fb_info_size = sizeof(struct fb_info);
struct fb_info *info;
char *p;
if (size)
fb_info_size += PADDING;
p = kzalloc(fb_info_size + size, GFP_KERNEL);
.....
}
什么是完成量,完成量的作用是
完成量就是进程间通信的手段,一般是一个线程A发起一个操作,需要等待线程B完成后,线程A才能继续,这时候就可以让进程A先休眠,当进程B完成后向线程A发出一个信号,表示已完成,这个就是完成量的工作原理代码步骤
第一步:初始化 (Init) —— init_completion第二步:等待 (Wait) —— wait_for_completion第三步:完成 (Complete) —— complete
注册中断为什么传入的是host,flag=0是什么意思
host是注册中断函数的最后一个(void* )dev_id 中断回调函数会传入这个参数,方便回调获取相关信息flag=0,代表中断配置和设备树保持一致
lcd是怎么触发中断的
eLCDIF(LCD接口)控制器,不停地把内存里的像素搬到屏幕上,LCD 控制器内部有一个计数器。当它数完了这一帧的最后一个像素,并且发出了 VSYNC(垂直同步)信号给外部屏幕后,代表“这一帧结束了”硬件设备会自动把 LCDIF_CTRL1 寄存器中的 VSYNC_EDGE_IRQ 这一位(Bit)由 0 变成 1。在硬件电路内部,有一个逻辑判断: 实际中断信号 = (状态位 VSYNC_EDGE_IRQ) AND (使能位 VSYNC_EDGE_IRQ_EN),如果你之前在驱动里开启了中断(Enable),那么这个逻辑门的输出就变成了 “真” (High Level)。LCD 控制器内部的那根连接 GIC 的内部中断线 (IRQ Line) 被拉高了,将中断信息交给cpu处理
时钟
pix:打像素点的时钟axi:dma的时钟disp_axi
timings = of_get_display_timings(display_np);
dt = display_timings_get(timings, timings->native_mode);
获取设备节点时序信息选取默认的方案
videomode_from_timing(dt, &vm);这个vm保持的是什么数据
vm (struct videomode) 保存的是一组 “清洗过的”、“标准化的”、“确定值的” 屏幕显示参数。
它和之前的 dt (display_timing) 最大的区别在于:dt 可能包含范围(最小值、典型值、最大值),而 vm 只有唯一的确定的值。
fb_info->fix.line_length = fb_info->var.xres * (fb_info->var.bits_per_pixel / 8);
计算一行的字节数,xres为800个像素点,var.bits_per_pixel一个像素点多少比特,除以8,就是多少字节
fb_info->fix.smem_len = fb_info->fix.line_length * fb_info->var.yres;计算一个屏幕的字节数
fb_info->screen_base = dma_alloc_writecombine(&pdev->dev, fb_info->fix.smem_len,
(dma_addr_t *)&fb_info->fix.smem_start,
GFP_KERNEL);分配dma空间
寄存器
imx6ull 的lcd寄存器 0x021C8000
fb_info->fbops = &mxsfb_ops;ops作用是
它把 Linux 内核通用的“抽象命令”,绑定到了你这块具体硬件的“实际操作”上fb_info只是负责下达命令,但是命令具体的实施,还是交给ops,这是因为不同的芯片使用的是不同的寄存需要根据具体的芯片具体配置,这也体现了linux代码的健壮性
static struct fb_ops mxsfb_ops = {
.owner = THIS_MODULE,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg,
.fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
每当你要改分辨率、改色深、或者初始化屏幕时,内核就会调用它。它负责把 fb_info->var 里的软件参数,翻译成具体的数值写入 LCD 控制器的硬件寄存器,还会去调整时钟频率。
mxsfb_set_par调色板设置
mxsfb_setcolreg
mxsfb_mmap
通常我们读写文件要用 read() / write(),但这对于屏幕来说太慢了(涉及到内核态和用户态的数据拷贝)。mmap 就像是在应用程序(用户空间)和显存(内核空间)之间打了个洞。一旦映射成功,应用程序可以直接操作一个指针(数组),往数组里写 0xFF0000,屏幕上立马出现红色。数据不需要经过 CPU 的反复拷贝,直接到达内存,速度极快。 剩下三个函数都有一个共同的前缀 cfb_,代表 Color Frame Buffer(内核通用的软件绘图库)。用来画图的
mxsfb_ops
LCD寄存器介绍
这里只讲讲重点的寄存器,其他的用到可以查阅imx6ull的数据手册
总开关与模式设定
LCDIF_CTRL
RUN (Bit 0): 启动/停止引擎。写 1 开始工作,写 0 停止。
MASTER (Bit 5): 这一位非常重要。
设为 1:i.MX6ULL 主动发信号(时钟、同步信号)给屏幕。绝大多数裸屏(RGB 接口)都用这个。
设为 0:i.MX6ULL 听屏幕指挥(通常用于智能屏,这里不讨论)。
WORD_LENGTH: 数据总线是一次吐出 16bit (RGB565) 还是 24bit (RGB888)?
DATA_FORMAT: 字节顺序。是 R-G-B 还是 B-G-R?(这就解释了为什么有时候屏幕颜色红蓝反了,就是要改这里)。

画布尺寸
LCDIF_TRANSFER_COUNT
高 16 位 (V_COUNT): 垂直分辨率 (Yres),比如 480。低 16 位 (H_COUNT): 水平分辨率 (Xres),比如 800。
时序控制
~
LCDIF_VDCTRL0
LCDIF_VDCTRL4
LCDIF_VDCTRL0
VSYNC/HSYNC_POL: 同步信号是高电平有效,还是低电平有效?ENABLE_PRESENT: 开启数据使能 (DE) 模式
LCDIF_VDCTRL1
扫完一帧画面,算上回扫休息的时间,总共相当于扫了多少行?
V_Total = Vsync + V_Back_Porch + Yres + V_Front_Porch
LCDIF_VDCTRL2
同步信号发出后,要空跑多少个点和多少行,才开始真正显示像素
LCDIF_VDCTRL4
这就填 Xres (800)。你可能会问 TRANSFER_COUNT 不都填过了吗?是的,这里又填一遍,是为了硬件逻辑的双重确认。
数据缓冲
LCDIF_CUR_BUF
一旦启动,DMA 引擎就会从这个地址开始,像抽水机一样把内存里的数据抽出来,发给屏幕。
LCDIF_NEXT_BUF
如果做双缓冲动画,当前帧还没扫完,你就可以先把下一帧的地址填在这里。等当前帧扫完,硬件会自动无缝切换到这个地址读取。
mxsfb_set_par
这个函数就是重新配置,lcd的设置
mxsfb_disable_controller(fb_info);
这个函数就是操作ctrl寄存器中的run位,将其停止在进行接下来的配置值得一提的是其内部实现
writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
你可能会觉得奇怪:“如果要清零,不应该是读出来,按位与(&~),再写回去吗?为什么是直接 writel?”
这就是 NXP i.MX 芯片寄存器设计的精妙之处。每一个硬件功能寄存器,在物理地址上其实有 4 个入口:
Address + 0x00 (Normal): 正常读写。写什么就是什么。
Address + 0x04 (REG_SET): 置位入口。往这里写 1 的位,原寄存器里对应的位就会变 1;写 0 的位保持不变。
Address + 0x08 (REG_CLR): 清除入口。往这里写 1 的位,原寄存器里对应的位就会变 0(被清除);写 0 的位保持不变。
Address + 0x0C (REG_TOG): 翻转入口。往这里写 1 的位,原寄存器里对应的位就会取反。
后面就是对寄存器做配置就不讲了
mxsfb_setcolreg
在 Linux 内核里,上层应用(或者是控制台 Console)想设置颜色时,它用的语言是 “标准普通话”:
红色:0 ~ 65535 (16 bit)绿色:0 ~ 65535 (16 bit)蓝色:0 ~ 65535 (16 bit)
而我们需要将三个16比特的颜色修改位RGB565,使用一个16比特表示
函数实现就是将颜色右移动5/6最后拼起来
它把计算好的像素值,存到了 info->pseudo_palette 这个数组里。当 Linux 控制台想要画一个“红色的字”时,它会去查 pal[红色索引],拿到刚才算好的 RGB565 值,然后直接把这个值写进显存。
中断函数
它的核心任务只有两件事:
工作信号:告诉驱动程序“图画好了”或者“同步信号来了”,唤醒睡觉的进程。
异常发生:检测硬件有没有出问题(比如数据断流),并尝试自动重启硬件。















暂无评论内容