友善之臂对这个的支持还是比较坑的,我买的开发板用的是X710屏,我嘞个去,没有X710的datasheet,网上也找不到,只能另辟蹊径了,幸好,友善提供的源代码里有X710的配置参数,然后也可以顺利地显示出自己定制化的开机logo.
在说驱动之前,我们先来看一下框架知识,和以往驱动不同的是,这里用到了framebuffer,如下图是用framebuffer和以往驱动的差异的框架图:
如上,开发LCD驱动一般的做法就是上面两种形式,当然,也可以通过块设备的方式进行开发,这里主要讲字符形式的开发,后边有机会的话,用块设备驱动的形式做做,然后再分享.
以往字符设备一般分为3层,驱动控制层,驱动核心层和驱动适配器层.而framebuffer则简化为了两层,以方便快速开发,和减少开发人员的工作量,常见的驱动操作数据的形式就是copy_from_user()和copy_to_user(),这样的形式,可以实现对用户层和驱动层之间的隔离和区分管控,但是特殊情况则特殊对待,LCD我们一般应用的场景都是播放连续的画面,这就是形成目前视频播放的原理,目前的视频就是由一张一张连续的画面连续的显示得到的,一般视频都是数据量比较大,经典驱动架构copy_from_user()则对此是低效的方式,因为要把数据转移两遍,第一遍是从用户空间拷贝到内核空间,第二遍是从内核空间拷贝到物理器件(这里是LCD控制器),这种方式对于小数据量是没问题的,但是对于视频这种大数据量时,则会增加内核的负载和CPU的负载,因为这种转移是依靠CPU来实现的,所以为了针对视频这种特殊情况,Linux调整了视频驱动的架构,直接把本该内核操作的内存地址透明给用户空间,使用户空间直接给相应的内存数据,同时为了减轻CPU搬运数据的负担,一般SOC都是有集成DMA外设的,比如我们使用这款exynos 4412就具备,所以,在这里对于视频数据,我们采用映射DMA内存的方式搬运数据,以腾出CPU去做别的事情,而这块被映射的DMA内存的视频缓冲区则称之为显存,我们这里使用的是映射到RAM的显存,属于集成显存,还有一种叫独立显存,比如独立显卡,嵌入式设备为了节约成本,一般不用独立显存,同时,也视频架构除了帧缓冲之外,还用一种控制台的形式的驱动,比如VGA控制台,这里则只介绍framebuffer帧缓冲.
我们回到实际开发中,我先来说一下,这块开发的分工:
framebuffer核心层---->Linux内核完成(fbmem.c)
LCD控制器层---------->芯片原厂完成(这里是三星提供, s3c-fb.c)
LCD屏物理参数-------->我们自己添加(tiny4412-lcds.c)
我们需要做的就是只有一点,就是添加平台总线数据,因为这个芯片的LCD控制器只有一个,而LCD屏则可以使多选的,比如我们可以使用5寸的也可以使用7寸的,这两者肯定是有一定的差异的,有差异,就可以采取数据总线的形式,这里就是,LCD驱动采用了数据总线,我们根据自己的屏幕的实际物理特性,把相关参数添加到已经构件的总台总线的驱动数据层即可.
tiny4412的平台自定义数据在mach-tiny4412.c里面,我们直接搜fb即可,然后会在smdk4x12_machine_init()里面发现如下代码:
#ifdef CONFIG_TOUCHSCREEN_FT5X0X
struct s3cfb_lcd *lcd = tiny4412_get_lcd();
ft5x0x_pdata.screen_max_x = lcd->width;
ft5x0x_pdata.screen_max_y = lcd->height;
#endif
其中的tiny4412_get_lcd()就是获取屏幕的参数的,这个函数定义在tiny4412-lcds.c,可以从这个c文件中发现X710屏幕的参数信息:
static struct s3cfb_lcd wsvga_x710 = {
.width = 1024, // 水平像素
.height = 600, // 垂直像素
.p_width = 154, // 物理水平尺寸
.p_height = 90, // 物理垂直尺寸,16:9
.bpp = 24, // 像素位宽,RGB888
.freq = 61, // 帧率
.timing = {
.h_fp = 84, // 水平前肩,水平无效时间
.h_bp = 84, // 水平折返时间
.h_sw = 88, // 水平稳定时间
.v_fp = 10, // 垂直无效时间
.v_fpe = 1, // 均匀场垂直前肩
.v_bp = 10, // 垂直折返时间
.v_bpe = 1, // 均匀场垂直后肩
.v_sw = 20, // 垂直稳定时间
},
.polarity = {
.rise_vclk = 1, // 上升沿数据有效
.inv_hsync = 1, // 水平极性反转
.inv_vsync = 1, // 垂直极性反转
.inv_vden = 0, // 数据使能
},
};
实际开发中,我们的工作量就是上面这么多,就这几个参数,别的都是要么Linux内核提供,要么芯片原厂提供,大大减小LCD驱动开发的难度,上面这些参数,一般响应屏幕的datasheet上会写出来,但这是个X710的,我没找到,不过,还是可以透过这个配置信息知道这个屏幕的属性,首先屏幕的分辨率是1024*600,物理尺寸是154x90(mm)接近于16:9,像素位宽是24,也就是RGB三原色各占8位,帧率从上面的数据可以大概算一下,接近于61Hz,一般也都要求至少60Hz才对人眼不会造成眩晕.然后timing结构体里的数据都是要datasheet上提供的,但是下载不到这个datasheet,这里没什么好说的,我都有写注释,自己可以百度到详细答案.polarity结构体里的东西也给了注释,就不多说了.填完这个之后,我们来验证一下效果,需要做以下事情:
1).让内核加载适合我们屏幕的参数:
这一步很坑,因为我根本没找到在哪里去做配置,这个内核没有使用设备树,然后也没有向全志一样使用script.bin的形式配置,make menuconfig也没找到在哪里配置,然后看到顶层目录的.config文件里有一个uboot传参的句式:
CONFIG_CMDLINE="root=/dev/mmcblk0p2 rootfstype=ext4 init=/linuxrc console=ttySAC0,115200 lcd=HD700 ctp=2 skipcali=y"
然后知道应该是可以通过uboot进行选择,但是,我这里直接了当,因为配置都是通过tiny4412_get_lcd()这个函数来获取的,所以,我直接修改这个函数即可,做如下修改:
struct s3cfb_lcd *tiny4412_get_lcd(void)
{
+++ lcd_idx = 7;
return tiny4412_lcd_config[lcd_idx].lcd;
}
其中新增加一句lcd_idx = 7;是因为X710是可选配置结构体里的第7个,这样.我们就选中了我们的屏幕X710.
2),配置了LCD之后,我们来验证一下LCD是否有效可用
最简单的方式就是打开启动logo,如果开机有启动logo,就说明配置成功,如下,首先make menuconfig:
画红框的都要选中,然后,重新编译内核,把新生成的zImage下载到内存卡,从内存卡启动,然后,如果顺利的话,就可以看到4个小企鹅了,一个核心对应一个小企鹅,4个代表有4颗核心,8个则代表8核心,以此类推.
下面这张图是填参数的依据,因为这里没有找到X710的datasheet,所以,这里就不讲这些了,不过如上都是有注明什么对应什么的,一般看一下,就可以明白了.(从左到右,从上到下依次是SOC的LCD控制器时序图,LCD屏幕datasheet时序图,Linux内核定义的时序图,就这3个,第4个和第2个是一个,别看错了,它们虽然名字不同,但是却是描述的同一个物理特性)
除了这些之外,我们还来做一个自己定制化的启动logo,玩一下,需要如下步骤:
1, 网上随便下载一张图片,比如jpg格式的,然后,我们通过GIMP软件把图片
像素修改成1028x600大小,位色改为224色,然后保存为ppm格式的ASCii文件
2, 把ppm图片放在如下路径drivers/video/logo/logo_meizi_clut224.ppm
3, drivers/video/logo/Makefile
obj-$(CONFIG_LOGO_MEIZI_CLUT224) += logo_meizi_clut224.o
4, drivers/video/logo/Kconfig
config LOGO_MEIZI_CLUT224
bool "Meizi Logo is very beautiful"
help
meizi meizi ,sexy lady
5,将logo文件编译进内核:
Device Drivers --->
Graphics support --->
[*] Bootup logo ---> //提供启动的图片
[*] Standard black and white Linux logo (NEW)
[*] Standard 16-color Linux logo (NEW)
[*] Standard 224-color Linux logo (NEW) //默认小企鹅图片
[*] Meizi Logo is very beautiful
6, 指定使用哪个logo:
include/linux/linux_logo.h
extern const struct linux_logo logo_meizi_clut224;
drivers/video/logo/logo.c
|
#ifdef CONFIG_LOGO_MEIZI_CLUT224
logo = &logo_meizi_clut224;
#endif
7, 编译内核:
make zImage
下载zImage到内存卡,然后启动就OK了.(因为uboot没有移植好,没法通过tftp下载,烧进mmc又麻烦,有机会再搞一下uboot).
因为之前领导说让我"兼职"做LCD这一块,所以,就准备深入学习这一块,但是目前实际上,并没有拿到驱动部门的任何资源,处境很尴尬,也始终得不到岗位调度,所以现在还是靠自己了,本来计划自己实现一个LCD驱动.目前情况来看,还有更迫切的事,所以先贴一个三星官方的,有空再替换成自己写的.
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <mach/map.h>
#include <mach/sysmmu.h>
#include <mach/s3c-fb.h>
#include <plat/regs-fb-v4.h>
#include <plat/fb.h>
#ifdef CONFIG_ION_EXYNOS
#define CONFIG_FB_ION_EXYNOS
#endif
#if defined(CONFIG_FB_ION_EXYNOS)
#include <linux/dma-buf.h>
#include <plat/iovmm.h>
#include <linux/sw_sync.h>
#include <plat/devs.h>
#include <linux/ion.h>
#include <linux/kthread.h>
#endif
/* This driver will export a number of framebuffer interfaces depending
* on the configuration passed in via the platform data. Each fb instance
* maps to a hardware window. Currently there is no support for runtime
* setting of the alpha-blending functions that each window has, so only
* window 0 is actually useful.
*
* Window 0 is treated specially, it is used for the basis of the LCD
* output timings and as the control for the output power-down state.
*/
/* note, the previous use of <mach/regs-fb.h> to get platform specific data
* has been replaced by using the platform device name to pick the correct
* configuration data for the system.
*/
#ifdef CONFIG_FB_S3C_DEBUG_REGWRITE
#undef writel
#define writel(v, r) do { \
pr_debug("%s: %08x => %p\n", __func__, (unsigned int)v, r); \
__raw_writel(v, r); \
} while (0)
#endif /* FB_S3C_DEBUG_REGWRITE */
/* irq_flags bits */
#define S3C_FB_VSYNC_IRQ_EN 0
#define VSYNC_TIMEOUT_MSEC 60
#undef CHECK_BANDWIDTH
#define MAX_BW_PER_WINDOW (800 * 1280 * 4 * 60)
/* disable blank */
#define ENABLE_FB_BLANK 0
struct s3c_fb;
extern struct ion_device *exynos_ion_dev;
#define VALID_BPP(x) (1 << ((x) - 1))
#define OSD_BASE(win, variant) ((variant).osd + ((win) * (variant).osd_stride))
#define VIDOSD_A(win, variant) (OSD_BASE(win, variant) + 0x00)
#define VIDOSD_B(win, variant) (OSD_BASE(win, variant) + 0x04)
#define VIDOSD_C(win, variant) (OSD_BASE(win, variant) + 0x08)
#define VIDOSD_D(win, variant) (OSD_BASE(win, variant) + 0x0C)
/**
* struct s3c_fb_variant - fb variant information
* @is_2443: Set if S3C2443/S3C2416 style hardware.
* @nr_windows: The number of windows.
* @vidtcon: The base for the VIDTCONx registers
* @wincon: The base for the WINxCON registers.
* @winmap: The base for the WINxMAP registers.
* @keycon: The abse for the WxKEYCON registers.
* @buf_start: Offset of buffer start registers.
* @buf_size: Offset of buffer size registers.
* @buf_end: Offset of buffer end registers.
* @osd: The base for the OSD registers.
* @palette: Address of palette memory, or 0 if none.
* @has_prtcon: Set if has PRTCON register.
* @has_shadowcon: Set if has SHADOWCON register.
* @has_blendcon: Set if has BLENDCON register.
* @has_clksel: Set if VIDCON0 register has CLKSEL bit.
* @has_fixvclk: Set if VIDCON1 register has FIXVCLK bits.
*/
struct s3c_fb_variant {
unsigned int is_2443:1;
unsigned short nr_windows;
unsigned int vidtcon;
unsigned short wincon;
unsigned short winmap;
unsigned short keycon;
unsigned short buf_start;
unsigned short buf_end;
unsigned short buf_size;
unsigned short osd;
unsigned short osd_stride;
unsigned short palette[S3C_FB_MAX_WIN];
unsigned int has_prtcon:1;
unsigned int has_shadowcon:1;
unsigned int has_blendcon:1;
unsigned int has_clksel:1;
unsigned int has_fixvclk:1;
};
/**
* struct s3c_fb_win_variant
* @has_osd_c: Set if has OSD C register.
* @has_osd_d: Set if has OSD D register.
* @has_osd_alpha: Set if can change alpha transparency for a window.
* @palette_sz: Size of palette in entries.
* @palette_16bpp: Set if palette is 16bits wide.
* @osd_size_off: If != 0, supports setting up OSD for a window; the appropriate
* register is located at the given offset from OSD_BASE.
* @valid_bpp: 1 bit per BPP setting to show valid bits-per-pixel.
*
* valid_bpp bit x is set if (x+1)BPP is supported.
*/
struct s3c_fb_win_variant {
unsigned int has_osd_c:1;
unsigned int has_osd_d:1;
unsigned int has_osd_alpha:1;
unsigned int palette_16bpp:1;
unsigned short osd_size_off;
unsigned short palette_sz;
u32 valid_bpp;
};
/**
* struct s3c_fb_driverdata - per-device type driver data for init time.
* @variant: The variant information for this driver.
* @win: The window information for each window.
*/
struct s3c_fb_driverdata {
struct s3c_fb_variant variant;
struct s3c_fb_win_variant *win[S3C_FB_MAX_WIN];
};
/**
* struct s3c_fb_palette - palette information
* @r: Red bitfield.
* @g: Green bitfield.
* @b: Blue bitfield.
* @a: Alpha bitfield.
*/
struct s3c_fb_palette {
struct fb_bitfield r;
struct fb_bitfield g;
struct fb_bitfield b;
struct fb_bitfield a;
};
#if defined(CONFIG_FB_ION_EXYNOS)
struct s3c_dma_buf_data {
struct ion_handle *ion_handle;
struct dma_buf *dma_buf;
struct dma_buf_attachment *attachment;
struct sg_table *sg_table;
dma_addr_t dma_addr;
struct sync_fence *fence;
};
struct s3c_reg_data {
struct list_head list;
u32 shadowcon;
u32 wincon[S3C_FB_MAX_WIN];
u32 win_rgborder[S3C_FB_MAX_WIN];
u32 winmap[S3C_FB_MAX_WIN];
u32 vidosd_a[S3C_FB_MAX_WIN];
u32 vidosd_b[S3C_FB_MAX_WIN];
u32 vidosd_c[S3C_FB_MAX_WIN];
u32 vidosd_d[S3C_FB_MAX_WIN];
u32 vidw_alpha0[S3C_FB_MAX_WIN];
u32 vidw_alpha1[S3C_FB_MAX_WIN];
u32 blendeq[S3C_FB_MAX_WIN - 1];
u32 vidw_buf_start[S3C_FB_MAX_WIN];
u32 vidw_buf_end[S3C_FB_MAX_WIN];
u32 vidw_buf_size[S3C_FB_MAX_WIN];
struct s3c_dma_buf_data dma_buf_data[S3C_FB_MAX_WIN];
};
#endif
/**
* struct s3c_fb_win - per window private data for each framebuffer.
* @windata: The platform data supplied for the window configuration.
* @parent: The hardware that this window is part of.
* @fbinfo: Pointer pack to the framebuffer info for this window.
* @varint: The variant information for this window.
* @palette_buffer: Buffer/cache to hold palette entries.
* @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
* @index: The window number of this window.
* @palette: The bitfields for changing r/g/b into a hardware palette entry.
*/
struct s3c_fb_win {
struct s3c_fb_pd_win *windata;
struct s3c_fb *parent;
struct fb_info *fbinfo;
struct s3c_fb_palette palette;
struct s3c_fb_win_variant variant;
u32 *palette_buffer;
u32 pseudo_palette[16];
unsigned int index;
#if defined(CONFIG_FB_ION_EXYNOS)
struct s3c_dma_buf_data dma_buf_data;
struct fb_var_screeninfo prev_var;
struct fb_fix_screeninfo prev_fix;
int fps;
#endif
};
/**
* struct s3c_fb_vsync - vsync information
* @wait: a queue for processes waiting for vsync
* @count: vsync interrupt count
*/
struct s3c_fb_vsync {
wait_queue_head_t wait;
#if defined(CONFIG_FB_ION_EXYNOS)
ktime_t timestamp;
bool active;
int irq_refcount;
struct mutex irq_lock;
struct task_struct *thread;
#endif
unsigned int count;
};
/**
* struct s3c_fb_user_window - User window information
* @x: X position of user window.
* @y: Y position of user window.
*/
struct s3c_fb_user_window {
int x;
int y;
};
/**
* struct s3c_fb_user_chroma - User chroma key information
* @enabled: Enabled/Disabled chroma key.
* @red: red color key value for transparent pixel effect.
* @green: green color key value for transparent pixel effect.
* @blue: blue color key value for transparent pixel effect.
*/
struct s3c_fb_user_chroma {
int enabled;
unsigned char red;
unsigned char green;
unsigned char blue;
};
/**
* struct s3c_fb - overall hardware state of the hardware
* @slock: The spinlock protection for this data sturcture.
* @dev: The device that we bound to, for printing, etc.
* @bus_clk: The clk (hclk) feeding our interface and possibly pixclk.
* @lcd_clk: The clk (sclk) feeding pixclk.
* @regs: The mapped hardware registers.
* @variant: Variant information for this hardware.
* @enabled: A bitmask of enabled hardware windows.
* @output_on: Flag if the physical output is enabled.
* @pdata: The platform configuration data passed with the device.
* @windows: The hardware windows that have been claimed.
* @irq_no: IRQ line number
* @irq_flags: irq flags
* @vsync_info: VSYNC-related information (count, queues...)
*/
struct s3c_fb {
spinlock_t slock;
struct device *dev;
struct clk *bus_clk;
struct clk *lcd_clk;
void __iomem *regs;
struct s3c_fb_variant variant;
unsigned char enabled;
bool output_on;
struct s3c_fb_platdata *pdata;
struct s3c_fb_win *windows[S3C_FB_MAX_WIN];
int irq_no;
unsigned long irq_flags;
struct s3c_fb_vsync vsync_info;
#if defined(CONFIG_FB_ION_EXYNOS)
struct mutex output_lock;
struct ion_client *fb_ion_client;
struct list_head update_regs_list;
struct mutex update_regs_list_lock;
struct kthread_worker update_regs_worker;
struct task_struct *update_regs_thread;
struct kthread_work update_regs_work;
struct sw_sync_timeline *timeline;
int timeline_max;
#endif
};
#if defined(CONFIG_FB_ION_EXYNOS)
static bool s3c_fb_validate_x_alignment(struct s3c_fb *sfb, int x, u32 w,
u32 bits_per_pixel)
{
uint8_t pixel_alignment = 32 / bits_per_pixel;
if (x % pixel_alignment) {
dev_err(sfb->dev, "left X coordinate not properly aligned to "
"%u-pixel boundary (bpp = %u, x = %u)\n",
pixel_alignment, bits_per_pixel, x);
return 0;
}
if ((x + w) % pixel_alignment) {
dev_err(sfb->dev, "right X coordinate not properly aligned to "
"%u-pixel boundary (bpp = %u, x = %u, w = %u)\n",
pixel_alignment, bits_per_pixel, x, w);
return 0;
}
return 1;
}
#endif
/**
* s3c_fb_validate_win_bpp - validate the bits-per-pixel for this mode.
* @win: The device window.
* @bpp: The bit depth.
*/
static bool s3c_fb_validate_win_bpp(struct s3c_fb_win *win, unsigned int bpp)
{
return win->variant.valid_bpp & VALID_BPP(bpp);
}
/**
* s3c_fb_check_var() - framebuffer layer request to verify a given mode.
* @var: The screen information to verify.
* @info: The framebuffer device.
*
* Framebuffer layer call to verify the given information and allow us to
* update various information depending on the hardware capabilities.
*/
static int s3c_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct s3c_fb_win *win = info->par;
struct s3c_fb *sfb = win->parent;
dev_dbg(sfb->dev, "checking parameters\n");
var->xres_virtual = max(var->xres_virtual, var->xres);
var->yres_virtual = max(var->yres_virtual, var->yres);
if (!s3c_fb_validate_win_bpp(win, var->bits_per_pixel)) {
dev_dbg(sfb->dev, "win %d: unsupported bpp %d\n",
win->index, var->bits_per_pixel);
return -EINVAL;
}
/* always ensure these are zero, for drop through cases below */
var->transp.offset = 0;
var->transp.length = 0;
switch (var->bits_per_pixel) {
case 1:
case 2:
case 4:
case 8:
if (sfb->variant.palette[win->index] != 0) {
/* non palletised, A:1,R:2,G:3,B:2 mode */
var->red.offset = 4;
var->green.offset = 2;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 3;
var->blue.length = 2;
var->transp.offset = 7;
var->transp.length = 1;
} else {
var->red.offset = 0;
var->red.length = var->bits_per_pixel;
var->green = var->red;
var->blue = var->red;
}
break;
case 19:
/* 666 with one bit alpha/transparency */
var->transp.offset = 18;
var->transp.length = 1;
case 18:
var->bits_per_pixel = 32;
/* 666 format */
var->red.offset = 12;
var->green.offset = 6;
var->blue.offset = 0;
var->red.length = 6;
var->green.length = 6;
var->blue.length = 6;
#ifdef CONFIG_FB_S3C_ORDER_BGR
swap(var->red.offset, var->blue.offset);
#endif
break;
case 16:
/* 16 bpp, 565 format */
var->red.offset = 11;
var->green.offset = 5;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 6;
var->blue.length = 5;
#ifdef CONFIG_FB_S3C_ORDER_BGR
swap(var->red.offset, var->blue.offset);
#endif
break;
case 32:
case 28:
case 25:
var->transp.length = var->bits_per_pixel - 24;
var->transp.offset = 24;
/* drop through */
case 24:
/* our 24bpp is unpacked, so 32bpp */
var->bits_per_pixel = 32;
var->red.offset = 16;
var->red.length = 8;
var->green.offset = 8;
var->green.length = 8;
var->blue.offset = 0;
var->blue.length = 8;
#ifdef CONFIG_FB_S3C_ORDER_BGR
swap(var->red.offset, var->blue.offset);
#endif
break;
default:
dev_err(sfb->dev, "invalid bpp\n");
}
#if defined(CONFIG_FB_ION_EXYNOS)
win->fps = 60;
#endif
dev_dbg(sfb->dev, "%s: verified parameters\n", __func__);
return 0;
}
/**
* s3c_fb_calc_pixclk() - calculate the divider to create the pixel clock.
* @sfb: The hardware state.
* @pixclock: The pixel clock wanted, in picoseconds.
*
* Given the specified pixel clock, work out the necessary divider to get
* close to the output frequency.
*/
static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk)
{
unsigned long clk;
unsigned long long tmp;
unsigned int result;
if (sfb->va