文章目录
- 环境
- LCD设备树信息
- imx6ull的LCD驱动
环境
正点提供的 linux 4.15
LCD设备树信息
看下imx6ull提供的设备树信息:
arch/arm/boot/dts/imx6ull.dtsi: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 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_lcdif_dat&pinctrl_lcdif_ctrl>;display = <&display0>;status = "okay"; /* 7寸1024*600 */display0: display {bits-per-pixel = <24>;bus-width = <24>;display-timings {native-mode = <&timing0>;timing0: timing0 {clock-frequency = <51200000>; // 不同尺寸时钟频率不同hactive = <1024>; // 下面这些长宽高、垂直/水平同步设置都不同vactive = <600>;hfront-porch = <160>;hback-porch = <140>;hsync-len = <20>;vback-porch = <20>;vfront-porch = <12>;vsync-len = <3>;// 这些active信息是一样的,想开就开不想开就不开hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};};};
};
其中提供了寄存器信息,中断信息,时钟信息。本来想去documetation下看看imx6ull有没有为用户提供些设备树使用说明什么的,发现没有文档:
那就无所谓了,也可能是我没找到,应该是原厂直接给正点提供的sdk里面有讲,但是没放到内核文件夹下。直接看驱动就可以了。本质上,原厂提供的sdk的设备树配置教程,就是驱动的一个映射,原厂的驱动要使用一些of函数解析这些property,所以才让用户按照要求写。
imx6ull的LCD驱动
当然原厂也不是乱写的,linux发展到现在,每一种设备的驱动都有框架的支持(如果不是特别奇怪的设备),而且大概率能通过框架的支持简化一些工作,平台化的工作可以提高效率。LCD用到的框架有:
- framebuffer框架
看一下imx6ull的LCD驱动,通过compatible找到:
drivers/video/fbdev/mxsfb.c:static const struct of_device_id mxsfb_dt_ids[] = {{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], }, // compatible匹配{ /* sentinel */ }
};
...
...
static struct platform_driver mxsfb_driver = {.probe = mxsfb_probe,.remove = mxsfb_remove,.shutdown = mxsfb_shutdown,.id_table = mxsfb_devtype,.driver = {.name = DRIVER_NAME,.of_match_table = mxsfb_dt_ids,.pm = &mxsfb_pm_ops,},
};
找到probe函数,也就是mxsfb_probe:
static int mxsfb_probe(struct platform_device *pdev)
{const struct of_device_id *of_id =of_match_device(mxsfb_dt_ids, &pdev->dev);struct resource *res;struct mxsfb_info *host; // g, 本驱动下自己定义的一个结构体,使用该结构体描述LCD的主控接口等等一些信息。struct fb_info *fb_info; // g, framebuffer结构体,框架相关struct pinctrl *pinctrl;int irq = platform_get_irq(pdev, 0); // g, in 获取pdev设备的irq,需要提供"interrupts"或"interrupts-extended"属性,或者手动为pdev->resource设置中断信息资源int gpio, ret;if (of_id)pdev->id_entry = of_id->data;gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0); // g,根据属性名获取GPIO号,但是设备树中是没有使用"enable-gpio"这个GPIO属名的,所以自然获取不到有效的gpio号,if (gpio == -EPROBE_DEFER) // g, 这个EPROBE_DEFER有点意思,因为驱动有依赖关系,如果某个驱动的probe函数返回了-EPROBE_DEFER,说明该驱动依赖的另外一个驱动没有准备好,会暂时把该驱动加入到一个延时链表中,过一会会重新执行probereturn -EPROBE_DEFER;if (gpio_is_valid(gpio)) { // g, 检测gpio号是否满足:0 <= gpio <= ARCH_NR_GPIOS(默认为ARCH_NR_GPIOS=512个GPIO)ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");if (ret) {dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);return ret;}}// g, 从设备树中获取eLCDIF接口控制器的寄存器首地址// g, 设备树中lcdif节点已经设置了eLCDIF寄存器首地址为0X021C8000(lcdif: lcdif@021c8000),因此res=0X021C8000res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) {dev_err(&pdev->dev, "Cannot get memory IO resource\n");return -ENODEV;}host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL); // g, 为主控接口host申请内存if (!host) {dev_err(&pdev->dev, "Failed to allocate IO resource\n");return -ENOMEM;}fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev); // g, 申请一个fb_infoif (!fb_info) {dev_err(&pdev->dev, "Failed to allocate fbdev\n");devm_kfree(&pdev->dev, host);return -ENOMEM;}host->fb_info = fb_info; // g, 设置host的fb指针指向申请的fb_infofb_info->par = host; // g, 设置parent指向hostret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0, // g, 为virq号(也就是这里的参数irq)申请一个中断处理函数(mxsfb_irq_handler),dev_name(&pdev->dev), host);if (ret) {dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",irq, ret);ret = -ENODEV;goto fb_release;}host->base = devm_ioremap_resource(&pdev->dev, res); // g, 开始映射实际物理基址到虚拟地址,并保存到host->base,以后就可以通过虚拟基址+偏移的方式访问一些寄存器。if (IS_ERR(host->base)) {dev_err(&pdev->dev, "ioremap failed\n");ret = PTR_ERR(host->base);goto fb_release;}host->pdev = pdev; // g, 绑定host->pdev指向本devplatform_set_drvdata(pdev, host);host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];// g, 最终找到要调用__of_clk_get_by_name(dev, "pix")函数// g, 该函数首先找到属性"clock-names"中"pix"是第几个index,然后使用该index去索引"clocks"属性。所以设备数中"clocks"属性内容与"clock-names"属性内容要一一对应。host->clk_pix = devm_clk_get(&host->pdev->dev, "pix"); if (IS_ERR(host->clk_pix)) {host->clk_pix = NULL;ret = PTR_ERR(host->clk_pix);goto fb_release;}host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");if (IS_ERR(host->clk_axi)) {host->clk_axi = NULL;ret = PTR_ERR(host->clk_axi);goto fb_release;}host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");if (IS_ERR(host->clk_disp_axi)) {host->clk_disp_axi = NULL;ret = PTR_ERR(host->clk_disp_axi);goto fb_release;}host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd"); // g, 电源相关,做PMIC驱动时分析过regulator。if (IS_ERR(host->reg_lcd))host->reg_lcd = NULL;fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, // g, 为pseudo_palette(伪16位调色板)分配内存GFP_KERNEL);if (!fb_info->pseudo_palette) {ret = -ENOMEM;goto fb_release;}INIT_LIST_HEAD(&fb_info->modelist);pm_runtime_enable(&host->pdev->dev);// g, 调用mxsfb_init_fbinfo函数初始化fb_info,重点是fb_info的var、fix、fbops,screen_base和screen_size,同时为fb_info绑定fbops为mxsfb_ops(在本文件下)。// g, 并在最后调用mxsfb_map_videomem为framebuffer申请显存,首地址保存在fb_info->screen_base中。ret = mxsfb_init_fbinfo(host);if (ret != 0)goto fb_pm_runtime_disable;// g, 下面这个函数不起作用,只有当设备数中有"disp-dev"这个属性的时候, 才会真的发挥作用// g, 根本没有disp设备?没时间看了mxsfb_dispdrv_init(pdev, fb_info);if (!host->dispdrv) { // g, 如果存在disp设备pinctrl = devm_pinctrl_get_select_default(&pdev->dev);if (IS_ERR(pinctrl)) {ret = PTR_ERR(pinctrl);goto fb_pm_runtime_disable;}}if (!host->enabled) { // g, 如果还没使能,就要配置一些重要的控制寄存器,下面的函数都是在配置一些寄存器。writel(0, host->base + LCDC_CTRL);mxsfb_set_par(fb_info);mxsfb_enable_controller(fb_info);pm_runtime_get_sync(&host->pdev->dev);}// g, 该api会调用do_register_framebuffer,通过device_create向fb_class(名为"graphics")这个类中注册fb设备,fb设备命名为"fb%d",index为registered_fb[]表中还没有使用的index// g, 可以在/sys/class/graphics/下找到fbx// g, 其中新注册的fb设备的父设备指向fb_info->device,在使用framebuffer_alloc时已经指向本LCD设备。// g, 注册完后会向registered_fb[]表中添加该fb设备。ret = register_framebuffer(fb_info); // g, 向内核注册framebufferif (ret != 0) {dev_err(&pdev->dev, "Failed to register framebuffer\n");goto fb_destroy;}console_lock();ret = fb_blank(fb_info, FB_BLANK_UNBLANK);console_unlock();if (ret < 0) {dev_err(&pdev->dev, "Failed to unblank framebuffer\n");goto fb_unregister;}dev_info(&pdev->dev, "initialized\n");return 0;fb_unregister:unregister_framebuffer(fb_info);
fb_destroy:if (host->enabled)clk_disable_unprepare(host->clk_pix);fb_destroy_modelist(&fb_info->modelist);
fb_pm_runtime_disable:pm_runtime_disable(&host->pdev->dev);devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:framebuffer_release(fb_info);devm_kfree(&pdev->dev, host);return ret;
}
做的工作大致可以分为:
- 获取设备树的信息,因为把设备加到了设备树里,所以基本上是使用启动时生成的device_node + of函数来获取信息的。
- 申请一个framebuffer结构体struct fb_info
- 使用解析设备树得到的信息初始化imx6ull自定义的结构体host以及刚刚申请的fb_info。
- 使用设备树信息初始化一些硬件寄存器。
- 调用register_framebuffer()向内核注册framebuffer设备。
主要分析一下framebuffer的初始化以及注册过程。首先分析一下fb在申请完之后的初始化过程:
drivers/video/fbdev/mxsfb.c:
static int mxsfb_init_fbinfo(struct mxsfb_info *host)
{struct fb_info *fb_info = host->fb_info;struct fb_var_screeninfo *var = &fb_info->var;struct fb_modelist *modelist;int ret;fb_info->fbops = &mxsfb_ops;fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST;fb_info->fix.type = FB_TYPE_PACKED_PIXELS;fb_info->fix.ypanstep = 1;fb_info->fix.ywrapstep = 1;fb_info->fix.visual = FB_VISUAL_TRUECOLOR,fb_info->fix.accel = FB_ACCEL_NONE;ret = mxsfb_init_fbinfo_dt(host);if (ret)return ret;if (host->id < 0)sprintf(fb_info->fix.id, "mxs-lcdif");elsesprintf(fb_info->fix.id, "mxs-lcdif%d", host->id);if (!list_empty(&fb_info->modelist)) {/* first video mode in the modelist as default video mode */modelist = list_first_entry(&fb_info->modelist,struct fb_modelist, list);fb_videomode_to_var(var, &modelist->mode);}/* save the sync value getting from dtb */host->sync = fb_info->var.sync;var->nonstd = 0;var->activate = FB_ACTIVATE_NOW;var->accel_flags = 0;var->vmode = FB_VMODE_NONINTERLACED;/* init the color fields */mxsfb_check_var(var, fb_info);fb_info->fix.line_length =fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);fb_info->fix.smem_len = SZ_32M;/* Memory allocation for framebuffer */if (mxsfb_map_videomem(fb_info) < 0)return -ENOMEM;if (mxsfb_restore_mode(host))memset((char *)fb_info->screen_base, 0, fb_info->fix.smem_len);return 0;
}
主要工作就是初始化fb_info的各个域,fb_info各个域的作用如下:
include/linux/fb.h:
struct fb_info {atomic_t count;int node;int flags;struct mutex lock; /* Lock for open/release/ioctl funcs, 互斥锁*/struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields,互斥锁,用于fb_mmap和smem_域 */struct fb_var_screeninfo var; /* Current var,当前可变参数*/struct fb_fix_screeninfo fix; /* Current fix,当前固定参数*/struct fb_monspecs monspecs; /* Current Monitor specs,当前显示器特性 */struct work_struct queue; /* Framebuffer event queue,帧缓冲事件队列 */struct fb_pixmap pixmap; /* Image hardware mapper,图像硬件映射 */struct fb_pixmap sprite; /* Cursor hardware mapper,光标硬件映射 */struct fb_cmap cmap; /* Current cmap,当前调色板 */struct list_head modelist; /* mode list,当前模式列表 */struct fb_videomode *mode; /* current mode,当前视频模式 */#ifdef CONFIG_FB_BACKLIGHT/* 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_IOstruct delayed_work deferred_work;struct fb_deferred_io *fbdefio;
#endifstruct fb_ops *fbops; /* 操作集,framebuffer框架会调用到这个的 */struct device *device; /* This is the parent,父设备*/struct device *dev; /* This is this fb device,当前dev设备 */int class_flag; /* private sysfs flags,sysfs相关 */
#ifdef CONFIG_FB_TILEBLITTINGstruct fb_tile_ops *tileops; /* Tile Blitting */
#endifchar __iomem *screen_base; /* Virtual address,显存映射的虚拟内存基址 */unsigned long screen_size; /* Amount of ioremapped VRAM or 0,虚拟显存大小 */ void *pseudo_palette; /* Fake palette of 16 colors,伪16位调色板 */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1u32 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 notsmem_start/size as smem_start may just be an objectallocated 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->fbops的初始化:
fb_info->fbops = &mxsfb_ops;
最终指向了imx自己定义的操作函数:
drivers/video/fbdev/mxsfb.c:
// g, 关于这个东西,最终的显示设备是/dev/fb0,是个字符设备,该字符设备的操作集为 fb_fops 。
// g, 以fb_fops->mmap为例,最终会调用到fb->fb_mmap,
// g, 过程就是:先通过file信息,从registered_fbp[]全局数组中找到对应fb_info结构体,然后找到该fb_info的fb_ops,再调用fb_ops->fb_mmap
static struct fb_ops mxsfb_ops = {.owner = THIS_MODULE,.fb_check_var = mxsfb_check_var,.fb_set_par = mxsfb_set_par,.fb_setcolreg = mxsfb_setcolreg,.fb_ioctl = mxsfb_ioctl,.fb_blank = mxsfb_blank,.fb_pan_display = mxsfb_pan_display,.fb_mmap = mxsfb_mmap,.fb_fillrect = cfb_fillrect,.fb_copyarea = cfb_copyarea,.fb_imageblit = cfb_imageblit,
};
这个后面大有用处,基本上很多驱动框架都是这一套模式,由框架为用户提供统一的api或者说设备文件,然后由框架提供一套标准的file_ops,然后在框架的file_ops中再调用差异性的ops,也就是我们这里的fb_info->fops,等会继续分析就知道了。其实与RTC驱动很像,框架提供一个RTC设备,然后绑定框架的ops,最终框架的ops再调用到我们自己的ops中。我猜这样做就是为了上层用户层的移植工作,相当于在提供hal层,因为框架提供的设备往往设备名都是一样的。就像是opencv一样,他底层封装的要打开什么名字的设备应该都是固定的。
初始化暂时只分析这一个点,接下来就是向系统中注册framebuffer设备,注意,这个framebuffer设备是每个fb_info对应的,并不是我们的硬件LCD,可以认为是个框架虚拟设备:
drivers/video/fbdev/core/fbmem.c:
int
register_framebuffer(struct fb_info *fb_info)
{int ret;mutex_lock(®istration_lock);ret = do_register_framebuffer(fb_info);mutex_unlock(®istration_lock);return ret;
}
...
...
static int do_register_framebuffer(struct fb_info *fb_info)
{int i, ret;struct fb_event event;struct fb_videomode mode;if (fb_check_foreignness(fb_info))return -ENOSYS;ret = do_remove_conflicting_framebuffers(fb_info->apertures,fb_info->fix.id,fb_is_primary_device(fb_info));if (ret)return ret;if (num_registered_fb == FB_MAX)return -ENXIO;num_registered_fb++;for (i = 0 ; i < FB_MAX; i++)if (!registered_fb[i])break;fb_info->node = i;atomic_set(&fb_info->count, 1);mutex_init(&fb_info->lock);mutex_init(&fb_info->mm_lock);// g, fb_class的name:graphics// g, 主设备号使用FB_MAJOR,与fbmem_init()中使用register_chrdev(FB_MAJOR, .., fops)时传入的主设备号相同// g, 所以会使用该主设备号对应的fops,也就是 fb_fops,在本文件下。fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);if (IS_ERR(fb_info->dev)) {/* Not fatal */printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));fb_info->dev = NULL;} elsefb_init_device(fb_info);if (fb_info->pixmap.addr == NULL) {fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);if (fb_info->pixmap.addr) {fb_info->pixmap.size = FBPIXMAPSIZE;fb_info->pixmap.buf_align = 1;fb_info->pixmap.scan_align = 1;fb_info->pixmap.access_align = 32;fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;}} fb_info->pixmap.offset = 0;if (!fb_info->pixmap.blit_x)fb_info->pixmap.blit_x = ~(u32)0;if (!fb_info->pixmap.blit_y)fb_info->pixmap.blit_y = ~(u32)0;if (!fb_info->modelist.prev || !fb_info->modelist.next)INIT_LIST_HEAD(&fb_info->modelist);if (fb_info->skip_vt_switch)pm_vt_switch_required(fb_info->dev, false);elsepm_vt_switch_required(fb_info->dev, true);fb_var_to_videomode(&mode, &fb_info->var);fb_add_videomode(&mode, &fb_info->modelist);registered_fb[i] = fb_info;event.info = fb_info;console_lock();if (!lock_fb_info(fb_info)) {console_unlock();return -ENODEV;}fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);unlock_fb_info(fb_info);console_unlock();return 0;
}
挑几个主要工作:
- 使用device_create()创建一个设备,信息有:①在fb_class这个类下注册设备;②注册设备名为"fb%d",index;③使用了设备号MKDEV(FB_MAJOR, i)
- 将该fb_info添加到了一个全局数组registered_fb[]中,索引号为index,与设备名fd%d使用的index相同
如果写过字符设备驱动,就知道该设备会在/sys/class/<fb_class>下出现,并且应该会有一个字符设备使用了相同的主设备号:
static int __init
fbmem_init(void)
{proc_create("fb", 0, NULL, &fb_proc_fops);if (register_chrdev(FB_MAJOR,"fb",&fb_fops))printk("unable to get major %d for fb devs\n", FB_MAJOR);fb_class = class_create(THIS_MODULE, "graphics");if (IS_ERR(fb_class)) {printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));fb_class = NULL;}return 0;
}
确实在init函数里注册了一个字符设备,绑定了操作集fb_fops,并且创建了一个名为"graphics"的fb_class类。所以当我们打开device_create()创建的设备(其实是mdev创建),也就是/dev/fb%x(index顺延,每注册一个+1)的时候,操作集使用的是同主设备号的字符设备的操作集,也就是fb_fops:
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,
};
其实用户程序能用到的函数,大概率就这么几个:
- open
- close
- mmap
- ioctl
就以mmap为例,看一下框架提供的fops – fb_mmap()是做了什么操作:
drivers/video/fbdev/core/fbmem.c:
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{struct fb_info *info = file_fb_info(file); // g, 回去registered_fb[]数组中找对应的注册了的struct fb_infostruct fb_ops *fb;unsigned long mmio_pgoff;unsigned long start;u32 len;if (!info)return -ENODEV;fb = info->fbops;if (!fb)return -ENODEV;mutex_lock(&info->mm_lock);if (fb->fb_mmap) {int res;res = fb->fb_mmap(info, vma); // g, 重点,如果fb_info->fb_mmap实现了,mutex_unlock(&info->mm_lock);return res;}/** Ugh. This can be either the frame buffer mapping, or* if pgoff points past it, the mmio mapping.*/start = info->fix.smem_start;len = info->fix.smem_len;mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;if (vma->vm_pgoff >= mmio_pgoff) {if (info->var.accel_flags) {mutex_unlock(&info->mm_lock);return -EINVAL;}vma->vm_pgoff -= mmio_pgoff;start = info->fix.mmio_start;len = info->fix.mmio_len;}mutex_unlock(&info->mm_lock);vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);fb_pgprotect(file, vma, start);return vm_iomap_memory(vma, start, len);
}
如果fb_info实现了mmap,最终还是会调用fb->fb_mmap,也就是:
static int mxsfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{u32 len;// g, vma->vm_pgoff表示这个vma中的第一页在地址空间里是第几页(页帧号)。unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; // g, vma->vm_pgoff单位为页,*2^12转换为单位字节if (offset < info->fix.smem_len) {/* mapping framebuffer memory */len = info->fix.smem_len - offset;// g, 在mxsfb_map_videomem()中,已经使用dma_alloc_writecombine()将info->fix.smem_start映射到了分配的DMA内存l中恶// g, 分配的DMA内存基址最终也会保存在LCD的显存基址寄存器中vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT;} elsereturn -EINVAL;len = PAGE_ALIGN(len);if (vma->vm_end - vma->vm_start > len)return -EINVAL;/* make buffers bufferable */vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);// g, 第三个参数是内核页的物理地址,这一片vma的物理页帧号起始// g, 最终将这一段使用mmap的用户空间vma映射到从在DMA中申请的显存中if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,vma->vm_end - vma->vm_start, vma->vm_page_prot)) {dev_dbg(info->device, "mmap remap_pfn_range failed\n");return -ENOBUFS;}return 0;
}
看一下注释把,基本上都是一样的套路。但是在计算vma->pgoff时没太看懂。有空再研究研究出现的几个内存api